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/coordinatesystem.hpp"
36 #include "proj/crs.hpp"
37 #include "proj/datum.hpp"
38 #include "proj/io.hpp"
39 #include "proj/metadata.hpp"
40 #include "proj/util.hpp"
41 
42 #include "proj/internal/internal.hpp"
43 #include "proj/internal/io_internal.hpp"
44 #include "proj/internal/lru_cache.hpp"
45 #include "proj/internal/tracing.hpp"
46 
47 #include "operation/coordinateoperation_internal.hpp"
48 #include "operation/parammappings.hpp"
49 
50 #include "sqlite3_utils.hpp"
51 
52 #include <cmath>
53 #include <cstdlib>
54 #include <cstring>
55 #include <iomanip>
56 #include <limits>
57 #include <locale>
58 #include <map>
59 #include <memory>
60 #include <sstream> // std::ostringstream
61 #include <stdexcept>
62 #include <string>
63 
64 #include "proj_constants.h"
65 
66 // PROJ include order is sensitive
67 // clang-format off
68 #include "proj.h"
69 #include "proj_internal.h"
70 #include "proj_api.h"
71 // clang-format on
72 
73 #include <sqlite3.h>
74 
75 // Custom SQLite VFS as our database is not supposed to be modified in
76 // parallel. This is slightly faster
77 #define ENABLE_CUSTOM_LOCKLESS_VFS
78 
79 using namespace NS_PROJ::internal;
80 using namespace NS_PROJ::common;
81 
82 NS_PROJ_START
83 namespace io {
84 
85 //! @cond Doxygen_Suppress
86 
87 // CRS subtypes
88 #define GEOG_2D "geographic 2D"
89 #define GEOG_3D "geographic 3D"
90 #define GEOCENTRIC "geocentric"
91 #define PROJECTED "projected"
92 #define VERTICAL "vertical"
93 #define COMPOUND "compound"
94 
95 #define GEOG_2D_SINGLE_QUOTED "'geographic 2D'"
96 #define GEOG_3D_SINGLE_QUOTED "'geographic 3D'"
97 #define GEOCENTRIC_SINGLE_QUOTED "'geocentric'"
98 
99 // See data/sql/metadata.sql for the semantics of those constants
100 constexpr int DATABASE_LAYOUT_VERSION_MAJOR = 1;
101 // If the code depends on the new additions, then DATABASE_LAYOUT_VERSION_MINOR
102 // must be incremented.
103 constexpr int DATABASE_LAYOUT_VERSION_MINOR = 0;
104 
105 // ---------------------------------------------------------------------------
106 
107 struct SQLValues {
108     enum class Type { STRING, DOUBLE };
109 
110     // cppcheck-suppress noExplicitConstructor
SQLValuesio::SQLValues111     SQLValues(const std::string &value) : type_(Type::STRING), str_(value) {}
112 
113     // cppcheck-suppress noExplicitConstructor
SQLValuesio::SQLValues114     SQLValues(double value) : type_(Type::DOUBLE), double_(value) {}
115 
typeio::SQLValues116     const Type &type() const { return type_; }
117 
118     // cppcheck-suppress functionStatic
stringValueio::SQLValues119     const std::string &stringValue() const { return str_; }
120 
121     // cppcheck-suppress functionStatic
doubleValueio::SQLValues122     double doubleValue() const { return double_; }
123 
124   private:
125     Type type_;
126     std::string str_{};
127     double double_ = 0.0;
128 };
129 
130 // ---------------------------------------------------------------------------
131 
132 using SQLRow = std::vector<std::string>;
133 using SQLResultSet = std::list<SQLRow>;
134 using ListOfParams = std::list<SQLValues>;
135 
136 // ---------------------------------------------------------------------------
137 
138 struct DatabaseContext::Private {
139     Private();
140     ~Private();
141 
142     void open(const std::string &databasePath, PJ_CONTEXT *ctx);
143     void setHandle(sqlite3 *sqlite_handle);
144 
handleio::DatabaseContext::Private145     sqlite3 *handle() const { return sqlite_handle_; }
146 
pjCtxtio::DatabaseContext::Private147     PJ_CONTEXT *pjCtxt() const { return pjCtxt_; }
setPjCtxtio::DatabaseContext::Private148     void setPjCtxt(PJ_CONTEXT *ctxt) { pjCtxt_ = ctxt; }
149 
150     SQLResultSet run(const std::string &sql,
151                      const ListOfParams &parameters = ListOfParams(),
152                      bool useMaxFloatPrecision = false);
153 
154     std::vector<std::string> getDatabaseStructure();
155 
156     // cppcheck-suppress functionStatic
getPathio::DatabaseContext::Private157     const std::string &getPath() const { return databasePath_; }
158 
159     void attachExtraDatabases(
160         const std::vector<std::string> &auxiliaryDatabasePaths);
161 
162     // Mechanism to detect recursion in calls from
163     // AuthorityFactory::createXXX() -> createFromUserInput() ->
164     // AuthorityFactory::createXXX()
165     struct RecursionDetector {
RecursionDetectorio::DatabaseContext::Private::RecursionDetector166         explicit RecursionDetector(const DatabaseContextNNPtr &context)
167             : dbContext_(context) {
168             if (dbContext_->getPrivate()->recLevel_ == 2) {
169                 // Throw exception before incrementing, since the destructor
170                 // will not be called
171                 throw FactoryException("Too many recursive calls");
172             }
173             ++dbContext_->getPrivate()->recLevel_;
174         }
175 
~RecursionDetectorio::DatabaseContext::Private::RecursionDetector176         ~RecursionDetector() { --dbContext_->getPrivate()->recLevel_; }
177 
178       private:
179         DatabaseContextNNPtr dbContext_;
180     };
181 
getMapCanonicalizeGRFNameio::DatabaseContext::Private182     std::map<std::string, std::list<SQLRow>> &getMapCanonicalizeGRFName() {
183         return mapCanonicalizeGRFName_;
184     }
185 
186     // cppcheck-suppress functionStatic
187     common::UnitOfMeasurePtr getUOMFromCache(const std::string &code);
188     // cppcheck-suppress functionStatic
189     void cache(const std::string &code, const common::UnitOfMeasureNNPtr &uom);
190 
191     // cppcheck-suppress functionStatic
192     crs::CRSPtr getCRSFromCache(const std::string &code);
193     // cppcheck-suppress functionStatic
194     void cache(const std::string &code, const crs::CRSNNPtr &crs);
195 
196     datum::GeodeticReferenceFramePtr
197         // cppcheck-suppress functionStatic
198         getGeodeticDatumFromCache(const std::string &code);
199     // cppcheck-suppress functionStatic
200     void cache(const std::string &code,
201                const datum::GeodeticReferenceFrameNNPtr &datum);
202 
203     datum::EllipsoidPtr
204         // cppcheck-suppress functionStatic
205         getEllipsoidFromCache(const std::string &code);
206     // cppcheck-suppress functionStatic
207     void cache(const std::string &code, const datum::EllipsoidNNPtr &ellipsoid);
208 
209     datum::PrimeMeridianPtr
210         // cppcheck-suppress functionStatic
211         getPrimeMeridianFromCache(const std::string &code);
212     // cppcheck-suppress functionStatic
213     void cache(const std::string &code, const datum::PrimeMeridianNNPtr &pm);
214 
215     // cppcheck-suppress functionStatic
216     cs::CoordinateSystemPtr
217     getCoordinateSystemFromCache(const std::string &code);
218     // cppcheck-suppress functionStatic
219     void cache(const std::string &code, const cs::CoordinateSystemNNPtr &cs);
220 
221     // cppcheck-suppress functionStatic
222     metadata::ExtentPtr getExtentFromCache(const std::string &code);
223     // cppcheck-suppress functionStatic
224     void cache(const std::string &code, const metadata::ExtentNNPtr &extent);
225 
226     // cppcheck-suppress functionStatic
227     bool getCRSToCRSCoordOpFromCache(
228         const std::string &code,
229         std::vector<operation::CoordinateOperationNNPtr> &list);
230     // cppcheck-suppress functionStatic
231     void cache(const std::string &code,
232                const std::vector<operation::CoordinateOperationNNPtr> &list);
233 
234     struct GridInfoCache {
235         std::string fullFilename{};
236         std::string packageName{};
237         std::string url{};
238         bool found = false;
239         bool directDownload = false;
240         bool openLicense = false;
241         bool gridAvailable = false;
242     };
243 
244     // cppcheck-suppress functionStatic
245     bool getGridInfoFromCache(const std::string &code, GridInfoCache &info);
246     // cppcheck-suppress functionStatic
247     void cache(const std::string &code, const GridInfoCache &info);
248 
249   private:
250     friend class DatabaseContext;
251 
252     std::string databasePath_{};
253     bool close_handle_ = true;
254     sqlite3 *sqlite_handle_{};
255     std::map<std::string, sqlite3_stmt *> mapSqlToStatement_{};
256     PJ_CONTEXT *pjCtxt_ = nullptr;
257     int recLevel_ = 0;
258     bool detach_ = false;
259     std::string lastMetadataValue_{};
260     std::map<std::string, std::list<SQLRow>> mapCanonicalizeGRFName_{};
261 
262     using LRUCacheOfObjects = lru11::Cache<std::string, util::BaseObjectPtr>;
263 
264     static constexpr size_t CACHE_SIZE = 128;
265     LRUCacheOfObjects cacheUOM_{CACHE_SIZE};
266     LRUCacheOfObjects cacheCRS_{CACHE_SIZE};
267     LRUCacheOfObjects cacheEllipsoid_{CACHE_SIZE};
268     LRUCacheOfObjects cacheGeodeticDatum_{CACHE_SIZE};
269     LRUCacheOfObjects cachePrimeMeridian_{CACHE_SIZE};
270     LRUCacheOfObjects cacheCS_{CACHE_SIZE};
271     LRUCacheOfObjects cacheExtent_{CACHE_SIZE};
272     lru11::Cache<std::string, std::vector<operation::CoordinateOperationNNPtr>>
273         cacheCRSToCrsCoordOp_{CACHE_SIZE};
274     lru11::Cache<std::string, GridInfoCache> cacheGridInfo_{CACHE_SIZE};
275 
276     std::map<std::string, std::vector<std::string>> cacheAllowedAuthorities_{};
277 
278     lru11::Cache<std::string, std::list<std::string>> cacheAliasNames_{
279         CACHE_SIZE};
280 
281     void checkDatabaseLayout();
282 
283     static void insertIntoCache(LRUCacheOfObjects &cache,
284                                 const std::string &code,
285                                 const util::BaseObjectPtr &obj);
286 
287     static void getFromCache(LRUCacheOfObjects &cache, const std::string &code,
288                              util::BaseObjectPtr &obj);
289 
290     void closeDB() noexcept;
291 
292     // cppcheck-suppress functionStatic
293     void registerFunctions();
294 
295 #ifdef ENABLE_CUSTOM_LOCKLESS_VFS
296     std::unique_ptr<SQLite3VFS> vfs_{};
297 #endif
298 
299     Private(const Private &) = delete;
300     Private &operator=(const Private &) = delete;
301 };
302 
303 // ---------------------------------------------------------------------------
304 
305 DatabaseContext::Private::Private() = default;
306 
307 // ---------------------------------------------------------------------------
308 
~Private()309 DatabaseContext::Private::~Private() {
310     assert(recLevel_ == 0);
311 
312     closeDB();
313 }
314 
315 // ---------------------------------------------------------------------------
316 
closeDB()317 void DatabaseContext::Private::closeDB() noexcept {
318 
319     if (detach_) {
320         // Workaround a bug visible in SQLite 3.8.1 and 3.8.2 that causes
321         // a crash in TEST(factory, attachExtraDatabases_auxiliary)
322         // due to possible wrong caching of key info.
323         // The bug is specific to using a memory file with shared cache as an
324         // auxiliary DB.
325         // The efinitive fix was likely in 3.8.8
326         // https://github.com/mackyle/sqlite/commit/d412d4b8731991ecbd8811874aa463d0821673eb
327         // But just after 3.8.2,
328         // https://github.com/mackyle/sqlite/commit/ccf328c4318eacedab9ed08c404bc4f402dcad19
329         // also seemed to hide the issue.
330         // Detaching a database hides the issue, not sure if it is by chance...
331         try {
332             run("DETACH DATABASE db_0");
333         } catch (...) {
334         }
335         detach_ = false;
336     }
337 
338     for (auto &pair : mapSqlToStatement_) {
339         sqlite3_finalize(pair.second);
340     }
341     mapSqlToStatement_.clear();
342 
343     if (close_handle_ && sqlite_handle_ != nullptr) {
344         sqlite3_close(sqlite_handle_);
345         sqlite_handle_ = nullptr;
346     }
347 }
348 
349 // ---------------------------------------------------------------------------
350 
insertIntoCache(LRUCacheOfObjects & cache,const std::string & code,const util::BaseObjectPtr & obj)351 void DatabaseContext::Private::insertIntoCache(LRUCacheOfObjects &cache,
352                                                const std::string &code,
353                                                const util::BaseObjectPtr &obj) {
354     cache.insert(code, obj);
355 }
356 
357 // ---------------------------------------------------------------------------
358 
getFromCache(LRUCacheOfObjects & cache,const std::string & code,util::BaseObjectPtr & obj)359 void DatabaseContext::Private::getFromCache(LRUCacheOfObjects &cache,
360                                             const std::string &code,
361                                             util::BaseObjectPtr &obj) {
362     cache.tryGet(code, obj);
363 }
364 
365 // ---------------------------------------------------------------------------
366 
getCRSToCRSCoordOpFromCache(const std::string & code,std::vector<operation::CoordinateOperationNNPtr> & list)367 bool DatabaseContext::Private::getCRSToCRSCoordOpFromCache(
368     const std::string &code,
369     std::vector<operation::CoordinateOperationNNPtr> &list) {
370     return cacheCRSToCrsCoordOp_.tryGet(code, list);
371 }
372 
373 // ---------------------------------------------------------------------------
374 
cache(const std::string & code,const std::vector<operation::CoordinateOperationNNPtr> & list)375 void DatabaseContext::Private::cache(
376     const std::string &code,
377     const std::vector<operation::CoordinateOperationNNPtr> &list) {
378     cacheCRSToCrsCoordOp_.insert(code, list);
379 }
380 
381 // ---------------------------------------------------------------------------
382 
getCRSFromCache(const std::string & code)383 crs::CRSPtr DatabaseContext::Private::getCRSFromCache(const std::string &code) {
384     util::BaseObjectPtr obj;
385     getFromCache(cacheCRS_, code, obj);
386     return std::static_pointer_cast<crs::CRS>(obj);
387 }
388 
389 // ---------------------------------------------------------------------------
390 
cache(const std::string & code,const crs::CRSNNPtr & crs)391 void DatabaseContext::Private::cache(const std::string &code,
392                                      const crs::CRSNNPtr &crs) {
393     insertIntoCache(cacheCRS_, code, crs.as_nullable());
394 }
395 
396 // ---------------------------------------------------------------------------
397 
398 common::UnitOfMeasurePtr
getUOMFromCache(const std::string & code)399 DatabaseContext::Private::getUOMFromCache(const std::string &code) {
400     util::BaseObjectPtr obj;
401     getFromCache(cacheUOM_, code, obj);
402     return std::static_pointer_cast<common::UnitOfMeasure>(obj);
403 }
404 
405 // ---------------------------------------------------------------------------
406 
cache(const std::string & code,const common::UnitOfMeasureNNPtr & uom)407 void DatabaseContext::Private::cache(const std::string &code,
408                                      const common::UnitOfMeasureNNPtr &uom) {
409     insertIntoCache(cacheUOM_, code, uom.as_nullable());
410 }
411 
412 // ---------------------------------------------------------------------------
413 
414 datum::GeodeticReferenceFramePtr
getGeodeticDatumFromCache(const std::string & code)415 DatabaseContext::Private::getGeodeticDatumFromCache(const std::string &code) {
416     util::BaseObjectPtr obj;
417     getFromCache(cacheGeodeticDatum_, code, obj);
418     return std::static_pointer_cast<datum::GeodeticReferenceFrame>(obj);
419 }
420 
421 // ---------------------------------------------------------------------------
422 
cache(const std::string & code,const datum::GeodeticReferenceFrameNNPtr & datum)423 void DatabaseContext::Private::cache(
424     const std::string &code, const datum::GeodeticReferenceFrameNNPtr &datum) {
425     insertIntoCache(cacheGeodeticDatum_, code, datum.as_nullable());
426 }
427 
428 // ---------------------------------------------------------------------------
429 
430 datum::EllipsoidPtr
getEllipsoidFromCache(const std::string & code)431 DatabaseContext::Private::getEllipsoidFromCache(const std::string &code) {
432     util::BaseObjectPtr obj;
433     getFromCache(cacheEllipsoid_, code, obj);
434     return std::static_pointer_cast<datum::Ellipsoid>(obj);
435 }
436 
437 // ---------------------------------------------------------------------------
438 
cache(const std::string & code,const datum::EllipsoidNNPtr & ellps)439 void DatabaseContext::Private::cache(const std::string &code,
440                                      const datum::EllipsoidNNPtr &ellps) {
441     insertIntoCache(cacheEllipsoid_, code, ellps.as_nullable());
442 }
443 
444 // ---------------------------------------------------------------------------
445 
446 datum::PrimeMeridianPtr
getPrimeMeridianFromCache(const std::string & code)447 DatabaseContext::Private::getPrimeMeridianFromCache(const std::string &code) {
448     util::BaseObjectPtr obj;
449     getFromCache(cachePrimeMeridian_, code, obj);
450     return std::static_pointer_cast<datum::PrimeMeridian>(obj);
451 }
452 
453 // ---------------------------------------------------------------------------
454 
cache(const std::string & code,const datum::PrimeMeridianNNPtr & pm)455 void DatabaseContext::Private::cache(const std::string &code,
456                                      const datum::PrimeMeridianNNPtr &pm) {
457     insertIntoCache(cachePrimeMeridian_, code, pm.as_nullable());
458 }
459 
460 // ---------------------------------------------------------------------------
461 
getCoordinateSystemFromCache(const std::string & code)462 cs::CoordinateSystemPtr DatabaseContext::Private::getCoordinateSystemFromCache(
463     const std::string &code) {
464     util::BaseObjectPtr obj;
465     getFromCache(cacheCS_, code, obj);
466     return std::static_pointer_cast<cs::CoordinateSystem>(obj);
467 }
468 
469 // ---------------------------------------------------------------------------
470 
cache(const std::string & code,const cs::CoordinateSystemNNPtr & cs)471 void DatabaseContext::Private::cache(const std::string &code,
472                                      const cs::CoordinateSystemNNPtr &cs) {
473     insertIntoCache(cacheCS_, code, cs.as_nullable());
474 }
475 
476 // ---------------------------------------------------------------------------
477 
478 metadata::ExtentPtr
getExtentFromCache(const std::string & code)479 DatabaseContext::Private::getExtentFromCache(const std::string &code) {
480     util::BaseObjectPtr obj;
481     getFromCache(cacheExtent_, code, obj);
482     return std::static_pointer_cast<metadata::Extent>(obj);
483 }
484 
485 // ---------------------------------------------------------------------------
486 
cache(const std::string & code,const metadata::ExtentNNPtr & extent)487 void DatabaseContext::Private::cache(const std::string &code,
488                                      const metadata::ExtentNNPtr &extent) {
489     insertIntoCache(cacheExtent_, code, extent.as_nullable());
490 }
491 
492 // ---------------------------------------------------------------------------
493 
getGridInfoFromCache(const std::string & code,GridInfoCache & info)494 bool DatabaseContext::Private::getGridInfoFromCache(const std::string &code,
495                                                     GridInfoCache &info) {
496     return cacheGridInfo_.tryGet(code, info);
497 }
498 
499 // ---------------------------------------------------------------------------
500 
cache(const std::string & code,const GridInfoCache & info)501 void DatabaseContext::Private::cache(const std::string &code,
502                                      const GridInfoCache &info) {
503     cacheGridInfo_.insert(code, info);
504 }
505 
506 // ---------------------------------------------------------------------------
507 
open(const std::string & databasePath,PJ_CONTEXT * ctx)508 void DatabaseContext::Private::open(const std::string &databasePath,
509                                     PJ_CONTEXT *ctx) {
510     if (!ctx) {
511         ctx = pj_get_default_ctx();
512     }
513 
514     const int sqlite3VersionNumber = sqlite3_libversion_number();
515     // Minimum version for correct performance: 3.11
516     if (sqlite3VersionNumber < 3 * 1000000 + 11 * 1000) {
517         pj_log(ctx, PJ_LOG_ERROR,
518                "SQLite3 version is %s, whereas at least 3.11 should be used",
519                sqlite3_libversion());
520     }
521 
522     setPjCtxt(ctx);
523     std::string path(databasePath);
524     if (path.empty()) {
525         path.resize(2048);
526         const bool found =
527             pj_find_file(pjCtxt(), "proj.db", &path[0], path.size() - 1) != 0;
528         path.resize(strlen(path.c_str()));
529         if (!found) {
530             throw FactoryException("Cannot find proj.db");
531         }
532     }
533 
534     std::string vfsName;
535 #ifdef ENABLE_CUSTOM_LOCKLESS_VFS
536     if (ctx->custom_sqlite3_vfs_name.empty()) {
537         vfs_ = SQLite3VFS::create(false, true, true);
538         if (vfs_ == nullptr) {
539             throw FactoryException("Open of " + path + " failed");
540         }
541         vfsName = vfs_->name();
542     } else
543 #endif
544     {
545         vfsName = ctx->custom_sqlite3_vfs_name;
546     }
547     if (sqlite3_open_v2(path.c_str(), &sqlite_handle_,
548                         SQLITE_OPEN_READONLY | SQLITE_OPEN_NOMUTEX,
549                         vfsName.empty() ? nullptr : vfsName.c_str()) !=
550             SQLITE_OK ||
551         !sqlite_handle_) {
552         throw FactoryException("Open of " + path + " failed");
553     }
554 
555     databasePath_ = path;
556     registerFunctions();
557 }
558 
559 // ---------------------------------------------------------------------------
560 
checkDatabaseLayout()561 void DatabaseContext::Private::checkDatabaseLayout() {
562     auto res = run("SELECT key, value FROM metadata WHERE key IN "
563                    "('DATABASE.LAYOUT.VERSION.MAJOR', "
564                    "'DATABASE.LAYOUT.VERSION.MINOR')");
565     if (res.size() != 2) {
566         // The database layout of PROJ 7.2 that shipped with EPSG v10.003 is
567         // at the time of writing still compatible of the one we support.
568         static_assert(
569             // cppcheck-suppress knownConditionTrueFalse
570             DATABASE_LAYOUT_VERSION_MAJOR == 1 &&
571                 // cppcheck-suppress knownConditionTrueFalse
572                 DATABASE_LAYOUT_VERSION_MINOR == 0,
573             "remove that assertion and below lines next time we upgrade "
574             "database structure");
575         res = run("SELECT 1 FROM metadata WHERE key = 'EPSG.VERSION' AND "
576                   "value = 'v10.003'");
577         if (!res.empty()) {
578             return;
579         }
580 
581         throw FactoryException(
582             databasePath_ +
583             " lacks DATABASE.LAYOUT.VERSION.MAJOR / "
584             "DATABASE.LAYOUT.VERSION.MINOR "
585             "metadata. It comes from another PROJ installation.");
586     }
587     int nMajor = 0;
588     int nMinor = 0;
589     for (const auto &row : res) {
590         if (row[0] == "DATABASE.LAYOUT.VERSION.MAJOR") {
591             nMajor = atoi(row[1].c_str());
592         } else if (row[0] == "DATABASE.LAYOUT.VERSION.MINOR") {
593             nMinor = atoi(row[1].c_str());
594         }
595     }
596     if (nMajor != DATABASE_LAYOUT_VERSION_MAJOR) {
597         throw FactoryException(databasePath_ +
598                                " contains DATABASE.LAYOUT.VERSION.MAJOR = " +
599                                toString(nMajor) + " whereas " +
600                                toString(DATABASE_LAYOUT_VERSION_MAJOR) +
601                                " is expected. "
602                                "It comes from another PROJ installation.");
603     }
604     if (nMinor < DATABASE_LAYOUT_VERSION_MINOR) {
605         throw FactoryException(databasePath_ +
606                                " contains DATABASE.LAYOUT.VERSION.MINOR = " +
607                                toString(nMinor) + " whereas a number >= " +
608                                toString(DATABASE_LAYOUT_VERSION_MINOR) +
609                                " is expected. "
610                                "It comes from another PROJ installation.");
611     }
612 }
613 
614 // ---------------------------------------------------------------------------
615 
setHandle(sqlite3 * sqlite_handle)616 void DatabaseContext::Private::setHandle(sqlite3 *sqlite_handle) {
617 
618     assert(sqlite_handle);
619     assert(!sqlite_handle_);
620     sqlite_handle_ = sqlite_handle;
621     close_handle_ = false;
622 
623     registerFunctions();
624 }
625 
626 // ---------------------------------------------------------------------------
627 
getDatabaseStructure()628 std::vector<std::string> DatabaseContext::Private::getDatabaseStructure() {
629     const char *sqls[] = {
630         "SELECT sql FROM sqlite_master WHERE type = 'table'",
631         "SELECT sql FROM sqlite_master WHERE type = 'view'",
632         "SELECT sql FROM sqlite_master WHERE type = 'trigger'"};
633     std::vector<std::string> res;
634     for (const auto &sql : sqls) {
635         auto sqlRes = run(sql);
636         for (const auto &row : sqlRes) {
637             res.emplace_back(row[0]);
638         }
639     }
640     return res;
641 }
642 
643 // ---------------------------------------------------------------------------
644 
attachExtraDatabases(const std::vector<std::string> & auxiliaryDatabasePaths)645 void DatabaseContext::Private::attachExtraDatabases(
646     const std::vector<std::string> &auxiliaryDatabasePaths) {
647     assert(close_handle_);
648     assert(sqlite_handle_);
649 
650     auto tables =
651         run("SELECT name FROM sqlite_master WHERE type IN ('table', 'view')");
652     std::map<std::string, std::vector<std::string>> tableStructure;
653     for (const auto &rowTable : tables) {
654         auto tableName = rowTable[0];
655         auto tableInfo = run("PRAGMA table_info(\"" +
656                              replaceAll(tableName, "\"", "\"\"") + "\")");
657         for (const auto &rowCol : tableInfo) {
658             const auto &colName = rowCol[1];
659             tableStructure[tableName].push_back(colName);
660         }
661     }
662 
663     closeDB();
664 
665     sqlite3_open_v2(":memory:", &sqlite_handle_,
666                     SQLITE_OPEN_READWRITE | SQLITE_OPEN_NOMUTEX
667 #ifdef SQLITE_OPEN_URI
668                         | SQLITE_OPEN_URI
669 #endif
670                     ,
671                     nullptr);
672     if (!sqlite_handle_) {
673         throw FactoryException("cannot create in memory database");
674     }
675 
676     run("ATTACH DATABASE '" + replaceAll(databasePath_, "'", "''") +
677         "' AS db_0");
678     detach_ = true;
679     int count = 1;
680     for (const auto &otherDb : auxiliaryDatabasePaths) {
681         std::string sql = "ATTACH DATABASE '";
682         sql += replaceAll(otherDb, "'", "''");
683         sql += "' AS db_";
684         sql += toString(static_cast<int>(count));
685         count++;
686         run(sql);
687     }
688 
689     for (const auto &pair : tableStructure) {
690         std::string sql("CREATE TEMP VIEW ");
691         sql += pair.first;
692         sql += " AS ";
693         for (size_t i = 0; i <= auxiliaryDatabasePaths.size(); ++i) {
694             std::string selectFromAux("SELECT ");
695             bool firstCol = true;
696             for (const auto &colName : pair.second) {
697                 if (!firstCol) {
698                     selectFromAux += ", ";
699                 }
700                 firstCol = false;
701                 selectFromAux += colName;
702             }
703             selectFromAux += " FROM db_";
704             selectFromAux += toString(static_cast<int>(i));
705             selectFromAux += ".";
706             selectFromAux += pair.first;
707 
708             try {
709                 // Check that the request will succeed. In case of 'sparse'
710                 // databases...
711                 run(selectFromAux + " LIMIT 0");
712 
713                 if (i > 0) {
714                     sql += " UNION ALL ";
715                 }
716                 sql += selectFromAux;
717             } catch (const std::exception &) {
718             }
719         }
720         run(sql);
721     }
722 
723     registerFunctions();
724 }
725 
726 // ---------------------------------------------------------------------------
727 
PROJ_SQLITE_GetValAsDouble(sqlite3_value * val,bool & gotVal)728 static double PROJ_SQLITE_GetValAsDouble(sqlite3_value *val, bool &gotVal) {
729     switch (sqlite3_value_type(val)) {
730     case SQLITE_FLOAT:
731         gotVal = true;
732         return sqlite3_value_double(val);
733 
734     case SQLITE_INTEGER:
735         gotVal = true;
736         return static_cast<double>(sqlite3_value_int64(val));
737 
738     default:
739         gotVal = false;
740         return 0.0;
741     }
742 }
743 
744 // ---------------------------------------------------------------------------
745 
PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context * pContext,int,sqlite3_value ** argv)746 static void PROJ_SQLITE_pseudo_area_from_swne(sqlite3_context *pContext,
747                                               int /* argc */,
748                                               sqlite3_value **argv) {
749     bool b0, b1, b2, b3;
750     double south_lat = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
751     double west_lon = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
752     double north_lat = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
753     double east_lon = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
754     if (!b0 || !b1 || !b2 || !b3) {
755         sqlite3_result_null(pContext);
756         return;
757     }
758     // Deal with area crossing antimeridian
759     if (east_lon < west_lon) {
760         east_lon += 360.0;
761     }
762     // Integrate cos(lat) between south_lat and north_lat
763     double pseudo_area = (east_lon - west_lon) *
764                          (std::sin(common::Angle(north_lat).getSIValue()) -
765                           std::sin(common::Angle(south_lat).getSIValue()));
766     sqlite3_result_double(pContext, pseudo_area);
767 }
768 
769 // ---------------------------------------------------------------------------
770 
PROJ_SQLITE_intersects_bbox(sqlite3_context * pContext,int,sqlite3_value ** argv)771 static void PROJ_SQLITE_intersects_bbox(sqlite3_context *pContext,
772                                         int /* argc */, sqlite3_value **argv) {
773     bool b0, b1, b2, b3, b4, b5, b6, b7;
774     double south_lat1 = PROJ_SQLITE_GetValAsDouble(argv[0], b0);
775     double west_lon1 = PROJ_SQLITE_GetValAsDouble(argv[1], b1);
776     double north_lat1 = PROJ_SQLITE_GetValAsDouble(argv[2], b2);
777     double east_lon1 = PROJ_SQLITE_GetValAsDouble(argv[3], b3);
778     double south_lat2 = PROJ_SQLITE_GetValAsDouble(argv[4], b4);
779     double west_lon2 = PROJ_SQLITE_GetValAsDouble(argv[5], b5);
780     double north_lat2 = PROJ_SQLITE_GetValAsDouble(argv[6], b6);
781     double east_lon2 = PROJ_SQLITE_GetValAsDouble(argv[7], b7);
782     if (!b0 || !b1 || !b2 || !b3 || !b4 || !b5 || !b6 || !b7) {
783         sqlite3_result_null(pContext);
784         return;
785     }
786     auto bbox1 = metadata::GeographicBoundingBox::create(west_lon1, south_lat1,
787                                                          east_lon1, north_lat1);
788     auto bbox2 = metadata::GeographicBoundingBox::create(west_lon2, south_lat2,
789                                                          east_lon2, north_lat2);
790     sqlite3_result_int(pContext, bbox1->intersects(bbox2) ? 1 : 0);
791 }
792 
793 // ---------------------------------------------------------------------------
794 
795 #ifndef SQLITE_DETERMINISTIC
796 #define SQLITE_DETERMINISTIC 0
797 #endif
798 
registerFunctions()799 void DatabaseContext::Private::registerFunctions() {
800     sqlite3_create_function(sqlite_handle_, "pseudo_area_from_swne", 4,
801                             SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
802                             PROJ_SQLITE_pseudo_area_from_swne, nullptr,
803                             nullptr);
804 
805     sqlite3_create_function(sqlite_handle_, "intersects_bbox", 8,
806                             SQLITE_UTF8 | SQLITE_DETERMINISTIC, nullptr,
807                             PROJ_SQLITE_intersects_bbox, nullptr, nullptr);
808 }
809 
810 // ---------------------------------------------------------------------------
811 
run(const std::string & sql,const ListOfParams & parameters,bool useMaxFloatPrecision)812 SQLResultSet DatabaseContext::Private::run(const std::string &sql,
813                                            const ListOfParams &parameters,
814                                            bool useMaxFloatPrecision) {
815 
816     sqlite3_stmt *stmt = nullptr;
817     auto iter = mapSqlToStatement_.find(sql);
818     if (iter != mapSqlToStatement_.end()) {
819         stmt = iter->second;
820         sqlite3_reset(stmt);
821     } else {
822         if (sqlite3_prepare_v2(sqlite_handle_, sql.c_str(),
823                                static_cast<int>(sql.size()), &stmt,
824                                nullptr) != SQLITE_OK) {
825             throw FactoryException("SQLite error on " + sql + ": " +
826                                    sqlite3_errmsg(sqlite_handle_));
827         }
828         mapSqlToStatement_.insert(
829             std::pair<std::string, sqlite3_stmt *>(sql, stmt));
830     }
831 
832     int nBindField = 1;
833     for (const auto &param : parameters) {
834         if (param.type() == SQLValues::Type::STRING) {
835             auto strValue = param.stringValue();
836             sqlite3_bind_text(stmt, nBindField, strValue.c_str(),
837                               static_cast<int>(strValue.size()),
838                               SQLITE_TRANSIENT);
839         } else {
840             assert(param.type() == SQLValues::Type::DOUBLE);
841             sqlite3_bind_double(stmt, nBindField, param.doubleValue());
842         }
843         nBindField++;
844     }
845 
846 #ifdef TRACE_DATABASE
847     size_t nPos = 0;
848     std::string sqlSubst(sql);
849     for (const auto &param : parameters) {
850         nPos = sqlSubst.find('?', nPos);
851         assert(nPos != std::string::npos);
852         std::string strValue;
853         if (param.type() == SQLValues::Type::STRING) {
854             strValue = '\'' + param.stringValue() + '\'';
855         } else {
856             strValue = toString(param.doubleValue());
857         }
858         sqlSubst =
859             sqlSubst.substr(0, nPos) + strValue + sqlSubst.substr(nPos + 1);
860         nPos += strValue.size();
861     }
862     logTrace(sqlSubst, "DATABASE");
863 #endif
864 
865     SQLResultSet result;
866     const int column_count = sqlite3_column_count(stmt);
867     while (true) {
868         int ret = sqlite3_step(stmt);
869         if (ret == SQLITE_ROW) {
870             SQLRow row(column_count);
871             for (int i = 0; i < column_count; i++) {
872                 if (useMaxFloatPrecision &&
873                     sqlite3_column_type(stmt, i) == SQLITE_FLOAT) {
874                     // sqlite3_column_text() does not use maximum precision
875                     std::ostringstream buffer;
876                     buffer.imbue(std::locale::classic());
877                     buffer << std::setprecision(18);
878                     buffer << sqlite3_column_double(stmt, i);
879                     row[i] = buffer.str();
880                 } else {
881                     const char *txt = reinterpret_cast<const char *>(
882                         sqlite3_column_text(stmt, i));
883                     if (txt) {
884                         row[i] = txt;
885                     }
886                 }
887             }
888             result.emplace_back(std::move(row));
889         } else if (ret == SQLITE_DONE) {
890             break;
891         } else {
892             throw FactoryException("SQLite error on " + sql + ": " +
893                                    sqlite3_errmsg(sqlite_handle_));
894         }
895     }
896     return result;
897 }
898 
899 //! @endcond
900 
901 // ---------------------------------------------------------------------------
902 
903 //! @cond Doxygen_Suppress
904 DatabaseContext::~DatabaseContext() = default;
905 //! @endcond
906 
907 // ---------------------------------------------------------------------------
908 
DatabaseContext()909 DatabaseContext::DatabaseContext() : d(internal::make_unique<Private>()) {}
910 
911 // ---------------------------------------------------------------------------
912 
913 /** \brief Instantiate a database context.
914  *
915  * This database context should be used only by one thread at a time.
916  *
917  * @param databasePath Path and filename of the database. Might be empty
918  * string for the default rules to locate the default proj.db
919  * @param auxiliaryDatabasePaths Path and filename of auxiliary databases.
920  * Might be empty.
921  * @param ctx Context used for file search.
922  * @throw FactoryException
923  */
924 DatabaseContextNNPtr
create(const std::string & databasePath,const std::vector<std::string> & auxiliaryDatabasePaths,PJ_CONTEXT * ctx)925 DatabaseContext::create(const std::string &databasePath,
926                         const std::vector<std::string> &auxiliaryDatabasePaths,
927                         PJ_CONTEXT *ctx) {
928     auto dbCtx = DatabaseContext::nn_make_shared<DatabaseContext>();
929     dbCtx->getPrivate()->open(databasePath, ctx);
930     if (!auxiliaryDatabasePaths.empty()) {
931         dbCtx->getPrivate()->attachExtraDatabases(auxiliaryDatabasePaths);
932     }
933     dbCtx->getPrivate()->checkDatabaseLayout();
934     return dbCtx;
935 }
936 
937 // ---------------------------------------------------------------------------
938 
939 /** \brief Return the list of authorities used in the database.
940  */
getAuthorities() const941 std::set<std::string> DatabaseContext::getAuthorities() const {
942     auto res = d->run("SELECT auth_name FROM authority_list");
943     std::set<std::string> list;
944     for (const auto &row : res) {
945         list.insert(row[0]);
946     }
947     return list;
948 }
949 
950 // ---------------------------------------------------------------------------
951 
952 /** \brief Return the list of SQL commands (CREATE TABLE, CREATE TRIGGER,
953  * CREATE VIEW) needed to initialize a new database.
954  */
getDatabaseStructure() const955 std::vector<std::string> DatabaseContext::getDatabaseStructure() const {
956     return d->getDatabaseStructure();
957 }
958 
959 // ---------------------------------------------------------------------------
960 
961 /** \brief Return the path to the database.
962  */
getPath() const963 const std::string &DatabaseContext::getPath() const { return d->getPath(); }
964 
965 // ---------------------------------------------------------------------------
966 
967 /** \brief Return a metadata item.
968  *
969  * Value remains valid while this is alive and to the next call to getMetadata
970  */
getMetadata(const char * key) const971 const char *DatabaseContext::getMetadata(const char *key) const {
972     auto res =
973         d->run("SELECT value FROM metadata WHERE key = ?", {std::string(key)});
974     if (res.empty()) {
975         return nullptr;
976     }
977     d->lastMetadataValue_ = res.front()[0];
978     return d->lastMetadataValue_.c_str();
979 }
980 
981 // ---------------------------------------------------------------------------
982 
983 //! @cond Doxygen_Suppress
984 
create(void * sqlite_handle)985 DatabaseContextNNPtr DatabaseContext::create(void *sqlite_handle) {
986     auto ctxt = DatabaseContext::nn_make_shared<DatabaseContext>();
987     ctxt->getPrivate()->setHandle(static_cast<sqlite3 *>(sqlite_handle));
988     return ctxt;
989 }
990 
991 // ---------------------------------------------------------------------------
992 
getSqliteHandle() const993 void *DatabaseContext::getSqliteHandle() const {
994     return getPrivate()->handle();
995 }
996 
997 // ---------------------------------------------------------------------------
998 
lookForGridAlternative(const std::string & officialName,std::string & projFilename,std::string & projFormat,bool & inverse) const999 bool DatabaseContext::lookForGridAlternative(const std::string &officialName,
1000                                              std::string &projFilename,
1001                                              std::string &projFormat,
1002                                              bool &inverse) const {
1003     auto res = d->run(
1004         "SELECT proj_grid_name, proj_grid_format, inverse_direction FROM "
1005         "grid_alternatives WHERE original_grid_name = ? AND "
1006         "proj_grid_name <> ''",
1007         {officialName});
1008     if (res.empty()) {
1009         return false;
1010     }
1011     const auto &row = res.front();
1012     projFilename = row[0];
1013     projFormat = row[1];
1014     inverse = row[2] == "1";
1015     return true;
1016 }
1017 
1018 // ---------------------------------------------------------------------------
1019 
lookForGridInfo(const std::string & projFilename,bool considerKnownGridsAsAvailable,std::string & fullFilename,std::string & packageName,std::string & url,bool & directDownload,bool & openLicense,bool & gridAvailable) const1020 bool DatabaseContext::lookForGridInfo(
1021     const std::string &projFilename, bool considerKnownGridsAsAvailable,
1022     std::string &fullFilename, std::string &packageName, std::string &url,
1023     bool &directDownload, bool &openLicense, bool &gridAvailable) const {
1024     Private::GridInfoCache info;
1025     const std::string key(projFilename +
1026                           (considerKnownGridsAsAvailable ? "true" : "false"));
1027     if (d->getGridInfoFromCache(key, info)) {
1028         fullFilename = info.fullFilename;
1029         packageName = info.packageName;
1030         url = info.url;
1031         directDownload = info.directDownload;
1032         openLicense = info.openLicense;
1033         gridAvailable = info.gridAvailable;
1034         return info.found;
1035     }
1036 
1037     fullFilename.clear();
1038     packageName.clear();
1039     url.clear();
1040     openLicense = false;
1041     directDownload = false;
1042 
1043     if (considerKnownGridsAsAvailable) {
1044         fullFilename = projFilename;
1045     } else {
1046         fullFilename.resize(2048);
1047         if (d->pjCtxt() == nullptr) {
1048             d->setPjCtxt(pj_get_default_ctx());
1049         }
1050         int errno_before = proj_context_errno(d->pjCtxt());
1051         gridAvailable =
1052             pj_find_file(d->pjCtxt(), projFilename.c_str(), &fullFilename[0],
1053                          fullFilename.size() - 1) != 0;
1054         proj_context_errno_set(d->pjCtxt(), errno_before);
1055         fullFilename.resize(strlen(fullFilename.c_str()));
1056     }
1057 
1058     auto res =
1059         d->run("SELECT "
1060                "grid_packages.package_name, "
1061                "grid_alternatives.url, "
1062                "grid_packages.url AS package_url, "
1063                "grid_alternatives.open_license, "
1064                "grid_packages.open_license AS package_open_license, "
1065                "grid_alternatives.direct_download, "
1066                "grid_packages.direct_download AS package_direct_download "
1067                "FROM grid_alternatives "
1068                "LEFT JOIN grid_packages ON "
1069                "grid_alternatives.package_name = grid_packages.package_name "
1070                "WHERE proj_grid_name = ? OR old_proj_grid_name = ?",
1071                {projFilename, projFilename});
1072     bool ret = !res.empty();
1073     if (ret) {
1074         const auto &row = res.front();
1075         packageName = std::move(row[0]);
1076         url = row[1].empty() ? std::move(row[2]) : std::move(row[1]);
1077         openLicense = (row[3].empty() ? row[4] : row[3]) == "1";
1078         directDownload = (row[5].empty() ? row[6] : row[5]) == "1";
1079 
1080         if (considerKnownGridsAsAvailable &&
1081             (!packageName.empty() || (!url.empty() && openLicense))) {
1082             gridAvailable = true;
1083         }
1084 
1085         info.fullFilename = fullFilename;
1086         info.packageName = packageName;
1087         info.url = url;
1088         info.directDownload = directDownload;
1089         info.openLicense = openLicense;
1090     }
1091     info.gridAvailable = gridAvailable;
1092     info.found = ret;
1093     d->cache(key, info);
1094     return ret;
1095 }
1096 
1097 // ---------------------------------------------------------------------------
1098 
isKnownName(const std::string & name,const std::string & tableName) const1099 bool DatabaseContext::isKnownName(const std::string &name,
1100                                   const std::string &tableName) const {
1101     std::string sql("SELECT 1 FROM \"");
1102     sql += replaceAll(tableName, "\"", "\"\"");
1103     sql += "\" WHERE name = ? LIMIT 1";
1104     return !d->run(sql, {name}).empty();
1105 }
1106 // ---------------------------------------------------------------------------
1107 
1108 std::string
getProjGridName(const std::string & oldProjGridName)1109 DatabaseContext::getProjGridName(const std::string &oldProjGridName) {
1110     auto res = d->run("SELECT proj_grid_name FROM grid_alternatives WHERE "
1111                       "old_proj_grid_name = ?",
1112                       {oldProjGridName});
1113     if (res.empty()) {
1114         return std::string();
1115     }
1116     return res.front()[0];
1117 }
1118 
1119 // ---------------------------------------------------------------------------
1120 
getOldProjGridName(const std::string & gridName)1121 std::string DatabaseContext::getOldProjGridName(const std::string &gridName) {
1122     auto res = d->run("SELECT old_proj_grid_name FROM grid_alternatives WHERE "
1123                       "proj_grid_name = ?",
1124                       {gridName});
1125     if (res.empty()) {
1126         return std::string();
1127     }
1128     return res.front()[0];
1129 }
1130 
1131 // ---------------------------------------------------------------------------
1132 
1133 // FIXME: as we don't support datum ensemble yet, add it from name
removeEnsembleSuffix(const std::string & name)1134 static std::string removeEnsembleSuffix(const std::string &name) {
1135     if (name == "World Geodetic System 1984 ensemble") {
1136         return "World Geodetic System 1984";
1137     } else if (name == "European Terrestrial Reference System 1989 ensemble") {
1138         return "European Terrestrial Reference System 1989";
1139     }
1140     return name;
1141 }
1142 
1143 // ---------------------------------------------------------------------------
1144 
1145 /** \brief Gets the alias name from an official name.
1146  *
1147  * @param officialName Official name. Mandatory
1148  * @param tableName Table name/category. Mandatory
1149  * @param source Source of the alias. Mandatory
1150  * @return Alias name (or empty if not found).
1151  * @throw FactoryException
1152  */
1153 std::string
getAliasFromOfficialName(const std::string & officialName,const std::string & tableName,const std::string & source) const1154 DatabaseContext::getAliasFromOfficialName(const std::string &officialName,
1155                                           const std::string &tableName,
1156                                           const std::string &source) const {
1157     std::string sql("SELECT auth_name, code FROM \"");
1158     sql += replaceAll(tableName, "\"", "\"\"");
1159     sql += "\" WHERE name = ?";
1160     if (tableName == "geodetic_crs") {
1161         sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
1162     }
1163     auto res = d->run(sql, {officialName});
1164     if (res.empty()) {
1165         res = d->run(
1166             "SELECT auth_name, code FROM alias_name WHERE table_name = ? AND "
1167             "alt_name = ? AND source IN ('EPSG', 'PROJ')",
1168             {tableName, officialName});
1169         if (res.size() != 1) {
1170             return std::string();
1171         }
1172     }
1173     const auto &row = res.front();
1174     res = d->run("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
1175                  "auth_name = ? AND code = ? AND source = ?",
1176                  {tableName, row[0], row[1], source});
1177     if (res.empty()) {
1178         return std::string();
1179     }
1180     return res.front()[0];
1181 }
1182 
1183 // ---------------------------------------------------------------------------
1184 
1185 /** \brief Gets the alias names for an object.
1186  *
1187  * Either authName + code or officialName must be non empty.
1188  *
1189  * @param authName Authority.
1190  * @param code Code.
1191  * @param officialName Official name.
1192  * @param tableName Table name/category. Mandatory
1193  * @param source Source of the alias. May be empty.
1194  * @return Aliases
1195  */
getAliases(const std::string & authName,const std::string & code,const std::string & officialName,const std::string & tableName,const std::string & source) const1196 std::list<std::string> DatabaseContext::getAliases(
1197     const std::string &authName, const std::string &code,
1198     const std::string &officialName, const std::string &tableName,
1199     const std::string &source) const {
1200 
1201     std::list<std::string> res;
1202     const auto key(authName + code + officialName + tableName + source);
1203     if (d->cacheAliasNames_.tryGet(key, res)) {
1204         return res;
1205     }
1206 
1207     std::string resolvedAuthName(authName);
1208     std::string resolvedCode(code);
1209     if (authName.empty() || code.empty()) {
1210         std::string sql("SELECT auth_name, code FROM \"");
1211         sql += replaceAll(tableName, "\"", "\"\"");
1212         sql += "\" WHERE name = ?";
1213         if (tableName == "geodetic_crs") {
1214             sql += " AND type = " GEOG_2D_SINGLE_QUOTED;
1215         }
1216         auto resSql = d->run(sql, {officialName});
1217         if (resSql.empty()) {
1218             resSql = d->run("SELECT auth_name, code FROM alias_name WHERE "
1219                             "table_name = ? AND "
1220                             "alt_name = ? AND source IN ('EPSG', 'PROJ')",
1221                             {tableName, officialName});
1222             if (resSql.size() != 1) {
1223                 d->cacheAliasNames_.insert(key, res);
1224                 return res;
1225             }
1226         }
1227         const auto &row = resSql.front();
1228         resolvedAuthName = row[0];
1229         resolvedCode = row[1];
1230     }
1231     std::string sql("SELECT alt_name FROM alias_name WHERE table_name = ? AND "
1232                     "auth_name = ? AND code = ?");
1233     ListOfParams params{tableName, resolvedAuthName, resolvedCode};
1234     if (!source.empty()) {
1235         sql += " AND source = ?";
1236         params.emplace_back(source);
1237     }
1238     auto resSql = d->run(sql, params);
1239     for (const auto &row : resSql) {
1240         res.emplace_back(row[0]);
1241     }
1242     d->cacheAliasNames_.insert(key, res);
1243     return res;
1244 }
1245 
1246 // ---------------------------------------------------------------------------
1247 
1248 /** \brief Return the 'text_definition' column of a table for an object
1249  *
1250  * @param tableName Table name/category.
1251  * @param authName Authority name of the object.
1252  * @param code Code of the object
1253  * @return Text definition (or empty)
1254  * @throw FactoryException
1255  */
getTextDefinition(const std::string & tableName,const std::string & authName,const std::string & code) const1256 std::string DatabaseContext::getTextDefinition(const std::string &tableName,
1257                                                const std::string &authName,
1258                                                const std::string &code) const {
1259     std::string sql("SELECT text_definition FROM \"");
1260     sql += replaceAll(tableName, "\"", "\"\"");
1261     sql += "\" WHERE auth_name = ? AND code = ?";
1262     auto res = d->run(sql, {authName, code});
1263     if (res.empty()) {
1264         return std::string();
1265     }
1266     return res.front()[0];
1267 }
1268 
1269 // ---------------------------------------------------------------------------
1270 
1271 /** \brief Return the allowed authorities when researching transformations
1272  * between different authorities.
1273  *
1274  * @throw FactoryException
1275  */
getAllowedAuthorities(const std::string & sourceAuthName,const std::string & targetAuthName) const1276 std::vector<std::string> DatabaseContext::getAllowedAuthorities(
1277     const std::string &sourceAuthName,
1278     const std::string &targetAuthName) const {
1279 
1280     const auto key(sourceAuthName + targetAuthName);
1281     auto hit = d->cacheAllowedAuthorities_.find(key);
1282     if (hit != d->cacheAllowedAuthorities_.end()) {
1283         return hit->second;
1284     }
1285 
1286     auto sqlRes = d->run(
1287         "SELECT allowed_authorities FROM authority_to_authority_preference "
1288         "WHERE source_auth_name = ? AND target_auth_name = ?",
1289         {sourceAuthName, targetAuthName});
1290     if (sqlRes.empty()) {
1291         sqlRes = d->run(
1292             "SELECT allowed_authorities FROM authority_to_authority_preference "
1293             "WHERE source_auth_name = ? AND target_auth_name = 'any'",
1294             {sourceAuthName});
1295     }
1296     if (sqlRes.empty()) {
1297         sqlRes = d->run(
1298             "SELECT allowed_authorities FROM authority_to_authority_preference "
1299             "WHERE source_auth_name = 'any' AND target_auth_name = ?",
1300             {targetAuthName});
1301     }
1302     if (sqlRes.empty()) {
1303         sqlRes = d->run(
1304             "SELECT allowed_authorities FROM authority_to_authority_preference "
1305             "WHERE source_auth_name = 'any' AND target_auth_name = 'any'",
1306             {});
1307     }
1308     if (sqlRes.empty()) {
1309         d->cacheAllowedAuthorities_[key] = std::vector<std::string>();
1310         return std::vector<std::string>();
1311     }
1312     auto res = split(sqlRes.front()[0], ',');
1313     d->cacheAllowedAuthorities_[key] = res;
1314     return res;
1315 }
1316 
1317 // ---------------------------------------------------------------------------
1318 
1319 std::list<std::pair<std::string, std::string>>
getNonDeprecated(const std::string & tableName,const std::string & authName,const std::string & code) const1320 DatabaseContext::getNonDeprecated(const std::string &tableName,
1321                                   const std::string &authName,
1322                                   const std::string &code) const {
1323     auto sqlRes =
1324         d->run("SELECT replacement_auth_name, replacement_code, source "
1325                "FROM deprecation "
1326                "WHERE table_name = ? AND deprecated_auth_name = ? "
1327                "AND deprecated_code = ?",
1328                {tableName, authName, code});
1329     std::list<std::pair<std::string, std::string>> res;
1330     for (const auto &row : sqlRes) {
1331         const auto &source = row[2];
1332         if (source == "PROJ") {
1333             const auto &replacement_auth_name = row[0];
1334             const auto &replacement_code = row[1];
1335             res.emplace_back(replacement_auth_name, replacement_code);
1336         }
1337     }
1338     if (!res.empty()) {
1339         return res;
1340     }
1341     for (const auto &row : sqlRes) {
1342         const auto &replacement_auth_name = row[0];
1343         const auto &replacement_code = row[1];
1344         res.emplace_back(replacement_auth_name, replacement_code);
1345     }
1346     return res;
1347 }
1348 
1349 // ---------------------------------------------------------------------------
1350 
1351 std::vector<operation::CoordinateOperationNNPtr>
getTransformationsForGridName(const DatabaseContextNNPtr & databaseContext,const std::string & gridName)1352 DatabaseContext::getTransformationsForGridName(
1353     const DatabaseContextNNPtr &databaseContext, const std::string &gridName) {
1354     auto sqlRes = databaseContext->d->run(
1355         "SELECT auth_name, code FROM grid_transformation "
1356         "WHERE grid_name = ? OR grid_name = "
1357         "(SELECT original_grid_name FROM grid_alternatives "
1358         "WHERE proj_grid_name = ?)",
1359         {gridName, gridName});
1360     std::vector<operation::CoordinateOperationNNPtr> res;
1361     for (const auto &row : sqlRes) {
1362         res.emplace_back(AuthorityFactory::create(databaseContext, row[0])
1363                              ->createCoordinateOperation(row[1], true));
1364     }
1365     return res;
1366 }
1367 
1368 //! @endcond
1369 
1370 // ---------------------------------------------------------------------------
1371 
1372 //! @cond Doxygen_Suppress
1373 struct AuthorityFactory::Private {
Privateio::AuthorityFactory::Private1374     Private(const DatabaseContextNNPtr &contextIn,
1375             const std::string &authorityName)
1376         : context_(contextIn), authority_(authorityName) {}
1377 
authorityio::AuthorityFactory::Private1378     inline const std::string &authority() PROJ_PURE_DEFN { return authority_; }
1379 
contextio::AuthorityFactory::Private1380     inline const DatabaseContextNNPtr &context() PROJ_PURE_DEFN {
1381         return context_;
1382     }
1383 
1384     // cppcheck-suppress functionStatic
setThisio::AuthorityFactory::Private1385     void setThis(AuthorityFactoryNNPtr factory) {
1386         thisFactory_ = factory.as_nullable();
1387     }
1388 
1389     // cppcheck-suppress functionStatic
getSharedFromThisio::AuthorityFactory::Private1390     AuthorityFactoryPtr getSharedFromThis() { return thisFactory_.lock(); }
1391 
createFactoryio::AuthorityFactory::Private1392     inline AuthorityFactoryNNPtr createFactory(const std::string &auth_name) {
1393         if (auth_name == authority_) {
1394             return NN_NO_CHECK(thisFactory_.lock());
1395         }
1396         return AuthorityFactory::create(context_, auth_name);
1397     }
1398 
1399     bool rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr &op,
1400                                   bool considerKnownGridsAsAvailable);
1401 
1402     UnitOfMeasure createUnitOfMeasure(const std::string &auth_name,
1403                                       const std::string &code);
1404 
1405     util::PropertyMap
1406     createProperties(const std::string &code, const std::string &name,
1407                      bool deprecated,
1408                      const std::vector<ObjectDomainNNPtr> &usages);
1409 
1410     util::PropertyMap
1411     createPropertiesSearchUsages(const std::string &table_name,
1412                                  const std::string &code,
1413                                  const std::string &name, bool deprecated);
1414 
1415     util::PropertyMap createPropertiesSearchUsages(
1416         const std::string &table_name, const std::string &code,
1417         const std::string &name, bool deprecated, const std::string &remarks);
1418 
1419     SQLResultSet run(const std::string &sql,
1420                      const ListOfParams &parameters = ListOfParams());
1421 
1422     SQLResultSet runWithCodeParam(const std::string &sql,
1423                                   const std::string &code);
1424 
1425     SQLResultSet runWithCodeParam(const char *sql, const std::string &code);
1426 
hasAuthorityRestrictionio::AuthorityFactory::Private1427     bool hasAuthorityRestriction() const {
1428         return !authority_.empty() && authority_ != "any";
1429     }
1430 
1431     SQLResultSet createProjectedCRSBegin(const std::string &code);
1432     crs::ProjectedCRSNNPtr createProjectedCRSEnd(const std::string &code,
1433                                                  const SQLResultSet &res);
1434 
1435   private:
1436     DatabaseContextNNPtr context_;
1437     std::string authority_;
1438     std::weak_ptr<AuthorityFactory> thisFactory_{};
1439 };
1440 
1441 // ---------------------------------------------------------------------------
1442 
run(const std::string & sql,const ListOfParams & parameters)1443 SQLResultSet AuthorityFactory::Private::run(const std::string &sql,
1444                                             const ListOfParams &parameters) {
1445     return context()->getPrivate()->run(sql, parameters);
1446 }
1447 
1448 // ---------------------------------------------------------------------------
1449 
1450 SQLResultSet
runWithCodeParam(const std::string & sql,const std::string & code)1451 AuthorityFactory::Private::runWithCodeParam(const std::string &sql,
1452                                             const std::string &code) {
1453     return run(sql, {authority(), code});
1454 }
1455 
1456 // ---------------------------------------------------------------------------
1457 
1458 SQLResultSet
runWithCodeParam(const char * sql,const std::string & code)1459 AuthorityFactory::Private::runWithCodeParam(const char *sql,
1460                                             const std::string &code) {
1461     return runWithCodeParam(std::string(sql), code);
1462 }
1463 
1464 // ---------------------------------------------------------------------------
1465 
1466 UnitOfMeasure
createUnitOfMeasure(const std::string & auth_name,const std::string & code)1467 AuthorityFactory::Private::createUnitOfMeasure(const std::string &auth_name,
1468                                                const std::string &code) {
1469     return *(createFactory(auth_name)->createUnitOfMeasure(code));
1470 }
1471 
1472 // ---------------------------------------------------------------------------
1473 
createProperties(const std::string & code,const std::string & name,bool deprecated,const std::vector<ObjectDomainNNPtr> & usages)1474 util::PropertyMap AuthorityFactory::Private::createProperties(
1475     const std::string &code, const std::string &name, bool deprecated,
1476     const std::vector<ObjectDomainNNPtr> &usages) {
1477     auto props = util::PropertyMap()
1478                      .set(metadata::Identifier::CODESPACE_KEY, authority())
1479                      .set(metadata::Identifier::CODE_KEY, code)
1480                      .set(common::IdentifiedObject::NAME_KEY, name);
1481     if (deprecated) {
1482         props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
1483     }
1484     if (!usages.empty()) {
1485 
1486         auto array(util::ArrayOfBaseObject::create());
1487         for (const auto &usage : usages) {
1488             array->add(usage);
1489         }
1490         props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
1491                   util::nn_static_pointer_cast<util::BaseObject>(array));
1492     }
1493     return props;
1494 }
1495 
1496 // ---------------------------------------------------------------------------
1497 
createPropertiesSearchUsages(const std::string & table_name,const std::string & code,const std::string & name,bool deprecated)1498 util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
1499     const std::string &table_name, const std::string &code,
1500     const std::string &name, bool deprecated) {
1501 
1502     const std::string sql(
1503         "SELECT extent.description, extent.south_lat, "
1504         "extent.north_lat, extent.west_lon, extent.east_lon, "
1505         "scope.scope, "
1506         "(CASE WHEN scope.scope LIKE '%large scale%' THEN 0 ELSE 1 END) "
1507         "AS score "
1508         "FROM usage "
1509         "JOIN extent ON usage.extent_auth_name = extent.auth_name AND "
1510         "usage.extent_code = extent.code "
1511         "JOIN scope ON usage.scope_auth_name = scope.auth_name AND "
1512         "usage.scope_code = scope.code "
1513         "WHERE object_table_name = ? AND object_auth_name = ? AND "
1514         "object_code = ? "
1515         "ORDER BY score, usage.auth_name, usage.code");
1516     auto res = run(sql, {table_name, authority(), code});
1517     std::vector<ObjectDomainNNPtr> usages;
1518     for (const auto &row : res) {
1519         try {
1520             size_t idx = 0;
1521             const auto &extent_description = row[idx++];
1522             const auto &south_lat_str = row[idx++];
1523             const auto &north_lat_str = row[idx++];
1524             const auto &west_lon_str = row[idx++];
1525             const auto &east_lon_str = row[idx++];
1526             const auto &scope = row[idx];
1527 
1528             util::optional<std::string> scopeOpt;
1529             if (!scope.empty()) {
1530                 scopeOpt = scope;
1531             }
1532 
1533             metadata::ExtentPtr extent;
1534             if (south_lat_str.empty()) {
1535                 extent = metadata::Extent::create(
1536                              util::optional<std::string>(extent_description),
1537                              {}, {}, {})
1538                              .as_nullable();
1539             } else {
1540                 double south_lat = c_locale_stod(south_lat_str);
1541                 double north_lat = c_locale_stod(north_lat_str);
1542                 double west_lon = c_locale_stod(west_lon_str);
1543                 double east_lon = c_locale_stod(east_lon_str);
1544                 auto bbox = metadata::GeographicBoundingBox::create(
1545                     west_lon, south_lat, east_lon, north_lat);
1546                 extent = metadata::Extent::create(
1547                              util::optional<std::string>(extent_description),
1548                              std::vector<metadata::GeographicExtentNNPtr>{bbox},
1549                              std::vector<metadata::VerticalExtentNNPtr>(),
1550                              std::vector<metadata::TemporalExtentNNPtr>())
1551                              .as_nullable();
1552             }
1553 
1554             usages.emplace_back(ObjectDomain::create(scopeOpt, extent));
1555         } catch (const std::exception &) {
1556         }
1557     }
1558     return createProperties(code, name, deprecated, std::move(usages));
1559 }
1560 
1561 // ---------------------------------------------------------------------------
1562 
createPropertiesSearchUsages(const std::string & table_name,const std::string & code,const std::string & name,bool deprecated,const std::string & remarks)1563 util::PropertyMap AuthorityFactory::Private::createPropertiesSearchUsages(
1564     const std::string &table_name, const std::string &code,
1565     const std::string &name, bool deprecated, const std::string &remarks) {
1566     auto props =
1567         createPropertiesSearchUsages(table_name, code, name, deprecated);
1568     if (!remarks.empty())
1569         props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
1570     return props;
1571 }
1572 
1573 // ---------------------------------------------------------------------------
1574 
rejectOpDueToMissingGrid(const operation::CoordinateOperationNNPtr & op,bool considerKnownGridsAsAvailable)1575 bool AuthorityFactory::Private::rejectOpDueToMissingGrid(
1576     const operation::CoordinateOperationNNPtr &op,
1577     bool considerKnownGridsAsAvailable) {
1578     for (const auto &gridDesc :
1579          op->gridsNeeded(context(), considerKnownGridsAsAvailable)) {
1580         if (!gridDesc.available) {
1581             return true;
1582         }
1583     }
1584     return false;
1585 }
1586 
1587 //! @endcond
1588 
1589 // ---------------------------------------------------------------------------
1590 
1591 //! @cond Doxygen_Suppress
1592 AuthorityFactory::~AuthorityFactory() = default;
1593 //! @endcond
1594 
1595 // ---------------------------------------------------------------------------
1596 
AuthorityFactory(const DatabaseContextNNPtr & context,const std::string & authorityName)1597 AuthorityFactory::AuthorityFactory(const DatabaseContextNNPtr &context,
1598                                    const std::string &authorityName)
1599     : d(internal::make_unique<Private>(context, authorityName)) {}
1600 
1601 // ---------------------------------------------------------------------------
1602 
1603 // clang-format off
1604 /** \brief Instantiate a AuthorityFactory.
1605  *
1606  * The authority name might be set to the empty string in the particular case
1607  * where createFromCoordinateReferenceSystemCodes(const std::string&,const std::string&,const std::string&,const std::string&) const
1608  * is called.
1609  *
1610  * @param context Contexte.
1611  * @param authorityName Authority name.
1612  * @return new AuthorityFactory.
1613  */
1614 // clang-format on
1615 
1616 AuthorityFactoryNNPtr
create(const DatabaseContextNNPtr & context,const std::string & authorityName)1617 AuthorityFactory::create(const DatabaseContextNNPtr &context,
1618                          const std::string &authorityName) {
1619     const auto getFactory = [&context, &authorityName]() {
1620         for (const auto &knownName : {"EPSG", "ESRI", "PROJ"}) {
1621             if (ci_equal(authorityName, knownName)) {
1622                 return AuthorityFactory::nn_make_shared<AuthorityFactory>(
1623                     context, knownName);
1624             }
1625         }
1626         return AuthorityFactory::nn_make_shared<AuthorityFactory>(
1627             context, authorityName);
1628     };
1629     auto factory = getFactory();
1630     factory->d->setThis(factory);
1631     return factory;
1632 }
1633 
1634 // ---------------------------------------------------------------------------
1635 
1636 /** \brief Returns the database context. */
databaseContext() const1637 const DatabaseContextNNPtr &AuthorityFactory::databaseContext() const {
1638     return d->context();
1639 }
1640 
1641 // ---------------------------------------------------------------------------
1642 
1643 //! @cond Doxygen_Suppress
CRSInfo()1644 AuthorityFactory::CRSInfo::CRSInfo()
1645     : authName{}, code{}, name{}, type{ObjectType::CRS}, deprecated{},
1646       bbox_valid{}, west_lon_degree{}, south_lat_degree{}, east_lon_degree{},
1647       north_lat_degree{}, areaName{}, projectionMethodName{} {}
1648 //! @endcond
1649 
1650 // ---------------------------------------------------------------------------
1651 
1652 /** \brief Returns an arbitrary object from a code.
1653  *
1654  * The returned object will typically be an instance of Datum,
1655  * CoordinateSystem, ReferenceSystem or CoordinateOperation. If the type of
1656  * the object is know at compile time, it is recommended to invoke the most
1657  * precise method instead of this one (for example
1658  * createCoordinateReferenceSystem(code) instead of createObject(code)
1659  * if the caller know he is asking for a coordinate reference system).
1660  *
1661  * If there are several objects with the same code, a FactoryException is
1662  * thrown.
1663  *
1664  * @param code Object code allocated by authority. (e.g. "4326")
1665  * @return object.
1666  * @throw NoSuchAuthorityCodeException
1667  * @throw FactoryException
1668  */
1669 
1670 util::BaseObjectNNPtr
createObject(const std::string & code) const1671 AuthorityFactory::createObject(const std::string &code) const {
1672 
1673     auto res = d->runWithCodeParam("SELECT table_name, type FROM object_view "
1674                                    "WHERE auth_name = ? AND code = ?",
1675                                    code);
1676     if (res.empty()) {
1677         throw NoSuchAuthorityCodeException("not found", d->authority(), code);
1678     }
1679     if (res.size() != 1) {
1680         std::string msg(
1681             "More than one object matching specified code. Objects found in ");
1682         bool first = true;
1683         for (const auto &row : res) {
1684             if (!first)
1685                 msg += ", ";
1686             msg += row[0];
1687             first = false;
1688         }
1689         throw FactoryException(msg);
1690     }
1691     const auto &first_row = res.front();
1692     const auto &table_name = first_row[0];
1693     const auto &type = first_row[1];
1694     if (table_name == "extent") {
1695         return util::nn_static_pointer_cast<util::BaseObject>(
1696             createExtent(code));
1697     }
1698     if (table_name == "unit_of_measure") {
1699         return util::nn_static_pointer_cast<util::BaseObject>(
1700             createUnitOfMeasure(code));
1701     }
1702     if (table_name == "prime_meridian") {
1703         return util::nn_static_pointer_cast<util::BaseObject>(
1704             createPrimeMeridian(code));
1705     }
1706     if (table_name == "ellipsoid") {
1707         return util::nn_static_pointer_cast<util::BaseObject>(
1708             createEllipsoid(code));
1709     }
1710     if (table_name == "geodetic_datum") {
1711         if (type == "ensemble") {
1712             return util::nn_static_pointer_cast<util::BaseObject>(
1713                 createDatumEnsemble(code, table_name));
1714         }
1715         return util::nn_static_pointer_cast<util::BaseObject>(
1716             createGeodeticDatum(code));
1717     }
1718     if (table_name == "vertical_datum") {
1719         if (type == "ensemble") {
1720             return util::nn_static_pointer_cast<util::BaseObject>(
1721                 createDatumEnsemble(code, table_name));
1722         }
1723         return util::nn_static_pointer_cast<util::BaseObject>(
1724             createVerticalDatum(code));
1725     }
1726     if (table_name == "geodetic_crs") {
1727         return util::nn_static_pointer_cast<util::BaseObject>(
1728             createGeodeticCRS(code));
1729     }
1730     if (table_name == "vertical_crs") {
1731         return util::nn_static_pointer_cast<util::BaseObject>(
1732             createVerticalCRS(code));
1733     }
1734     if (table_name == "projected_crs") {
1735         return util::nn_static_pointer_cast<util::BaseObject>(
1736             createProjectedCRS(code));
1737     }
1738     if (table_name == "compound_crs") {
1739         return util::nn_static_pointer_cast<util::BaseObject>(
1740             createCompoundCRS(code));
1741     }
1742     if (table_name == "conversion") {
1743         return util::nn_static_pointer_cast<util::BaseObject>(
1744             createConversion(code));
1745     }
1746     if (table_name == "helmert_transformation" ||
1747         table_name == "grid_transformation" ||
1748         table_name == "other_transformation" ||
1749         table_name == "concatenated_operation") {
1750         return util::nn_static_pointer_cast<util::BaseObject>(
1751             createCoordinateOperation(code, false));
1752     }
1753     throw FactoryException("unimplemented factory for " + res.front()[0]);
1754 }
1755 
1756 // ---------------------------------------------------------------------------
1757 
1758 //! @cond Doxygen_Suppress
buildFactoryException(const char * type,const std::string & code,const std::exception & ex)1759 static FactoryException buildFactoryException(const char *type,
1760                                               const std::string &code,
1761                                               const std::exception &ex) {
1762     return FactoryException(std::string("cannot build ") + type + " " + code +
1763                             ": " + ex.what());
1764 }
1765 //! @endcond
1766 
1767 // ---------------------------------------------------------------------------
1768 
1769 /** \brief Returns a metadata::Extent from the specified code.
1770  *
1771  * @param code Object code allocated by authority.
1772  * @return object.
1773  * @throw NoSuchAuthorityCodeException
1774  * @throw FactoryException
1775  */
1776 
1777 metadata::ExtentNNPtr
createExtent(const std::string & code) const1778 AuthorityFactory::createExtent(const std::string &code) const {
1779     const auto cacheKey(d->authority() + code);
1780     {
1781         auto extent = d->context()->d->getExtentFromCache(cacheKey);
1782         if (extent) {
1783             return NN_NO_CHECK(extent);
1784         }
1785     }
1786     auto sql = "SELECT description, south_lat, north_lat, west_lon, east_lon, "
1787                "deprecated FROM extent WHERE auth_name = ? AND code = ?";
1788     auto res = d->runWithCodeParam(sql, code);
1789     if (res.empty()) {
1790         throw NoSuchAuthorityCodeException("extent not found", d->authority(),
1791                                            code);
1792     }
1793     try {
1794         const auto &row = res.front();
1795         const auto &description = row[0];
1796         if (row[1].empty()) {
1797             auto extent = metadata::Extent::create(
1798                 util::optional<std::string>(description), {}, {}, {});
1799             d->context()->d->cache(cacheKey, extent);
1800             return extent;
1801         }
1802         double south_lat = c_locale_stod(row[1]);
1803         double north_lat = c_locale_stod(row[2]);
1804         double west_lon = c_locale_stod(row[3]);
1805         double east_lon = c_locale_stod(row[4]);
1806         auto bbox = metadata::GeographicBoundingBox::create(
1807             west_lon, south_lat, east_lon, north_lat);
1808 
1809         auto extent = metadata::Extent::create(
1810             util::optional<std::string>(description),
1811             std::vector<metadata::GeographicExtentNNPtr>{bbox},
1812             std::vector<metadata::VerticalExtentNNPtr>(),
1813             std::vector<metadata::TemporalExtentNNPtr>());
1814         d->context()->d->cache(cacheKey, extent);
1815         return extent;
1816 
1817     } catch (const std::exception &ex) {
1818         throw buildFactoryException("extent", code, ex);
1819     }
1820 }
1821 
1822 // ---------------------------------------------------------------------------
1823 
1824 /** \brief Returns a common::UnitOfMeasure from the specified code.
1825  *
1826  * @param code Object code allocated by authority.
1827  * @return object.
1828  * @throw NoSuchAuthorityCodeException
1829  * @throw FactoryException
1830  */
1831 
1832 UnitOfMeasureNNPtr
createUnitOfMeasure(const std::string & code) const1833 AuthorityFactory::createUnitOfMeasure(const std::string &code) const {
1834     const auto cacheKey(d->authority() + code);
1835     {
1836         auto uom = d->context()->d->getUOMFromCache(cacheKey);
1837         if (uom) {
1838             return NN_NO_CHECK(uom);
1839         }
1840     }
1841     auto res = d->context()->d->run(
1842         "SELECT name, conv_factor, type, deprecated FROM unit_of_measure WHERE "
1843         "auth_name = ? AND code = ?",
1844         {d->authority(), code}, true);
1845     if (res.empty()) {
1846         throw NoSuchAuthorityCodeException("unit of measure not found",
1847                                            d->authority(), code);
1848     }
1849     try {
1850         const auto &row = res.front();
1851         const auto &name =
1852             (row[0] == "degree (supplier to define representation)")
1853                 ? UnitOfMeasure::DEGREE.name()
1854                 : row[0];
1855         double conv_factor = (code == "9107" || code == "9108")
1856                                  ? UnitOfMeasure::DEGREE.conversionToSI()
1857                                  : c_locale_stod(row[1]);
1858         constexpr double EPS = 1e-10;
1859         if (std::fabs(conv_factor - UnitOfMeasure::DEGREE.conversionToSI()) <
1860             EPS * UnitOfMeasure::DEGREE.conversionToSI()) {
1861             conv_factor = UnitOfMeasure::DEGREE.conversionToSI();
1862         }
1863         if (std::fabs(conv_factor -
1864                       UnitOfMeasure::ARC_SECOND.conversionToSI()) <
1865             EPS * UnitOfMeasure::ARC_SECOND.conversionToSI()) {
1866             conv_factor = UnitOfMeasure::ARC_SECOND.conversionToSI();
1867         }
1868         const auto &type_str = row[2];
1869         UnitOfMeasure::Type unitType = UnitOfMeasure::Type::UNKNOWN;
1870         if (type_str == "length")
1871             unitType = UnitOfMeasure::Type::LINEAR;
1872         else if (type_str == "angle")
1873             unitType = UnitOfMeasure::Type::ANGULAR;
1874         else if (type_str == "scale")
1875             unitType = UnitOfMeasure::Type::SCALE;
1876         else if (type_str == "time")
1877             unitType = UnitOfMeasure::Type::TIME;
1878         auto uom = util::nn_make_shared<UnitOfMeasure>(
1879             name, conv_factor, unitType, d->authority(), code);
1880         d->context()->d->cache(cacheKey, uom);
1881         return uom;
1882     } catch (const std::exception &ex) {
1883         throw buildFactoryException("unit of measure", code, ex);
1884     }
1885 }
1886 
1887 // ---------------------------------------------------------------------------
1888 
1889 //! @cond Doxygen_Suppress
normalizeMeasure(const std::string & uom_code,const std::string & value,std::string & normalized_uom_code)1890 static double normalizeMeasure(const std::string &uom_code,
1891                                const std::string &value,
1892                                std::string &normalized_uom_code) {
1893     if (uom_code == "9110") // DDD.MMSSsss.....
1894     {
1895         double normalized_value = c_locale_stod(value);
1896         std::ostringstream buffer;
1897         buffer.imbue(std::locale::classic());
1898         constexpr size_t precision = 12;
1899         buffer << std::fixed << std::setprecision(precision)
1900                << normalized_value;
1901         auto formatted = buffer.str();
1902         size_t dotPos = formatted.find('.');
1903         assert(dotPos + 1 + precision == formatted.size());
1904         auto minutes = formatted.substr(dotPos + 1, 2);
1905         auto seconds = formatted.substr(dotPos + 3);
1906         assert(seconds.size() == precision - 2);
1907         normalized_value =
1908             (normalized_value < 0 ? -1.0 : 1.0) *
1909             (std::floor(std::fabs(normalized_value)) +
1910              c_locale_stod(minutes) / 60. +
1911              (c_locale_stod(seconds) / std::pow(10, seconds.size() - 2)) /
1912                  3600.);
1913         normalized_uom_code = common::UnitOfMeasure::DEGREE.code();
1914         /* coverity[overflow_sink] */
1915         return normalized_value;
1916     } else {
1917         normalized_uom_code = uom_code;
1918         return c_locale_stod(value);
1919     }
1920 }
1921 //! @endcond
1922 
1923 // ---------------------------------------------------------------------------
1924 
1925 /** \brief Returns a datum::PrimeMeridian from the specified code.
1926  *
1927  * @param code Object code allocated by authority.
1928  * @return object.
1929  * @throw NoSuchAuthorityCodeException
1930  * @throw FactoryException
1931  */
1932 
1933 datum::PrimeMeridianNNPtr
createPrimeMeridian(const std::string & code) const1934 AuthorityFactory::createPrimeMeridian(const std::string &code) const {
1935     const auto cacheKey(d->authority() + code);
1936     {
1937         auto pm = d->context()->d->getPrimeMeridianFromCache(cacheKey);
1938         if (pm) {
1939             return NN_NO_CHECK(pm);
1940         }
1941     }
1942     auto res = d->runWithCodeParam(
1943         "SELECT name, longitude, uom_auth_name, uom_code, deprecated FROM "
1944         "prime_meridian WHERE "
1945         "auth_name = ? AND code = ?",
1946         code);
1947     if (res.empty()) {
1948         throw NoSuchAuthorityCodeException("prime meridian not found",
1949                                            d->authority(), code);
1950     }
1951     try {
1952         const auto &row = res.front();
1953         const auto &name = row[0];
1954         const auto &longitude = row[1];
1955         const auto &uom_auth_name = row[2];
1956         const auto &uom_code = row[3];
1957         const bool deprecated = row[4] == "1";
1958 
1959         std::string normalized_uom_code(uom_code);
1960         const double normalized_value =
1961             normalizeMeasure(uom_code, longitude, normalized_uom_code);
1962 
1963         auto uom = d->createUnitOfMeasure(uom_auth_name, normalized_uom_code);
1964         auto props = d->createProperties(code, name, deprecated, {});
1965         auto pm = datum::PrimeMeridian::create(
1966             props, common::Angle(normalized_value, uom));
1967         d->context()->d->cache(cacheKey, pm);
1968         return pm;
1969     } catch (const std::exception &ex) {
1970         throw buildFactoryException("prime meridian", code, ex);
1971     }
1972 }
1973 
1974 // ---------------------------------------------------------------------------
1975 
1976 /** \brief Identify a celestial body from an approximate radius.
1977  *
1978  * @param semi_major_axis Approximate semi-major axis.
1979  * @param tolerance Relative error allowed.
1980  * @return celestial body name if one single match found.
1981  * @throw FactoryException
1982  */
1983 
1984 std::string
identifyBodyFromSemiMajorAxis(double semi_major_axis,double tolerance) const1985 AuthorityFactory::identifyBodyFromSemiMajorAxis(double semi_major_axis,
1986                                                 double tolerance) const {
1987     auto res =
1988         d->run("SELECT name, (ABS(semi_major_axis - ?) / semi_major_axis ) "
1989                "AS rel_error FROM celestial_body WHERE rel_error <= ?",
1990                {semi_major_axis, tolerance});
1991     if (res.empty()) {
1992         throw FactoryException("no match found");
1993     }
1994     if (res.size() > 1) {
1995         throw FactoryException("more than one match found");
1996     }
1997     return res.front()[0];
1998 }
1999 
2000 // ---------------------------------------------------------------------------
2001 
2002 /** \brief Returns a datum::Ellipsoid from the specified code.
2003  *
2004  * @param code Object code allocated by authority.
2005  * @return object.
2006  * @throw NoSuchAuthorityCodeException
2007  * @throw FactoryException
2008  */
2009 
2010 datum::EllipsoidNNPtr
createEllipsoid(const std::string & code) const2011 AuthorityFactory::createEllipsoid(const std::string &code) const {
2012     const auto cacheKey(d->authority() + code);
2013     {
2014         auto ellps = d->context()->d->getEllipsoidFromCache(cacheKey);
2015         if (ellps) {
2016             return NN_NO_CHECK(ellps);
2017         }
2018     }
2019     auto res = d->runWithCodeParam(
2020         "SELECT ellipsoid.name, ellipsoid.semi_major_axis, "
2021         "ellipsoid.uom_auth_name, ellipsoid.uom_code, "
2022         "ellipsoid.inv_flattening, ellipsoid.semi_minor_axis, "
2023         "celestial_body.name AS body_name, ellipsoid.deprecated FROM "
2024         "ellipsoid JOIN celestial_body "
2025         "ON ellipsoid.celestial_body_auth_name = celestial_body.auth_name AND "
2026         "ellipsoid.celestial_body_code = celestial_body.code WHERE "
2027         "ellipsoid.auth_name = ? AND ellipsoid.code = ?",
2028         code);
2029     if (res.empty()) {
2030         throw NoSuchAuthorityCodeException("ellipsoid not found",
2031                                            d->authority(), code);
2032     }
2033     try {
2034         const auto &row = res.front();
2035         const auto &name = row[0];
2036         const auto &semi_major_axis_str = row[1];
2037         double semi_major_axis = c_locale_stod(semi_major_axis_str);
2038         const auto &uom_auth_name = row[2];
2039         const auto &uom_code = row[3];
2040         const auto &inv_flattening_str = row[4];
2041         const auto &semi_minor_axis_str = row[5];
2042         const auto &body = row[6];
2043         const bool deprecated = row[7] == "1";
2044         auto uom = d->createUnitOfMeasure(uom_auth_name, uom_code);
2045         auto props = d->createProperties(code, name, deprecated, {});
2046         if (!inv_flattening_str.empty()) {
2047             auto ellps = datum::Ellipsoid::createFlattenedSphere(
2048                 props, common::Length(semi_major_axis, uom),
2049                 common::Scale(c_locale_stod(inv_flattening_str)), body);
2050             d->context()->d->cache(cacheKey, ellps);
2051             return ellps;
2052         } else if (semi_major_axis_str == semi_minor_axis_str) {
2053             auto ellps = datum::Ellipsoid::createSphere(
2054                 props, common::Length(semi_major_axis, uom), body);
2055             d->context()->d->cache(cacheKey, ellps);
2056             return ellps;
2057         } else {
2058             auto ellps = datum::Ellipsoid::createTwoAxis(
2059                 props, common::Length(semi_major_axis, uom),
2060                 common::Length(c_locale_stod(semi_minor_axis_str), uom), body);
2061             d->context()->d->cache(cacheKey, ellps);
2062             return ellps;
2063         }
2064     } catch (const std::exception &ex) {
2065         throw buildFactoryException("ellipsoid", code, ex);
2066     }
2067 }
2068 
2069 // ---------------------------------------------------------------------------
2070 
2071 /** \brief Returns a datum::GeodeticReferenceFrame from the specified code.
2072  *
2073  * @param code Object code allocated by authority.
2074  * @return object.
2075  * @throw NoSuchAuthorityCodeException
2076  * @throw FactoryException
2077  */
2078 
2079 datum::GeodeticReferenceFrameNNPtr
createGeodeticDatum(const std::string & code) const2080 AuthorityFactory::createGeodeticDatum(const std::string &code) const {
2081     const auto cacheKey(d->authority() + code);
2082     {
2083         auto datum = d->context()->d->getGeodeticDatumFromCache(cacheKey);
2084         if (datum) {
2085             return NN_NO_CHECK(datum);
2086         }
2087     }
2088     auto res =
2089         d->runWithCodeParam("SELECT name, ellipsoid_auth_name, ellipsoid_code, "
2090                             "prime_meridian_auth_name, prime_meridian_code, "
2091                             "publication_date, frame_reference_epoch, "
2092                             "deprecated FROM geodetic_datum "
2093                             "WHERE "
2094                             "auth_name = ? AND code = ?",
2095                             code);
2096     if (res.empty()) {
2097         throw NoSuchAuthorityCodeException("geodetic datum not found",
2098                                            d->authority(), code);
2099     }
2100     try {
2101         const auto &row = res.front();
2102         const auto &name = row[0];
2103         const auto &ellipsoid_auth_name = row[1];
2104         const auto &ellipsoid_code = row[2];
2105         const auto &prime_meridian_auth_name = row[3];
2106         const auto &prime_meridian_code = row[4];
2107         const auto &publication_date = row[5];
2108         const auto &frame_reference_epoch = row[6];
2109         const bool deprecated = row[7] == "1";
2110         auto ellipsoid = d->createFactory(ellipsoid_auth_name)
2111                              ->createEllipsoid(ellipsoid_code);
2112         auto pm = d->createFactory(prime_meridian_auth_name)
2113                       ->createPrimeMeridian(prime_meridian_code);
2114         auto props = d->createPropertiesSearchUsages(
2115             "geodetic_datum", code, removeEnsembleSuffix(name), deprecated);
2116         auto anchor = util::optional<std::string>();
2117         if (!publication_date.empty()) {
2118             props.set("PUBLICATION_DATE", publication_date);
2119         }
2120         auto datum =
2121             frame_reference_epoch.empty()
2122                 ? datum::GeodeticReferenceFrame::create(props, ellipsoid,
2123                                                         anchor, pm)
2124                 : util::nn_static_pointer_cast<datum::GeodeticReferenceFrame>(
2125                       datum::DynamicGeodeticReferenceFrame::create(
2126                           props, ellipsoid, anchor, pm,
2127                           common::Measure(c_locale_stod(frame_reference_epoch),
2128                                           common::UnitOfMeasure::YEAR),
2129                           util::optional<std::string>()));
2130         d->context()->d->cache(cacheKey, datum);
2131         return datum;
2132     } catch (const std::exception &ex) {
2133         throw buildFactoryException("geodetic reference frame", code, ex);
2134     }
2135 }
2136 
2137 // ---------------------------------------------------------------------------
2138 
2139 /** \brief Returns a datum::VerticalReferenceFrame from the specified code.
2140  *
2141  * @param code Object code allocated by authority.
2142  * @return object.
2143  * @throw NoSuchAuthorityCodeException
2144  * @throw FactoryException
2145  */
2146 
2147 datum::VerticalReferenceFrameNNPtr
createVerticalDatum(const std::string & code) const2148 AuthorityFactory::createVerticalDatum(const std::string &code) const {
2149     auto res =
2150         d->runWithCodeParam("SELECT name, publication_date, "
2151                             "frame_reference_epoch, deprecated FROM "
2152                             "vertical_datum WHERE auth_name = ? AND code = ?",
2153                             code);
2154     if (res.empty()) {
2155         throw NoSuchAuthorityCodeException("vertical datum not found",
2156                                            d->authority(), code);
2157     }
2158     try {
2159         const auto &row = res.front();
2160         const auto &name = row[0];
2161         const auto &publication_date = row[1];
2162         const auto &frame_reference_epoch = row[2];
2163         const bool deprecated = row[3] == "1";
2164         auto props = d->createPropertiesSearchUsages("vertical_datum", code,
2165                                                      name, deprecated);
2166         if (!publication_date.empty()) {
2167             props.set("PUBLICATION_DATE", publication_date);
2168         }
2169         if (d->authority() == "ESRI" && starts_with(code, "from_geogdatum_")) {
2170             props.set("VERT_DATUM_TYPE", "2002");
2171         }
2172         auto anchor = util::optional<std::string>();
2173         if (frame_reference_epoch.empty()) {
2174             return datum::VerticalReferenceFrame::create(props, anchor);
2175         } else {
2176             return datum::DynamicVerticalReferenceFrame::create(
2177                 props, anchor, util::optional<datum::RealizationMethod>(),
2178                 common::Measure(c_locale_stod(frame_reference_epoch),
2179                                 common::UnitOfMeasure::YEAR),
2180                 util::optional<std::string>());
2181         }
2182     } catch (const std::exception &ex) {
2183         throw buildFactoryException("vertical reference frame", code, ex);
2184     }
2185 }
2186 
2187 // ---------------------------------------------------------------------------
2188 
2189 /** \brief Returns a datum::DatumEnsemble from the specified code.
2190  *
2191  * @param code Object code allocated by authority.
2192  * @param type "geodetic_datum", "vertical_datum" or empty string if unknown
2193  * @return object.
2194  * @throw NoSuchAuthorityCodeException
2195  * @throw FactoryException
2196  */
2197 
2198 datum::DatumEnsembleNNPtr
createDatumEnsemble(const std::string & code,const std::string & type) const2199 AuthorityFactory::createDatumEnsemble(const std::string &code,
2200                                       const std::string &type) const {
2201     auto res = d->run(
2202         "SELECT 'geodetic_datum', name, ensemble_accuracy, deprecated FROM "
2203         "geodetic_datum WHERE "
2204         "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL "
2205         "UNION ALL "
2206         "SELECT 'vertical_datum', name, ensemble_accuracy, deprecated FROM "
2207         "vertical_datum WHERE "
2208         "auth_name = ? AND code = ? AND ensemble_accuracy IS NOT NULL",
2209         {d->authority(), code, d->authority(), code});
2210     if (res.empty()) {
2211         throw NoSuchAuthorityCodeException("datum ensemble not found",
2212                                            d->authority(), code);
2213     }
2214     for (const auto &row : res) {
2215         const std::string &gotType = row[0];
2216         const std::string &name = row[1];
2217         const std::string &ensembleAccuracy = row[2];
2218         const bool deprecated = row[3] == "1";
2219         if (type.empty() || type == gotType) {
2220             auto resMembers =
2221                 d->run("SELECT member_auth_name, member_code FROM " + gotType +
2222                            "_ensemble_member WHERE "
2223                            "ensemble_auth_name = ? AND ensemble_code = ? "
2224                            "ORDER BY sequence",
2225                        {d->authority(), code});
2226 
2227             std::vector<datum::DatumNNPtr> members;
2228             for (const auto &memberRow : resMembers) {
2229                 members.push_back(
2230                     d->createFactory(memberRow[0])->createDatum(memberRow[1]));
2231             }
2232             auto props = d->createPropertiesSearchUsages(gotType, code, name,
2233                                                          deprecated);
2234             return datum::DatumEnsemble::create(
2235                 props, std::move(members),
2236                 metadata::PositionalAccuracy::create(ensembleAccuracy));
2237         }
2238     }
2239     throw NoSuchAuthorityCodeException("datum ensemble not found",
2240                                        d->authority(), code);
2241 }
2242 
2243 // ---------------------------------------------------------------------------
2244 
2245 /** \brief Returns a datum::Datum from the specified code.
2246  *
2247  * @param code Object code allocated by authority.
2248  * @return object.
2249  * @throw NoSuchAuthorityCodeException
2250  * @throw FactoryException
2251  */
2252 
createDatum(const std::string & code) const2253 datum::DatumNNPtr AuthorityFactory::createDatum(const std::string &code) const {
2254     auto res =
2255         d->run("SELECT 'geodetic_datum' FROM geodetic_datum WHERE "
2256                "auth_name = ? AND code = ? "
2257                "UNION ALL SELECT 'vertical_datum' FROM vertical_datum WHERE "
2258                "auth_name = ? AND code = ?",
2259                {d->authority(), code, d->authority(), code});
2260     if (res.empty()) {
2261         throw NoSuchAuthorityCodeException("datum not found", d->authority(),
2262                                            code);
2263     }
2264     if (res.front()[0] == "geodetic_datum") {
2265         return createGeodeticDatum(code);
2266     }
2267     return createVerticalDatum(code);
2268 }
2269 
2270 // ---------------------------------------------------------------------------
2271 
2272 //! @cond Doxygen_Suppress
createMeridian(const std::string & val)2273 static cs::MeridianPtr createMeridian(const std::string &val) {
2274     try {
2275         const std::string degW(std::string("\xC2\xB0") + "W");
2276         if (ends_with(val, degW)) {
2277             return cs::Meridian::create(common::Angle(
2278                 -c_locale_stod(val.substr(0, val.size() - degW.size()))));
2279         }
2280         const std::string degE(std::string("\xC2\xB0") + "E");
2281         if (ends_with(val, degE)) {
2282             return cs::Meridian::create(common::Angle(
2283                 c_locale_stod(val.substr(0, val.size() - degE.size()))));
2284         }
2285     } catch (const std::exception &) {
2286     }
2287     return nullptr;
2288 }
2289 //! @endcond
2290 
2291 // ---------------------------------------------------------------------------
2292 
2293 /** \brief Returns a cs::CoordinateSystem from the specified code.
2294  *
2295  * @param code Object code allocated by authority.
2296  * @return object.
2297  * @throw NoSuchAuthorityCodeException
2298  * @throw FactoryException
2299  */
2300 
2301 cs::CoordinateSystemNNPtr
createCoordinateSystem(const std::string & code) const2302 AuthorityFactory::createCoordinateSystem(const std::string &code) const {
2303     const auto cacheKey(d->authority() + code);
2304     {
2305         auto cs = d->context()->d->getCoordinateSystemFromCache(cacheKey);
2306         if (cs) {
2307             return NN_NO_CHECK(cs);
2308         }
2309     }
2310     auto res = d->runWithCodeParam(
2311         "SELECT axis.name, abbrev, orientation, uom_auth_name, uom_code, "
2312         "cs.type FROM "
2313         "axis LEFT JOIN coordinate_system cs ON "
2314         "axis.coordinate_system_auth_name = cs.auth_name AND "
2315         "axis.coordinate_system_code = cs.code WHERE "
2316         "coordinate_system_auth_name = ? AND coordinate_system_code = ? ORDER "
2317         "BY coordinate_system_order",
2318         code);
2319     if (res.empty()) {
2320         throw NoSuchAuthorityCodeException("coordinate system not found",
2321                                            d->authority(), code);
2322     }
2323 
2324     const auto &csType = res.front()[5];
2325     std::vector<cs::CoordinateSystemAxisNNPtr> axisList;
2326     for (const auto &row : res) {
2327         const auto &name = row[0];
2328         const auto &abbrev = row[1];
2329         const auto &orientation = row[2];
2330         const auto &uom_auth_name = row[3];
2331         const auto &uom_code = row[4];
2332         if (uom_auth_name.empty() && csType != "ordinal") {
2333             throw FactoryException("no unit of measure for an axis is only "
2334                                    "supported for ordinatal CS");
2335         }
2336         auto uom = uom_auth_name.empty()
2337                        ? common::UnitOfMeasure::NONE
2338                        : d->createUnitOfMeasure(uom_auth_name, uom_code);
2339         auto props =
2340             util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, name);
2341         const cs::AxisDirection *direction =
2342             cs::AxisDirection::valueOf(orientation);
2343         cs::MeridianPtr meridian;
2344         if (direction == nullptr) {
2345             if (orientation == "Geocentre > equator/0"
2346                                "\xC2\xB0"
2347                                "E") {
2348                 direction = &(cs::AxisDirection::GEOCENTRIC_X);
2349             } else if (orientation == "Geocentre > equator/90"
2350                                       "\xC2\xB0"
2351                                       "E") {
2352                 direction = &(cs::AxisDirection::GEOCENTRIC_Y);
2353             } else if (orientation == "Geocentre > north pole") {
2354                 direction = &(cs::AxisDirection::GEOCENTRIC_Z);
2355             } else if (starts_with(orientation, "North along ")) {
2356                 direction = &(cs::AxisDirection::NORTH);
2357                 meridian =
2358                     createMeridian(orientation.substr(strlen("North along ")));
2359             } else if (starts_with(orientation, "South along ")) {
2360                 direction = &(cs::AxisDirection::SOUTH);
2361                 meridian =
2362                     createMeridian(orientation.substr(strlen("South along ")));
2363             } else {
2364                 throw FactoryException("unknown axis direction: " +
2365                                        orientation);
2366             }
2367         }
2368         axisList.emplace_back(cs::CoordinateSystemAxis::create(
2369             props, abbrev, *direction, uom, meridian));
2370     }
2371 
2372     const auto cacheAndRet = [this,
2373                               &cacheKey](const cs::CoordinateSystemNNPtr &cs) {
2374         d->context()->d->cache(cacheKey, cs);
2375         return cs;
2376     };
2377 
2378     auto props = util::PropertyMap()
2379                      .set(metadata::Identifier::CODESPACE_KEY, d->authority())
2380                      .set(metadata::Identifier::CODE_KEY, code);
2381     if (csType == "ellipsoidal") {
2382         if (axisList.size() == 2) {
2383             return cacheAndRet(
2384                 cs::EllipsoidalCS::create(props, axisList[0], axisList[1]));
2385         }
2386         if (axisList.size() == 3) {
2387             return cacheAndRet(cs::EllipsoidalCS::create(
2388                 props, axisList[0], axisList[1], axisList[2]));
2389         }
2390         throw FactoryException("invalid number of axis for EllipsoidalCS");
2391     }
2392     if (csType == "Cartesian") {
2393         if (axisList.size() == 2) {
2394             return cacheAndRet(
2395                 cs::CartesianCS::create(props, axisList[0], axisList[1]));
2396         }
2397         if (axisList.size() == 3) {
2398             return cacheAndRet(cs::CartesianCS::create(
2399                 props, axisList[0], axisList[1], axisList[2]));
2400         }
2401         throw FactoryException("invalid number of axis for CartesianCS");
2402     }
2403     if (csType == "vertical") {
2404         if (axisList.size() == 1) {
2405             return cacheAndRet(cs::VerticalCS::create(props, axisList[0]));
2406         }
2407         throw FactoryException("invalid number of axis for VerticalCS");
2408     }
2409     if (csType == "ordinal") {
2410         return cacheAndRet(cs::OrdinalCS::create(props, axisList));
2411     }
2412     throw FactoryException("unhandled coordinate system type: " + csType);
2413 }
2414 
2415 // ---------------------------------------------------------------------------
2416 
2417 /** \brief Returns a crs::GeodeticCRS from the specified code.
2418  *
2419  * @param code Object code allocated by authority.
2420  * @return object.
2421  * @throw NoSuchAuthorityCodeException
2422  * @throw FactoryException
2423  */
2424 
2425 crs::GeodeticCRSNNPtr
createGeodeticCRS(const std::string & code) const2426 AuthorityFactory::createGeodeticCRS(const std::string &code) const {
2427     return createGeodeticCRS(code, false);
2428 }
2429 
2430 // ---------------------------------------------------------------------------
2431 
2432 /** \brief Returns a crs::GeographicCRS from the specified code.
2433  *
2434  * @param code Object code allocated by authority.
2435  * @return object.
2436  * @throw NoSuchAuthorityCodeException
2437  * @throw FactoryException
2438  */
2439 
2440 crs::GeographicCRSNNPtr
createGeographicCRS(const std::string & code) const2441 AuthorityFactory::createGeographicCRS(const std::string &code) const {
2442     return NN_NO_CHECK(util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
2443         createGeodeticCRS(code, true)));
2444 }
2445 
2446 // ---------------------------------------------------------------------------
2447 
2448 static crs::GeodeticCRSNNPtr
cloneWithProps(const crs::GeodeticCRSNNPtr & geodCRS,const util::PropertyMap & props)2449 cloneWithProps(const crs::GeodeticCRSNNPtr &geodCRS,
2450                const util::PropertyMap &props) {
2451     auto cs = geodCRS->coordinateSystem();
2452     auto ellipsoidalCS = util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
2453     if (ellipsoidalCS) {
2454         return crs::GeographicCRS::create(props, geodCRS->datum(),
2455                                           geodCRS->datumEnsemble(),
2456                                           NN_NO_CHECK(ellipsoidalCS));
2457     }
2458     auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
2459     if (geocentricCS) {
2460         return crs::GeodeticCRS::create(props, geodCRS->datum(),
2461                                         geodCRS->datumEnsemble(),
2462                                         NN_NO_CHECK(geocentricCS));
2463     }
2464     return geodCRS;
2465 }
2466 
2467 // ---------------------------------------------------------------------------
2468 
2469 crs::GeodeticCRSNNPtr
createGeodeticCRS(const std::string & code,bool geographicOnly) const2470 AuthorityFactory::createGeodeticCRS(const std::string &code,
2471                                     bool geographicOnly) const {
2472     const auto cacheKey(d->authority() + code);
2473     auto crs = d->context()->d->getCRSFromCache(cacheKey);
2474     if (crs) {
2475         auto geogCRS = std::dynamic_pointer_cast<crs::GeodeticCRS>(crs);
2476         if (geogCRS) {
2477             return NN_NO_CHECK(geogCRS);
2478         }
2479         throw NoSuchAuthorityCodeException("geodeticCRS not found",
2480                                            d->authority(), code);
2481     }
2482     std::string sql("SELECT name, type, coordinate_system_auth_name, "
2483                     "coordinate_system_code, datum_auth_name, datum_code, "
2484                     "text_definition, "
2485                     "deprecated FROM "
2486                     "geodetic_crs WHERE auth_name = ? AND code = ?");
2487     if (geographicOnly) {
2488         sql += " AND type in (" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED
2489                ")";
2490     }
2491     auto res = d->runWithCodeParam(sql, code);
2492     if (res.empty()) {
2493         throw NoSuchAuthorityCodeException("geodeticCRS not found",
2494                                            d->authority(), code);
2495     }
2496     try {
2497         const auto &row = res.front();
2498         const auto &name = row[0];
2499         const auto &type = row[1];
2500         const auto &cs_auth_name = row[2];
2501         const auto &cs_code = row[3];
2502         const auto &datum_auth_name = row[4];
2503         const auto &datum_code = row[5];
2504         const auto &text_definition = row[6];
2505         const bool deprecated = row[7] == "1";
2506 
2507         auto props = d->createPropertiesSearchUsages("geodetic_crs", code, name,
2508                                                      deprecated);
2509 
2510         if (!text_definition.empty()) {
2511             DatabaseContext::Private::RecursionDetector detector(d->context());
2512             auto obj = createFromUserInput(
2513                 pj_add_type_crs_if_needed(text_definition), d->context());
2514             auto geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(obj);
2515             if (geodCRS) {
2516                 auto crsRet = cloneWithProps(NN_NO_CHECK(geodCRS), props);
2517                 d->context()->d->cache(cacheKey, crsRet);
2518                 return crsRet;
2519             }
2520 
2521             auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
2522             if (boundCRS) {
2523                 geodCRS = util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
2524                     boundCRS->baseCRS());
2525                 if (geodCRS) {
2526                     auto newBoundCRS = crs::BoundCRS::create(
2527                         cloneWithProps(NN_NO_CHECK(geodCRS), props),
2528                         boundCRS->hubCRS(), boundCRS->transformation());
2529                     return NN_NO_CHECK(
2530                         util::nn_dynamic_pointer_cast<crs::GeodeticCRS>(
2531                             newBoundCRS->baseCRSWithCanonicalBoundCRS()));
2532                 }
2533             }
2534 
2535             throw FactoryException(
2536                 "text_definition does not define a GeodeticCRS");
2537         }
2538 
2539         auto cs =
2540             d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
2541         auto datum =
2542             d->createFactory(datum_auth_name)->createGeodeticDatum(datum_code);
2543 
2544         auto ellipsoidalCS =
2545             util::nn_dynamic_pointer_cast<cs::EllipsoidalCS>(cs);
2546         if ((type == GEOG_2D || type == GEOG_3D) && ellipsoidalCS) {
2547             auto crsRet = crs::GeographicCRS::create(
2548                 props, datum, NN_NO_CHECK(ellipsoidalCS));
2549             d->context()->d->cache(cacheKey, crsRet);
2550             return crsRet;
2551         }
2552         auto geocentricCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
2553         if (type == GEOCENTRIC && geocentricCS) {
2554             auto crsRet = crs::GeodeticCRS::create(props, datum,
2555                                                    NN_NO_CHECK(geocentricCS));
2556             d->context()->d->cache(cacheKey, crsRet);
2557             return crsRet;
2558         }
2559         throw FactoryException("unsupported (type, CS type) for geodeticCRS: " +
2560                                type + ", " + cs->getWKT2Type(true));
2561     } catch (const std::exception &ex) {
2562         throw buildFactoryException("geodeticCRS", code, ex);
2563     }
2564 }
2565 
2566 // ---------------------------------------------------------------------------
2567 
2568 /** \brief Returns a crs::VerticalCRS from the specified code.
2569  *
2570  * @param code Object code allocated by authority.
2571  * @return object.
2572  * @throw NoSuchAuthorityCodeException
2573  * @throw FactoryException
2574  */
2575 
2576 crs::VerticalCRSNNPtr
createVerticalCRS(const std::string & code) const2577 AuthorityFactory::createVerticalCRS(const std::string &code) const {
2578     const auto cacheKey(d->authority() + code);
2579     auto crs = d->context()->d->getCRSFromCache(cacheKey);
2580     if (crs) {
2581         auto projCRS = std::dynamic_pointer_cast<crs::VerticalCRS>(crs);
2582         if (projCRS) {
2583             return NN_NO_CHECK(projCRS);
2584         }
2585         throw NoSuchAuthorityCodeException("verticalCRS not found",
2586                                            d->authority(), code);
2587     }
2588     auto res = d->runWithCodeParam(
2589         "SELECT name, coordinate_system_auth_name, "
2590         "coordinate_system_code, datum_auth_name, datum_code, "
2591         "deprecated FROM "
2592         "vertical_crs WHERE auth_name = ? AND code = ?",
2593         code);
2594     if (res.empty()) {
2595         throw NoSuchAuthorityCodeException("verticalCRS not found",
2596                                            d->authority(), code);
2597     }
2598     try {
2599         const auto &row = res.front();
2600         const auto &name = row[0];
2601         const auto &cs_auth_name = row[1];
2602         const auto &cs_code = row[2];
2603         const auto &datum_auth_name = row[3];
2604         const auto &datum_code = row[4];
2605         const bool deprecated = row[5] == "1";
2606         auto cs =
2607             d->createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
2608         auto datum =
2609             d->createFactory(datum_auth_name)->createVerticalDatum(datum_code);
2610 
2611         auto props = d->createPropertiesSearchUsages("vertical_crs", code, name,
2612                                                      deprecated);
2613 
2614         auto verticalCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(cs);
2615         if (verticalCS) {
2616             auto crsRet =
2617                 crs::VerticalCRS::create(props, datum, NN_NO_CHECK(verticalCS));
2618             d->context()->d->cache(cacheKey, crsRet);
2619             return crsRet;
2620         }
2621         throw FactoryException("unsupported CS type for verticalCRS: " +
2622                                cs->getWKT2Type(true));
2623     } catch (const std::exception &ex) {
2624         throw buildFactoryException("verticalCRS", code, ex);
2625     }
2626 }
2627 
2628 // ---------------------------------------------------------------------------
2629 
2630 /** \brief Returns a operation::Conversion from the specified code.
2631  *
2632  * @param code Object code allocated by authority.
2633  * @return object.
2634  * @throw NoSuchAuthorityCodeException
2635  * @throw FactoryException
2636  */
2637 
2638 operation::ConversionNNPtr
createConversion(const std::string & code) const2639 AuthorityFactory::createConversion(const std::string &code) const {
2640 
2641     static const char *sql =
2642         "SELECT name, description, "
2643         "method_auth_name, method_code, method_name, "
2644 
2645         "param1_auth_name, param1_code, param1_name, param1_value, "
2646         "param1_uom_auth_name, param1_uom_code, "
2647 
2648         "param2_auth_name, param2_code, param2_name, param2_value, "
2649         "param2_uom_auth_name, param2_uom_code, "
2650 
2651         "param3_auth_name, param3_code, param3_name, param3_value, "
2652         "param3_uom_auth_name, param3_uom_code, "
2653 
2654         "param4_auth_name, param4_code, param4_name, param4_value, "
2655         "param4_uom_auth_name, param4_uom_code, "
2656 
2657         "param5_auth_name, param5_code, param5_name, param5_value, "
2658         "param5_uom_auth_name, param5_uom_code, "
2659 
2660         "param6_auth_name, param6_code, param6_name, param6_value, "
2661         "param6_uom_auth_name, param6_uom_code, "
2662 
2663         "param7_auth_name, param7_code, param7_name, param7_value, "
2664         "param7_uom_auth_name, param7_uom_code, "
2665 
2666         "deprecated FROM conversion WHERE auth_name = ? AND code = ?";
2667 
2668     auto res = d->runWithCodeParam(sql, code);
2669     if (res.empty()) {
2670         try {
2671             // Conversions using methods Change of Vertical Unit or
2672             // Height Depth Reversal are stored in other_transformation
2673             auto op = createCoordinateOperation(
2674                 code, false /* allowConcatenated */,
2675                 false /* usePROJAlternativeGridNames */,
2676                 "other_transformation");
2677             auto conv =
2678                 util::nn_dynamic_pointer_cast<operation::Conversion>(op);
2679             if (conv) {
2680                 return NN_NO_CHECK(conv);
2681             }
2682         } catch (const std::exception &) {
2683         }
2684         throw NoSuchAuthorityCodeException("conversion not found",
2685                                            d->authority(), code);
2686     }
2687     try {
2688         const auto &row = res.front();
2689         size_t idx = 0;
2690         const auto &name = row[idx++];
2691         const auto &description = row[idx++];
2692         const auto &method_auth_name = row[idx++];
2693         const auto &method_code = row[idx++];
2694         const auto &method_name = row[idx++];
2695         const size_t base_param_idx = idx;
2696         std::vector<operation::OperationParameterNNPtr> parameters;
2697         std::vector<operation::ParameterValueNNPtr> values;
2698         constexpr int N_MAX_PARAMS = 7;
2699         for (int i = 0; i < N_MAX_PARAMS; ++i) {
2700             const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
2701             if (param_auth_name.empty()) {
2702                 break;
2703             }
2704             const auto &param_code = row[base_param_idx + i * 6 + 1];
2705             const auto &param_name = row[base_param_idx + i * 6 + 2];
2706             const auto &param_value = row[base_param_idx + i * 6 + 3];
2707             const auto &param_uom_auth_name = row[base_param_idx + i * 6 + 4];
2708             const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
2709             parameters.emplace_back(operation::OperationParameter::create(
2710                 util::PropertyMap()
2711                     .set(metadata::Identifier::CODESPACE_KEY, param_auth_name)
2712                     .set(metadata::Identifier::CODE_KEY, param_code)
2713                     .set(common::IdentifiedObject::NAME_KEY, param_name)));
2714             std::string normalized_uom_code(param_uom_code);
2715             const double normalized_value = normalizeMeasure(
2716                 param_uom_code, param_value, normalized_uom_code);
2717             auto uom = d->createUnitOfMeasure(param_uom_auth_name,
2718                                               normalized_uom_code);
2719             values.emplace_back(operation::ParameterValue::create(
2720                 common::Measure(normalized_value, uom)));
2721         }
2722         const bool deprecated = row[base_param_idx + N_MAX_PARAMS * 6] == "1";
2723 
2724         auto propConversion = d->createPropertiesSearchUsages(
2725             "conversion", code, name, deprecated);
2726         if (!description.empty())
2727             propConversion.set(common::IdentifiedObject::REMARKS_KEY,
2728                                description);
2729 
2730         auto propMethod = util::PropertyMap().set(
2731             common::IdentifiedObject::NAME_KEY, method_name);
2732         if (!method_auth_name.empty()) {
2733             propMethod
2734                 .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
2735                 .set(metadata::Identifier::CODE_KEY, method_code);
2736         }
2737 
2738         return operation::Conversion::create(propConversion, propMethod,
2739                                              parameters, values);
2740     } catch (const std::exception &ex) {
2741         throw buildFactoryException("conversion", code, ex);
2742     }
2743 }
2744 
2745 // ---------------------------------------------------------------------------
2746 
2747 /** \brief Returns a crs::ProjectedCRS from the specified code.
2748  *
2749  * @param code Object code allocated by authority.
2750  * @return object.
2751  * @throw NoSuchAuthorityCodeException
2752  * @throw FactoryException
2753  */
2754 
2755 crs::ProjectedCRSNNPtr
createProjectedCRS(const std::string & code) const2756 AuthorityFactory::createProjectedCRS(const std::string &code) const {
2757     const auto cacheKey(d->authority() + code);
2758     auto crs = d->context()->d->getCRSFromCache(cacheKey);
2759     if (crs) {
2760         auto projCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
2761         if (projCRS) {
2762             return NN_NO_CHECK(projCRS);
2763         }
2764         throw NoSuchAuthorityCodeException("projectedCRS not found",
2765                                            d->authority(), code);
2766     }
2767     return d->createProjectedCRSEnd(code, d->createProjectedCRSBegin(code));
2768 }
2769 
2770 // ---------------------------------------------------------------------------
2771 //! @cond Doxygen_Suppress
2772 
2773 /** Returns the result of the SQL query needed by createProjectedCRSEnd
2774  *
2775  * The split in two functions is for createFromCoordinateReferenceSystemCodes()
2776  * convenience, to avoid throwing exceptions.
2777  */
2778 SQLResultSet
createProjectedCRSBegin(const std::string & code)2779 AuthorityFactory::Private::createProjectedCRSBegin(const std::string &code) {
2780     return runWithCodeParam(
2781         "SELECT name, coordinate_system_auth_name, "
2782         "coordinate_system_code, geodetic_crs_auth_name, geodetic_crs_code, "
2783         "conversion_auth_name, conversion_code, "
2784         "text_definition, "
2785         "deprecated FROM projected_crs WHERE auth_name = ? AND code = ?",
2786         code);
2787 }
2788 
2789 // ---------------------------------------------------------------------------
2790 
2791 /** Build a ProjectedCRS from the result of createProjectedCRSBegin() */
2792 crs::ProjectedCRSNNPtr
createProjectedCRSEnd(const std::string & code,const SQLResultSet & res)2793 AuthorityFactory::Private::createProjectedCRSEnd(const std::string &code,
2794                                                  const SQLResultSet &res) {
2795     const auto cacheKey(authority() + code);
2796     if (res.empty()) {
2797         throw NoSuchAuthorityCodeException("projectedCRS not found",
2798                                            authority(), code);
2799     }
2800     try {
2801         const auto &row = res.front();
2802         const auto &name = row[0];
2803         const auto &cs_auth_name = row[1];
2804         const auto &cs_code = row[2];
2805         const auto &geodetic_crs_auth_name = row[3];
2806         const auto &geodetic_crs_code = row[4];
2807         const auto &conversion_auth_name = row[5];
2808         const auto &conversion_code = row[6];
2809         const auto &text_definition = row[7];
2810         const bool deprecated = row[8] == "1";
2811 
2812         auto props = createPropertiesSearchUsages("projected_crs", code, name,
2813                                                   deprecated);
2814 
2815         if (!text_definition.empty()) {
2816             DatabaseContext::Private::RecursionDetector detector(context());
2817             auto obj = createFromUserInput(
2818                 pj_add_type_crs_if_needed(text_definition), context());
2819             auto projCRS = dynamic_cast<const crs::ProjectedCRS *>(obj.get());
2820             if (projCRS) {
2821                 const auto conv = projCRS->derivingConversion();
2822                 auto newConv =
2823                     (conv->nameStr() == "unnamed")
2824                         ? operation::Conversion::create(
2825                               util::PropertyMap().set(
2826                                   common::IdentifiedObject::NAME_KEY, name),
2827                               conv->method(), conv->parameterValues())
2828                         : conv;
2829                 auto crsRet = crs::ProjectedCRS::create(
2830                     props, projCRS->baseCRS(), newConv,
2831                     projCRS->coordinateSystem());
2832                 context()->d->cache(cacheKey, crsRet);
2833                 return crsRet;
2834             }
2835 
2836             auto boundCRS = dynamic_cast<const crs::BoundCRS *>(obj.get());
2837             if (boundCRS) {
2838                 projCRS = dynamic_cast<const crs::ProjectedCRS *>(
2839                     boundCRS->baseCRS().get());
2840                 if (projCRS) {
2841                     auto newBoundCRS = crs::BoundCRS::create(
2842                         crs::ProjectedCRS::create(props, projCRS->baseCRS(),
2843                                                   projCRS->derivingConversion(),
2844                                                   projCRS->coordinateSystem()),
2845                         boundCRS->hubCRS(), boundCRS->transformation());
2846                     return NN_NO_CHECK(
2847                         util::nn_dynamic_pointer_cast<crs::ProjectedCRS>(
2848                             newBoundCRS->baseCRSWithCanonicalBoundCRS()));
2849                 }
2850             }
2851 
2852             throw FactoryException(
2853                 "text_definition does not define a ProjectedCRS");
2854         }
2855 
2856         auto cs = createFactory(cs_auth_name)->createCoordinateSystem(cs_code);
2857 
2858         auto baseCRS = createFactory(geodetic_crs_auth_name)
2859                            ->createGeodeticCRS(geodetic_crs_code);
2860 
2861         auto conv = createFactory(conversion_auth_name)
2862                         ->createConversion(conversion_code);
2863         if (conv->nameStr() == "unnamed") {
2864             conv = conv->shallowClone();
2865             conv->setProperties(util::PropertyMap().set(
2866                 common::IdentifiedObject::NAME_KEY, name));
2867         }
2868 
2869         auto cartesianCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
2870         if (cartesianCS) {
2871             auto crsRet = crs::ProjectedCRS::create(props, baseCRS, conv,
2872                                                     NN_NO_CHECK(cartesianCS));
2873             context()->d->cache(cacheKey, crsRet);
2874             return crsRet;
2875         }
2876         throw FactoryException("unsupported CS type for projectedCRS: " +
2877                                cs->getWKT2Type(true));
2878     } catch (const std::exception &ex) {
2879         throw buildFactoryException("projectedCRS", code, ex);
2880     }
2881 }
2882 //! @endcond
2883 
2884 // ---------------------------------------------------------------------------
2885 
2886 /** \brief Returns a crs::CompoundCRS from the specified code.
2887  *
2888  * @param code Object code allocated by authority.
2889  * @return object.
2890  * @throw NoSuchAuthorityCodeException
2891  * @throw FactoryException
2892  */
2893 
2894 crs::CompoundCRSNNPtr
createCompoundCRS(const std::string & code) const2895 AuthorityFactory::createCompoundCRS(const std::string &code) const {
2896     auto res =
2897         d->runWithCodeParam("SELECT name, horiz_crs_auth_name, horiz_crs_code, "
2898                             "vertical_crs_auth_name, vertical_crs_code, "
2899                             "deprecated FROM "
2900                             "compound_crs WHERE auth_name = ? AND code = ?",
2901                             code);
2902     if (res.empty()) {
2903         throw NoSuchAuthorityCodeException("compoundCRS not found",
2904                                            d->authority(), code);
2905     }
2906     try {
2907         const auto &row = res.front();
2908         const auto &name = row[0];
2909         const auto &horiz_crs_auth_name = row[1];
2910         const auto &horiz_crs_code = row[2];
2911         const auto &vertical_crs_auth_name = row[3];
2912         const auto &vertical_crs_code = row[4];
2913         const bool deprecated = row[5] == "1";
2914 
2915         auto horizCRS =
2916             d->createFactory(horiz_crs_auth_name)
2917                 ->createCoordinateReferenceSystem(horiz_crs_code, false);
2918         auto vertCRS = d->createFactory(vertical_crs_auth_name)
2919                            ->createVerticalCRS(vertical_crs_code);
2920 
2921         auto props = d->createPropertiesSearchUsages("compound_crs", code, name,
2922                                                      deprecated);
2923         return crs::CompoundCRS::create(
2924             props, std::vector<crs::CRSNNPtr>{horizCRS, vertCRS});
2925     } catch (const std::exception &ex) {
2926         throw buildFactoryException("compoundCRS", code, ex);
2927     }
2928 }
2929 
2930 // ---------------------------------------------------------------------------
2931 
2932 /** \brief Returns a crs::CRS from the specified code.
2933  *
2934  * @param code Object code allocated by authority.
2935  * @return object.
2936  * @throw NoSuchAuthorityCodeException
2937  * @throw FactoryException
2938  */
2939 
createCoordinateReferenceSystem(const std::string & code) const2940 crs::CRSNNPtr AuthorityFactory::createCoordinateReferenceSystem(
2941     const std::string &code) const {
2942     return createCoordinateReferenceSystem(code, true);
2943 }
2944 
2945 //! @cond Doxygen_Suppress
2946 
2947 crs::CRSNNPtr
createCoordinateReferenceSystem(const std::string & code,bool allowCompound) const2948 AuthorityFactory::createCoordinateReferenceSystem(const std::string &code,
2949                                                   bool allowCompound) const {
2950     const auto cacheKey(d->authority() + code);
2951     auto crs = d->context()->d->getCRSFromCache(cacheKey);
2952     if (crs) {
2953         return NN_NO_CHECK(crs);
2954     }
2955     auto res = d->runWithCodeParam(
2956         "SELECT type FROM crs_view WHERE auth_name = ? AND code = ?", code);
2957     if (res.empty()) {
2958         throw NoSuchAuthorityCodeException("crs not found", d->authority(),
2959                                            code);
2960     }
2961     const auto &type = res.front()[0];
2962     if (type == GEOG_2D || type == GEOG_3D || type == GEOCENTRIC) {
2963         return createGeodeticCRS(code);
2964     }
2965     if (type == VERTICAL) {
2966         return createVerticalCRS(code);
2967     }
2968     if (type == PROJECTED) {
2969         return createProjectedCRS(code);
2970     }
2971     if (allowCompound && type == COMPOUND) {
2972         return createCompoundCRS(code);
2973     }
2974     throw FactoryException("unhandled CRS type: " + type);
2975 }
2976 
2977 //! @endcond
2978 
2979 // ---------------------------------------------------------------------------
2980 
2981 //! @cond Doxygen_Suppress
2982 
createMapNameEPSGCode(const std::string & name,int code)2983 static util::PropertyMap createMapNameEPSGCode(const std::string &name,
2984                                                int code) {
2985     return util::PropertyMap()
2986         .set(common::IdentifiedObject::NAME_KEY, name)
2987         .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
2988         .set(metadata::Identifier::CODE_KEY, code);
2989 }
2990 
2991 // ---------------------------------------------------------------------------
2992 
createOpParamNameEPSGCode(int code)2993 static operation::OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
2994     const char *name = operation::OperationParameter::getNameForEPSGCode(code);
2995     assert(name);
2996     return operation::OperationParameter::create(
2997         createMapNameEPSGCode(name, code));
2998 }
2999 
createLength(const std::string & value,const UnitOfMeasure & uom)3000 static operation::ParameterValueNNPtr createLength(const std::string &value,
3001                                                    const UnitOfMeasure &uom) {
3002     return operation::ParameterValue::create(
3003         common::Length(c_locale_stod(value), uom));
3004 }
3005 
createAngle(const std::string & value,const UnitOfMeasure & uom)3006 static operation::ParameterValueNNPtr createAngle(const std::string &value,
3007                                                   const UnitOfMeasure &uom) {
3008     return operation::ParameterValue::create(
3009         common::Angle(c_locale_stod(value), uom));
3010 }
3011 
3012 //! @endcond
3013 
3014 // ---------------------------------------------------------------------------
3015 
3016 /** \brief Returns a operation::CoordinateOperation from the specified code.
3017  *
3018  * @param code Object code allocated by authority.
3019  * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
3020  * should be substituted to the official grid names.
3021  * @return object.
3022  * @throw NoSuchAuthorityCodeException
3023  * @throw FactoryException
3024  */
3025 
createCoordinateOperation(const std::string & code,bool usePROJAlternativeGridNames) const3026 operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
3027     const std::string &code, bool usePROJAlternativeGridNames) const {
3028     return createCoordinateOperation(code, true, usePROJAlternativeGridNames,
3029                                      std::string());
3030 }
3031 
createCoordinateOperation(const std::string & code,bool allowConcatenated,bool usePROJAlternativeGridNames,const std::string & typeIn) const3032 operation::CoordinateOperationNNPtr AuthorityFactory::createCoordinateOperation(
3033     const std::string &code, bool allowConcatenated,
3034     bool usePROJAlternativeGridNames, const std::string &typeIn) const {
3035     std::string type(typeIn);
3036     if (type.empty()) {
3037         auto res = d->runWithCodeParam(
3038             "SELECT type FROM coordinate_operation_with_conversion_view "
3039             "WHERE auth_name = ? AND code = ?",
3040             code);
3041         if (res.empty()) {
3042             throw NoSuchAuthorityCodeException("coordinate operation not found",
3043                                                d->authority(), code);
3044         }
3045         type = res.front()[0];
3046     }
3047 
3048     if (type == "conversion") {
3049         return createConversion(code);
3050     }
3051 
3052     if (type == "helmert_transformation") {
3053 
3054         auto res = d->runWithCodeParam(
3055             "SELECT name, description, "
3056             "method_auth_name, method_code, method_name, "
3057             "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
3058             "target_crs_code, "
3059             "accuracy, tx, ty, tz, translation_uom_auth_name, "
3060             "translation_uom_code, rx, ry, rz, rotation_uom_auth_name, "
3061             "rotation_uom_code, scale_difference, "
3062             "scale_difference_uom_auth_name, scale_difference_uom_code, "
3063             "rate_tx, rate_ty, rate_tz, rate_translation_uom_auth_name, "
3064             "rate_translation_uom_code, rate_rx, rate_ry, rate_rz, "
3065             "rate_rotation_uom_auth_name, rate_rotation_uom_code, "
3066             "rate_scale_difference, rate_scale_difference_uom_auth_name, "
3067             "rate_scale_difference_uom_code, epoch, epoch_uom_auth_name, "
3068             "epoch_uom_code, px, py, pz, pivot_uom_auth_name, pivot_uom_code, "
3069             "operation_version, deprecated FROM "
3070             "helmert_transformation WHERE auth_name = ? AND code = ?",
3071             code);
3072         if (res.empty()) {
3073             // shouldn't happen if foreign keys are OK
3074             throw NoSuchAuthorityCodeException(
3075                 "helmert_transformation not found", d->authority(), code);
3076         }
3077         try {
3078             const auto &row = res.front();
3079             size_t idx = 0;
3080             const auto &name = row[idx++];
3081             const auto &description = row[idx++];
3082             const auto &method_auth_name = row[idx++];
3083             const auto &method_code = row[idx++];
3084             const auto &method_name = row[idx++];
3085             const auto &source_crs_auth_name = row[idx++];
3086             const auto &source_crs_code = row[idx++];
3087             const auto &target_crs_auth_name = row[idx++];
3088             const auto &target_crs_code = row[idx++];
3089             const auto &accuracy = row[idx++];
3090 
3091             const auto &tx = row[idx++];
3092             const auto &ty = row[idx++];
3093             const auto &tz = row[idx++];
3094             const auto &translation_uom_auth_name = row[idx++];
3095             const auto &translation_uom_code = row[idx++];
3096             const auto &rx = row[idx++];
3097             const auto &ry = row[idx++];
3098             const auto &rz = row[idx++];
3099             const auto &rotation_uom_auth_name = row[idx++];
3100             const auto &rotation_uom_code = row[idx++];
3101             const auto &scale_difference = row[idx++];
3102             const auto &scale_difference_uom_auth_name = row[idx++];
3103             const auto &scale_difference_uom_code = row[idx++];
3104 
3105             const auto &rate_tx = row[idx++];
3106             const auto &rate_ty = row[idx++];
3107             const auto &rate_tz = row[idx++];
3108             const auto &rate_translation_uom_auth_name = row[idx++];
3109             const auto &rate_translation_uom_code = row[idx++];
3110             const auto &rate_rx = row[idx++];
3111             const auto &rate_ry = row[idx++];
3112             const auto &rate_rz = row[idx++];
3113             const auto &rate_rotation_uom_auth_name = row[idx++];
3114             const auto &rate_rotation_uom_code = row[idx++];
3115             const auto &rate_scale_difference = row[idx++];
3116             const auto &rate_scale_difference_uom_auth_name = row[idx++];
3117             const auto &rate_scale_difference_uom_code = row[idx++];
3118 
3119             const auto &epoch = row[idx++];
3120             const auto &epoch_uom_auth_name = row[idx++];
3121             const auto &epoch_uom_code = row[idx++];
3122 
3123             const auto &px = row[idx++];
3124             const auto &py = row[idx++];
3125             const auto &pz = row[idx++];
3126             const auto &pivot_uom_auth_name = row[idx++];
3127             const auto &pivot_uom_code = row[idx++];
3128 
3129             const auto &operation_version = row[idx++];
3130             const auto &deprecated_str = row[idx++];
3131             const bool deprecated = deprecated_str == "1";
3132             assert(idx == row.size());
3133 
3134             auto uom_translation = d->createUnitOfMeasure(
3135                 translation_uom_auth_name, translation_uom_code);
3136 
3137             auto uom_epoch = epoch_uom_auth_name.empty()
3138                                  ? common::UnitOfMeasure::NONE
3139                                  : d->createUnitOfMeasure(epoch_uom_auth_name,
3140                                                           epoch_uom_code);
3141 
3142             auto sourceCRS =
3143                 d->createFactory(source_crs_auth_name)
3144                     ->createCoordinateReferenceSystem(source_crs_code);
3145             auto targetCRS =
3146                 d->createFactory(target_crs_auth_name)
3147                     ->createCoordinateReferenceSystem(target_crs_code);
3148 
3149             std::vector<operation::OperationParameterNNPtr> parameters;
3150             std::vector<operation::ParameterValueNNPtr> values;
3151 
3152             parameters.emplace_back(createOpParamNameEPSGCode(
3153                 EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
3154             values.emplace_back(createLength(tx, uom_translation));
3155 
3156             parameters.emplace_back(createOpParamNameEPSGCode(
3157                 EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
3158             values.emplace_back(createLength(ty, uom_translation));
3159 
3160             parameters.emplace_back(createOpParamNameEPSGCode(
3161                 EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
3162             values.emplace_back(createLength(tz, uom_translation));
3163 
3164             if (!rx.empty()) {
3165                 // Helmert 7-, 8-, 10- or 15- parameter cases
3166                 auto uom_rotation = d->createUnitOfMeasure(
3167                     rotation_uom_auth_name, rotation_uom_code);
3168 
3169                 parameters.emplace_back(createOpParamNameEPSGCode(
3170                     EPSG_CODE_PARAMETER_X_AXIS_ROTATION));
3171                 values.emplace_back(createAngle(rx, uom_rotation));
3172 
3173                 parameters.emplace_back(createOpParamNameEPSGCode(
3174                     EPSG_CODE_PARAMETER_Y_AXIS_ROTATION));
3175                 values.emplace_back(createAngle(ry, uom_rotation));
3176 
3177                 parameters.emplace_back(createOpParamNameEPSGCode(
3178                     EPSG_CODE_PARAMETER_Z_AXIS_ROTATION));
3179                 values.emplace_back(createAngle(rz, uom_rotation));
3180 
3181                 auto uom_scale_difference =
3182                     scale_difference_uom_auth_name.empty()
3183                         ? common::UnitOfMeasure::NONE
3184                         : d->createUnitOfMeasure(scale_difference_uom_auth_name,
3185                                                  scale_difference_uom_code);
3186 
3187                 parameters.emplace_back(createOpParamNameEPSGCode(
3188                     EPSG_CODE_PARAMETER_SCALE_DIFFERENCE));
3189                 values.emplace_back(operation::ParameterValue::create(
3190                     common::Scale(c_locale_stod(scale_difference),
3191                                   uom_scale_difference)));
3192             }
3193 
3194             if (!rate_tx.empty()) {
3195                 // Helmert 15-parameter
3196 
3197                 auto uom_rate_translation = d->createUnitOfMeasure(
3198                     rate_translation_uom_auth_name, rate_translation_uom_code);
3199 
3200                 parameters.emplace_back(createOpParamNameEPSGCode(
3201                     EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION));
3202                 values.emplace_back(
3203                     createLength(rate_tx, uom_rate_translation));
3204 
3205                 parameters.emplace_back(createOpParamNameEPSGCode(
3206                     EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION));
3207                 values.emplace_back(
3208                     createLength(rate_ty, uom_rate_translation));
3209 
3210                 parameters.emplace_back(createOpParamNameEPSGCode(
3211                     EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION));
3212                 values.emplace_back(
3213                     createLength(rate_tz, uom_rate_translation));
3214 
3215                 auto uom_rate_rotation = d->createUnitOfMeasure(
3216                     rate_rotation_uom_auth_name, rate_rotation_uom_code);
3217 
3218                 parameters.emplace_back(createOpParamNameEPSGCode(
3219                     EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION));
3220                 values.emplace_back(createAngle(rate_rx, uom_rate_rotation));
3221 
3222                 parameters.emplace_back(createOpParamNameEPSGCode(
3223                     EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION));
3224                 values.emplace_back(createAngle(rate_ry, uom_rate_rotation));
3225 
3226                 parameters.emplace_back(createOpParamNameEPSGCode(
3227                     EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION));
3228                 values.emplace_back(createAngle(rate_rz, uom_rate_rotation));
3229 
3230                 auto uom_rate_scale_difference =
3231                     d->createUnitOfMeasure(rate_scale_difference_uom_auth_name,
3232                                            rate_scale_difference_uom_code);
3233                 parameters.emplace_back(createOpParamNameEPSGCode(
3234                     EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE));
3235                 values.emplace_back(operation::ParameterValue::create(
3236                     common::Scale(c_locale_stod(rate_scale_difference),
3237                                   uom_rate_scale_difference)));
3238 
3239                 parameters.emplace_back(createOpParamNameEPSGCode(
3240                     EPSG_CODE_PARAMETER_REFERENCE_EPOCH));
3241                 values.emplace_back(operation::ParameterValue::create(
3242                     common::Measure(c_locale_stod(epoch), uom_epoch)));
3243             } else if (uom_epoch != common::UnitOfMeasure::NONE) {
3244                 // Helmert 8-parameter
3245                 parameters.emplace_back(createOpParamNameEPSGCode(
3246                     EPSG_CODE_PARAMETER_TRANSFORMATION_REFERENCE_EPOCH));
3247                 values.emplace_back(operation::ParameterValue::create(
3248                     common::Measure(c_locale_stod(epoch), uom_epoch)));
3249             } else if (!px.empty()) {
3250                 // Molodensky-Badekas case
3251                 auto uom_pivot =
3252                     d->createUnitOfMeasure(pivot_uom_auth_name, pivot_uom_code);
3253 
3254                 parameters.emplace_back(createOpParamNameEPSGCode(
3255                     EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT));
3256                 values.emplace_back(createLength(px, uom_pivot));
3257 
3258                 parameters.emplace_back(createOpParamNameEPSGCode(
3259                     EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT));
3260                 values.emplace_back(createLength(py, uom_pivot));
3261 
3262                 parameters.emplace_back(createOpParamNameEPSGCode(
3263                     EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT));
3264                 values.emplace_back(createLength(pz, uom_pivot));
3265             }
3266 
3267             auto props = d->createPropertiesSearchUsages(
3268                 type, code, name, deprecated, description);
3269             if (!operation_version.empty()) {
3270                 props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
3271                           operation_version);
3272             }
3273 
3274             auto propsMethod =
3275                 util::PropertyMap()
3276                     .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
3277                     .set(metadata::Identifier::CODE_KEY, method_code)
3278                     .set(common::IdentifiedObject::NAME_KEY, method_name);
3279 
3280             std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
3281             if (!accuracy.empty()) {
3282                 accuracies.emplace_back(
3283                     metadata::PositionalAccuracy::create(accuracy));
3284             }
3285             return operation::Transformation::create(
3286                 props, sourceCRS, targetCRS, nullptr, propsMethod, parameters,
3287                 values, accuracies);
3288 
3289         } catch (const std::exception &ex) {
3290             throw buildFactoryException("transformation", code, ex);
3291         }
3292     }
3293 
3294     if (type == "grid_transformation") {
3295         auto res = d->runWithCodeParam(
3296             "SELECT name, description, "
3297             "method_auth_name, method_code, method_name, "
3298             "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
3299             "target_crs_code, "
3300             "accuracy, grid_param_auth_name, grid_param_code, grid_param_name, "
3301             "grid_name, "
3302             "grid2_param_auth_name, grid2_param_code, grid2_param_name, "
3303             "grid2_name, "
3304             "interpolation_crs_auth_name, interpolation_crs_code, "
3305             "operation_version, deprecated FROM "
3306             "grid_transformation WHERE auth_name = ? AND code = ?",
3307             code);
3308         if (res.empty()) {
3309             // shouldn't happen if foreign keys are OK
3310             throw NoSuchAuthorityCodeException("grid_transformation not found",
3311                                                d->authority(), code);
3312         }
3313         try {
3314             const auto &row = res.front();
3315             size_t idx = 0;
3316             const auto &name = row[idx++];
3317             const auto &description = row[idx++];
3318             const auto &method_auth_name = row[idx++];
3319             const auto &method_code = row[idx++];
3320             const auto &method_name = row[idx++];
3321             const auto &source_crs_auth_name = row[idx++];
3322             const auto &source_crs_code = row[idx++];
3323             const auto &target_crs_auth_name = row[idx++];
3324             const auto &target_crs_code = row[idx++];
3325             const auto &accuracy = row[idx++];
3326             const auto &grid_param_auth_name = row[idx++];
3327             const auto &grid_param_code = row[idx++];
3328             const auto &grid_param_name = row[idx++];
3329             const auto &grid_name = row[idx++];
3330             const auto &grid2_param_auth_name = row[idx++];
3331             const auto &grid2_param_code = row[idx++];
3332             const auto &grid2_param_name = row[idx++];
3333             const auto &grid2_name = row[idx++];
3334             const auto &interpolation_crs_auth_name = row[idx++];
3335             const auto &interpolation_crs_code = row[idx++];
3336             const auto &operation_version = row[idx++];
3337             const auto &deprecated_str = row[idx++];
3338             const bool deprecated = deprecated_str == "1";
3339             assert(idx == row.size());
3340 
3341             auto sourceCRS =
3342                 d->createFactory(source_crs_auth_name)
3343                     ->createCoordinateReferenceSystem(source_crs_code);
3344             auto targetCRS =
3345                 d->createFactory(target_crs_auth_name)
3346                     ->createCoordinateReferenceSystem(target_crs_code);
3347             auto interpolationCRS =
3348                 interpolation_crs_auth_name.empty()
3349                     ? nullptr
3350                     : d->createFactory(interpolation_crs_auth_name)
3351                           ->createCoordinateReferenceSystem(
3352                               interpolation_crs_code)
3353                           .as_nullable();
3354 
3355             std::vector<operation::OperationParameterNNPtr> parameters;
3356             std::vector<operation::ParameterValueNNPtr> values;
3357 
3358             parameters.emplace_back(operation::OperationParameter::create(
3359                 util::PropertyMap()
3360                     .set(common::IdentifiedObject::NAME_KEY, grid_param_name)
3361                     .set(metadata::Identifier::CODESPACE_KEY,
3362                          grid_param_auth_name)
3363                     .set(metadata::Identifier::CODE_KEY, grid_param_code)));
3364             values.emplace_back(
3365                 operation::ParameterValue::createFilename(grid_name));
3366             if (!grid2_name.empty()) {
3367                 parameters.emplace_back(operation::OperationParameter::create(
3368                     util::PropertyMap()
3369                         .set(common::IdentifiedObject::NAME_KEY,
3370                              grid2_param_name)
3371                         .set(metadata::Identifier::CODESPACE_KEY,
3372                              grid2_param_auth_name)
3373                         .set(metadata::Identifier::CODE_KEY,
3374                              grid2_param_code)));
3375                 values.emplace_back(
3376                     operation::ParameterValue::createFilename(grid2_name));
3377             }
3378 
3379             auto props = d->createPropertiesSearchUsages(
3380                 type, code, name, deprecated, description);
3381             if (!operation_version.empty()) {
3382                 props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
3383                           operation_version);
3384             }
3385             auto propsMethod =
3386                 util::PropertyMap()
3387                     .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
3388                     .set(metadata::Identifier::CODE_KEY, method_code)
3389                     .set(common::IdentifiedObject::NAME_KEY, method_name);
3390 
3391             std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
3392             if (!accuracy.empty()) {
3393                 accuracies.emplace_back(
3394                     metadata::PositionalAccuracy::create(accuracy));
3395             }
3396             auto transf = operation::Transformation::create(
3397                 props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
3398                 parameters, values, accuracies);
3399             if (usePROJAlternativeGridNames) {
3400                 return transf->substitutePROJAlternativeGridNames(d->context());
3401             }
3402             return transf;
3403 
3404         } catch (const std::exception &ex) {
3405             throw buildFactoryException("transformation", code, ex);
3406         }
3407     }
3408 
3409     if (type == "other_transformation") {
3410         std::ostringstream buffer;
3411         buffer.imbue(std::locale::classic());
3412         buffer
3413             << "SELECT name, description, "
3414                "method_auth_name, method_code, method_name, "
3415                "source_crs_auth_name, source_crs_code, target_crs_auth_name, "
3416                "target_crs_code, "
3417                "interpolation_crs_auth_name, interpolation_crs_code, "
3418                "operation_version, accuracy, deprecated";
3419         constexpr int N_MAX_PARAMS = 7;
3420         for (int i = 1; i <= N_MAX_PARAMS; ++i) {
3421             buffer << ", param" << i << "_auth_name";
3422             buffer << ", param" << i << "_code";
3423             buffer << ", param" << i << "_name";
3424             buffer << ", param" << i << "_value";
3425             buffer << ", param" << i << "_uom_auth_name";
3426             buffer << ", param" << i << "_uom_code";
3427         }
3428         buffer << " FROM other_transformation "
3429                   "WHERE auth_name = ? AND code = ?";
3430 
3431         auto res = d->runWithCodeParam(buffer.str(), code);
3432         if (res.empty()) {
3433             // shouldn't happen if foreign keys are OK
3434             throw NoSuchAuthorityCodeException("other_transformation not found",
3435                                                d->authority(), code);
3436         }
3437         try {
3438             const auto &row = res.front();
3439             size_t idx = 0;
3440             const auto &name = row[idx++];
3441             const auto &description = row[idx++];
3442             const auto &method_auth_name = row[idx++];
3443             const auto &method_code = row[idx++];
3444             const auto &method_name = row[idx++];
3445             const auto &source_crs_auth_name = row[idx++];
3446             const auto &source_crs_code = row[idx++];
3447             const auto &target_crs_auth_name = row[idx++];
3448             const auto &target_crs_code = row[idx++];
3449             const auto &interpolation_crs_auth_name = row[idx++];
3450             const auto &interpolation_crs_code = row[idx++];
3451             const auto &operation_version = row[idx++];
3452             const auto &accuracy = row[idx++];
3453             const auto &deprecated_str = row[idx++];
3454             const bool deprecated = deprecated_str == "1";
3455 
3456             const size_t base_param_idx = idx;
3457             std::vector<operation::OperationParameterNNPtr> parameters;
3458             std::vector<operation::ParameterValueNNPtr> values;
3459             for (int i = 0; i < N_MAX_PARAMS; ++i) {
3460                 const auto &param_auth_name = row[base_param_idx + i * 6 + 0];
3461                 if (param_auth_name.empty()) {
3462                     break;
3463                 }
3464                 const auto &param_code = row[base_param_idx + i * 6 + 1];
3465                 const auto &param_name = row[base_param_idx + i * 6 + 2];
3466                 const auto &param_value = row[base_param_idx + i * 6 + 3];
3467                 const auto &param_uom_auth_name =
3468                     row[base_param_idx + i * 6 + 4];
3469                 const auto &param_uom_code = row[base_param_idx + i * 6 + 5];
3470                 parameters.emplace_back(operation::OperationParameter::create(
3471                     util::PropertyMap()
3472                         .set(metadata::Identifier::CODESPACE_KEY,
3473                              param_auth_name)
3474                         .set(metadata::Identifier::CODE_KEY, param_code)
3475                         .set(common::IdentifiedObject::NAME_KEY, param_name)));
3476                 std::string normalized_uom_code(param_uom_code);
3477                 const double normalized_value = normalizeMeasure(
3478                     param_uom_code, param_value, normalized_uom_code);
3479                 auto uom = d->createUnitOfMeasure(param_uom_auth_name,
3480                                                   normalized_uom_code);
3481                 values.emplace_back(operation::ParameterValue::create(
3482                     common::Measure(normalized_value, uom)));
3483             }
3484             idx = base_param_idx + 6 * N_MAX_PARAMS;
3485             (void)idx;
3486             assert(idx == row.size());
3487 
3488             auto sourceCRS =
3489                 d->createFactory(source_crs_auth_name)
3490                     ->createCoordinateReferenceSystem(source_crs_code);
3491             auto targetCRS =
3492                 d->createFactory(target_crs_auth_name)
3493                     ->createCoordinateReferenceSystem(target_crs_code);
3494             auto interpolationCRS =
3495                 interpolation_crs_auth_name.empty()
3496                     ? nullptr
3497                     : d->createFactory(interpolation_crs_auth_name)
3498                           ->createCoordinateReferenceSystem(
3499                               interpolation_crs_code)
3500                           .as_nullable();
3501 
3502             auto props = d->createPropertiesSearchUsages(
3503                 type, code, name, deprecated, description);
3504             if (!operation_version.empty()) {
3505                 props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
3506                           operation_version);
3507             }
3508 
3509             std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
3510             if (!accuracy.empty()) {
3511                 accuracies.emplace_back(
3512                     metadata::PositionalAccuracy::create(accuracy));
3513             }
3514 
3515             if (method_auth_name == "PROJ") {
3516                 if (method_code == "PROJString") {
3517                     auto op = operation::SingleOperation::createPROJBased(
3518                         props, method_name, sourceCRS, targetCRS, accuracies);
3519                     op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
3520                     return op;
3521                 } else if (method_code == "WKT") {
3522                     auto op = util::nn_dynamic_pointer_cast<
3523                         operation::CoordinateOperation>(
3524                         WKTParser().createFromWKT(method_name));
3525                     if (!op) {
3526                         throw FactoryException("WKT string does not express a "
3527                                                "coordinate operation");
3528                     }
3529                     op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
3530                     return NN_NO_CHECK(op);
3531                 }
3532             }
3533 
3534             auto propsMethod =
3535                 util::PropertyMap()
3536                     .set(metadata::Identifier::CODESPACE_KEY, method_auth_name)
3537                     .set(metadata::Identifier::CODE_KEY, method_code)
3538                     .set(common::IdentifiedObject::NAME_KEY, method_name);
3539 
3540             if (method_auth_name == metadata::Identifier::EPSG) {
3541                 int method_code_int = std::atoi(method_code.c_str());
3542                 if (operation::isAxisOrderReversal(method_code_int) ||
3543                     method_code_int == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
3544                     method_code_int == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
3545                     auto op = operation::Conversion::create(props, propsMethod,
3546                                                             parameters, values);
3547                     op->setCRSs(sourceCRS, targetCRS, interpolationCRS);
3548                     return op;
3549                 }
3550             }
3551             return operation::Transformation::create(
3552                 props, sourceCRS, targetCRS, interpolationCRS, propsMethod,
3553                 parameters, values, accuracies);
3554 
3555         } catch (const std::exception &ex) {
3556             throw buildFactoryException("transformation", code, ex);
3557         }
3558     }
3559 
3560     if (allowConcatenated && type == "concatenated_operation") {
3561         auto res = d->runWithCodeParam(
3562             "SELECT name, description, "
3563             "source_crs_auth_name, source_crs_code, "
3564             "target_crs_auth_name, target_crs_code, "
3565             "accuracy, "
3566             "operation_version, deprecated FROM "
3567             "concatenated_operation WHERE auth_name = ? AND code = ?",
3568             code);
3569         if (res.empty()) {
3570             // shouldn't happen if foreign keys are OK
3571             throw NoSuchAuthorityCodeException(
3572                 "concatenated_operation not found", d->authority(), code);
3573         }
3574 
3575         auto resSteps = d->runWithCodeParam(
3576             "SELECT step_auth_name, step_code FROM "
3577             "concatenated_operation_step WHERE operation_auth_name = ? "
3578             "AND operation_code = ? ORDER BY step_number",
3579             code);
3580 
3581         try {
3582             const auto &row = res.front();
3583             size_t idx = 0;
3584             const auto &name = row[idx++];
3585             const auto &description = row[idx++];
3586             const auto &source_crs_auth_name = row[idx++];
3587             const auto &source_crs_code = row[idx++];
3588             const auto &target_crs_auth_name = row[idx++];
3589             const auto &target_crs_code = row[idx++];
3590             const auto &accuracy = row[idx++];
3591             const auto &operation_version = row[idx++];
3592             const auto &deprecated_str = row[idx++];
3593             const bool deprecated = deprecated_str == "1";
3594 
3595             std::vector<operation::CoordinateOperationNNPtr> operations;
3596             for (const auto &rowStep : resSteps) {
3597                 const auto &step_auth_name = rowStep[0];
3598                 const auto &step_code = rowStep[1];
3599                 operations.push_back(
3600                     d->createFactory(step_auth_name)
3601                         ->createCoordinateOperation(step_code, false,
3602                                                     usePROJAlternativeGridNames,
3603                                                     std::string()));
3604             }
3605 
3606             operation::ConcatenatedOperation::fixStepsDirection(
3607                 d->createFactory(source_crs_auth_name)
3608                     ->createCoordinateReferenceSystem(source_crs_code),
3609                 d->createFactory(target_crs_auth_name)
3610                     ->createCoordinateReferenceSystem(target_crs_code),
3611                 operations);
3612 
3613             auto props = d->createPropertiesSearchUsages(
3614                 type, code, name, deprecated, description);
3615             if (!operation_version.empty()) {
3616                 props.set(operation::CoordinateOperation::OPERATION_VERSION_KEY,
3617                           operation_version);
3618             }
3619 
3620             std::vector<metadata::PositionalAccuracyNNPtr> accuracies;
3621             if (!accuracy.empty()) {
3622                 accuracies.emplace_back(
3623                     metadata::PositionalAccuracy::create(accuracy));
3624             } else {
3625                 // Try to compute a reasonable accuracy from the members
3626                 double totalAcc = -1;
3627                 try {
3628                     for (const auto &op : operations) {
3629                         auto accs = op->coordinateOperationAccuracies();
3630                         if (accs.size() == 1) {
3631                             double acc = c_locale_stod(accs[0]->value());
3632                             if (totalAcc < 0) {
3633                                 totalAcc = acc;
3634                             } else {
3635                                 totalAcc += acc;
3636                             }
3637                         } else if (dynamic_cast<const operation::Conversion *>(
3638                                        op.get())) {
3639                             // A conversion is perfectly accurate.
3640                             if (totalAcc < 0) {
3641                                 totalAcc = 0;
3642                             }
3643                         } else {
3644                             totalAcc = -1;
3645                             break;
3646                         }
3647                     }
3648                     if (totalAcc >= 0) {
3649                         accuracies.emplace_back(
3650                             metadata::PositionalAccuracy::create(
3651                                 toString(totalAcc)));
3652                     }
3653                 } catch (const std::exception &) {
3654                 }
3655             }
3656             return operation::ConcatenatedOperation::create(props, operations,
3657                                                             accuracies);
3658 
3659         } catch (const std::exception &ex) {
3660             throw buildFactoryException("transformation", code, ex);
3661         }
3662     }
3663 
3664     throw FactoryException("unhandled coordinate operation type: " + type);
3665 }
3666 
3667 // ---------------------------------------------------------------------------
3668 
3669 /** \brief Returns a list operation::CoordinateOperation between two CRS.
3670  *
3671  * The list is ordered with preferred operations first. No attempt is made
3672  * at inferring operations that are not explicitly in the database.
3673  *
3674  * Deprecated operations are rejected.
3675  *
3676  * @param sourceCRSCode Source CRS code allocated by authority.
3677  * @param targetCRSCode Source CRS code allocated by authority.
3678  * @return list of coordinate operations
3679  * @throw NoSuchAuthorityCodeException
3680  * @throw FactoryException
3681  */
3682 
3683 std::vector<operation::CoordinateOperationNNPtr>
createFromCoordinateReferenceSystemCodes(const std::string & sourceCRSCode,const std::string & targetCRSCode) const3684 AuthorityFactory::createFromCoordinateReferenceSystemCodes(
3685     const std::string &sourceCRSCode, const std::string &targetCRSCode) const {
3686     return createFromCoordinateReferenceSystemCodes(
3687         d->authority(), sourceCRSCode, d->authority(), targetCRSCode, false,
3688         false, false, false);
3689 }
3690 
3691 // ---------------------------------------------------------------------------
3692 
3693 /** \brief Returns a list operation::CoordinateOperation between two CRS.
3694  *
3695  * The list is ordered with preferred operations first. No attempt is made
3696  * at inferring operations that are not explicitly in the database (see
3697  * createFromCRSCodesWithIntermediates() for that), and only
3698  * source -> target operations are searched (ie if target -> source is present,
3699  * you need to call this method with the arguments reversed, and apply the
3700  * reverse transformations).
3701  *
3702  * Deprecated operations are rejected.
3703  *
3704  * If getAuthority() returns empty, then coordinate operations from all
3705  * authorities are considered.
3706  *
3707  * @param sourceCRSAuthName Authority name of sourceCRSCode
3708  * @param sourceCRSCode Source CRS code allocated by authority
3709  * sourceCRSAuthName.
3710  * @param targetCRSAuthName Authority name of targetCRSCode
3711  * @param targetCRSCode Source CRS code allocated by authority
3712  * targetCRSAuthName.
3713  * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
3714  * should be substituted to the official grid names.
3715  * @param discardIfMissingGrid Whether coordinate operations that reference
3716  * missing grids should be removed from the result set.
3717  * @param considerKnownGridsAsAvailable Whether known grids should be considered
3718  * as available (typically when network is enabled).
3719  * @param discardSuperseded Whether cordinate operations that are superseded
3720  * (but not deprecated) should be removed from the result set.
3721  * @param tryReverseOrder whether to search in the reverse order too (and thus
3722  * inverse results found that way)
3723  * @param reportOnlyIntersectingTransformations if intersectingExtent1 and
3724  * intersectingExtent2 should be honored in a strict way.
3725  * @param intersectingExtent1 Optional extent that the resulting operations
3726  * must intersect.
3727  * @param intersectingExtent2 Optional extent that the resulting operations
3728  * must intersect.
3729  * @return list of coordinate operations
3730  * @throw NoSuchAuthorityCodeException
3731  * @throw FactoryException
3732  */
3733 
3734 std::vector<operation::CoordinateOperationNNPtr>
createFromCoordinateReferenceSystemCodes(const std::string & sourceCRSAuthName,const std::string & sourceCRSCode,const std::string & targetCRSAuthName,const std::string & targetCRSCode,bool usePROJAlternativeGridNames,bool discardIfMissingGrid,bool considerKnownGridsAsAvailable,bool discardSuperseded,bool tryReverseOrder,bool reportOnlyIntersectingTransformations,const metadata::ExtentPtr & intersectingExtent1,const metadata::ExtentPtr & intersectingExtent2) const3735 AuthorityFactory::createFromCoordinateReferenceSystemCodes(
3736     const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
3737     const std::string &targetCRSAuthName, const std::string &targetCRSCode,
3738     bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
3739     bool considerKnownGridsAsAvailable, bool discardSuperseded,
3740     bool tryReverseOrder, bool reportOnlyIntersectingTransformations,
3741     const metadata::ExtentPtr &intersectingExtent1,
3742     const metadata::ExtentPtr &intersectingExtent2) const {
3743 
3744     auto cacheKey(d->authority());
3745     cacheKey += sourceCRSAuthName.empty() ? "{empty}" : sourceCRSAuthName;
3746     cacheKey += sourceCRSCode;
3747     cacheKey += targetCRSAuthName.empty() ? "{empty}" : targetCRSAuthName;
3748     cacheKey += targetCRSCode;
3749     cacheKey += (usePROJAlternativeGridNames ? '1' : '0');
3750     cacheKey += (discardIfMissingGrid ? '1' : '0');
3751     cacheKey += (considerKnownGridsAsAvailable ? '1' : '0');
3752     cacheKey += (discardSuperseded ? '1' : '0');
3753     cacheKey += (tryReverseOrder ? '1' : '0');
3754     cacheKey += (reportOnlyIntersectingTransformations ? '1' : '0');
3755     for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
3756         if (extent) {
3757             const auto &geogExtent = extent->geographicElements();
3758             if (geogExtent.size() == 1) {
3759                 auto bbox =
3760                     dynamic_cast<const metadata::GeographicBoundingBox *>(
3761                         geogExtent[0].get());
3762                 if (bbox) {
3763                     cacheKey += toString(bbox->southBoundLatitude());
3764                     cacheKey += toString(bbox->westBoundLongitude());
3765                     cacheKey += toString(bbox->northBoundLatitude());
3766                     cacheKey += toString(bbox->eastBoundLongitude());
3767                 }
3768             }
3769         }
3770     }
3771 
3772     std::vector<operation::CoordinateOperationNNPtr> list;
3773 
3774     if (d->context()->d->getCRSToCRSCoordOpFromCache(cacheKey, list)) {
3775         return list;
3776     }
3777 
3778     // Check if sourceCRS would be the base of a ProjectedCRS targetCRS
3779     // In which case use the conversion of the ProjectedCRS
3780     if (!targetCRSAuthName.empty()) {
3781         auto targetFactory = d->createFactory(targetCRSAuthName);
3782         const auto cacheKeyProjectedCRS(targetFactory->d->authority() +
3783                                         targetCRSCode);
3784         auto crs = targetFactory->d->context()->d->getCRSFromCache(
3785             cacheKeyProjectedCRS);
3786         crs::ProjectedCRSPtr targetProjCRS;
3787         if (crs) {
3788             targetProjCRS = std::dynamic_pointer_cast<crs::ProjectedCRS>(crs);
3789         } else {
3790             const auto sqlRes =
3791                 targetFactory->d->createProjectedCRSBegin(targetCRSCode);
3792             if (!sqlRes.empty()) {
3793                 try {
3794                     targetProjCRS =
3795                         targetFactory->d
3796                             ->createProjectedCRSEnd(targetCRSCode, sqlRes)
3797                             .as_nullable();
3798                 } catch (const std::exception &) {
3799                 }
3800             }
3801         }
3802         if (targetProjCRS) {
3803             const auto &baseIds = targetProjCRS->baseCRS()->identifiers();
3804             if (sourceCRSAuthName.empty() ||
3805                 (!baseIds.empty() &&
3806                  *(baseIds.front()->codeSpace()) == sourceCRSAuthName &&
3807                  baseIds.front()->code() == sourceCRSCode)) {
3808                 bool ok = true;
3809                 auto conv = targetProjCRS->derivingConversion();
3810                 if (d->hasAuthorityRestriction()) {
3811                     ok = *(conv->identifiers().front()->codeSpace()) ==
3812                          d->authority();
3813                 }
3814                 if (ok) {
3815                     list.emplace_back(conv);
3816                     d->context()->d->cache(cacheKey, list);
3817                     return list;
3818                 }
3819             }
3820         }
3821     }
3822 
3823     std::string sql;
3824     if (discardSuperseded) {
3825         sql = "SELECT source_crs_auth_name, source_crs_code, "
3826               "target_crs_auth_name, target_crs_code, "
3827               "cov.auth_name, cov.code, cov.table_name, "
3828               "extent.south_lat, extent.west_lon, extent.north_lat, "
3829               "extent.east_lon, "
3830               "ss.replacement_auth_name, ss.replacement_code FROM "
3831               "coordinate_operation_view cov "
3832               "JOIN usage ON "
3833               "usage.object_table_name = cov.table_name AND "
3834               "usage.object_auth_name = cov.auth_name AND "
3835               "usage.object_code = cov.code "
3836               "JOIN extent "
3837               "ON extent.auth_name = usage.extent_auth_name AND "
3838               "extent.code = usage.extent_code "
3839               "LEFT JOIN supersession ss ON "
3840               "ss.superseded_table_name = cov.table_name AND "
3841               "ss.superseded_auth_name = cov.auth_name AND "
3842               "ss.superseded_code = cov.code AND "
3843               "ss.superseded_table_name = ss.replacement_table_name AND "
3844               "ss.same_source_target_crs = 1 "
3845               "WHERE ";
3846     } else {
3847         sql = "SELECT source_crs_auth_name, source_crs_code, "
3848               "target_crs_auth_name, target_crs_code, "
3849               "cov.auth_name, cov.code, cov.table_name, "
3850               "extent.south_lat, extent.west_lon, extent.north_lat, "
3851               "extent.east_lon "
3852               "FROM "
3853               "coordinate_operation_view cov "
3854               "JOIN usage ON "
3855               "usage.object_table_name = cov.table_name AND "
3856               "usage.object_auth_name = cov.auth_name AND "
3857               "usage.object_code = cov.code "
3858               "JOIN extent "
3859               "ON extent.auth_name = usage.extent_auth_name AND "
3860               "extent.code = usage.extent_code "
3861               "WHERE ";
3862     }
3863     ListOfParams params;
3864     if (!sourceCRSAuthName.empty() && !targetCRSAuthName.empty()) {
3865         if (tryReverseOrder) {
3866             sql += "((source_crs_auth_name = ? AND source_crs_code = ? AND "
3867                    "target_crs_auth_name = ? AND target_crs_code = ?) OR "
3868                    "(source_crs_auth_name = ? AND source_crs_code = ? AND "
3869                    "target_crs_auth_name = ? AND target_crs_code = ?)) AND ";
3870             params.emplace_back(sourceCRSAuthName);
3871             params.emplace_back(sourceCRSCode);
3872             params.emplace_back(targetCRSAuthName);
3873             params.emplace_back(targetCRSCode);
3874             params.emplace_back(targetCRSAuthName);
3875             params.emplace_back(targetCRSCode);
3876             params.emplace_back(sourceCRSAuthName);
3877             params.emplace_back(sourceCRSCode);
3878         } else {
3879             sql += "source_crs_auth_name = ? AND source_crs_code = ? AND "
3880                    "target_crs_auth_name = ? AND target_crs_code = ? AND ";
3881             params.emplace_back(sourceCRSAuthName);
3882             params.emplace_back(sourceCRSCode);
3883             params.emplace_back(targetCRSAuthName);
3884             params.emplace_back(targetCRSCode);
3885         }
3886     } else if (!sourceCRSAuthName.empty()) {
3887         if (tryReverseOrder) {
3888             sql += "((source_crs_auth_name = ? AND source_crs_code = ?) OR "
3889                    "(target_crs_auth_name = ? AND target_crs_code = ?)) AND ";
3890             params.emplace_back(sourceCRSAuthName);
3891             params.emplace_back(sourceCRSCode);
3892             params.emplace_back(sourceCRSAuthName);
3893             params.emplace_back(sourceCRSCode);
3894         } else {
3895             sql += "source_crs_auth_name = ? AND source_crs_code = ? AND ";
3896             params.emplace_back(sourceCRSAuthName);
3897             params.emplace_back(sourceCRSCode);
3898         }
3899     } else if (!targetCRSAuthName.empty()) {
3900         if (tryReverseOrder) {
3901             sql += "((source_crs_auth_name = ? AND source_crs_code = ?) OR "
3902                    "(target_crs_auth_name = ? AND target_crs_code = ?)) AND ";
3903             params.emplace_back(targetCRSAuthName);
3904             params.emplace_back(targetCRSCode);
3905             params.emplace_back(targetCRSAuthName);
3906             params.emplace_back(targetCRSCode);
3907         } else {
3908             sql += "target_crs_auth_name = ? AND target_crs_code = ? AND ";
3909             params.emplace_back(targetCRSAuthName);
3910             params.emplace_back(targetCRSCode);
3911         }
3912     }
3913     sql += "cov.deprecated = 0";
3914     if (d->hasAuthorityRestriction()) {
3915         sql += " AND cov.auth_name = ?";
3916         params.emplace_back(d->authority());
3917     }
3918     sql += " ORDER BY pseudo_area_from_swne(south_lat, west_lon, north_lat, "
3919            "east_lon) DESC, "
3920            "(CASE WHEN accuracy is NULL THEN 1 ELSE 0 END), accuracy";
3921     auto res = d->run(sql, params);
3922     std::set<std::pair<std::string, std::string>> setTransf;
3923     if (discardSuperseded) {
3924         for (const auto &row : res) {
3925             const auto &auth_name = row[4];
3926             const auto &code = row[5];
3927             setTransf.insert(
3928                 std::pair<std::string, std::string>(auth_name, code));
3929         }
3930     }
3931 
3932     // Do a pass to determine if there are transformations that intersect
3933     // intersectingExtent1 & intersectingExtent2
3934     std::vector<bool> intersectingTransformations;
3935     intersectingTransformations.resize(res.size());
3936     bool hasIntersectingTransformations = false;
3937     size_t i = 0;
3938     for (const auto &row : res) {
3939         size_t thisI = i;
3940         ++i;
3941         if (discardSuperseded) {
3942             const auto &replacement_auth_name = row[11];
3943             const auto &replacement_code = row[12];
3944             if (!replacement_auth_name.empty() &&
3945                 setTransf.find(std::pair<std::string, std::string>(
3946                     replacement_auth_name, replacement_code)) !=
3947                     setTransf.end()) {
3948                 // Skip transformations that are superseded by others that got
3949                 // returned in the result set.
3950                 continue;
3951             }
3952         }
3953 
3954         bool intersecting = true;
3955         try {
3956             double south_lat = c_locale_stod(row[7]);
3957             double west_lon = c_locale_stod(row[8]);
3958             double north_lat = c_locale_stod(row[9]);
3959             double east_lon = c_locale_stod(row[10]);
3960             auto transf_extent = metadata::Extent::createFromBBOX(
3961                 west_lon, south_lat, east_lon, north_lat);
3962 
3963             for (const auto &extent :
3964                  {intersectingExtent1, intersectingExtent2}) {
3965                 if (extent) {
3966                     if (!transf_extent->intersects(NN_NO_CHECK(extent))) {
3967                         intersecting = false;
3968                         break;
3969                     }
3970                 }
3971             }
3972         } catch (const std::exception &) {
3973         }
3974 
3975         intersectingTransformations[thisI] = intersecting;
3976         if (intersecting)
3977             hasIntersectingTransformations = true;
3978     }
3979 
3980     // If there are intersecting transformations, then only report those ones
3981     // If there are no intersecting transformations, report all of them
3982     // This is for the "projinfo -s EPSG:32631 -t EPSG:2171" use case where we
3983     // still want to be able to use the Pulkovo datum shift if EPSG:32631
3984     // coordinates are used
3985     i = 0;
3986     for (const auto &row : res) {
3987         size_t thisI = i;
3988         ++i;
3989         if ((hasIntersectingTransformations ||
3990              reportOnlyIntersectingTransformations) &&
3991             !intersectingTransformations[thisI]) {
3992             continue;
3993         }
3994         if (discardSuperseded) {
3995             const auto &replacement_auth_name = row[11];
3996             const auto &replacement_code = row[12];
3997             if (!replacement_auth_name.empty() &&
3998                 setTransf.find(std::pair<std::string, std::string>(
3999                     replacement_auth_name, replacement_code)) !=
4000                     setTransf.end()) {
4001                 // Skip transformations that are superseded by others that got
4002                 // returned in the result set.
4003                 continue;
4004             }
4005         }
4006 
4007         const auto &source_crs_auth_name = row[0];
4008         const auto &source_crs_code = row[1];
4009         const auto &target_crs_auth_name = row[2];
4010         const auto &target_crs_code = row[3];
4011         const auto &auth_name = row[4];
4012         const auto &code = row[5];
4013         const auto &table_name = row[6];
4014         auto op = d->createFactory(auth_name)->createCoordinateOperation(
4015             code, true, usePROJAlternativeGridNames, table_name);
4016         if (tryReverseOrder &&
4017             (!sourceCRSAuthName.empty()
4018                  ? (source_crs_auth_name != sourceCRSAuthName ||
4019                     source_crs_code != sourceCRSCode)
4020                  : (target_crs_auth_name != targetCRSAuthName ||
4021                     target_crs_code != targetCRSCode))) {
4022             op = op->inverse();
4023         }
4024         if (!discardIfMissingGrid ||
4025             !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
4026             list.emplace_back(op);
4027         }
4028     }
4029     d->context()->d->cache(cacheKey, list);
4030     return list;
4031 }
4032 
4033 // ---------------------------------------------------------------------------
4034 
4035 //! @cond Doxygen_Suppress
useIrrelevantPivot(const operation::CoordinateOperationNNPtr & op,const std::string & sourceCRSAuthName,const std::string & sourceCRSCode,const std::string & targetCRSAuthName,const std::string & targetCRSCode)4036 static bool useIrrelevantPivot(const operation::CoordinateOperationNNPtr &op,
4037                                const std::string &sourceCRSAuthName,
4038                                const std::string &sourceCRSCode,
4039                                const std::string &targetCRSAuthName,
4040                                const std::string &targetCRSCode) {
4041     auto concat =
4042         dynamic_cast<const operation::ConcatenatedOperation *>(op.get());
4043     if (!concat) {
4044         return false;
4045     }
4046     auto ops = concat->operations();
4047     for (size_t i = 0; i + 1 < ops.size(); i++) {
4048         auto targetCRS = ops[i]->targetCRS();
4049         if (targetCRS) {
4050             const auto &ids = targetCRS->identifiers();
4051             if (ids.size() == 1 &&
4052                 ((*ids[0]->codeSpace() == sourceCRSAuthName &&
4053                   ids[0]->code() == sourceCRSCode) ||
4054                  (*ids[0]->codeSpace() == targetCRSAuthName &&
4055                   ids[0]->code() == targetCRSCode))) {
4056                 return true;
4057             }
4058         }
4059     }
4060     return false;
4061 }
4062 //! @endcond
4063 
4064 // ---------------------------------------------------------------------------
4065 
4066 /** \brief Returns a list operation::CoordinateOperation between two CRS,
4067  * using intermediate codes.
4068  *
4069  * The list is ordered with preferred operations first.
4070  *
4071  * Deprecated operations are rejected.
4072  *
4073  * The method will take care of considering all potential combinations (ie
4074  * contrary to createFromCoordinateReferenceSystemCodes(), you do not need to
4075  * call it with sourceCRS and targetCRS switched)
4076  *
4077  * If getAuthority() returns empty, then coordinate operations from all
4078  * authorities are considered.
4079  *
4080  * @param sourceCRSAuthName Authority name of sourceCRSCode
4081  * @param sourceCRSCode Source CRS code allocated by authority
4082  * sourceCRSAuthName.
4083  * @param targetCRSAuthName Authority name of targetCRSCode
4084  * @param targetCRSCode Source CRS code allocated by authority
4085  * targetCRSAuthName.
4086  * @param usePROJAlternativeGridNames Whether PROJ alternative grid names
4087  * should be substituted to the official grid names.
4088  * @param discardIfMissingGrid Whether coordinate operations that reference
4089  * missing grids should be removed from the result set.
4090  * @param considerKnownGridsAsAvailable Whether known grids should be considered
4091  * as available (typically when network is enabled).
4092  * @param discardSuperseded Whether cordinate operations that are superseded
4093  * (but not deprecated) should be removed from the result set.
4094  * @param intermediateCRSAuthCodes List of (auth_name, code) of CRS that can be
4095  * used as potential intermediate CRS. If the list is empty, the database will
4096  * be used to find common CRS in operations involving both the source and
4097  * target CRS.
4098  * @param allowedIntermediateObjectType Restrict the type of the intermediate
4099  * object considered.
4100  * Only ObjectType::CRS and ObjectType::GEOGRAPHIC_CRS supported currently
4101  * @param allowedAuthorities One or several authority name allowed for the two
4102  * coordinate operations that are going to be searched. When this vector is
4103  * no empty, it overrides the authority of this object. This is useful for
4104  * example when the coordinate operations to chain belong to two different
4105  * allowed authorities.
4106  * @param intersectingExtent1 Optional extent that the resulting operations
4107  * must intersect.
4108  * @param intersectingExtent2 Optional extent that the resulting operations
4109  * must intersect.
4110  * @return list of coordinate operations
4111  * @throw NoSuchAuthorityCodeException
4112  * @throw FactoryException
4113  */
4114 
4115 std::vector<operation::CoordinateOperationNNPtr>
createFromCRSCodesWithIntermediates(const std::string & sourceCRSAuthName,const std::string & sourceCRSCode,const std::string & targetCRSAuthName,const std::string & targetCRSCode,bool usePROJAlternativeGridNames,bool discardIfMissingGrid,bool considerKnownGridsAsAvailable,bool discardSuperseded,const std::vector<std::pair<std::string,std::string>> & intermediateCRSAuthCodes,ObjectType allowedIntermediateObjectType,const std::vector<std::string> & allowedAuthorities,const metadata::ExtentPtr & intersectingExtent1,const metadata::ExtentPtr & intersectingExtent2) const4116 AuthorityFactory::createFromCRSCodesWithIntermediates(
4117     const std::string &sourceCRSAuthName, const std::string &sourceCRSCode,
4118     const std::string &targetCRSAuthName, const std::string &targetCRSCode,
4119     bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
4120     bool considerKnownGridsAsAvailable, bool discardSuperseded,
4121     const std::vector<std::pair<std::string, std::string>>
4122         &intermediateCRSAuthCodes,
4123     ObjectType allowedIntermediateObjectType,
4124     const std::vector<std::string> &allowedAuthorities,
4125     const metadata::ExtentPtr &intersectingExtent1,
4126     const metadata::ExtentPtr &intersectingExtent2) const {
4127 
4128     std::vector<operation::CoordinateOperationNNPtr> listTmp;
4129 
4130     if (sourceCRSAuthName == targetCRSAuthName &&
4131         sourceCRSCode == targetCRSCode) {
4132         return listTmp;
4133     }
4134 
4135     const std::string sqlProlog(
4136         discardSuperseded
4137             ?
4138 
4139             "SELECT v1.table_name as table1, "
4140             "v1.auth_name AS auth_name1, v1.code AS code1, "
4141             "v1.accuracy AS accuracy1, "
4142             "v2.table_name as table2, "
4143             "v2.auth_name AS auth_name2, v2.code AS code2, "
4144             "v2.accuracy as accuracy2, "
4145             "a1.south_lat AS south_lat1, "
4146             "a1.west_lon AS west_lon1, "
4147             "a1.north_lat AS north_lat1, "
4148             "a1.east_lon AS east_lon1, "
4149             "a2.south_lat AS south_lat2, "
4150             "a2.west_lon AS west_lon2, "
4151             "a2.north_lat AS north_lat2, "
4152             "a2.east_lon AS east_lon2, "
4153             "ss1.replacement_auth_name AS replacement_auth_name1, "
4154             "ss1.replacement_code AS replacement_code1, "
4155             "ss2.replacement_auth_name AS replacement_auth_name2, "
4156             "ss2.replacement_code AS replacement_code2 "
4157             "FROM coordinate_operation_view v1 "
4158             "JOIN coordinate_operation_view v2 "
4159             :
4160 
4161             "SELECT v1.table_name as table1, "
4162             "v1.auth_name AS auth_name1, v1.code AS code1, "
4163             "v1.accuracy AS accuracy1, "
4164             "v2.table_name as table2, "
4165             "v2.auth_name AS auth_name2, v2.code AS code2, "
4166             "v2.accuracy as accuracy2, "
4167             "a1.south_lat AS south_lat1, "
4168             "a1.west_lon AS west_lon1, "
4169             "a1.north_lat AS north_lat1, "
4170             "a1.east_lon AS east_lon1, "
4171             "a2.south_lat AS south_lat2, "
4172             "a2.west_lon AS west_lon2, "
4173             "a2.north_lat AS north_lat2, "
4174             "a2.east_lon AS east_lon2 "
4175             "FROM coordinate_operation_view v1 "
4176             "JOIN coordinate_operation_view v2 ");
4177 
4178     const std::string joinSupersession(
4179         "LEFT JOIN supersession ss1 ON "
4180         "ss1.superseded_table_name = v1.table_name AND "
4181         "ss1.superseded_auth_name = v1.auth_name AND "
4182         "ss1.superseded_code = v1.code AND "
4183         "ss1.superseded_table_name = ss1.replacement_table_name AND "
4184         "ss1.same_source_target_crs = 1 "
4185         "LEFT JOIN supersession ss2 ON "
4186         "ss2.superseded_table_name = v2.table_name AND "
4187         "ss2.superseded_auth_name = v2.auth_name AND "
4188         "ss2.superseded_code = v2.code AND "
4189         "ss2.superseded_table_name = ss2.replacement_table_name AND "
4190         "ss2.same_source_target_crs = 1 ");
4191     const std::string joinArea(
4192         (discardSuperseded ? joinSupersession : std::string()) +
4193         "JOIN usage u1 ON "
4194         "u1.object_table_name = v1.table_name AND "
4195         "u1.object_auth_name = v1.auth_name AND "
4196         "u1.object_code = v1.code "
4197         "JOIN extent a1 "
4198         "ON a1.auth_name = u1.extent_auth_name AND "
4199         "a1.code = u1.extent_code "
4200         "JOIN usage u2 ON "
4201         "u2.object_table_name = v2.table_name AND "
4202         "u2.object_auth_name = v2.auth_name AND "
4203         "u2.object_code = v2.code "
4204         "JOIN extent a2 "
4205         "ON a2.auth_name = u2.extent_auth_name AND "
4206         "a2.code = u2.extent_code ");
4207     const std::string orderBy(
4208         "ORDER BY (CASE WHEN accuracy1 is NULL THEN 1 ELSE 0 END) + "
4209         "(CASE WHEN accuracy2 is NULL THEN 1 ELSE 0 END), "
4210         "accuracy1 + accuracy2");
4211 
4212     // Case (source->intermediate) and (intermediate->target)
4213     std::string sql(
4214         sqlProlog + "ON v1.target_crs_auth_name = v2.source_crs_auth_name "
4215                     "AND v1.target_crs_code = v2.source_crs_code " +
4216         joinArea +
4217         "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
4218         "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ");
4219     std::string minDate;
4220     std::string criterionOnIntermediateCRS;
4221     if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
4222         auto sourceCRS = d->createFactory(sourceCRSAuthName)
4223                              ->createGeodeticCRS(sourceCRSCode);
4224         auto targetCRS = d->createFactory(targetCRSAuthName)
4225                              ->createGeodeticCRS(targetCRSCode);
4226         const auto &sourceDatum = sourceCRS->datum();
4227         const auto &targetDatum = targetCRS->datum();
4228         if (sourceDatum && sourceDatum->publicationDate().has_value() &&
4229             targetDatum && targetDatum->publicationDate().has_value()) {
4230             const auto sourceDate(sourceDatum->publicationDate()->toString());
4231             const auto targetDate(targetDatum->publicationDate()->toString());
4232             minDate = std::min(sourceDate, targetDate);
4233             // Check that the datum of the intermediateCRS has a publication
4234             // date most recent that the one of the source and the target CRS
4235             // Except when using the usual WGS84 pivot which happens to have a
4236             // NULL publication date.
4237             criterionOnIntermediateCRS =
4238                 "AND EXISTS(SELECT 1 FROM geodetic_crs x "
4239                 "JOIN geodetic_datum y "
4240                 "ON "
4241                 "y.auth_name = x.datum_auth_name AND "
4242                 "y.code = x.datum_code "
4243                 "WHERE "
4244                 "x.auth_name = v1.target_crs_auth_name AND "
4245                 "x.code = v1.target_crs_code AND "
4246                 "x.type IN ('geographic 2D', 'geographic 3D') AND "
4247                 "(y.publication_date IS NULL OR "
4248                 "(y.publication_date >= '" +
4249                 minDate + "'))) ";
4250         } else {
4251             criterionOnIntermediateCRS =
4252                 "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
4253                 "x.auth_name = v1.target_crs_auth_name AND "
4254                 "x.code = v1.target_crs_code AND "
4255                 "x.type IN ('geographic 2D', 'geographic 3D')) ";
4256         }
4257         sql += criterionOnIntermediateCRS;
4258     }
4259     auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
4260                                targetCRSAuthName, targetCRSCode};
4261     std::string additionalWhere(
4262         "AND v1.deprecated = 0 AND v2.deprecated = 0 "
4263         "AND intersects_bbox(south_lat1, west_lon1, north_lat1, east_lon1, "
4264         "south_lat2, west_lon2, north_lat2, east_lon2) = 1 ");
4265     if (!allowedAuthorities.empty()) {
4266         additionalWhere += "AND v1.auth_name IN (";
4267         for (size_t i = 0; i < allowedAuthorities.size(); i++) {
4268             if (i > 0)
4269                 additionalWhere += ',';
4270             additionalWhere += '?';
4271         }
4272         additionalWhere += ") AND v2.auth_name IN (";
4273         for (size_t i = 0; i < allowedAuthorities.size(); i++) {
4274             if (i > 0)
4275                 additionalWhere += ',';
4276             additionalWhere += '?';
4277         }
4278         additionalWhere += ')';
4279         for (const auto &allowedAuthority : allowedAuthorities) {
4280             params.emplace_back(allowedAuthority);
4281         }
4282         for (const auto &allowedAuthority : allowedAuthorities) {
4283             params.emplace_back(allowedAuthority);
4284         }
4285     }
4286     if (d->hasAuthorityRestriction()) {
4287         additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
4288         params.emplace_back(d->authority());
4289         params.emplace_back(d->authority());
4290     }
4291     for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
4292         if (extent) {
4293             const auto &geogExtent = extent->geographicElements();
4294             if (geogExtent.size() == 1) {
4295                 auto bbox =
4296                     dynamic_cast<const metadata::GeographicBoundingBox *>(
4297                         geogExtent[0].get());
4298                 if (bbox) {
4299                     const double south_lat = bbox->southBoundLatitude();
4300                     const double west_lon = bbox->westBoundLongitude();
4301                     const double north_lat = bbox->northBoundLatitude();
4302                     const double east_lon = bbox->eastBoundLongitude();
4303                     if (south_lat != -90.0 || west_lon != -180.0 ||
4304                         north_lat != 90.0 || east_lon != 180.0) {
4305                         additionalWhere +=
4306                             "AND intersects_bbox(south_lat1, "
4307                             "west_lon1, north_lat1, east_lon1, ?, ?, ?, ?) AND "
4308                             "intersects_bbox(south_lat2, west_lon2, "
4309                             "north_lat2, east_lon2, ?, ?, ?, ?) ";
4310                         params.emplace_back(south_lat);
4311                         params.emplace_back(west_lon);
4312                         params.emplace_back(north_lat);
4313                         params.emplace_back(east_lon);
4314                         params.emplace_back(south_lat);
4315                         params.emplace_back(west_lon);
4316                         params.emplace_back(north_lat);
4317                         params.emplace_back(east_lon);
4318                     }
4319                 }
4320             }
4321         }
4322     }
4323 
4324     const auto buildIntermediateWhere = [&intermediateCRSAuthCodes](
4325         const std::string &first_field, const std::string &second_field) {
4326         if (intermediateCRSAuthCodes.empty()) {
4327             return std::string();
4328         }
4329         std::string l_sql(" AND (");
4330         for (size_t i = 0; i < intermediateCRSAuthCodes.size(); ++i) {
4331             if (i > 0) {
4332                 l_sql += " OR";
4333             }
4334             l_sql += "(v1." + first_field + "_crs_auth_name = ? AND ";
4335             l_sql += "v1." + first_field + "_crs_code = ? AND ";
4336             l_sql += "v2." + second_field + "_crs_auth_name = ? AND ";
4337             l_sql += "v2." + second_field + "_crs_code = ?) ";
4338         }
4339         l_sql += ')';
4340         return l_sql;
4341     };
4342 
4343     std::string intermediateWhere = buildIntermediateWhere("target", "source");
4344     for (const auto &pair : intermediateCRSAuthCodes) {
4345         params.emplace_back(pair.first);
4346         params.emplace_back(pair.second);
4347         params.emplace_back(pair.first);
4348         params.emplace_back(pair.second);
4349     }
4350     auto res =
4351         d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
4352 
4353     const auto filterOutSuperseded = [](SQLResultSet &&resultSet) {
4354         std::set<std::pair<std::string, std::string>> setTransf1;
4355         std::set<std::pair<std::string, std::string>> setTransf2;
4356         for (const auto &row : resultSet) {
4357             // table1
4358             const auto &auth_name1 = row[1];
4359             const auto &code1 = row[2];
4360             // accuracy1
4361             // table2
4362             const auto &auth_name2 = row[5];
4363             const auto &code2 = row[6];
4364             setTransf1.insert(
4365                 std::pair<std::string, std::string>(auth_name1, code1));
4366             setTransf2.insert(
4367                 std::pair<std::string, std::string>(auth_name2, code2));
4368         }
4369         SQLResultSet filteredResultSet;
4370         for (const auto &row : resultSet) {
4371             const auto &replacement_auth_name1 = row[16];
4372             const auto &replacement_code1 = row[17];
4373             const auto &replacement_auth_name2 = row[18];
4374             const auto &replacement_code2 = row[19];
4375             if (!replacement_auth_name1.empty() &&
4376                 setTransf1.find(std::pair<std::string, std::string>(
4377                     replacement_auth_name1, replacement_code1)) !=
4378                     setTransf1.end()) {
4379                 // Skip transformations that are superseded by others that got
4380                 // returned in the result set.
4381                 continue;
4382             }
4383             if (!replacement_auth_name2.empty() &&
4384                 setTransf2.find(std::pair<std::string, std::string>(
4385                     replacement_auth_name2, replacement_code2)) !=
4386                     setTransf2.end()) {
4387                 // Skip transformations that are superseded by others that got
4388                 // returned in the result set.
4389                 continue;
4390             }
4391             filteredResultSet.emplace_back(row);
4392         }
4393         return filteredResultSet;
4394     };
4395 
4396     if (discardSuperseded) {
4397         res = filterOutSuperseded(std::move(res));
4398     }
4399     for (const auto &row : res) {
4400         const auto &table1 = row[0];
4401         const auto &auth_name1 = row[1];
4402         const auto &code1 = row[2];
4403         // const auto &accuracy1 = row[3];
4404         const auto &table2 = row[4];
4405         const auto &auth_name2 = row[5];
4406         const auto &code2 = row[6];
4407         // const auto &accuracy2 = row[7];
4408         auto op1 = d->createFactory(auth_name1)
4409                        ->createCoordinateOperation(
4410                            code1, true, usePROJAlternativeGridNames, table1);
4411         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
4412                                targetCRSAuthName, targetCRSCode)) {
4413             continue;
4414         }
4415         auto op2 = d->createFactory(auth_name2)
4416                        ->createCoordinateOperation(
4417                            code2, true, usePROJAlternativeGridNames, table2);
4418         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
4419                                targetCRSAuthName, targetCRSCode)) {
4420             continue;
4421         }
4422 
4423         listTmp.emplace_back(
4424             operation::ConcatenatedOperation::createComputeMetadata({op1, op2},
4425                                                                     false));
4426     }
4427 
4428     // Case (source->intermediate) and (target->intermediate)
4429     sql = sqlProlog + "ON v1.target_crs_auth_name = v2.target_crs_auth_name "
4430                       "AND v1.target_crs_code = v2.target_crs_code " +
4431           joinArea +
4432           "WHERE v1.source_crs_auth_name = ? AND v1.source_crs_code = ? "
4433           "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
4434     if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
4435         sql += criterionOnIntermediateCRS;
4436     }
4437     intermediateWhere = buildIntermediateWhere("target", "target");
4438     res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
4439     if (discardSuperseded) {
4440         res = filterOutSuperseded(std::move(res));
4441     }
4442     for (const auto &row : res) {
4443         const auto &table1 = row[0];
4444         const auto &auth_name1 = row[1];
4445         const auto &code1 = row[2];
4446         // const auto &accuracy1 = row[3];
4447         const auto &table2 = row[4];
4448         const auto &auth_name2 = row[5];
4449         const auto &code2 = row[6];
4450         // const auto &accuracy2 = row[7];
4451         auto op1 = d->createFactory(auth_name1)
4452                        ->createCoordinateOperation(
4453                            code1, true, usePROJAlternativeGridNames, table1);
4454         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
4455                                targetCRSAuthName, targetCRSCode)) {
4456             continue;
4457         }
4458         auto op2 = d->createFactory(auth_name2)
4459                        ->createCoordinateOperation(
4460                            code2, true, usePROJAlternativeGridNames, table2);
4461         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
4462                                targetCRSAuthName, targetCRSCode)) {
4463             continue;
4464         }
4465 
4466         listTmp.emplace_back(
4467             operation::ConcatenatedOperation::createComputeMetadata(
4468                 {op1, op2->inverse()}, false));
4469     }
4470 
4471     // Case (intermediate->source) and (intermediate->target)
4472     sql = sqlProlog + "ON v1.source_crs_auth_name = v2.source_crs_auth_name "
4473                       "AND v1.source_crs_code = v2.source_crs_code " +
4474           joinArea +
4475           "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
4476           "AND v2.target_crs_auth_name = ? AND v2.target_crs_code = ? ";
4477     if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
4478         if (!minDate.empty()) {
4479             criterionOnIntermediateCRS =
4480                 "AND EXISTS(SELECT 1 FROM geodetic_crs x "
4481                 "JOIN geodetic_datum y "
4482                 "ON "
4483                 "y.auth_name = x.datum_auth_name AND "
4484                 "y.code = x.datum_code "
4485                 "WHERE "
4486                 "x.auth_name = v1.source_crs_auth_name AND "
4487                 "x.code = v1.source_crs_code AND "
4488                 "x.type IN ('geographic 2D', 'geographic 3D') AND "
4489                 "(y.publication_date IS NULL OR "
4490                 "(y.publication_date >= '" +
4491                 minDate + "'))) ";
4492         } else {
4493             criterionOnIntermediateCRS =
4494                 "AND EXISTS(SELECT 1 FROM geodetic_crs x WHERE "
4495                 "x.auth_name = v1.source_crs_auth_name AND "
4496                 "x.code = v1.source_crs_code AND "
4497                 "x.type IN ('geographic 2D', 'geographic 3D')) ";
4498         }
4499         sql += criterionOnIntermediateCRS;
4500     }
4501     intermediateWhere = buildIntermediateWhere("source", "source");
4502     res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
4503     if (discardSuperseded) {
4504         res = filterOutSuperseded(std::move(res));
4505     }
4506     for (const auto &row : res) {
4507         const auto &table1 = row[0];
4508         const auto &auth_name1 = row[1];
4509         const auto &code1 = row[2];
4510         // const auto &accuracy1 = row[3];
4511         const auto &table2 = row[4];
4512         const auto &auth_name2 = row[5];
4513         const auto &code2 = row[6];
4514         // const auto &accuracy2 = row[7];
4515         auto op1 = d->createFactory(auth_name1)
4516                        ->createCoordinateOperation(
4517                            code1, true, usePROJAlternativeGridNames, table1);
4518         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
4519                                targetCRSAuthName, targetCRSCode)) {
4520             continue;
4521         }
4522         auto op2 = d->createFactory(auth_name2)
4523                        ->createCoordinateOperation(
4524                            code2, true, usePROJAlternativeGridNames, table2);
4525         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
4526                                targetCRSAuthName, targetCRSCode)) {
4527             continue;
4528         }
4529 
4530         listTmp.emplace_back(
4531             operation::ConcatenatedOperation::createComputeMetadata(
4532                 {op1->inverse(), op2}, false));
4533     }
4534 
4535     // Case (intermediate->source) and (target->intermediate)
4536     sql = sqlProlog + "ON v1.source_crs_auth_name = v2.target_crs_auth_name "
4537                       "AND v1.source_crs_code = v2.target_crs_code " +
4538           joinArea +
4539           "WHERE v1.target_crs_auth_name = ? AND v1.target_crs_code = ? "
4540           "AND v2.source_crs_auth_name = ? AND v2.source_crs_code = ? ";
4541     if (allowedIntermediateObjectType == ObjectType::GEOGRAPHIC_CRS) {
4542         sql += criterionOnIntermediateCRS;
4543     }
4544     intermediateWhere = buildIntermediateWhere("source", "target");
4545     res = d->run(sql + additionalWhere + intermediateWhere + orderBy, params);
4546     if (discardSuperseded) {
4547         res = filterOutSuperseded(std::move(res));
4548     }
4549     for (const auto &row : res) {
4550         const auto &table1 = row[0];
4551         const auto &auth_name1 = row[1];
4552         const auto &code1 = row[2];
4553         // const auto &accuracy1 = row[3];
4554         const auto &table2 = row[4];
4555         const auto &auth_name2 = row[5];
4556         const auto &code2 = row[6];
4557         // const auto &accuracy2 = row[7];
4558         auto op1 = d->createFactory(auth_name1)
4559                        ->createCoordinateOperation(
4560                            code1, true, usePROJAlternativeGridNames, table1);
4561         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
4562                                targetCRSAuthName, targetCRSCode)) {
4563             continue;
4564         }
4565         auto op2 = d->createFactory(auth_name2)
4566                        ->createCoordinateOperation(
4567                            code2, true, usePROJAlternativeGridNames, table2);
4568         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
4569                                targetCRSAuthName, targetCRSCode)) {
4570             continue;
4571         }
4572 
4573         listTmp.emplace_back(
4574             operation::ConcatenatedOperation::createComputeMetadata(
4575                 {op1->inverse(), op2->inverse()}, false));
4576     }
4577 
4578     std::vector<operation::CoordinateOperationNNPtr> list;
4579     for (const auto &op : listTmp) {
4580         if (!discardIfMissingGrid ||
4581             !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
4582             list.emplace_back(op);
4583         }
4584     }
4585 
4586     return list;
4587 }
4588 
4589 // ---------------------------------------------------------------------------
4590 
4591 //! @cond Doxygen_Suppress
4592 
4593 std::vector<operation::CoordinateOperationNNPtr>
createBetweenGeodeticCRSWithDatumBasedIntermediates(const crs::CRSNNPtr & sourceCRS,const std::string & sourceCRSAuthName,const std::string & sourceCRSCode,const crs::CRSNNPtr & targetCRS,const std::string & targetCRSAuthName,const std::string & targetCRSCode,bool usePROJAlternativeGridNames,bool discardIfMissingGrid,bool considerKnownGridsAsAvailable,bool discardSuperseded,const std::vector<std::string> & allowedAuthorities,const metadata::ExtentPtr & intersectingExtent1,const metadata::ExtentPtr & intersectingExtent2) const4594 AuthorityFactory::createBetweenGeodeticCRSWithDatumBasedIntermediates(
4595     const crs::CRSNNPtr &sourceCRS, const std::string &sourceCRSAuthName,
4596     const std::string &sourceCRSCode, const crs::CRSNNPtr &targetCRS,
4597     const std::string &targetCRSAuthName, const std::string &targetCRSCode,
4598     bool usePROJAlternativeGridNames, bool discardIfMissingGrid,
4599     bool considerKnownGridsAsAvailable, bool discardSuperseded,
4600     const std::vector<std::string> &allowedAuthorities,
4601     const metadata::ExtentPtr &intersectingExtent1,
4602     const metadata::ExtentPtr &intersectingExtent2) const {
4603 
4604     std::vector<operation::CoordinateOperationNNPtr> listTmp;
4605 
4606     if (sourceCRSAuthName == targetCRSAuthName &&
4607         sourceCRSCode == targetCRSCode) {
4608         return listTmp;
4609     }
4610 
4611     std::string minDate;
4612     const auto &sourceDatum =
4613         dynamic_cast<crs::GeodeticCRS *>(sourceCRS.get())->datum();
4614     const auto &targetDatum =
4615         dynamic_cast<crs::GeodeticCRS *>(targetCRS.get())->datum();
4616     if (sourceDatum && sourceDatum->publicationDate().has_value() &&
4617         targetDatum && targetDatum->publicationDate().has_value()) {
4618         const auto sourceDate(sourceDatum->publicationDate()->toString());
4619         const auto targetDate(targetDatum->publicationDate()->toString());
4620         minDate = std::min(sourceDate, targetDate);
4621     }
4622 
4623     // For some reason, filtering on v1.deprecated and v2.deprecated kills
4624     // performance
4625     const std::string sqlProlog("SELECT v1.table_name as table1, "
4626                                 "v1.auth_name AS auth_name1, v1.code AS code1, "
4627                                 "v1.deprecated AS deprecated1, "
4628                                 "v2.table_name as table2, "
4629                                 "v2.auth_name AS auth_name2, v2.code AS code2, "
4630                                 "v2.deprecated AS deprecated2 "
4631                                 "FROM coordinate_operation_view v1 "
4632                                 "JOIN coordinate_operation_view v2 "
4633                                 "JOIN geodetic_crs g_source "
4634                                 "JOIN geodetic_crs g_v1s "
4635                                 "JOIN geodetic_crs g_v1t "
4636                                 "JOIN geodetic_crs g_v2s "
4637                                 "JOIN geodetic_crs g_v2t "
4638                                 "JOIN geodetic_crs g_target "
4639                                 "ON g_v1s.auth_name = v1.source_crs_auth_name "
4640                                 "AND g_v1s.code = v1.source_crs_code "
4641                                 "AND g_v1t.auth_name = v1.target_crs_auth_name "
4642                                 "AND g_v1t.code = v1.target_crs_code "
4643                                 "AND g_v2s.auth_name = v2.source_crs_auth_name "
4644                                 "AND g_v2s.code = v2.source_crs_code "
4645                                 "AND g_v2t.auth_name = v2.target_crs_auth_name "
4646                                 "AND g_v2t.code = v2.target_crs_code ");
4647     const std::string joinArea("JOIN usage u1 ON "
4648                                "u1.object_table_name = v1.table_name AND "
4649                                "u1.object_auth_name = v1.auth_name AND "
4650                                "u1.object_code = v1.code "
4651                                "JOIN extent a1 "
4652                                "ON a1.auth_name = u1.extent_auth_name AND "
4653                                "a1.code = u1.extent_code "
4654                                "JOIN usage u2 ON "
4655                                "u2.object_table_name = v2.table_name AND "
4656                                "u2.object_auth_name = v2.auth_name AND "
4657                                "u2.object_code = v2.code "
4658                                "JOIN extent a2 "
4659                                "ON a2.auth_name = u2.extent_auth_name AND "
4660                                "a2.code = u2.extent_code ");
4661 
4662     auto params = ListOfParams{sourceCRSAuthName, sourceCRSCode,
4663                                targetCRSAuthName, targetCRSCode};
4664 
4665     std::string additionalWhere(joinArea);
4666     additionalWhere +=
4667         "WHERE g_source.auth_name = ? AND g_source.code = ? "
4668         "AND g_target.auth_name = ? AND g_target.code = ? "
4669         "AND intersects_bbox("
4670         "a1.south_lat, a1.west_lon, a1.north_lat, a1.east_lon, "
4671         "a2.south_lat, a2.west_lon, a2.north_lat, a2.east_lon) = 1 ";
4672 
4673 #if 0
4674     // While those additional constraints are correct, they are found to
4675     // kill performance. So enforce them as post-processing
4676 
4677     if (!allowedAuthorities.empty()) {
4678         additionalWhere += "AND v1.auth_name IN (";
4679         for (size_t i = 0; i < allowedAuthorities.size(); i++) {
4680             if (i > 0)
4681                 additionalWhere += ',';
4682             additionalWhere += '?';
4683         }
4684         additionalWhere += ") AND v2.auth_name IN (";
4685         for (size_t i = 0; i < allowedAuthorities.size(); i++) {
4686             if (i > 0)
4687                 additionalWhere += ',';
4688             additionalWhere += '?';
4689         }
4690         additionalWhere += ") ";
4691         for (const auto &allowedAuthority : allowedAuthorities) {
4692             params.emplace_back(allowedAuthority);
4693         }
4694         for (const auto &allowedAuthority : allowedAuthorities) {
4695             params.emplace_back(allowedAuthority);
4696         }
4697     }
4698     if (d->hasAuthorityRestriction()) {
4699         additionalWhere += "AND v1.auth_name = ? AND v2.auth_name = ? ";
4700         params.emplace_back(d->authority());
4701         params.emplace_back(d->authority());
4702     }
4703 #endif
4704 
4705     for (const auto &extent : {intersectingExtent1, intersectingExtent2}) {
4706         if (extent) {
4707             const auto &geogExtent = extent->geographicElements();
4708             if (geogExtent.size() == 1) {
4709                 auto bbox =
4710                     dynamic_cast<const metadata::GeographicBoundingBox *>(
4711                         geogExtent[0].get());
4712                 if (bbox) {
4713                     const double south_lat = bbox->southBoundLatitude();
4714                     const double west_lon = bbox->westBoundLongitude();
4715                     const double north_lat = bbox->northBoundLatitude();
4716                     const double east_lon = bbox->eastBoundLongitude();
4717                     if (south_lat != -90.0 || west_lon != -180.0 ||
4718                         north_lat != 90.0 || east_lon != 180.0) {
4719                         additionalWhere +=
4720                             "AND intersects_bbox(a1.south_lat, a1.west_lon, "
4721                             "a1.north_lat, a1.east_lon, ?, ?, ?, ?) AND "
4722                             "intersects_bbox(a2.south_lat, a2.west_lon, "
4723                             "a2.north_lat, a2.east_lon, ?, ?, ?, ?)  ";
4724                         params.emplace_back(south_lat);
4725                         params.emplace_back(west_lon);
4726                         params.emplace_back(north_lat);
4727                         params.emplace_back(east_lon);
4728                         params.emplace_back(south_lat);
4729                         params.emplace_back(west_lon);
4730                         params.emplace_back(north_lat);
4731                         params.emplace_back(east_lon);
4732                     }
4733                 }
4734             }
4735         }
4736     }
4737 
4738     // Case (source->intermediate) and (intermediate->target)
4739     std::string sql(sqlProlog +
4740                     "AND g_v1t.datum_auth_name = g_v2s.datum_auth_name "
4741                     "AND g_v1t.datum_code = g_v2s.datum_code "
4742                     "AND g_v1s.datum_auth_name = g_source.datum_auth_name "
4743                     "AND g_v1s.datum_code = g_source.datum_code "
4744                     "AND g_v2t.datum_auth_name = g_target.datum_auth_name "
4745                     "AND g_v2t.datum_code = g_target.datum_code ");
4746 
4747     if (!minDate.empty()) {
4748         sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y "
4749                "WHERE "
4750                "y.auth_name = g_v1t.datum_auth_name AND "
4751                "y.code = g_v1t.datum_code AND "
4752                "(y.publication_date IS NULL OR "
4753                "(y.publication_date >= '" +
4754                minDate + "'))) ";
4755     }
4756 
4757     // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str());
4758     auto res = d->run(sql + additionalWhere, params);
4759     // fprintf(stderr, "after\n");
4760 
4761     const auto filterDeprecatedAndNotMatchingAuth =
4762         [&](SQLResultSet &&resultSet) {
4763 
4764             SQLResultSet filteredResultSet;
4765             for (const auto &row : resultSet) {
4766                 const auto &deprecated1 = row[3];
4767                 const auto &deprecated2 = row[7];
4768                 if (deprecated1 == "1" || deprecated2 == "1") {
4769                     continue;
4770                 }
4771                 const auto &auth_name1 = row[1];
4772                 const auto &auth_name2 = row[5];
4773                 if (d->hasAuthorityRestriction()) {
4774                     if (auth_name1 != d->authority() ||
4775                         auth_name2 != d->authority()) {
4776                         continue;
4777                     }
4778                 }
4779                 if (!allowedAuthorities.empty()) {
4780                     {
4781                         bool found = false;
4782                         for (const auto &auth : allowedAuthorities) {
4783                             if (auth_name1 == auth) {
4784                                 found = true;
4785                                 break;
4786                             }
4787                         }
4788                         if (!found) {
4789                             continue;
4790                         }
4791                     }
4792                     {
4793                         bool found = false;
4794                         for (const auto &auth : allowedAuthorities) {
4795                             if (auth_name2 == auth) {
4796                                 found = true;
4797                                 break;
4798                             }
4799                         }
4800                         if (!found) {
4801                             continue;
4802                         }
4803                     }
4804                 }
4805 
4806                 filteredResultSet.emplace_back(row);
4807             }
4808             return filteredResultSet;
4809         };
4810 
4811     const auto filterOutSuperseded = [&](SQLResultSet &&resultSet) {
4812         std::set<std::pair<std::string, std::string>> setTransf;
4813         std::string findSupersededSql(
4814             "SELECT superseded_table_name, "
4815             "superseded_auth_name, superseded_code, "
4816             "replacement_auth_name, replacement_code "
4817             "FROM supersession WHERE same_source_target_crs = 1 AND (");
4818         bool findSupersededFirstWhere = true;
4819         ListOfParams findSupersededParams;
4820 
4821         std::set<std::string> setAlreadyAsked;
4822         const auto keyMapSupersession = [](
4823             const std::string &table_name, const std::string &auth_name,
4824             const std::string &code) { return table_name + auth_name + code; };
4825 
4826         for (const auto &row : resultSet) {
4827             const auto &table1 = row[0];
4828             const auto &auth_name1 = row[1];
4829             const auto &code1 = row[2];
4830             const auto key1 = keyMapSupersession(table1, auth_name1, code1);
4831             if (setAlreadyAsked.find(key1) == setAlreadyAsked.end()) {
4832                 setAlreadyAsked.insert(key1);
4833                 if (!findSupersededFirstWhere)
4834                     findSupersededSql += " OR ";
4835                 findSupersededFirstWhere = false;
4836                 findSupersededSql +=
4837                     "(superseded_table_name = ? AND replacement_table_name = "
4838                     "superseded_table_name AND superseded_auth_name = ? AND "
4839                     "superseded_code = ?)";
4840                 findSupersededParams.push_back(table1);
4841                 findSupersededParams.push_back(auth_name1);
4842                 findSupersededParams.push_back(code1);
4843             }
4844 
4845             const auto &table2 = row[4];
4846             const auto &auth_name2 = row[5];
4847             const auto &code2 = row[6];
4848             const auto key2 = keyMapSupersession(table2, auth_name2, code2);
4849             if (setAlreadyAsked.find(key2) == setAlreadyAsked.end()) {
4850                 setAlreadyAsked.insert(key2);
4851                 if (!findSupersededFirstWhere)
4852                     findSupersededSql += " OR ";
4853                 findSupersededFirstWhere = false;
4854                 findSupersededSql +=
4855                     "(superseded_table_name = ? AND replacement_table_name = "
4856                     "superseded_table_name AND superseded_auth_name = ? AND "
4857                     "superseded_code = ?)";
4858                 findSupersededParams.push_back(table2);
4859                 findSupersededParams.push_back(auth_name2);
4860                 findSupersededParams.push_back(code2);
4861             }
4862 
4863             setTransf.insert(
4864                 std::pair<std::string, std::string>(auth_name1, code1));
4865             setTransf.insert(
4866                 std::pair<std::string, std::string>(auth_name2, code2));
4867         }
4868         findSupersededSql += ')';
4869 
4870         std::map<std::string, std::vector<std::pair<std::string, std::string>>>
4871             mapSupersession;
4872 
4873         if (!findSupersededParams.empty()) {
4874             const auto resSuperseded =
4875                 d->run(findSupersededSql, findSupersededParams);
4876             for (const auto &row : resSuperseded) {
4877                 const auto &superseded_table_name = row[0];
4878                 const auto &superseded_auth_name = row[1];
4879                 const auto &superseded_code = row[2];
4880                 const auto &replacement_auth_name = row[3];
4881                 const auto &replacement_code = row[4];
4882                 mapSupersession[keyMapSupersession(superseded_table_name,
4883                                                    superseded_auth_name,
4884                                                    superseded_code)]
4885                     .push_back(std::pair<std::string, std::string>(
4886                         replacement_auth_name, replacement_code));
4887             }
4888         }
4889 
4890         SQLResultSet filteredResultSet;
4891         for (const auto &row : resultSet) {
4892             const auto &table1 = row[0];
4893             const auto &auth_name1 = row[1];
4894             const auto &code1 = row[2];
4895             const auto &table2 = row[4];
4896             const auto &auth_name2 = row[5];
4897             const auto &code2 = row[6];
4898 
4899             auto iter1 = mapSupersession.find(
4900                 keyMapSupersession(table1, auth_name1, code1));
4901             if (iter1 != mapSupersession.end()) {
4902                 bool foundReplacement = false;
4903                 for (const auto &replacement : iter1->second) {
4904                     const auto &replacement_auth_name = replacement.first;
4905                     const auto &replacement_code = replacement.second;
4906                     if (setTransf.find(std::pair<std::string, std::string>(
4907                             replacement_auth_name, replacement_code)) !=
4908                         setTransf.end()) {
4909                         // Skip transformations that are superseded by others
4910                         // that got
4911                         // returned in the result set.
4912                         foundReplacement = true;
4913                         break;
4914                     }
4915                 }
4916                 if (foundReplacement) {
4917                     continue;
4918                 }
4919             }
4920 
4921             auto iter2 = mapSupersession.find(
4922                 keyMapSupersession(table2, auth_name2, code2));
4923             if (iter2 != mapSupersession.end()) {
4924                 bool foundReplacement = false;
4925                 for (const auto &replacement : iter2->second) {
4926                     const auto &replacement_auth_name = replacement.first;
4927                     const auto &replacement_code = replacement.second;
4928                     if (setTransf.find(std::pair<std::string, std::string>(
4929                             replacement_auth_name, replacement_code)) !=
4930                         setTransf.end()) {
4931                         // Skip transformations that are superseded by others
4932                         // that got
4933                         // returned in the result set.
4934                         foundReplacement = true;
4935                         break;
4936                     }
4937                 }
4938                 if (foundReplacement) {
4939                     continue;
4940                 }
4941             }
4942 
4943             filteredResultSet.emplace_back(row);
4944         }
4945         return filteredResultSet;
4946     };
4947 
4948     res = filterDeprecatedAndNotMatchingAuth(std::move(res));
4949     if (discardSuperseded) {
4950         res = filterOutSuperseded(std::move(res));
4951     }
4952 
4953     auto opFactory = operation::CoordinateOperationFactory::create();
4954 
4955     for (const auto &row : res) {
4956         const auto &table1 = row[0];
4957         const auto &auth_name1 = row[1];
4958         const auto &code1 = row[2];
4959         const auto &table2 = row[4];
4960         const auto &auth_name2 = row[5];
4961         const auto &code2 = row[6];
4962 
4963         auto op1 = d->createFactory(auth_name1)
4964                        ->createCoordinateOperation(
4965                            code1, true, usePROJAlternativeGridNames, table1);
4966         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
4967                                targetCRSAuthName, targetCRSCode)) {
4968             continue;
4969         }
4970         auto op2 = d->createFactory(auth_name2)
4971                        ->createCoordinateOperation(
4972                            code2, true, usePROJAlternativeGridNames, table2);
4973         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
4974                                targetCRSAuthName, targetCRSCode)) {
4975             continue;
4976         }
4977 
4978         const auto &op1Source = op1->sourceCRS();
4979         const auto &op1Target = op1->targetCRS();
4980         const auto &op2Source = op2->sourceCRS();
4981         const auto &op2Target = op2->targetCRS();
4982         if (op1Source && op1Target && op2Source && op2Target) {
4983             std::vector<operation::CoordinateOperationNNPtr> steps;
4984 
4985             if (!sourceCRS->isEquivalentTo(
4986                     op1Source.get(),
4987                     util::IComparable::Criterion::EQUIVALENT)) {
4988                 auto opFirst = opFactory->createOperation(
4989                     sourceCRS, NN_NO_CHECK(op1Source));
4990                 assert(opFirst);
4991                 steps.emplace_back(NN_NO_CHECK(opFirst));
4992             }
4993 
4994             steps.emplace_back(op1);
4995 
4996             if (!op1Target->isEquivalentTo(
4997                     op2Source.get(),
4998                     util::IComparable::Criterion::EQUIVALENT)) {
4999                 auto opMiddle = opFactory->createOperation(
5000                     NN_NO_CHECK(op1Target), NN_NO_CHECK(op2Source));
5001                 assert(opMiddle);
5002                 steps.emplace_back(NN_NO_CHECK(opMiddle));
5003             }
5004 
5005             steps.emplace_back(op2);
5006 
5007             if (!op2Target->isEquivalentTo(
5008                     targetCRS.get(),
5009                     util::IComparable::Criterion::EQUIVALENT)) {
5010                 auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Target),
5011                                                          targetCRS);
5012                 assert(opLast);
5013                 steps.emplace_back(NN_NO_CHECK(opLast));
5014             }
5015 
5016             listTmp.emplace_back(
5017                 operation::ConcatenatedOperation::createComputeMetadata(steps,
5018                                                                         false));
5019         }
5020     }
5021 
5022     // Case (source->intermediate) and (target->intermediate)
5023     sql = sqlProlog + "AND g_v1t.datum_auth_name = g_v2t.datum_auth_name "
5024                       "AND g_v1t.datum_code = g_v2t.datum_code "
5025                       "AND g_v1s.datum_auth_name = g_source.datum_auth_name "
5026                       "AND g_v1s.datum_code = g_source.datum_code "
5027                       "AND g_v2s.datum_auth_name = g_target.datum_auth_name "
5028                       "AND g_v2s.datum_code = g_target.datum_code ";
5029 
5030     if (!minDate.empty()) {
5031         sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y "
5032                "WHERE "
5033                "y.auth_name = g_v1t.datum_auth_name AND "
5034                "y.code = g_v1t.datum_code AND "
5035                "(y.publication_date IS NULL OR "
5036                "(y.publication_date >= '" +
5037                minDate + "'))) ";
5038     }
5039 
5040     // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str());
5041     res = d->run(sql + additionalWhere, params);
5042     // fprintf(stderr, "after\n");
5043 
5044     res = filterDeprecatedAndNotMatchingAuth(std::move(res));
5045     if (discardSuperseded) {
5046         res = filterOutSuperseded(std::move(res));
5047     }
5048     for (const auto &row : res) {
5049         const auto &table1 = row[0];
5050         const auto &auth_name1 = row[1];
5051         const auto &code1 = row[2];
5052         const auto &table2 = row[4];
5053         const auto &auth_name2 = row[5];
5054         const auto &code2 = row[6];
5055 
5056         auto op1 = d->createFactory(auth_name1)
5057                        ->createCoordinateOperation(
5058                            code1, true, usePROJAlternativeGridNames, table1);
5059         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
5060                                targetCRSAuthName, targetCRSCode)) {
5061             continue;
5062         }
5063         auto op2 = d->createFactory(auth_name2)
5064                        ->createCoordinateOperation(
5065                            code2, true, usePROJAlternativeGridNames, table2);
5066         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
5067                                targetCRSAuthName, targetCRSCode)) {
5068             continue;
5069         }
5070 
5071         const auto &op1Source = op1->sourceCRS();
5072         const auto &op1Target = op1->targetCRS();
5073         const auto &op2Source = op2->sourceCRS();
5074         const auto &op2Target = op2->targetCRS();
5075         if (op1Source && op1Target && op2Source && op2Target) {
5076             std::vector<operation::CoordinateOperationNNPtr> steps;
5077 
5078             if (!sourceCRS->isEquivalentTo(
5079                     op1Source.get(),
5080                     util::IComparable::Criterion::EQUIVALENT)) {
5081                 auto opFirst = opFactory->createOperation(
5082                     sourceCRS, NN_NO_CHECK(op1Source));
5083                 assert(opFirst);
5084                 steps.emplace_back(NN_NO_CHECK(opFirst));
5085             }
5086 
5087             steps.emplace_back(op1);
5088 
5089             if (!op1Target->isEquivalentTo(
5090                     op2Target.get(),
5091                     util::IComparable::Criterion::EQUIVALENT)) {
5092                 auto opMiddle = opFactory->createOperation(
5093                     NN_NO_CHECK(op1Target), NN_NO_CHECK(op2Target));
5094                 assert(opMiddle);
5095                 steps.emplace_back(NN_NO_CHECK(opMiddle));
5096             }
5097 
5098             steps.emplace_back(op2->inverse());
5099 
5100             if (!op2Source->isEquivalentTo(
5101                     targetCRS.get(),
5102                     util::IComparable::Criterion::EQUIVALENT)) {
5103                 auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Source),
5104                                                          targetCRS);
5105                 assert(opLast);
5106                 steps.emplace_back(NN_NO_CHECK(opLast));
5107             }
5108 
5109             listTmp.emplace_back(
5110                 operation::ConcatenatedOperation::createComputeMetadata(steps,
5111                                                                         false));
5112         }
5113     }
5114 
5115     // Case (intermediate->source) and (intermediate->target)
5116     sql = sqlProlog + "AND g_v1s.datum_auth_name = g_v2s.datum_auth_name "
5117                       "AND g_v1s.datum_code = g_v2s.datum_code "
5118                       "AND g_v1t.datum_auth_name = g_source.datum_auth_name "
5119                       "AND g_v1t.datum_code = g_source.datum_code "
5120                       "AND g_v2t.datum_auth_name = g_target.datum_auth_name "
5121                       "AND g_v2t.datum_code = g_target.datum_code ";
5122 
5123     if (!minDate.empty()) {
5124         sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y "
5125                "WHERE "
5126                "y.auth_name = g_v1s.datum_auth_name AND "
5127                "y.code = g_v1s.datum_code AND "
5128                "(y.publication_date IS NULL OR "
5129                "(y.publication_date >= '" +
5130                minDate + "'))) ";
5131     }
5132 
5133     // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str());
5134     res = d->run(sql + additionalWhere, params);
5135     // fprintf(stderr, "after\n");
5136 
5137     res = filterDeprecatedAndNotMatchingAuth(std::move(res));
5138     if (discardSuperseded) {
5139         res = filterOutSuperseded(std::move(res));
5140     }
5141     for (const auto &row : res) {
5142         const auto &table1 = row[0];
5143         const auto &auth_name1 = row[1];
5144         const auto &code1 = row[2];
5145         const auto &table2 = row[4];
5146         const auto &auth_name2 = row[5];
5147         const auto &code2 = row[6];
5148 
5149         auto op1 = d->createFactory(auth_name1)
5150                        ->createCoordinateOperation(
5151                            code1, true, usePROJAlternativeGridNames, table1);
5152         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
5153                                targetCRSAuthName, targetCRSCode)) {
5154             continue;
5155         }
5156         auto op2 = d->createFactory(auth_name2)
5157                        ->createCoordinateOperation(
5158                            code2, true, usePROJAlternativeGridNames, table2);
5159         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
5160                                targetCRSAuthName, targetCRSCode)) {
5161             continue;
5162         }
5163 
5164         const auto &op1Source = op1->sourceCRS();
5165         const auto &op1Target = op1->targetCRS();
5166         const auto &op2Source = op2->sourceCRS();
5167         const auto &op2Target = op2->targetCRS();
5168         if (op1Source && op1Target && op2Source && op2Target) {
5169             std::vector<operation::CoordinateOperationNNPtr> steps;
5170 
5171             if (!sourceCRS->isEquivalentTo(
5172                     op1Target.get(),
5173                     util::IComparable::Criterion::EQUIVALENT)) {
5174                 auto opFirst = opFactory->createOperation(
5175                     sourceCRS, NN_NO_CHECK(op1Target));
5176                 assert(opFirst);
5177                 steps.emplace_back(NN_NO_CHECK(opFirst));
5178             }
5179 
5180             steps.emplace_back(op1->inverse());
5181 
5182             if (!op1Source->isEquivalentTo(
5183                     op2Source.get(),
5184                     util::IComparable::Criterion::EQUIVALENT)) {
5185                 auto opMiddle = opFactory->createOperation(
5186                     NN_NO_CHECK(op1Source), NN_NO_CHECK(op2Source));
5187                 assert(opMiddle);
5188                 steps.emplace_back(NN_NO_CHECK(opMiddle));
5189             }
5190 
5191             steps.emplace_back(op2);
5192 
5193             if (!op2Target->isEquivalentTo(
5194                     targetCRS.get(),
5195                     util::IComparable::Criterion::EQUIVALENT)) {
5196                 auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Target),
5197                                                          targetCRS);
5198                 assert(opLast);
5199                 steps.emplace_back(NN_NO_CHECK(opLast));
5200             }
5201 
5202             listTmp.emplace_back(
5203                 operation::ConcatenatedOperation::createComputeMetadata(steps,
5204                                                                         false));
5205         }
5206     }
5207 
5208     // Case (intermediate->source) and (target->intermediate)
5209     sql = sqlProlog + "AND g_v1s.datum_auth_name = g_v2t.datum_auth_name "
5210                       "AND g_v1s.datum_code = g_v2t.datum_code "
5211                       "AND g_v1t.datum_auth_name = g_source.datum_auth_name "
5212                       "AND g_v1t.datum_code = g_source.datum_code "
5213                       "AND g_v2s.datum_auth_name = g_target.datum_auth_name "
5214                       "AND g_v2s.datum_code = g_target.datum_code ";
5215 
5216     if (!minDate.empty()) {
5217         sql += "AND EXISTS(SELECT 1 FROM geodetic_datum y "
5218                "WHERE "
5219                "y.auth_name = g_v1s.datum_auth_name AND "
5220                "y.code = g_v1s.datum_code AND "
5221                "(y.publication_date IS NULL OR "
5222                "(y.publication_date >= '" +
5223                minDate + "'))) ";
5224     }
5225 
5226     // fprintf(stderr, "before %s\n", (sql + additionalWhere).c_str());
5227     res = d->run(sql + additionalWhere, params);
5228     // fprintf(stderr, "after\n");
5229 
5230     res = filterDeprecatedAndNotMatchingAuth(std::move(res));
5231     if (discardSuperseded) {
5232         res = filterOutSuperseded(std::move(res));
5233     }
5234     for (const auto &row : res) {
5235         const auto &table1 = row[0];
5236         const auto &auth_name1 = row[1];
5237         const auto &code1 = row[2];
5238         const auto &table2 = row[4];
5239         const auto &auth_name2 = row[5];
5240         const auto &code2 = row[6];
5241 
5242         auto op1 = d->createFactory(auth_name1)
5243                        ->createCoordinateOperation(
5244                            code1, true, usePROJAlternativeGridNames, table1);
5245         if (useIrrelevantPivot(op1, sourceCRSAuthName, sourceCRSCode,
5246                                targetCRSAuthName, targetCRSCode)) {
5247             continue;
5248         }
5249         auto op2 = d->createFactory(auth_name2)
5250                        ->createCoordinateOperation(
5251                            code2, true, usePROJAlternativeGridNames, table2);
5252         if (useIrrelevantPivot(op2, sourceCRSAuthName, sourceCRSCode,
5253                                targetCRSAuthName, targetCRSCode)) {
5254             continue;
5255         }
5256 
5257         const auto &op1Source = op1->sourceCRS();
5258         const auto &op1Target = op1->targetCRS();
5259         const auto &op2Source = op2->sourceCRS();
5260         const auto &op2Target = op2->targetCRS();
5261         if (op1Source && op1Target && op2Source && op2Target) {
5262             std::vector<operation::CoordinateOperationNNPtr> steps;
5263 
5264             if (!sourceCRS->isEquivalentTo(
5265                     op1Target.get(),
5266                     util::IComparable::Criterion::EQUIVALENT)) {
5267                 auto opFirst = opFactory->createOperation(
5268                     sourceCRS, NN_NO_CHECK(op1Target));
5269                 assert(opFirst);
5270                 steps.emplace_back(NN_NO_CHECK(opFirst));
5271             }
5272 
5273             steps.emplace_back(op1->inverse());
5274 
5275             if (!op1Source->isEquivalentTo(
5276                     op2Target.get(),
5277                     util::IComparable::Criterion::EQUIVALENT)) {
5278                 auto opMiddle = opFactory->createOperation(
5279                     NN_NO_CHECK(op1Source), NN_NO_CHECK(op2Target));
5280                 assert(opMiddle);
5281                 steps.emplace_back(NN_NO_CHECK(opMiddle));
5282             }
5283 
5284             steps.emplace_back(op2->inverse());
5285 
5286             if (!op2Source->isEquivalentTo(
5287                     targetCRS.get(),
5288                     util::IComparable::Criterion::EQUIVALENT)) {
5289                 auto opLast = opFactory->createOperation(NN_NO_CHECK(op2Source),
5290                                                          targetCRS);
5291                 assert(opLast);
5292                 steps.emplace_back(NN_NO_CHECK(opLast));
5293             }
5294 
5295             listTmp.emplace_back(
5296                 operation::ConcatenatedOperation::createComputeMetadata(steps,
5297                                                                         false));
5298         }
5299     }
5300 
5301     std::vector<operation::CoordinateOperationNNPtr> list;
5302     for (const auto &op : listTmp) {
5303         if (!discardIfMissingGrid ||
5304             !d->rejectOpDueToMissingGrid(op, considerKnownGridsAsAvailable)) {
5305             list.emplace_back(op);
5306         }
5307     }
5308 
5309     return list;
5310 }
5311 
5312 //! @endcond
5313 
5314 // ---------------------------------------------------------------------------
5315 
5316 /** \brief Returns the authority name associated to this factory.
5317  * @return name.
5318  */
getAuthority()5319 const std::string &AuthorityFactory::getAuthority() PROJ_PURE_DEFN {
5320     return d->authority();
5321 }
5322 
5323 // ---------------------------------------------------------------------------
5324 
5325 /** \brief Returns the set of authority codes of the given object type.
5326  *
5327  * @param type Object type.
5328  * @param allowDeprecated whether we should return deprecated objects as well.
5329  * @return the set of authority codes for spatial reference objects of the given
5330  * type
5331  * @throw FactoryException
5332  */
5333 std::set<std::string>
getAuthorityCodes(const ObjectType & type,bool allowDeprecated) const5334 AuthorityFactory::getAuthorityCodes(const ObjectType &type,
5335                                     bool allowDeprecated) const {
5336     std::string sql;
5337     switch (type) {
5338     case ObjectType::PRIME_MERIDIAN:
5339         sql = "SELECT code FROM prime_meridian WHERE ";
5340         break;
5341     case ObjectType::ELLIPSOID:
5342         sql = "SELECT code FROM ellipsoid WHERE ";
5343         break;
5344     case ObjectType::DATUM:
5345         sql = "SELECT code FROM object_view WHERE table_name IN "
5346               "('geodetic_datum', 'vertical_datum') AND ";
5347         break;
5348     case ObjectType::GEODETIC_REFERENCE_FRAME:
5349         sql = "SELECT code FROM geodetic_datum WHERE ";
5350         break;
5351     case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
5352         sql = "SELECT code FROM geodetic_datum WHERE "
5353               "frame_reference_epoch IS NOT NULL AND ";
5354         break;
5355     case ObjectType::VERTICAL_REFERENCE_FRAME:
5356         sql = "SELECT code FROM vertical_datum WHERE ";
5357         break;
5358     case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
5359         sql = "SELECT code FROM vertical_datum WHERE "
5360               "frame_reference_epoch IS NOT NULL AND ";
5361         break;
5362     case ObjectType::CRS:
5363         sql = "SELECT code FROM crs_view WHERE ";
5364         break;
5365     case ObjectType::GEODETIC_CRS:
5366         sql = "SELECT code FROM geodetic_crs WHERE ";
5367         break;
5368     case ObjectType::GEOCENTRIC_CRS:
5369         sql = "SELECT code FROM geodetic_crs WHERE type "
5370               "= " GEOCENTRIC_SINGLE_QUOTED " AND ";
5371         break;
5372     case ObjectType::GEOGRAPHIC_CRS:
5373         sql = "SELECT code FROM geodetic_crs WHERE type IN "
5374               "(" GEOG_2D_SINGLE_QUOTED "," GEOG_3D_SINGLE_QUOTED ") AND ";
5375         break;
5376     case ObjectType::GEOGRAPHIC_2D_CRS:
5377         sql =
5378             "SELECT code FROM geodetic_crs WHERE type = " GEOG_2D_SINGLE_QUOTED
5379             " AND ";
5380         break;
5381     case ObjectType::GEOGRAPHIC_3D_CRS:
5382         sql =
5383             "SELECT code FROM geodetic_crs WHERE type = " GEOG_3D_SINGLE_QUOTED
5384             " AND ";
5385         break;
5386     case ObjectType::VERTICAL_CRS:
5387         sql = "SELECT code FROM vertical_crs WHERE ";
5388         break;
5389     case ObjectType::PROJECTED_CRS:
5390         sql = "SELECT code FROM projected_crs WHERE ";
5391         break;
5392     case ObjectType::COMPOUND_CRS:
5393         sql = "SELECT code FROM compound_crs WHERE ";
5394         break;
5395     case ObjectType::COORDINATE_OPERATION:
5396         sql =
5397             "SELECT code FROM coordinate_operation_with_conversion_view WHERE ";
5398         break;
5399     case ObjectType::CONVERSION:
5400         sql = "SELECT code FROM conversion WHERE ";
5401         break;
5402     case ObjectType::TRANSFORMATION:
5403         sql = "SELECT code FROM coordinate_operation_view WHERE table_name != "
5404               "'concatenated_operation' AND ";
5405         break;
5406     case ObjectType::CONCATENATED_OPERATION:
5407         sql = "SELECT code FROM concatenated_operation WHERE ";
5408         break;
5409     }
5410 
5411     sql += "auth_name = ?";
5412     if (!allowDeprecated) {
5413         sql += " AND deprecated = 0";
5414     }
5415 
5416     auto res = d->run(sql, {d->authority()});
5417     std::set<std::string> set;
5418     for (const auto &row : res) {
5419         set.insert(row[0]);
5420     }
5421     return set;
5422 }
5423 
5424 // ---------------------------------------------------------------------------
5425 
5426 /** \brief Gets a description of the object corresponding to a code.
5427  *
5428  * \note In case of several objects of different types with the same code,
5429  * one of them will be arbitrarily selected. But if a CRS object is found, it
5430  * will be selected.
5431  *
5432  * @param code Object code allocated by authority. (e.g. "4326")
5433  * @return description.
5434  * @throw NoSuchAuthorityCodeException
5435  * @throw FactoryException
5436  */
5437 std::string
getDescriptionText(const std::string & code) const5438 AuthorityFactory::getDescriptionText(const std::string &code) const {
5439     auto sql = "SELECT name, table_name FROM object_view WHERE auth_name = ? "
5440                "AND code = ? ORDER BY table_name";
5441     auto sqlRes = d->runWithCodeParam(sql, code);
5442     if (sqlRes.empty()) {
5443         throw NoSuchAuthorityCodeException("object not found", d->authority(),
5444                                            code);
5445     }
5446     std::string text;
5447     for (const auto &row : sqlRes) {
5448         const auto &tableName = row[1];
5449         if (tableName == "geodetic_crs" || tableName == "projected_crs" ||
5450             tableName == "vertical_crs" || tableName == "compound_crs") {
5451             return row[0];
5452         } else if (text.empty()) {
5453             text = row[0];
5454         }
5455     }
5456     return text;
5457 }
5458 
5459 // ---------------------------------------------------------------------------
5460 
5461 /** \brief Return a list of information on CRS objects
5462  *
5463  * This is functionnaly equivalent of listing the codes from an authority,
5464  * instantiating
5465  * a CRS object for each of them and getting the information from this CRS
5466  * object, but this implementation has much less overhead.
5467  *
5468  * @throw FactoryException
5469  */
getCRSInfoList() const5470 std::list<AuthorityFactory::CRSInfo> AuthorityFactory::getCRSInfoList() const {
5471 
5472     const auto getSqlArea = [](const std::string &table_name) {
5473         return "JOIN usage u ON "
5474                "u.object_table_name = '" +
5475                table_name + "' AND "
5476                             "u.object_auth_name = c.auth_name AND "
5477                             "u.object_code = c.code "
5478                             "JOIN extent a "
5479                             "ON a.auth_name = u.extent_auth_name AND "
5480                             "a.code = u.extent_code ";
5481     };
5482 
5483     std::string sql = "SELECT c.auth_name, c.code, c.name, c.type, "
5484                       "c.deprecated, "
5485                       "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
5486                       "a.description, NULL FROM geodetic_crs c " +
5487                       getSqlArea("geodetic_crs");
5488     ListOfParams params;
5489     if (d->hasAuthorityRestriction()) {
5490         sql += " WHERE c.auth_name = ?";
5491         params.emplace_back(d->authority());
5492     }
5493     sql += " UNION ALL ";
5494     sql += "SELECT c.auth_name, c.code, c.name, 'projected', "
5495            "c.deprecated, "
5496            "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
5497            "a.description, cm.name AS conversion_method_name FROM "
5498            "projected_crs c " +
5499            getSqlArea("projected_crs") +
5500            "LEFT JOIN conversion_table conv ON "
5501            "c.conversion_auth_name = conv.auth_name AND "
5502            "c.conversion_code = conv.code "
5503            "LEFT JOIN conversion_method cm ON "
5504            "conv.method_auth_name = cm.auth_name AND "
5505            "conv.method_code = cm.code";
5506     if (d->hasAuthorityRestriction()) {
5507         sql += " WHERE c.auth_name = ?";
5508         params.emplace_back(d->authority());
5509     }
5510     sql += " UNION ALL ";
5511     sql += "SELECT c.auth_name, c.code, c.name, 'vertical', "
5512            "c.deprecated, "
5513            "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
5514            "a.description, NULL FROM vertical_crs c " +
5515            getSqlArea("vertical_crs");
5516     if (d->hasAuthorityRestriction()) {
5517         sql += " WHERE c.auth_name = ?";
5518         params.emplace_back(d->authority());
5519     }
5520     sql += " UNION ALL ";
5521     sql += "SELECT c.auth_name, c.code, c.name, 'compound', "
5522            "c.deprecated, "
5523            "a.west_lon, a.south_lat, a.east_lon, a.north_lat, "
5524            "a.description, NULL FROM compound_crs c " +
5525            getSqlArea("compound_crs");
5526     if (d->hasAuthorityRestriction()) {
5527         sql += " WHERE c.auth_name = ?";
5528         params.emplace_back(d->authority());
5529     }
5530     auto sqlRes = d->run(sql, params);
5531     std::list<AuthorityFactory::CRSInfo> res;
5532     for (const auto &row : sqlRes) {
5533         AuthorityFactory::CRSInfo info;
5534         info.authName = row[0];
5535         info.code = row[1];
5536         info.name = row[2];
5537         const auto &type = row[3];
5538         if (type == GEOG_2D) {
5539             info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS;
5540         } else if (type == GEOG_3D) {
5541             info.type = AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS;
5542         } else if (type == GEOCENTRIC) {
5543             info.type = AuthorityFactory::ObjectType::GEOCENTRIC_CRS;
5544         } else if (type == PROJECTED) {
5545             info.type = AuthorityFactory::ObjectType::PROJECTED_CRS;
5546         } else if (type == VERTICAL) {
5547             info.type = AuthorityFactory::ObjectType::VERTICAL_CRS;
5548         } else if (type == COMPOUND) {
5549             info.type = AuthorityFactory::ObjectType::COMPOUND_CRS;
5550         }
5551         info.deprecated = row[4] == "1";
5552         if (row[5].empty()) {
5553             info.bbox_valid = false;
5554         } else {
5555             info.bbox_valid = true;
5556             info.west_lon_degree = c_locale_stod(row[5]);
5557             info.south_lat_degree = c_locale_stod(row[6]);
5558             info.east_lon_degree = c_locale_stod(row[7]);
5559             info.north_lat_degree = c_locale_stod(row[8]);
5560         }
5561         info.areaName = row[9];
5562         info.projectionMethodName = row[10];
5563         res.emplace_back(info);
5564     }
5565     return res;
5566 }
5567 
5568 // ---------------------------------------------------------------------------
5569 
5570 //! @cond Doxygen_Suppress
UnitInfo()5571 AuthorityFactory::UnitInfo::UnitInfo()
5572     : authName{}, code{}, name{}, category{}, convFactor{}, projShortName{},
5573       deprecated{} {}
5574 //! @endcond
5575 
5576 // ---------------------------------------------------------------------------
5577 
5578 /** \brief Return the list of units.
5579  * @throw FactoryException
5580  *
5581  * @since 7.1
5582  */
getUnitList() const5583 std::list<AuthorityFactory::UnitInfo> AuthorityFactory::getUnitList() const {
5584     std::string sql = "SELECT auth_name, code, name, type, conv_factor, "
5585                       "proj_short_name, deprecated FROM unit_of_measure";
5586     ListOfParams params;
5587     if (d->hasAuthorityRestriction()) {
5588         sql += " WHERE auth_name = ?";
5589         params.emplace_back(d->authority());
5590     }
5591     sql += " ORDER BY auth_name, code";
5592 
5593     auto sqlRes = d->run(sql, params);
5594     std::list<AuthorityFactory::UnitInfo> res;
5595     for (const auto &row : sqlRes) {
5596         AuthorityFactory::UnitInfo info;
5597         info.authName = row[0];
5598         info.code = row[1];
5599         info.name = row[2];
5600         const std::string &raw_category(row[3]);
5601         if (raw_category == "length") {
5602             info.category = info.name.find(" per ") != std::string::npos
5603                                 ? "linear_per_time"
5604                                 : "linear";
5605         } else if (raw_category == "angle") {
5606             info.category = info.name.find(" per ") != std::string::npos
5607                                 ? "angular_per_time"
5608                                 : "angular";
5609         } else if (raw_category == "scale") {
5610             info.category =
5611                 info.name.find(" per year") != std::string::npos ||
5612                         info.name.find(" per second") != std::string::npos
5613                     ? "scale_per_time"
5614                     : "scale";
5615         } else {
5616             info.category = raw_category;
5617         }
5618         info.convFactor = row[4].empty() ? 0 : c_locale_stod(row[4]);
5619         info.projShortName = row[5];
5620         info.deprecated = row[6] == "1";
5621         res.emplace_back(info);
5622     }
5623     return res;
5624 }
5625 
5626 // ---------------------------------------------------------------------------
5627 
5628 /** \brief Gets the official name from a possibly alias name.
5629  *
5630  * @param aliasedName Alias name.
5631  * @param tableName Table name/category. Can help in case of ambiguities.
5632  * Or empty otherwise.
5633  * @param source Source of the alias. Can help in case of ambiguities.
5634  * Or empty otherwise.
5635  * @param tryEquivalentNameSpelling whether the comparison of aliasedName with
5636  * the alt_name column of the alis_name table should be done with using
5637  * metadata::Identifier::isEquivalentName() rather than strict string
5638  * comparison;
5639  * @param outTableName Table name in which the official name has been found.
5640  * @param outAuthName Authority name of the official name that has been found.
5641  * @param outCode Code of the official name that has been found.
5642  * @return official name (or empty if not found).
5643  * @throw FactoryException
5644  */
getOfficialNameFromAlias(const std::string & aliasedName,const std::string & tableName,const std::string & source,bool tryEquivalentNameSpelling,std::string & outTableName,std::string & outAuthName,std::string & outCode) const5645 std::string AuthorityFactory::getOfficialNameFromAlias(
5646     const std::string &aliasedName, const std::string &tableName,
5647     const std::string &source, bool tryEquivalentNameSpelling,
5648     std::string &outTableName, std::string &outAuthName,
5649     std::string &outCode) const {
5650 
5651     if (tryEquivalentNameSpelling) {
5652         std::string sql(
5653             "SELECT table_name, auth_name, code, alt_name FROM alias_name");
5654         ListOfParams params;
5655         if (!tableName.empty()) {
5656             sql += " WHERE table_name = ?";
5657             params.push_back(tableName);
5658         }
5659         if (!source.empty()) {
5660             if (!tableName.empty()) {
5661                 sql += " AND ";
5662             } else {
5663                 sql += " WHERE ";
5664             }
5665             sql += "source = ?";
5666             params.push_back(source);
5667         }
5668         auto res = d->run(sql, params);
5669         if (res.empty()) {
5670             return std::string();
5671         }
5672         for (const auto &row : res) {
5673             const auto &alt_name = row[3];
5674             if (metadata::Identifier::isEquivalentName(alt_name.c_str(),
5675                                                        aliasedName.c_str())) {
5676                 outTableName = row[0];
5677                 outAuthName = row[1];
5678                 outCode = row[2];
5679                 sql = "SELECT name FROM \"";
5680                 sql += replaceAll(outTableName, "\"", "\"\"");
5681                 sql += "\" WHERE auth_name = ? AND code = ?";
5682                 res = d->run(sql, {outAuthName, outCode});
5683                 if (res.empty()) { // shouldn't happen normally
5684                     return std::string();
5685                 }
5686                 return removeEnsembleSuffix(res.front()[0]);
5687             }
5688         }
5689         return std::string();
5690     } else {
5691         std::string sql(
5692             "SELECT table_name, auth_name, code FROM alias_name WHERE "
5693             "alt_name = ?");
5694         ListOfParams params{aliasedName};
5695         if (!tableName.empty()) {
5696             sql += " AND table_name = ?";
5697             params.push_back(tableName);
5698         }
5699         if (!source.empty()) {
5700             sql += " AND source = ?";
5701             params.push_back(source);
5702         }
5703         auto res = d->run(sql, params);
5704         if (res.empty()) {
5705             return std::string();
5706         }
5707 
5708         params.clear();
5709         sql.clear();
5710         bool first = true;
5711         for (const auto &row : res) {
5712             if (!first)
5713                 sql += " UNION ALL ";
5714             first = false;
5715             outTableName = row[0];
5716             outAuthName = row[1];
5717             outCode = row[2];
5718             sql += "SELECT name, ? AS table_name, auth_name, code, deprecated "
5719                    "FROM \"";
5720             sql += replaceAll(outTableName, "\"", "\"\"");
5721             sql += "\" WHERE auth_name = ? AND code = ?";
5722             params.emplace_back(outTableName);
5723             params.emplace_back(outAuthName);
5724             params.emplace_back(outCode);
5725         }
5726         sql = "SELECT name, table_name, auth_name, code FROM (" + sql +
5727               ") x ORDER BY deprecated LIMIT 1";
5728         res = d->run(sql, params);
5729         if (res.empty()) { // shouldn't happen normally
5730             return std::string();
5731         }
5732         const auto &row = res.front();
5733         outTableName = row[1];
5734         outAuthName = row[2];
5735         outCode = row[3];
5736         return removeEnsembleSuffix(row[0]);
5737     }
5738 }
5739 
5740 // ---------------------------------------------------------------------------
5741 
5742 /** \brief Return a list of objects, identified by their name
5743  *
5744  * @param searchedName Searched name. Must be at least 2 character long.
5745  * @param allowedObjectTypes List of object types into which to search. If
5746  * empty, all object types will be searched.
5747  * @param approximateMatch Whether approximate name identification is allowed.
5748  * @param limitResultCount Maximum number of results to return.
5749  * Or 0 for unlimited.
5750  * @return list of matched objects.
5751  * @throw FactoryException
5752  */
5753 std::list<common::IdentifiedObjectNNPtr>
createObjectsFromName(const std::string & searchedName,const std::vector<ObjectType> & allowedObjectTypes,bool approximateMatch,size_t limitResultCount) const5754 AuthorityFactory::createObjectsFromName(
5755     const std::string &searchedName,
5756     const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
5757     size_t limitResultCount) const {
5758     std::list<common::IdentifiedObjectNNPtr> res;
5759     const auto resTmp(createObjectsFromNameEx(
5760         searchedName, allowedObjectTypes, approximateMatch, limitResultCount));
5761     for (const auto &pair : resTmp) {
5762         res.emplace_back(pair.first);
5763     }
5764     return res;
5765 }
5766 
5767 // ---------------------------------------------------------------------------
5768 
5769 //! @cond Doxygen_Suppress
5770 
5771 /** \brief Return a list of objects, identifier by their name, with the name
5772  * on which the match occurred.
5773  *
5774  * The name on which the match occurred might be different from the object name,
5775  * if the match has been done on an alias name of that object.
5776  *
5777  * @param searchedName Searched name. Must be at least 2 character long.
5778  * @param allowedObjectTypes List of object types into which to search. If
5779  * empty, all object types will be searched.
5780  * @param approximateMatch Whether approximate name identification is allowed.
5781  * @param limitResultCount Maximum number of results to return.
5782  * Or 0 for unlimited.
5783  * @return list of matched objects.
5784  * @throw FactoryException
5785  */
5786 std::list<AuthorityFactory::PairObjectName>
createObjectsFromNameEx(const std::string & searchedName,const std::vector<ObjectType> & allowedObjectTypes,bool approximateMatch,size_t limitResultCount) const5787 AuthorityFactory::createObjectsFromNameEx(
5788     const std::string &searchedName,
5789     const std::vector<ObjectType> &allowedObjectTypes, bool approximateMatch,
5790     size_t limitResultCount) const {
5791     std::string searchedNameWithoutDeprecated(searchedName);
5792     bool deprecated = false;
5793     if (ends_with(searchedNameWithoutDeprecated, " (deprecated)")) {
5794         deprecated = true;
5795         searchedNameWithoutDeprecated.resize(
5796             searchedNameWithoutDeprecated.size() - strlen(" (deprecated)"));
5797     }
5798 
5799     const std::string canonicalizedSearchedName(
5800         metadata::Identifier::canonicalizeName(searchedNameWithoutDeprecated));
5801     if (canonicalizedSearchedName.size() <= 1) {
5802         return {};
5803     }
5804 
5805     std::string sql(
5806         "SELECT table_name, auth_name, code, name, deprecated, is_alias "
5807         "FROM (");
5808 
5809     const auto getTableAndTypeConstraints = [&allowedObjectTypes,
5810                                              &searchedName]() {
5811         typedef std::pair<std::string, std::string> TableType;
5812         std::list<TableType> res;
5813         // Hide ESRI D_ vertical datums
5814         const bool startsWithDUnderscore = starts_with(searchedName, "D_");
5815         if (allowedObjectTypes.empty()) {
5816             for (const auto &tableName :
5817                  {"prime_meridian", "ellipsoid", "geodetic_datum",
5818                   "vertical_datum", "geodetic_crs", "projected_crs",
5819                   "vertical_crs", "compound_crs", "conversion",
5820                   "helmert_transformation", "grid_transformation",
5821                   "other_transformation", "concatenated_operation"}) {
5822                 if (!(startsWithDUnderscore &&
5823                       strcmp(tableName, "vertical_datum") == 0)) {
5824                     res.emplace_back(TableType(tableName, std::string()));
5825                 }
5826             }
5827         } else {
5828             for (const auto type : allowedObjectTypes) {
5829                 switch (type) {
5830                 case ObjectType::PRIME_MERIDIAN:
5831                     res.emplace_back(
5832                         TableType("prime_meridian", std::string()));
5833                     break;
5834                 case ObjectType::ELLIPSOID:
5835                     res.emplace_back(TableType("ellipsoid", std::string()));
5836                     break;
5837                 case ObjectType::DATUM:
5838                     res.emplace_back(
5839                         TableType("geodetic_datum", std::string()));
5840                     if (!startsWithDUnderscore) {
5841                         res.emplace_back(
5842                             TableType("vertical_datum", std::string()));
5843                     }
5844                     break;
5845                 case ObjectType::GEODETIC_REFERENCE_FRAME:
5846                     res.emplace_back(
5847                         TableType("geodetic_datum", std::string()));
5848                     break;
5849                 case ObjectType::DYNAMIC_GEODETIC_REFERENCE_FRAME:
5850                     res.emplace_back(
5851                         TableType("geodetic_datum", "frame_reference_epoch"));
5852                     break;
5853                 case ObjectType::VERTICAL_REFERENCE_FRAME:
5854                     res.emplace_back(
5855                         TableType("vertical_datum", std::string()));
5856                     break;
5857                 case ObjectType::DYNAMIC_VERTICAL_REFERENCE_FRAME:
5858                     res.emplace_back(
5859                         TableType("vertical_datum", "frame_reference_epoch"));
5860                     break;
5861                 case ObjectType::CRS:
5862                     res.emplace_back(TableType("geodetic_crs", std::string()));
5863                     res.emplace_back(TableType("projected_crs", std::string()));
5864                     res.emplace_back(TableType("vertical_crs", std::string()));
5865                     res.emplace_back(TableType("compound_crs", std::string()));
5866                     break;
5867                 case ObjectType::GEODETIC_CRS:
5868                     res.emplace_back(TableType("geodetic_crs", std::string()));
5869                     break;
5870                 case ObjectType::GEOCENTRIC_CRS:
5871                     res.emplace_back(TableType("geodetic_crs", GEOCENTRIC));
5872                     break;
5873                 case ObjectType::GEOGRAPHIC_CRS:
5874                     res.emplace_back(TableType("geodetic_crs", GEOG_2D));
5875                     res.emplace_back(TableType("geodetic_crs", GEOG_3D));
5876                     break;
5877                 case ObjectType::GEOGRAPHIC_2D_CRS:
5878                     res.emplace_back(TableType("geodetic_crs", GEOG_2D));
5879                     break;
5880                 case ObjectType::GEOGRAPHIC_3D_CRS:
5881                     res.emplace_back(TableType("geodetic_crs", GEOG_3D));
5882                     break;
5883                 case ObjectType::PROJECTED_CRS:
5884                     res.emplace_back(TableType("projected_crs", std::string()));
5885                     break;
5886                 case ObjectType::VERTICAL_CRS:
5887                     res.emplace_back(TableType("vertical_crs", std::string()));
5888                     break;
5889                 case ObjectType::COMPOUND_CRS:
5890                     res.emplace_back(TableType("compound_crs", std::string()));
5891                     break;
5892                 case ObjectType::COORDINATE_OPERATION:
5893                     res.emplace_back(TableType("conversion", std::string()));
5894                     res.emplace_back(
5895                         TableType("helmert_transformation", std::string()));
5896                     res.emplace_back(
5897                         TableType("grid_transformation", std::string()));
5898                     res.emplace_back(
5899                         TableType("other_transformation", std::string()));
5900                     res.emplace_back(
5901                         TableType("concatenated_operation", std::string()));
5902                     break;
5903                 case ObjectType::CONVERSION:
5904                     res.emplace_back(TableType("conversion", std::string()));
5905                     break;
5906                 case ObjectType::TRANSFORMATION:
5907                     res.emplace_back(
5908                         TableType("helmert_transformation", std::string()));
5909                     res.emplace_back(
5910                         TableType("grid_transformation", std::string()));
5911                     res.emplace_back(
5912                         TableType("other_transformation", std::string()));
5913                     break;
5914                 case ObjectType::CONCATENATED_OPERATION:
5915                     res.emplace_back(
5916                         TableType("concatenated_operation", std::string()));
5917                     break;
5918                 }
5919             }
5920         }
5921         return res;
5922     };
5923 
5924     const auto listTableNameType = getTableAndTypeConstraints();
5925     bool first = true;
5926     ListOfParams params;
5927     for (const auto &tableNameTypePair : listTableNameType) {
5928         if (!first) {
5929             sql += " UNION ";
5930         }
5931         first = false;
5932         sql += "SELECT '";
5933         sql += tableNameTypePair.first;
5934         sql += "' AS table_name, auth_name, code, name, deprecated, "
5935                "0 AS is_alias FROM ";
5936         sql += tableNameTypePair.first;
5937         sql += " WHERE 1 = 1 ";
5938         if (!tableNameTypePair.second.empty()) {
5939             if (tableNameTypePair.second == "frame_reference_epoch") {
5940                 sql += "AND frame_reference_epoch IS NOT NULL ";
5941             } else {
5942                 sql += "AND type = '";
5943                 sql += tableNameTypePair.second;
5944                 sql += "' ";
5945             }
5946         }
5947         if (deprecated) {
5948             sql += "AND deprecated = 1 ";
5949         }
5950         if (!approximateMatch) {
5951             sql += "AND name = ? COLLATE NOCASE ";
5952             params.push_back(searchedNameWithoutDeprecated);
5953         }
5954         if (d->hasAuthorityRestriction()) {
5955             sql += "AND auth_name = ? ";
5956             params.emplace_back(d->authority());
5957         }
5958 
5959         sql += " UNION SELECT '";
5960         sql += tableNameTypePair.first;
5961         sql += "' AS table_name, "
5962                "ov.auth_name AS auth_name, "
5963                "ov.code AS code, a.alt_name AS name, "
5964                "ov.deprecated AS deprecated, 1 as is_alias FROM ";
5965         sql += tableNameTypePair.first;
5966         sql += " ov "
5967                "JOIN alias_name a ON "
5968                "ov.auth_name = a.auth_name AND ov.code = a.code WHERE "
5969                "a.table_name = '";
5970         sql += tableNameTypePair.first;
5971         sql += "' ";
5972         if (!tableNameTypePair.second.empty()) {
5973             if (tableNameTypePair.second == "frame_reference_epoch") {
5974                 sql += "AND ov.frame_reference_epoch IS NOT NULL ";
5975             } else {
5976                 sql += "AND ov.type = '";
5977                 sql += tableNameTypePair.second;
5978                 sql += "' ";
5979             }
5980         }
5981         if (deprecated) {
5982             sql += "AND ov.deprecated = 1 ";
5983         }
5984         if (!approximateMatch) {
5985             sql += "AND a.alt_name = ? COLLATE NOCASE ";
5986             params.push_back(searchedNameWithoutDeprecated);
5987         }
5988         if (d->hasAuthorityRestriction()) {
5989             sql += "AND ov.auth_name = ? ";
5990             params.emplace_back(d->authority());
5991         }
5992     }
5993 
5994     sql += ") ORDER BY deprecated, is_alias, length(name), name";
5995     if (limitResultCount > 0 &&
5996         limitResultCount <
5997             static_cast<size_t>(std::numeric_limits<int>::max()) &&
5998         !approximateMatch) {
5999         sql += " LIMIT ";
6000         sql += toString(static_cast<int>(limitResultCount));
6001     }
6002 
6003     std::list<PairObjectName> res;
6004     std::set<std::pair<std::string, std::string>> setIdentified;
6005 
6006     // Querying geodetic datum is a super hot path when importing from WKT1
6007     // so cache results.
6008     if (allowedObjectTypes.size() == 1 &&
6009         allowedObjectTypes[0] == ObjectType::GEODETIC_REFERENCE_FRAME &&
6010         approximateMatch && d->authority().empty()) {
6011         auto &mapCanonicalizeGRFName =
6012             d->context()->getPrivate()->getMapCanonicalizeGRFName();
6013         if (mapCanonicalizeGRFName.empty()) {
6014             auto sqlRes = d->run(sql, params);
6015             for (const auto &row : sqlRes) {
6016                 const auto &name = row[3];
6017                 const auto &deprecatedStr = row[4];
6018                 const auto canonicalizedName(
6019                     metadata::Identifier::canonicalizeName(name));
6020                 auto &v = mapCanonicalizeGRFName[canonicalizedName];
6021                 if (deprecatedStr == "0" || v.empty() || v.front()[4] == "1") {
6022                     v.push_back(row);
6023                 }
6024             }
6025         }
6026         auto iter = mapCanonicalizeGRFName.find(canonicalizedSearchedName);
6027         if (iter != mapCanonicalizeGRFName.end()) {
6028             const auto &listOfRow = iter->second;
6029             for (const auto &row : listOfRow) {
6030                 const auto &auth_name = row[1];
6031                 const auto &code = row[2];
6032                 const auto key =
6033                     std::pair<std::string, std::string>(auth_name, code);
6034                 if (setIdentified.find(key) != setIdentified.end()) {
6035                     continue;
6036                 }
6037                 setIdentified.insert(key);
6038                 auto factory = d->createFactory(auth_name);
6039                 const auto &name = row[3];
6040                 res.emplace_back(
6041                     PairObjectName(factory->createGeodeticDatum(code), name));
6042                 if (limitResultCount > 0 && res.size() == limitResultCount) {
6043                     break;
6044                 }
6045             }
6046         } else {
6047             for (const auto &pair : mapCanonicalizeGRFName) {
6048                 const auto &listOfRow = pair.second;
6049                 for (const auto &row : listOfRow) {
6050                     const auto &name = row[3];
6051                     bool match = ci_find(name, searchedNameWithoutDeprecated) !=
6052                                  std::string::npos;
6053                     if (!match) {
6054                         const auto &canonicalizedName(pair.first);
6055                         match = ci_find(canonicalizedName,
6056                                         canonicalizedSearchedName) !=
6057                                 std::string::npos;
6058                     }
6059                     if (!match) {
6060                         continue;
6061                     }
6062 
6063                     const auto &auth_name = row[1];
6064                     const auto &code = row[2];
6065                     const auto key =
6066                         std::pair<std::string, std::string>(auth_name, code);
6067                     if (setIdentified.find(key) != setIdentified.end()) {
6068                         continue;
6069                     }
6070                     setIdentified.insert(key);
6071                     auto factory = d->createFactory(auth_name);
6072                     res.emplace_back(PairObjectName(
6073                         factory->createGeodeticDatum(code), name));
6074                     if (limitResultCount > 0 &&
6075                         res.size() == limitResultCount) {
6076                         break;
6077                     }
6078                 }
6079                 if (limitResultCount > 0 && res.size() == limitResultCount) {
6080                     break;
6081                 }
6082             }
6083         }
6084     } else {
6085         auto sqlRes = d->run(sql, params);
6086         bool isFirst = true;
6087         bool firstIsDeprecated = false;
6088         bool foundExactMatch = false;
6089         std::size_t hashCodeFirstMatch = 0;
6090         for (const auto &row : sqlRes) {
6091             const auto &name = row[3];
6092             if (approximateMatch) {
6093                 bool match = ci_find(name, searchedNameWithoutDeprecated) !=
6094                              std::string::npos;
6095                 if (!match) {
6096                     const auto canonicalizedName(
6097                         metadata::Identifier::canonicalizeName(name));
6098                     match =
6099                         ci_find(canonicalizedName, canonicalizedSearchedName) !=
6100                         std::string::npos;
6101                 }
6102                 if (!match) {
6103                     continue;
6104                 }
6105             }
6106             const auto &table_name = row[0];
6107             const auto &auth_name = row[1];
6108             const auto &code = row[2];
6109             const auto key =
6110                 std::pair<std::string, std::string>(auth_name, code);
6111             if (setIdentified.find(key) != setIdentified.end()) {
6112                 continue;
6113             }
6114             setIdentified.insert(key);
6115             const auto &deprecatedStr = row[4];
6116             if (isFirst) {
6117                 firstIsDeprecated = deprecatedStr == "1";
6118                 isFirst = false;
6119             }
6120             if (deprecatedStr == "1" && !res.empty() && !firstIsDeprecated) {
6121                 break;
6122             }
6123             auto factory = d->createFactory(auth_name);
6124             auto getObject = [&factory](
6125                 const std::string &l_table_name,
6126                 const std::string &l_code) -> common::IdentifiedObjectNNPtr {
6127                 if (l_table_name == "prime_meridian") {
6128                     return factory->createPrimeMeridian(l_code);
6129                 } else if (l_table_name == "ellipsoid") {
6130                     return factory->createEllipsoid(l_code);
6131                 } else if (l_table_name == "geodetic_datum") {
6132                     return factory->createGeodeticDatum(l_code);
6133                 } else if (l_table_name == "vertical_datum") {
6134                     return factory->createVerticalDatum(l_code);
6135                 } else if (l_table_name == "geodetic_crs") {
6136                     return factory->createGeodeticCRS(l_code);
6137                 } else if (l_table_name == "projected_crs") {
6138                     return factory->createProjectedCRS(l_code);
6139                 } else if (l_table_name == "vertical_crs") {
6140                     return factory->createVerticalCRS(l_code);
6141                 } else if (l_table_name == "compound_crs") {
6142                     return factory->createCompoundCRS(l_code);
6143                 } else if (l_table_name == "conversion") {
6144                     return factory->createConversion(l_code);
6145                 } else if (l_table_name == "grid_transformation" ||
6146                            l_table_name == "helmert_transformation" ||
6147                            l_table_name == "other_transformation" ||
6148                            l_table_name == "concatenated_operation") {
6149                     return factory->createCoordinateOperation(l_code, true);
6150                 }
6151                 throw std::runtime_error("Unsupported table_name");
6152             };
6153             const auto obj = getObject(table_name, code);
6154             if (metadata::Identifier::canonicalizeName(obj->nameStr()) ==
6155                 canonicalizedSearchedName) {
6156                 foundExactMatch = true;
6157             }
6158 
6159             const auto objPtr = obj.get();
6160             if (res.empty()) {
6161                 hashCodeFirstMatch = typeid(*objPtr).hash_code();
6162             } else if (hashCodeFirstMatch != typeid(*objPtr).hash_code()) {
6163                 hashCodeFirstMatch = 0;
6164             }
6165 
6166             res.emplace_back(PairObjectName(obj, name));
6167             if (limitResultCount > 0 && res.size() == limitResultCount) {
6168                 break;
6169             }
6170         }
6171 
6172         // If we found a name that is an exact match, and all objects have the
6173         // same type, and we are not in approximate mode, only keep the objet(s)
6174         // with the exact name match.
6175         if (foundExactMatch && hashCodeFirstMatch != 0 && !approximateMatch) {
6176             std::list<PairObjectName> resTmp;
6177             for (const auto &pair : res) {
6178                 if (metadata::Identifier::canonicalizeName(
6179                         pair.first->nameStr()) == canonicalizedSearchedName) {
6180                     resTmp.emplace_back(pair);
6181                 }
6182             }
6183             res = std::move(resTmp);
6184         }
6185     }
6186 
6187     auto sortLambda = [](const PairObjectName &a, const PairObjectName &b) {
6188         const auto &aName = a.first->nameStr();
6189         const auto &bName = b.first->nameStr();
6190         if (aName.size() < bName.size()) {
6191             return true;
6192         }
6193         if (aName.size() > bName.size()) {
6194             return false;
6195         }
6196 
6197         const auto &aIds = a.first->identifiers();
6198         const auto &bIds = b.first->identifiers();
6199         if (aIds.size() < bIds.size()) {
6200             return true;
6201         }
6202         if (aIds.size() > bIds.size()) {
6203             return false;
6204         }
6205         for (size_t idx = 0; idx < aIds.size(); idx++) {
6206             const auto &aCodeSpace = *aIds[idx]->codeSpace();
6207             const auto &bCodeSpace = *bIds[idx]->codeSpace();
6208             const auto codeSpaceComparison = aCodeSpace.compare(bCodeSpace);
6209             if (codeSpaceComparison < 0) {
6210                 return true;
6211             }
6212             if (codeSpaceComparison > 0) {
6213                 return false;
6214             }
6215             const auto &aCode = aIds[idx]->code();
6216             const auto &bCode = bIds[idx]->code();
6217             const auto codeComparison = aCode.compare(bCode);
6218             if (codeComparison < 0) {
6219                 return true;
6220             }
6221             if (codeComparison > 0) {
6222                 return false;
6223             }
6224         }
6225         return strcmp(typeid(a.first.get()).name(),
6226                       typeid(b.first.get()).name()) < 0;
6227     };
6228 
6229     res.sort(sortLambda);
6230 
6231     return res;
6232 }
6233 //! @endcond
6234 
6235 // ---------------------------------------------------------------------------
6236 
6237 /** \brief Return a list of area of use from their name
6238  *
6239  * @param name Searched name.
6240  * @param approximateMatch Whether approximate name identification is allowed.
6241  * @return list of (auth_name, code) of matched objects.
6242  * @throw FactoryException
6243  */
6244 std::list<std::pair<std::string, std::string>>
listAreaOfUseFromName(const std::string & name,bool approximateMatch) const6245 AuthorityFactory::listAreaOfUseFromName(const std::string &name,
6246                                         bool approximateMatch) const {
6247     std::string sql(
6248         "SELECT auth_name, code FROM extent WHERE deprecated = 0 AND ");
6249     ListOfParams params;
6250     if (d->hasAuthorityRestriction()) {
6251         sql += " auth_name = ? AND ";
6252         params.emplace_back(d->authority());
6253     }
6254     sql += "name LIKE ?";
6255     if (!approximateMatch) {
6256         params.push_back(name);
6257     } else {
6258         params.push_back('%' + name + '%');
6259     }
6260     auto sqlRes = d->run(sql, params);
6261     std::list<std::pair<std::string, std::string>> res;
6262     for (const auto &row : sqlRes) {
6263         res.emplace_back(row[0], row[1]);
6264     }
6265     return res;
6266 }
6267 
6268 // ---------------------------------------------------------------------------
6269 
6270 //! @cond Doxygen_Suppress
createEllipsoidFromExisting(const datum::EllipsoidNNPtr & ellipsoid) const6271 std::list<datum::EllipsoidNNPtr> AuthorityFactory::createEllipsoidFromExisting(
6272     const datum::EllipsoidNNPtr &ellipsoid) const {
6273     std::string sql(
6274         "SELECT auth_name, code FROM ellipsoid WHERE "
6275         "abs(semi_major_axis - ?) < 1e-10 * abs(semi_major_axis) AND "
6276         "((semi_minor_axis IS NOT NULL AND "
6277         "abs(semi_minor_axis - ?) < 1e-10 * abs(semi_minor_axis)) OR "
6278         "((inv_flattening IS NOT NULL AND "
6279         "abs(inv_flattening - ?) < 1e-10 * abs(inv_flattening))))");
6280     ListOfParams params{ellipsoid->semiMajorAxis().getSIValue(),
6281                         ellipsoid->computeSemiMinorAxis().getSIValue(),
6282                         ellipsoid->computedInverseFlattening()};
6283     auto sqlRes = d->run(sql, params);
6284     std::list<datum::EllipsoidNNPtr> res;
6285     for (const auto &row : sqlRes) {
6286         const auto &auth_name = row[0];
6287         const auto &code = row[1];
6288         res.emplace_back(d->createFactory(auth_name)->createEllipsoid(code));
6289     }
6290     return res;
6291 }
6292 
6293 // ---------------------------------------------------------------------------
6294 
6295 //! @cond Doxygen_Suppress
createGeodeticCRSFromDatum(const std::string & datum_auth_name,const std::string & datum_code,const std::string & geodetic_crs_type) const6296 std::list<crs::GeodeticCRSNNPtr> AuthorityFactory::createGeodeticCRSFromDatum(
6297     const std::string &datum_auth_name, const std::string &datum_code,
6298     const std::string &geodetic_crs_type) const {
6299     std::string sql(
6300         "SELECT auth_name, code FROM geodetic_crs WHERE "
6301         "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
6302     ListOfParams params{datum_auth_name, datum_code};
6303     if (d->hasAuthorityRestriction()) {
6304         sql += " AND auth_name = ?";
6305         params.emplace_back(d->authority());
6306     }
6307     if (!geodetic_crs_type.empty()) {
6308         sql += " AND type = ?";
6309         params.emplace_back(geodetic_crs_type);
6310     }
6311     sql += " ORDER BY auth_name, code";
6312     auto sqlRes = d->run(sql, params);
6313     std::list<crs::GeodeticCRSNNPtr> res;
6314     for (const auto &row : sqlRes) {
6315         const auto &auth_name = row[0];
6316         const auto &code = row[1];
6317         res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
6318     }
6319     return res;
6320 }
6321 //! @endcond
6322 
6323 // ---------------------------------------------------------------------------
6324 
6325 //! @cond Doxygen_Suppress
createVerticalCRSFromDatum(const std::string & datum_auth_name,const std::string & datum_code) const6326 std::list<crs::VerticalCRSNNPtr> AuthorityFactory::createVerticalCRSFromDatum(
6327     const std::string &datum_auth_name, const std::string &datum_code) const {
6328     std::string sql(
6329         "SELECT auth_name, code FROM vertical_crs WHERE "
6330         "datum_auth_name = ? AND datum_code = ? AND deprecated = 0");
6331     ListOfParams params{datum_auth_name, datum_code};
6332     if (d->hasAuthorityRestriction()) {
6333         sql += " AND auth_name = ?";
6334         params.emplace_back(d->authority());
6335     }
6336     auto sqlRes = d->run(sql, params);
6337     std::list<crs::VerticalCRSNNPtr> res;
6338     for (const auto &row : sqlRes) {
6339         const auto &auth_name = row[0];
6340         const auto &code = row[1];
6341         res.emplace_back(d->createFactory(auth_name)->createVerticalCRS(code));
6342     }
6343     return res;
6344 }
6345 //! @endcond
6346 
6347 // ---------------------------------------------------------------------------
6348 
6349 //! @cond Doxygen_Suppress
6350 std::list<crs::GeodeticCRSNNPtr>
createGeodeticCRSFromEllipsoid(const std::string & ellipsoid_auth_name,const std::string & ellipsoid_code,const std::string & geodetic_crs_type) const6351 AuthorityFactory::createGeodeticCRSFromEllipsoid(
6352     const std::string &ellipsoid_auth_name, const std::string &ellipsoid_code,
6353     const std::string &geodetic_crs_type) const {
6354     std::string sql(
6355         "SELECT geodetic_crs.auth_name, geodetic_crs.code FROM geodetic_crs "
6356         "JOIN geodetic_datum ON "
6357         "geodetic_crs.datum_auth_name = geodetic_datum.auth_name AND "
6358         "geodetic_crs.datum_code = geodetic_datum.code WHERE "
6359         "geodetic_datum.ellipsoid_auth_name = ? AND "
6360         "geodetic_datum.ellipsoid_code = ? AND "
6361         "geodetic_datum.deprecated = 0 AND "
6362         "geodetic_crs.deprecated = 0");
6363     ListOfParams params{ellipsoid_auth_name, ellipsoid_code};
6364     if (d->hasAuthorityRestriction()) {
6365         sql += " AND geodetic_crs.auth_name = ?";
6366         params.emplace_back(d->authority());
6367     }
6368     if (!geodetic_crs_type.empty()) {
6369         sql += " AND geodetic_crs.type = ?";
6370         params.emplace_back(geodetic_crs_type);
6371     }
6372     auto sqlRes = d->run(sql, params);
6373     std::list<crs::GeodeticCRSNNPtr> res;
6374     for (const auto &row : sqlRes) {
6375         const auto &auth_name = row[0];
6376         const auto &code = row[1];
6377         res.emplace_back(d->createFactory(auth_name)->createGeodeticCRS(code));
6378     }
6379     return res;
6380 }
6381 //! @endcond
6382 
6383 // ---------------------------------------------------------------------------
6384 
6385 //! @cond Doxygen_Suppress
buildSqlLookForAuthNameCode(const std::list<std::pair<crs::CRSNNPtr,int>> & list,ListOfParams & params,const char * prefixField)6386 static std::string buildSqlLookForAuthNameCode(
6387     const std::list<std::pair<crs::CRSNNPtr, int>> &list, ListOfParams &params,
6388     const char *prefixField) {
6389     std::string sql("(");
6390 
6391     std::set<std::string> authorities;
6392     for (const auto &crs : list) {
6393         auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
6394         const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
6395                                    : crs.first->identifiers();
6396         if (!ids.empty()) {
6397             authorities.insert(*(ids[0]->codeSpace()));
6398         }
6399     }
6400     bool firstAuth = true;
6401     for (const auto &auth_name : authorities) {
6402         if (!firstAuth) {
6403             sql += " OR ";
6404         }
6405         firstAuth = false;
6406         sql += "( ";
6407         sql += prefixField;
6408         sql += "auth_name = ? AND ";
6409         sql += prefixField;
6410         sql += "code IN (";
6411         params.emplace_back(auth_name);
6412         bool firstGeodCRSForAuth = true;
6413         for (const auto &crs : list) {
6414             auto boundCRS = dynamic_cast<crs::BoundCRS *>(crs.first.get());
6415             const auto &ids = boundCRS ? boundCRS->baseCRS()->identifiers()
6416                                        : crs.first->identifiers();
6417             if (!ids.empty() && *(ids[0]->codeSpace()) == auth_name) {
6418                 if (!firstGeodCRSForAuth) {
6419                     sql += ',';
6420                 }
6421                 firstGeodCRSForAuth = false;
6422                 sql += '?';
6423                 params.emplace_back(ids[0]->code());
6424             }
6425         }
6426         sql += "))";
6427     }
6428     sql += ')';
6429     return sql;
6430 }
6431 //! @endcond
6432 
6433 // ---------------------------------------------------------------------------
6434 
6435 //! @cond Doxygen_Suppress
6436 std::list<crs::ProjectedCRSNNPtr>
createProjectedCRSFromExisting(const crs::ProjectedCRSNNPtr & crs) const6437 AuthorityFactory::createProjectedCRSFromExisting(
6438     const crs::ProjectedCRSNNPtr &crs) const {
6439     std::list<crs::ProjectedCRSNNPtr> res;
6440 
6441     const auto &conv = crs->derivingConversionRef();
6442     const auto &method = conv->method();
6443     const auto methodEPSGCode = method->getEPSGCode();
6444     if (methodEPSGCode == 0) {
6445         return res;
6446     }
6447 
6448     auto lockedThisFactory(d->getSharedFromThis());
6449     assert(lockedThisFactory);
6450     const auto &baseCRS(crs->baseCRS());
6451     auto candidatesGeodCRS = baseCRS->crs::CRS::identify(lockedThisFactory);
6452     auto geogCRS = dynamic_cast<const crs::GeographicCRS *>(baseCRS.get());
6453     if (geogCRS) {
6454         const auto axisOrder = geogCRS->coordinateSystem()->axisOrder();
6455         if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
6456             axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
6457             const auto &unit =
6458                 geogCRS->coordinateSystem()->axisList()[0]->unit();
6459             auto otherOrderGeogCRS = crs::GeographicCRS::create(
6460                 util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6461                                         geogCRS->nameStr()),
6462                 geogCRS->datum(), geogCRS->datumEnsemble(),
6463                 axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
6464                     ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
6465                     : cs::EllipsoidalCS::createLongitudeLatitude(unit));
6466             auto otherCandidatesGeodCRS =
6467                 otherOrderGeogCRS->crs::CRS::identify(lockedThisFactory);
6468             candidatesGeodCRS.insert(candidatesGeodCRS.end(),
6469                                      otherCandidatesGeodCRS.begin(),
6470                                      otherCandidatesGeodCRS.end());
6471         }
6472     }
6473 
6474     std::string sql(
6475         "SELECT projected_crs.auth_name, projected_crs.code FROM projected_crs "
6476         "JOIN conversion_table conv ON "
6477         "projected_crs.conversion_auth_name = conv.auth_name AND "
6478         "projected_crs.conversion_code = conv.code WHERE "
6479         "projected_crs.deprecated = 0 AND ");
6480     ListOfParams params;
6481     if (!candidatesGeodCRS.empty()) {
6482         sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
6483                                            "projected_crs.geodetic_crs_");
6484         sql += " AND ";
6485     }
6486     sql += "conv.method_auth_name = 'EPSG' AND "
6487            "conv.method_code = ?";
6488     params.emplace_back(toString(methodEPSGCode));
6489     if (d->hasAuthorityRestriction()) {
6490         sql += " AND projected_crs.auth_name = ?";
6491         params.emplace_back(d->authority());
6492     }
6493 
6494     int iParam = 0;
6495     bool hasLat1stStd = false;
6496     double lat1stStd = 0;
6497     int iParamLat1stStd = 0;
6498     bool hasLat2ndStd = false;
6499     double lat2ndStd = 0;
6500     int iParamLat2ndStd = 0;
6501     for (const auto &genOpParamvalue : conv->parameterValues()) {
6502         iParam++;
6503         auto opParamvalue =
6504             dynamic_cast<const operation::OperationParameterValue *>(
6505                 genOpParamvalue.get());
6506         if (!opParamvalue) {
6507             break;
6508         }
6509         const auto paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
6510         const auto &parameterValue = opParamvalue->parameterValue();
6511         if (!(paramEPSGCode > 0 &&
6512               parameterValue->type() ==
6513                   operation::ParameterValue::Type::MEASURE)) {
6514             break;
6515         }
6516         const auto &measure = parameterValue->value();
6517         const auto &unit = measure.unit();
6518         if (unit == common::UnitOfMeasure::DEGREE &&
6519             geogCRS->coordinateSystem()->axisList()[0]->unit() == unit) {
6520             if (methodEPSGCode ==
6521                 EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
6522                 // Special case for standard parallels of LCC_2SP. See below
6523                 if (paramEPSGCode ==
6524                     EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) {
6525                     hasLat1stStd = true;
6526                     lat1stStd = measure.value();
6527                     iParamLat1stStd = iParam;
6528                     continue;
6529                 } else if (paramEPSGCode ==
6530                            EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
6531                     hasLat2ndStd = true;
6532                     lat2ndStd = measure.value();
6533                     iParamLat2ndStd = iParam;
6534                     continue;
6535                 }
6536             }
6537             const auto iParamAsStr(toString(iParam));
6538             sql += " AND conv.param";
6539             sql += iParamAsStr;
6540             sql += "_code = ? AND conv.param";
6541             sql += iParamAsStr;
6542             sql += "_auth_name = 'EPSG' AND conv.param";
6543             sql += iParamAsStr;
6544             sql += "_value BETWEEN ? AND ?";
6545             // As angles might be expressed with the odd unit EPSG:9110
6546             // "sexagesimal DMS", we have to provide a broad range
6547             params.emplace_back(toString(paramEPSGCode));
6548             params.emplace_back(measure.value() - 1);
6549             params.emplace_back(measure.value() + 1);
6550         }
6551     }
6552 
6553     // Special case for standard parallels of LCC_2SP: they can be switched
6554     if (methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
6555         hasLat1stStd && hasLat2ndStd) {
6556         const auto iParam1AsStr(toString(iParamLat1stStd));
6557         const auto iParam2AsStr(toString(iParamLat2ndStd));
6558         sql += " AND conv.param";
6559         sql += iParam1AsStr;
6560         sql += "_code = ? AND conv.param";
6561         sql += iParam1AsStr;
6562         sql += "_auth_name = 'EPSG' AND conv.param";
6563         sql += iParam2AsStr;
6564         sql += "_code = ? AND conv.param";
6565         sql += iParam2AsStr;
6566         sql += "_auth_name = 'EPSG' AND ((";
6567         params.emplace_back(
6568             toString(EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL));
6569         params.emplace_back(
6570             toString(EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL));
6571         double val1 = lat1stStd;
6572         double val2 = lat2ndStd;
6573         for (int i = 0; i < 2; i++) {
6574             if (i == 1) {
6575                 sql += ") OR (";
6576                 std::swap(val1, val2);
6577             }
6578             sql += "conv.param";
6579             sql += iParam1AsStr;
6580             sql += "_value BETWEEN ? AND ? AND conv.param";
6581             sql += iParam2AsStr;
6582             sql += "_value BETWEEN ? AND ?";
6583             params.emplace_back(val1 - 1);
6584             params.emplace_back(val1 + 1);
6585             params.emplace_back(val2 - 1);
6586             params.emplace_back(val2 + 1);
6587         }
6588         sql += "))";
6589     }
6590     auto sqlRes = d->run(sql, params);
6591 
6592     params.clear();
6593 
6594     sql = "SELECT auth_name, code FROM projected_crs WHERE "
6595           "deprecated = 0 AND conversion_auth_name IS NULL AND ";
6596     if (!candidatesGeodCRS.empty()) {
6597         sql += buildSqlLookForAuthNameCode(candidatesGeodCRS, params,
6598                                            "geodetic_crs_");
6599         sql += " AND ";
6600     }
6601 
6602     const auto escapeLikeStr = [](const std::string &str) {
6603         return replaceAll(replaceAll(replaceAll(str, "\\", "\\\\"), "_", "\\_"),
6604                           "%", "\\%");
6605     };
6606 
6607     const auto ellpsSemiMajorStr =
6608         toString(baseCRS->ellipsoid()->semiMajorAxis().getSIValue(), 10);
6609 
6610     sql += "(text_definition LIKE ? ESCAPE '\\'";
6611 
6612     // WKT2 definition
6613     {
6614         std::string patternVal("%");
6615 
6616         patternVal += ',';
6617         patternVal += ellpsSemiMajorStr;
6618         patternVal += '%';
6619 
6620         patternVal += escapeLikeStr(method->nameStr());
6621         patternVal += '%';
6622 
6623         params.emplace_back(patternVal);
6624     }
6625 
6626     const auto *mapping = getMapping(method.get());
6627     if (mapping && mapping->proj_name_main) {
6628         sql += " OR (text_definition LIKE ? AND (text_definition LIKE ?";
6629 
6630         std::string patternVal("%");
6631         patternVal += "proj=";
6632         patternVal += mapping->proj_name_main;
6633         patternVal += '%';
6634         params.emplace_back(patternVal);
6635 
6636         // could be a= or R=
6637         patternVal = "%=";
6638         patternVal += ellpsSemiMajorStr;
6639         patternVal += '%';
6640         params.emplace_back(patternVal);
6641 
6642         std::string projEllpsName;
6643         std::string ellpsName;
6644         if (baseCRS->ellipsoid()->lookForProjWellKnownEllps(projEllpsName,
6645                                                             ellpsName)) {
6646             sql += " OR text_definition LIKE ?";
6647             // Could be ellps= or datum=
6648             patternVal = "%=";
6649             patternVal += projEllpsName;
6650             patternVal += '%';
6651             params.emplace_back(patternVal);
6652         }
6653 
6654         sql += "))";
6655     }
6656 
6657     // WKT1_GDAL definition
6658     const char *wkt1GDALMethodName = conv->getWKT1GDALMethodName();
6659     if (wkt1GDALMethodName) {
6660         sql += " OR text_definition LIKE ? ESCAPE '\\'";
6661         std::string patternVal("%");
6662 
6663         patternVal += ',';
6664         patternVal += ellpsSemiMajorStr;
6665         patternVal += '%';
6666 
6667         patternVal += escapeLikeStr(wkt1GDALMethodName);
6668         patternVal += '%';
6669 
6670         params.emplace_back(patternVal);
6671     }
6672 
6673     // WKT1_ESRI definition
6674     const char *esriMethodName = conv->getESRIMethodName();
6675     if (esriMethodName) {
6676         sql += " OR text_definition LIKE ? ESCAPE '\\'";
6677         std::string patternVal("%");
6678 
6679         patternVal += ',';
6680         patternVal += ellpsSemiMajorStr;
6681         patternVal += '%';
6682 
6683         patternVal += escapeLikeStr(esriMethodName);
6684         patternVal += '%';
6685 
6686         auto fe =
6687             &conv->parameterValueMeasure(EPSG_CODE_PARAMETER_FALSE_EASTING);
6688         if (*fe == Measure()) {
6689             fe = &conv->parameterValueMeasure(
6690                 EPSG_CODE_PARAMETER_EASTING_FALSE_ORIGIN);
6691         }
6692         if (!(*fe == Measure())) {
6693             patternVal += "PARAMETER[\"False\\_Easting\",";
6694             patternVal +=
6695                 toString(fe->convertToUnit(
6696                              crs->coordinateSystem()->axisList()[0]->unit()),
6697                          10);
6698             patternVal += '%';
6699         }
6700 
6701         auto lat = &conv->parameterValueMeasure(
6702             EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
6703         if (*lat == Measure()) {
6704             lat = &conv->parameterValueMeasure(
6705                 EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN);
6706         }
6707         if (!(*lat == Measure())) {
6708             patternVal += "PARAMETER[\"Latitude\\_Of\\_Origin\",";
6709             const auto &angularUnit =
6710                 dynamic_cast<crs::GeographicCRS *>(crs->baseCRS().get())
6711                     ? crs->baseCRS()->coordinateSystem()->axisList()[0]->unit()
6712                     : UnitOfMeasure::DEGREE;
6713             patternVal += toString(lat->convertToUnit(angularUnit), 10);
6714             patternVal += '%';
6715         }
6716 
6717         params.emplace_back(patternVal);
6718     }
6719     sql += ")";
6720     if (d->hasAuthorityRestriction()) {
6721         sql += " AND auth_name = ?";
6722         params.emplace_back(d->authority());
6723     }
6724 
6725     auto sqlRes2 = d->run(sql, params);
6726 
6727     if (sqlRes.size() <= 200) {
6728         for (const auto &row : sqlRes) {
6729             const auto &auth_name = row[0];
6730             const auto &code = row[1];
6731             res.emplace_back(
6732                 d->createFactory(auth_name)->createProjectedCRS(code));
6733         }
6734     }
6735     if (sqlRes2.size() <= 200) {
6736         for (const auto &row : sqlRes2) {
6737             const auto &auth_name = row[0];
6738             const auto &code = row[1];
6739             res.emplace_back(
6740                 d->createFactory(auth_name)->createProjectedCRS(code));
6741         }
6742     }
6743 
6744     return res;
6745 }
6746 
6747 // ---------------------------------------------------------------------------
6748 
6749 std::list<crs::CompoundCRSNNPtr>
createCompoundCRSFromExisting(const crs::CompoundCRSNNPtr & crs) const6750 AuthorityFactory::createCompoundCRSFromExisting(
6751     const crs::CompoundCRSNNPtr &crs) const {
6752     std::list<crs::CompoundCRSNNPtr> res;
6753 
6754     auto lockedThisFactory(d->getSharedFromThis());
6755     assert(lockedThisFactory);
6756 
6757     const auto &components = crs->componentReferenceSystems();
6758     if (components.size() != 2) {
6759         return res;
6760     }
6761     auto candidatesHorizCRS = components[0]->identify(lockedThisFactory);
6762     auto candidatesVertCRS = components[1]->identify(lockedThisFactory);
6763     if (candidatesHorizCRS.empty() && candidatesVertCRS.empty()) {
6764         return res;
6765     }
6766 
6767     std::string sql("SELECT auth_name, code FROM compound_crs WHERE "
6768                     "deprecated = 0 AND ");
6769     ListOfParams params;
6770     bool addAnd = false;
6771     if (!candidatesHorizCRS.empty()) {
6772         sql += buildSqlLookForAuthNameCode(candidatesHorizCRS, params,
6773                                            "horiz_crs_");
6774         addAnd = true;
6775     }
6776     if (!candidatesVertCRS.empty()) {
6777         if (addAnd) {
6778             sql += " AND ";
6779         }
6780         sql += buildSqlLookForAuthNameCode(candidatesVertCRS, params,
6781                                            "vertical_crs_");
6782         addAnd = true;
6783     }
6784     if (d->hasAuthorityRestriction()) {
6785         if (addAnd) {
6786             sql += " AND ";
6787         }
6788         sql += "auth_name = ?";
6789         params.emplace_back(d->authority());
6790     }
6791 
6792     auto sqlRes = d->run(sql, params);
6793     for (const auto &row : sqlRes) {
6794         const auto &auth_name = row[0];
6795         const auto &code = row[1];
6796         res.emplace_back(d->createFactory(auth_name)->createCompoundCRS(code));
6797     }
6798     return res;
6799 }
6800 
6801 // ---------------------------------------------------------------------------
6802 
6803 //! @cond Doxygen_Suppress
6804 std::vector<operation::CoordinateOperationNNPtr>
getTransformationsForGeoid(const std::string & geoidName,bool usePROJAlternativeGridNames) const6805 AuthorityFactory::getTransformationsForGeoid(
6806     const std::string &geoidName, bool usePROJAlternativeGridNames) const {
6807     std::vector<operation::CoordinateOperationNNPtr> res;
6808 
6809     const std::string sql("SELECT operation_auth_name, operation_code FROM "
6810                           "geoid_model WHERE name = ?");
6811     auto sqlRes = d->run(sql, {geoidName});
6812     for (const auto &row : sqlRes) {
6813         const auto &auth_name = row[0];
6814         const auto &code = row[1];
6815         res.emplace_back(d->createFactory(auth_name)->createCoordinateOperation(
6816             code, usePROJAlternativeGridNames));
6817     }
6818 
6819     return res;
6820 }
6821 //! @endcond
6822 
6823 // ---------------------------------------------------------------------------
6824 
6825 //! @cond Doxygen_Suppress
FactoryException(const char * message)6826 FactoryException::FactoryException(const char *message) : Exception(message) {}
6827 
6828 // ---------------------------------------------------------------------------
6829 
FactoryException(const std::string & message)6830 FactoryException::FactoryException(const std::string &message)
6831     : Exception(message) {}
6832 
6833 // ---------------------------------------------------------------------------
6834 
6835 FactoryException::~FactoryException() = default;
6836 
6837 // ---------------------------------------------------------------------------
6838 
6839 FactoryException::FactoryException(const FactoryException &) = default;
6840 //! @endcond
6841 
6842 // ---------------------------------------------------------------------------
6843 
6844 //! @cond Doxygen_Suppress
6845 
6846 struct NoSuchAuthorityCodeException::Private {
6847     std::string authority_;
6848     std::string code_;
6849 
Privateio::NoSuchAuthorityCodeException::Private6850     Private(const std::string &authority, const std::string &code)
6851         : authority_(authority), code_(code) {}
6852 };
6853 
6854 // ---------------------------------------------------------------------------
6855 
NoSuchAuthorityCodeException(const std::string & message,const std::string & authority,const std::string & code)6856 NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
6857     const std::string &message, const std::string &authority,
6858     const std::string &code)
6859     : FactoryException(message),
6860       d(internal::make_unique<Private>(authority, code)) {}
6861 
6862 // ---------------------------------------------------------------------------
6863 
6864 NoSuchAuthorityCodeException::~NoSuchAuthorityCodeException() = default;
6865 
6866 // ---------------------------------------------------------------------------
6867 
NoSuchAuthorityCodeException(const NoSuchAuthorityCodeException & other)6868 NoSuchAuthorityCodeException::NoSuchAuthorityCodeException(
6869     const NoSuchAuthorityCodeException &other)
6870     : FactoryException(other), d(internal::make_unique<Private>(*(other.d))) {}
6871 //! @endcond
6872 
6873 // ---------------------------------------------------------------------------
6874 
6875 /** \brief Returns authority name. */
getAuthority() const6876 const std::string &NoSuchAuthorityCodeException::getAuthority() const {
6877     return d->authority_;
6878 }
6879 
6880 // ---------------------------------------------------------------------------
6881 
6882 /** \brief Returns authority code. */
getAuthorityCode() const6883 const std::string &NoSuchAuthorityCodeException::getAuthorityCode() const {
6884     return d->code_;
6885 }
6886 
6887 // ---------------------------------------------------------------------------
6888 
6889 } // namespace io
6890 NS_PROJ_END
6891