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 ¶meters = 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 ¶meters,
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 ¶m : 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 ¶m : 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 ¶meters = 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 ¶meters) {
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 ¶m_auth_name = row[base_param_idx + i * 6 + 0];
2701 if (param_auth_name.empty()) {
2702 break;
2703 }
2704 const auto ¶m_code = row[base_param_idx + i * 6 + 1];
2705 const auto ¶m_name = row[base_param_idx + i * 6 + 2];
2706 const auto ¶m_value = row[base_param_idx + i * 6 + 3];
2707 const auto ¶m_uom_auth_name = row[base_param_idx + i * 6 + 4];
2708 const auto ¶m_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 ¶m_auth_name = row[base_param_idx + i * 6 + 0];
3461 if (param_auth_name.empty()) {
3462 break;
3463 }
3464 const auto ¶m_code = row[base_param_idx + i * 6 + 1];
3465 const auto ¶m_name = row[base_param_idx + i * 6 + 2];
3466 const auto ¶m_value = row[base_param_idx + i * 6 + 3];
3467 const auto ¶m_uom_auth_name =
3468 row[base_param_idx + i * 6 + 4];
3469 const auto ¶m_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 ¶ms,
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 ¶meterValue = 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