QGIS API Documentation 4.3.0-Master (cc10e4e83e5)
Loading...
Searching...
No Matches
qgscoordinatereferencesystem.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscoordinatereferencesystem.cpp
3
4 -------------------
5 begin : 2007
6 copyright : (C) 2007 by Gary E. Sherman
7 email : sherman@mrcc.com
8***************************************************************************/
9
10/***************************************************************************
11 * *
12 * This program is free software; you can redistribute it and/or modify *
13 * it under the terms of the GNU General Public License as published by *
14 * the Free Software Foundation; either version 2 of the License, or *
15 * (at your option) any later version. *
16 * *
17 ***************************************************************************/
19
20#include <cmath>
21#include <nlohmann/json.hpp>
22#include <proj.h>
23#include <proj_constants.h>
24#include <proj_experimental.h>
25#include <sqlite3.h>
26
27#include "qgis.h"
28#include "qgsapplication.h"
32#include "qgsdatums.h"
33#include "qgsellipsoidutils.h"
34#include "qgslocalec.h"
35#include "qgslogger.h"
36#include "qgsmessagelog.h"
37#include "qgsogcutils.h"
38#include "qgsogrutils.h"
40#include "qgsprojoperation.h"
41#include "qgsprojutils.h"
42#include "qgsreadwritelocker.h"
43#include "qgssettings.h"
44
45#include <QDir>
46#include <QDomElement>
47#include <QDomNode>
48#include <QFile>
49#include <QFileInfo>
50#include <QRegularExpression>
51#include <QString>
52#include <QTextStream>
53
54#include "moc_qgscoordinatereferencesystem.cpp"
55
56using namespace Qt::StringLiterals;
57
58//gdal and ogr includes (needed for == operator)
59#include <ogr_srs_api.h>
60#include <cpl_error.h>
61#include <cpl_conv.h>
62#include <cpl_csv.h>
63
64CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
65
66typedef QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash;
67typedef QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash;
68
69Q_GLOBAL_STATIC( QReadWriteLock, sSrIdCacheLock )
71bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
72
73Q_GLOBAL_STATIC( QReadWriteLock, sOgcLock )
75bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
76
77Q_GLOBAL_STATIC( QReadWriteLock, sProj4CacheLock )
79bool QgsCoordinateReferenceSystem::sDisableProjCache = false;
80
81Q_GLOBAL_STATIC( QReadWriteLock, sCRSWktLock )
83bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
84
85Q_GLOBAL_STATIC( QReadWriteLock, sCRSSrsIdLock )
87bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
88
89Q_GLOBAL_STATIC( QReadWriteLock, sCrsStringLock )
91bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
92
93QString getFullProjString( PJ *obj )
94{
95 // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
96 // use proj_as_proj_string
97 QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
98 if ( boundCrs )
99 {
100 if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
101 {
102 return QString( proj4src );
103 }
104 }
105
106 return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
107}
108//--------------------------
109
116
118{
119 d = new QgsCoordinateReferenceSystemPrivate();
120 createFromString( definition );
121}
122
124{
125 d = new QgsCoordinateReferenceSystemPrivate();
127 createFromId( id, type );
129}
130
132 : d( srs.d )
133 , mValidationHint( srs.mValidationHint )
134 , mNativeFormat( srs.mNativeFormat )
135{}
136
138{
139 if ( &srs == this )
140 return *this;
141
142 d = srs.d;
143 mValidationHint = srs.mValidationHint;
144 mNativeFormat = srs.mNativeFormat;
145 return *this;
146}
147
149{
150 QList<long> results;
151 // check both standard & user defined projection databases
153
154 const auto constDbs = dbs;
155 for ( const QString &db : constDbs )
156 {
157 QFileInfo myInfo( db );
158 if ( !myInfo.exists() )
159 {
160 QgsDebugError( "failed : " + db + " does not exist!" );
161 continue;
162 }
163
166
167 //check the db is available
168 int result = openDatabase( db, database );
169 if ( result != SQLITE_OK )
170 {
171 QgsDebugError( "failed : " + db + " could not be opened!" );
172 continue;
173 }
174
175 QString sql = u"select srs_id from tbl_srs"_s;
176 int rc;
177 statement = database.prepare( sql, rc );
178 while ( true )
179 {
180 // this one is an infinitive loop, intended to fetch any row
181 int ret = statement.step();
182
183 if ( ret == SQLITE_DONE )
184 {
185 // there are no more rows to fetch - we can stop looping
186 break;
187 }
188
189 if ( ret == SQLITE_ROW )
190 {
191 results.append( statement.columnAsInt64( 0 ) );
192 }
193 else
194 {
195 QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
196 break;
197 }
198 }
199 }
200 std::sort( results.begin(), results.end() );
201 return results;
202}
203
205{
207 crs.createFromOgcWmsCrs( ogcCrs );
208 return crs;
209}
210
212{
213 QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
214 if ( res.isValid() )
215 return res;
216
217 // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
218 res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
219 if ( res.isValid() )
220 return res;
221
223}
224
226{
227 return fromProj( proj4 );
228}
229
231{
233 crs.createFromProj( proj );
234 return crs;
235}
236
238{
240 crs.createFromWkt( wkt );
241 return crs;
242}
243
250
252{
253 error.clear();
254 PJ *horizontalObj = horizontalCrs.projObject();
255 PJ *verticalObj = verticalCrs.projObject();
256 if ( horizontalObj && verticalObj )
257 {
258 QStringList errors;
259 QgsProjUtils::proj_pj_unique_ptr compoundCrs = QgsProjUtils::createCompoundCrs( horizontalObj, verticalObj, &errors );
260 if ( compoundCrs )
261 return QgsCoordinateReferenceSystem::fromProjObject( compoundCrs.get() );
262
263 QStringList formattedErrorList;
264 for ( const QString &rawError : std::as_const( errors ) )
265 {
266 QString formattedError = rawError;
267 formattedError.replace( "proj_create_compound_crs: "_L1, QString() );
268 formattedErrorList.append( formattedError );
269 }
270 error = formattedErrorList.join( '\n' );
271 }
273}
274
276{
278 if ( !ellipsoidParams.valid )
280
281 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_geocentric_crs(
283 /*crs_name*/ nullptr,
284 /*datum_name*/ nullptr,
285 /*ellps_name*/ nullptr,
286 /*semi_major_metre*/ ellipsoidParams.semiMajor,
287 /*inv_flattening*/ ellipsoidParams.inverseFlattening,
288 /*prime_meridian_name*/ nullptr,
289 /*prime_meridian_offset*/ 0,
290 /*angular_units*/ nullptr, // "NULL for degrees"
291 /*angular_units_conv*/ 0, // "0 for degrees if angular_units == NULL"
292 /*linear_units*/ nullptr, // "NULL for meter"
293 /*linear_units_conv*/ 0 // "0 for Metre if linear_units == NULL"
294 ) );
296}
297
300
302{
303 bool result = false;
304 switch ( type )
305 {
306 case InternalCrsId:
307 result = createFromSrsId( id );
308 break;
309 case PostgisCrsId:
311 result = createFromSrid( id );
313 break;
314 case EpsgCrsId:
315 result = createFromOgcWmsCrs( u"EPSG:%1"_s.arg( id ) );
316 break;
317 default:
318 //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
319 QgsDebugError( u"Unexpected case reached!"_s );
320 };
321 return result;
322}
323
324bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
325{
326 if ( definition.isEmpty() )
327 return false;
328
329 QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Read );
330 if ( !sDisableStringCache )
331 {
332 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache()->constFind( definition );
333 if ( crsIt != sStringCache()->constEnd() )
334 {
335 // found a match in the cache
336 *this = crsIt.value();
337 return d->mIsValid;
338 }
339 }
340 locker.unlock();
341
342 bool result = false;
343 const thread_local QRegularExpression reCrsId( u"^(epsg|esri|osgeo|ignf|ogc|nkg|zangi|iau_2015|iau2000|postgis|internal|user)\\:(\\w+)$"_s, QRegularExpression::CaseInsensitiveOption );
344 QRegularExpressionMatch match = reCrsId.match( definition );
345 if ( match.capturedStart() == 0 )
346 {
347 QString authName = match.captured( 1 ).toLower();
348 if ( authName == "epsg"_L1 )
349 {
350 result = createFromOgcWmsCrs( definition );
351 }
352 else if ( authName == "postgis"_L1 )
353 {
354 const long id = match.captured( 2 ).toLong();
356 result = createFromSrid( id );
358 }
359 else if ( authName == "esri"_L1
360 || authName == "osgeo"_L1
361 || authName == "ignf"_L1
362 || authName == "zangi"_L1
363 || authName == "iau2000"_L1
364 || authName == "ogc"_L1
365 || authName == "nkg"_L1
366 || authName == "iau_2015"_L1 )
367 {
368 result = createFromOgcWmsCrs( definition );
369 }
370 else
371 {
372 const long id = match.captured( 2 ).toLong();
374 result = createFromId( id, InternalCrsId );
376 }
377 }
378 else
379 {
380 const thread_local QRegularExpression reCrsStr( u"^(?:(wkt|proj4|proj)\\:)?(.+)$"_s, QRegularExpression::CaseInsensitiveOption );
381 match = reCrsStr.match( definition );
382 if ( match.capturedStart() == 0 )
383 {
384 if ( match.captured( 1 ).startsWith( "proj"_L1, Qt::CaseInsensitive ) )
385 {
386 result = createFromProj( match.captured( 2 ) );
387 }
388 else
389 {
390 result = createFromWkt( match.captured( 2 ) );
391 }
392 }
393 }
394
396 if ( !sDisableStringCache )
397 sStringCache()->insert( definition, *this );
398 return result;
399}
400
402{
403 if ( definition.isEmpty() )
404 return false;
405
406 QString userWkt;
407 OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
408
409 if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
410 {
412 OSRDestroySpatialReference( crs );
413 }
414 //QgsDebugMsgLevel( "definition: " + definition + " wkt = " + wkt, 2 );
415 return createFromWkt( userWkt );
416}
417
419{
420 // make sure towgs84 parameter is loaded if gdal >= 1.9
421 // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
422 const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
423 const char *configNew = "GEOGCS";
424 // only set if it was not set, to let user change the value if needed
425 if ( strcmp( configOld, "" ) == 0 )
426 {
427 CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
428 if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
429 QgsLogger::warning( u"GDAL_FIX_ESRI_WKT could not be set to %1 : %2"_s.arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
430 QgsDebugMsgLevel( u"set GDAL_FIX_ESRI_WKT : %1"_s.arg( configNew ), 4 );
431 }
432 else
433 {
434 QgsDebugMsgLevel( u"GDAL_FIX_ESRI_WKT was already set : %1"_s.arg( configNew ), 4 );
435 }
436}
437
439{
440 if ( crs.isEmpty() )
441 return false;
442
443 QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Read );
444 if ( !sDisableOgcCache )
445 {
446 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache()->constFind( crs );
447 if ( crsIt != sOgcCache()->constEnd() )
448 {
449 // found a match in the cache
450 *this = crsIt.value();
451 return d->mIsValid;
452 }
453 }
454 locker.unlock();
455
456 QString wmsCrs = crs;
457
458 QString authority;
459 QString code;
460 const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( crs, authority, code );
461 const QString authorityLower = authority.toLower();
462 if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::AUTH_CODE && ( authorityLower == "user"_L1 || authorityLower == "custom"_L1 || authorityLower == "qgis"_L1 ) )
463 {
464 if ( createFromSrsId( code.toInt() ) )
465 {
467 if ( !sDisableOgcCache )
468 sOgcCache()->insert( crs, *this );
469 return d->mIsValid;
470 }
471 }
472 else if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
473 {
474 wmsCrs = authority + ':' + code;
475 }
476
477 // first chance for proj 6 - scan through legacy systems and try to use authid directly
478 const QString legacyKey = wmsCrs.toLower();
479 for ( auto it = authIdToQgisSrsIdMap().constBegin(); it != authIdToQgisSrsIdMap().constEnd(); ++it )
480 {
481 if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
482 {
483 const QStringList parts = it.key().split( ':' );
484 const QString auth = parts.at( 0 );
485 const QString code = parts.at( 1 );
486 if ( loadFromAuthCode( auth, code ) )
487 {
489 if ( !sDisableOgcCache )
490 sOgcCache()->insert( crs, *this );
491 return d->mIsValid;
492 }
493 }
494 }
495
496 if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), u"lower(auth_name||':'||auth_id)"_s, wmsCrs.toLower() ) )
497 {
499 if ( !sDisableOgcCache )
500 sOgcCache()->insert( crs, *this );
501 return d->mIsValid;
502 }
503
504 // NAD27
505 if ( wmsCrs.compare( "CRS:27"_L1, Qt::CaseInsensitive ) == 0 || wmsCrs.compare( "OGC:CRS27"_L1, Qt::CaseInsensitive ) == 0 )
506 {
507 // TODO: verify same axis orientation
508 return createFromOgcWmsCrs( u"EPSG:4267"_s );
509 }
510
511 // NAD83
512 if ( wmsCrs.compare( "CRS:83"_L1, Qt::CaseInsensitive ) == 0 || wmsCrs.compare( "OGC:CRS83"_L1, Qt::CaseInsensitive ) == 0 )
513 {
514 // TODO: verify same axis orientation
515 return createFromOgcWmsCrs( u"EPSG:4269"_s );
516 }
517
518 // WGS84
519 if ( wmsCrs.compare( "CRS:84"_L1, Qt::CaseInsensitive ) == 0 || wmsCrs.compare( "OGC:CRS84"_L1, Qt::CaseInsensitive ) == 0 )
520 {
521 if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), u"lower(auth_name||':'||auth_id)"_s, u"epsg:4326"_s ) )
522 {
523 d->mAxisInverted = false;
524 d->mAxisInvertedDirty = false;
525 }
526
528 if ( !sDisableOgcCache )
529 sOgcCache()->insert( crs, *this );
530
531 return d->mIsValid;
532 }
533
534 // Try loading from Proj's db using authority and code
535 // While this CRS wasn't found in QGIS' srs db, it may be present in proj's
536 if ( !authority.isEmpty() && !code.isEmpty() && loadFromAuthCode( authority, code ) )
537 {
539 if ( !sDisableOgcCache )
540 sOgcCache()->insert( crs, *this );
541 return d->mIsValid;
542 }
543
545 if ( !sDisableOgcCache )
546 sOgcCache()->insert( crs, QgsCoordinateReferenceSystem() );
547 return d->mIsValid;
548}
549
550// Misc helper functions -----------------------
551
552
554{
555 if ( d->mIsValid || !sCustomSrsValidation )
556 return;
557
558 // try to validate using custom validation routines
559 if ( sCustomSrsValidation )
560 sCustomSrsValidation( *this );
561}
562
564{
565 QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Read );
566 if ( !sDisableSrIdCache )
567 {
568 QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache()->constFind( id );
569 if ( crsIt != sSrIdCache()->constEnd() )
570 {
571 // found a match in the cache
572 *this = crsIt.value();
573 return d->mIsValid;
574 }
575 }
576 locker.unlock();
577
578 // first chance for proj 6 - scan through legacy systems and try to use authid directly
579 for ( auto it = authIdToQgisSrsIdMap().constBegin(); it != authIdToQgisSrsIdMap().constEnd(); ++it )
580 {
581 if ( it.value().endsWith( u",%1"_s.arg( id ) ) )
582 {
583 const QStringList parts = it.key().split( ':' );
584 const QString auth = parts.at( 0 );
585 const QString code = parts.at( 1 );
586 if ( loadFromAuthCode( auth, code ) )
587 {
589 if ( !sDisableSrIdCache )
590 sSrIdCache()->insert( id, *this );
591
592 return d->mIsValid;
593 }
594 }
595 }
596
597 bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), u"srid"_s, QString::number( id ) );
598
600 if ( !sDisableSrIdCache )
601 sSrIdCache()->insert( id, *this );
602
603 return result;
604}
605
607{
608 QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Read );
609 if ( !sDisableSrsIdCache )
610 {
611 QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache()->constFind( id );
612 if ( crsIt != sSrsIdCache()->constEnd() )
613 {
614 // found a match in the cache
615 *this = crsIt.value();
616 return d->mIsValid;
617 }
618 }
619 locker.unlock();
620
621 // first chance for proj 6 - scan through legacy systems and try to use authid directly
622 for ( auto it = authIdToQgisSrsIdMap().constBegin(); it != authIdToQgisSrsIdMap().constEnd(); ++it )
623 {
624 if ( it.value().startsWith( QString::number( id ) + ',' ) )
625 {
626 const QStringList parts = it.key().split( ':' );
627 const QString auth = parts.at( 0 );
628 const QString code = parts.at( 1 );
629 if ( loadFromAuthCode( auth, code ) )
630 {
632 if ( !sDisableSrsIdCache )
633 sSrsIdCache()->insert( id, *this );
634 return d->mIsValid;
635 }
636 }
637 }
638
639 bool result = loadFromDatabase( id < Qgis::USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() : QgsApplication::qgisUserDatabaseFilePath(), u"srs_id"_s, QString::number( id ) );
640
642 if ( !sDisableSrsIdCache )
643 sSrsIdCache()->insert( id, *this );
644 return result;
645}
646
647bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
648{
649 d.detach();
650
651 QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
652 d->mIsValid = false;
653 d->mWktPreferred.clear();
654
655 QFileInfo myInfo( db );
656 if ( !myInfo.exists() )
657 {
658 QgsDebugError( "failed : " + db + " does not exist!" );
659 return d->mIsValid;
660 }
661
662 sqlite3_database_unique_ptr database;
663 sqlite3_statement_unique_ptr statement;
664 int myResult;
665 //check the db is available
666 myResult = openDatabase( db, database );
667 if ( myResult != SQLITE_OK )
668 {
669 return d->mIsValid;
670 }
671
672 /*
673 srs_id INTEGER PRIMARY KEY,
674 description text NOT NULL,
675 projection_acronym text NOT NULL,
676 ellipsoid_acronym NOT NULL,
677 parameters text NOT NULL,
678 srid integer NOT NULL,
679 auth_name varchar NOT NULL,
680 auth_id integer NOT NULL,
681 is_geo integer NOT NULL);
682 */
683
684 QString mySql = "select srs_id,description,projection_acronym,"
685 "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo,wkt "
686 "from tbl_srs where "
687 + expression
688 + '='
690 + " order by deprecated";
691 statement = database.prepare( mySql, myResult );
692 QString wkt;
693 // XXX Need to free memory from the error msg if one is set
694 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
695 {
696 d->mSrsId = statement.columnAsText( 0 ).toLong();
697 d->mDescription = statement.columnAsText( 1 );
698 d->mProjectionAcronym = statement.columnAsText( 2 );
699 d->mEllipsoidAcronym.clear();
700 d->mProj4 = statement.columnAsText( 4 );
701 d->mWktPreferred.clear();
702 d->mSRID = statement.columnAsText( 5 ).toLong();
703 d->mAuthId = statement.columnAsText( 6 );
704 d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
705 wkt = statement.columnAsText( 8 );
706 d->mAxisInvertedDirty = true;
707
708 if ( d->mSrsId >= Qgis::USER_CRS_START_ID && ( d->mAuthId.isEmpty() || d->mAuthId == QChar( ':' ) ) )
709 {
710 d->mAuthId = u"USER:%1"_s.arg( d->mSrsId );
711 }
712 else if ( !d->mAuthId.startsWith( "USER:"_L1, Qt::CaseInsensitive ) )
713 {
714 QStringList parts = d->mAuthId.split( ':' );
715 QString auth = parts.at( 0 );
716 QString code = parts.at( 1 );
717
718 {
719 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
720 d->setPj( QgsProjUtils::unboundCrs( crs.get() ) );
721 }
722
723 d->mIsValid = d->hasPj();
724 setMapUnits();
725 }
726 setExtraProperties();
727
728 if ( !d->mIsValid )
729 {
730 if ( !wkt.isEmpty() )
731 {
732 setWktString( wkt );
733 // set WKT string resets the description to that description embedded in the WKT, so manually overwrite this back to the
734 // value from the user DB
735 d->mDescription = statement.columnAsText( 1 );
736 }
737 else
738 setProjString( d->mProj4 );
739 }
740 }
741 else
742 {
743 QgsDebugMsgLevel( "failed : " + mySql, 4 );
744 }
745 return d->mIsValid;
746}
747
748void QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
749{
750 // Not completely sure about object order destruction after main() has
751 // exited. So it is safer to check sDisableCache before using sCacheLock
752 // in case sCacheLock would have been destroyed before the current TLS
753 // QgsProjContext object that has called us...
754
755 if ( !sDisableSrIdCache )
756 {
757 QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Write );
758 if ( !sDisableSrIdCache )
759 {
760 for ( auto it = sSrIdCache()->begin(); it != sSrIdCache()->end(); )
761 {
762 auto &v = it.value();
763 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
764 it = sSrIdCache()->erase( it );
765 else
766 ++it;
767 }
768 }
769 }
770 if ( !sDisableOgcCache )
771 {
772 QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Write );
773 if ( !sDisableOgcCache )
774 {
775 for ( auto it = sOgcCache()->begin(); it != sOgcCache()->end(); )
776 {
777 auto &v = it.value();
778 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
779 it = sOgcCache()->erase( it );
780 else
781 ++it;
782 }
783 }
784 }
785 if ( !sDisableProjCache )
786 {
787 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Write );
788 if ( !sDisableProjCache )
789 {
790 for ( auto it = sProj4Cache()->begin(); it != sProj4Cache()->end(); )
791 {
792 auto &v = it.value();
793 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
794 it = sProj4Cache()->erase( it );
795 else
796 ++it;
797 }
798 }
799 }
800 if ( !sDisableWktCache )
801 {
802 QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Write );
803 if ( !sDisableWktCache )
804 {
805 for ( auto it = sWktCache()->begin(); it != sWktCache()->end(); )
806 {
807 auto &v = it.value();
808 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
809 it = sWktCache()->erase( it );
810 else
811 ++it;
812 }
813 }
814 }
815 if ( !sDisableSrsIdCache )
816 {
817 QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Write );
818 if ( !sDisableSrsIdCache )
819 {
820 for ( auto it = sSrsIdCache()->begin(); it != sSrsIdCache()->end(); )
821 {
822 auto &v = it.value();
823 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
824 it = sSrsIdCache()->erase( it );
825 else
826 ++it;
827 }
828 }
829 }
830 if ( !sDisableStringCache )
831 {
832 QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Write );
833 if ( !sDisableStringCache )
834 {
835 for ( auto it = sStringCache()->begin(); it != sStringCache()->end(); )
836 {
837 auto &v = it.value();
838 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
839 it = sStringCache()->erase( it );
840 else
841 ++it;
842 }
843 }
844 }
845}
846
848{
849 if ( d->mAxisInvertedDirty )
850 {
851 d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->threadLocalProjObject() );
852 d->mAxisInvertedDirty = false;
853 }
854
855 return d->mAxisInverted;
856}
857
858QList<Qgis::CrsAxisDirection> QgsCoordinateReferenceSystem::axisOrdering() const
859{
860 if ( type() == Qgis::CrsType::Compound )
862
863 const PJ *projObject = d->threadLocalProjObject();
864 if ( !projObject )
865 return {};
866
867 PJ_CONTEXT *context = QgsProjContext::get();
868 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, projObject ) );
869 if ( !pjCs )
870 return {};
871
872 const thread_local QMap< Qgis::CrsAxisDirection, QString > mapping = {
873 { Qgis::CrsAxisDirection::North, u"north"_s },
874 { Qgis::CrsAxisDirection::NorthNorthEast, u"northNorthEast"_s },
875 { Qgis::CrsAxisDirection::NorthEast, u"northEast"_s },
876 { Qgis::CrsAxisDirection::EastNorthEast, u"eastNorthEast"_s },
877 { Qgis::CrsAxisDirection::East, u"east"_s },
878 { Qgis::CrsAxisDirection::EastSouthEast, u"eastSouthEast"_s },
879 { Qgis::CrsAxisDirection::SouthEast, u"southEast"_s },
880 { Qgis::CrsAxisDirection::SouthSouthEast, u"southSouthEast"_s },
881 { Qgis::CrsAxisDirection::South, u"south"_s },
882 { Qgis::CrsAxisDirection::SouthSouthWest, u"southSouthWest"_s },
883 { Qgis::CrsAxisDirection::SouthWest, u"southWest"_s },
884 { Qgis::CrsAxisDirection::WestSouthWest, u"westSouthWest"_s },
885 { Qgis::CrsAxisDirection::West, u"west"_s },
886 { Qgis::CrsAxisDirection::WestNorthWest, u"westNorthWest"_s },
887 { Qgis::CrsAxisDirection::NorthWest, u"northWest"_s },
888 { Qgis::CrsAxisDirection::NorthNorthWest, u"northNorthWest"_s },
889 { Qgis::CrsAxisDirection::GeocentricX, u"geocentricX"_s },
890 { Qgis::CrsAxisDirection::GeocentricY, u"geocentricY"_s },
891 { Qgis::CrsAxisDirection::GeocentricZ, u"geocentricZ"_s },
892 { Qgis::CrsAxisDirection::Up, u"up"_s },
893 { Qgis::CrsAxisDirection::Down, u"down"_s },
894 { Qgis::CrsAxisDirection::Forward, u"forward"_s },
895 { Qgis::CrsAxisDirection::Aft, u"aft"_s },
896 { Qgis::CrsAxisDirection::Port, u"port"_s },
897 { Qgis::CrsAxisDirection::Starboard, u"starboard"_s },
898 { Qgis::CrsAxisDirection::Clockwise, u"clockwise"_s },
899 { Qgis::CrsAxisDirection::CounterClockwise, u"counterClockwise"_s },
900 { Qgis::CrsAxisDirection::ColumnPositive, u"columnPositive"_s },
901 { Qgis::CrsAxisDirection::ColumnNegative, u"columnNegative"_s },
902 { Qgis::CrsAxisDirection::RowPositive, u"rowPositive"_s },
903 { Qgis::CrsAxisDirection::RowNegative, u"rowNegative"_s },
904 { Qgis::CrsAxisDirection::DisplayRight, u"displayRight"_s },
905 { Qgis::CrsAxisDirection::DisplayLeft, u"displayLeft"_s },
906 { Qgis::CrsAxisDirection::DisplayUp, u"displayUp"_s },
907 { Qgis::CrsAxisDirection::DisplayDown, u"displayDown"_s },
908 { Qgis::CrsAxisDirection::Future, u"future"_s },
909 { Qgis::CrsAxisDirection::Past, u"past"_s },
910 { Qgis::CrsAxisDirection::Towards, u"towards"_s },
911 { Qgis::CrsAxisDirection::AwayFrom, u"awayFrom"_s },
912 };
913
914 QList< Qgis::CrsAxisDirection > res;
915 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
916 if ( axisCount > 0 )
917 {
918 res.reserve( axisCount );
919
920 for ( int i = 0; i < axisCount; ++i )
921 {
922 const char *outDirection = nullptr;
923 proj_cs_get_axis_info( context, pjCs.get(), i, nullptr, nullptr, &outDirection, nullptr, nullptr, nullptr, nullptr );
924 // get first word of direction only
925 const thread_local QRegularExpression rx( u"([^\\s]+).*"_s );
926 const QRegularExpressionMatch match = rx.match( QString( outDirection ) );
927 if ( !match.hasMatch() )
928 continue;
929
930 const QString direction = match.captured( 1 );
932 for ( auto it = mapping.constBegin(); it != mapping.constEnd(); ++it )
933 {
934 if ( it.value().compare( direction, Qt::CaseInsensitive ) == 0 )
935 {
936 dir = it.key();
937 break;
938 }
939 }
940
941 res.append( dir );
942 }
943 }
944 return res;
945}
946
948{
949 return createFromWktInternal( wkt, QString() );
950}
951
952bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
953{
954 if ( wkt.isEmpty() )
955 return false;
956
957 d.detach();
958
959 QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
960 if ( !sDisableWktCache )
961 {
962 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
963 if ( crsIt != sWktCache()->constEnd() )
964 {
965 // found a match in the cache
966 *this = crsIt.value();
967
968 if ( !description.isEmpty() && d->mDescription.isEmpty() )
969 {
970 // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
971 d->mDescription = description;
972 locker.changeMode( QgsReadWriteLocker::Write );
973 sWktCache()->insert( wkt, *this );
974 }
975 return d->mIsValid;
976 }
977 }
978 locker.unlock();
979
980 d->mIsValid = false;
981 d->mProj4.clear();
982 d->mWktPreferred.clear();
983 if ( wkt.isEmpty() )
984 {
985 QgsDebugMsgLevel( u"theWkt is uninitialized, operation failed"_s, 4 );
986 return d->mIsValid;
987 }
988
989 // try to match against user crs
990 QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
991 if ( !record.empty() )
992 {
993 long srsId = record[u"srs_id"_s].toLong();
994 if ( srsId > 0 )
995 {
996 createFromSrsId( srsId );
997 }
998 }
999 else
1000 {
1001 setWktString( wkt );
1002 if ( !description.isEmpty() )
1003 {
1004 d->mDescription = description;
1005 }
1006 if ( d->mSrsId == 0 )
1007 {
1008 // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
1009 long id = matchToUserCrs();
1010 if ( id >= Qgis::USER_CRS_START_ID )
1011 {
1012 createFromSrsId( id );
1013 }
1014 }
1015 }
1016
1017 locker.changeMode( QgsReadWriteLocker::Write );
1018 if ( !sDisableWktCache )
1019 sWktCache()->insert( wkt, *this );
1020
1021 return d->mIsValid;
1022 //setMapunits will be called by createfromproj above
1023}
1024
1026{
1027 return d->mIsValid;
1028}
1029
1030bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
1031{
1032 return createFromProj( proj4String );
1033}
1034
1035bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
1036{
1037 if ( projString.isEmpty() )
1038 return false;
1039
1040 d.detach();
1041
1042 if ( projString.trimmed().isEmpty() )
1043 {
1044 d->mIsValid = false;
1045 d->mProj4.clear();
1046 d->mWktPreferred.clear();
1047 return false;
1048 }
1049
1050 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
1051 if ( !sDisableProjCache )
1052 {
1053 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
1054 if ( crsIt != sProj4Cache()->constEnd() )
1055 {
1056 // found a match in the cache
1057 *this = crsIt.value();
1058 return d->mIsValid;
1059 }
1060 }
1061 locker.unlock();
1062
1063 //
1064 // Examples:
1065 // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
1066 // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
1067 //
1068 // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
1069 // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
1070 //
1071 QString myProj4String = projString.trimmed();
1072 myProj4String.remove( u"+type=crs"_s );
1073 myProj4String = myProj4String.trimmed();
1074
1075 d->mIsValid = false;
1076 d->mWktPreferred.clear();
1077
1078 if ( identify )
1079 {
1080 // first, try to use proj to do this for us...
1081 const QString projCrsString = myProj4String + ( myProj4String.contains( u"+type=crs"_s ) ? QString() : u" +type=crs"_s );
1082 QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
1083 if ( crs )
1084 {
1085 QString authName;
1086 QString authCode;
1088 {
1089 const QString authid = u"%1:%2"_s.arg( authName, authCode );
1090 if ( createFromOgcWmsCrs( authid ) )
1091 {
1093 if ( !sDisableProjCache )
1094 sProj4Cache()->insert( projString, *this );
1095 return d->mIsValid;
1096 }
1097 }
1098 }
1099
1100 // try a direct match against user crses
1101 QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
1102 long id = 0;
1103 if ( !myRecord.empty() )
1104 {
1105 id = myRecord[u"srs_id"_s].toLong();
1106 if ( id >= Qgis::USER_CRS_START_ID )
1107 {
1108 createFromSrsId( id );
1109 }
1110 }
1111 if ( id < Qgis::USER_CRS_START_ID )
1112 {
1113 // no direct matches, so go ahead and create a new proj object based on the proj string alone.
1114 setProjString( myProj4String );
1115
1116 // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
1117 id = matchToUserCrs();
1118 if ( id >= Qgis::USER_CRS_START_ID )
1119 {
1120 createFromSrsId( id );
1121 }
1122 }
1123 }
1124 else
1125 {
1126 setProjString( myProj4String );
1127 }
1128
1130 if ( !sDisableProjCache )
1131 sProj4Cache()->insert( projString, *this );
1132
1133 return d->mIsValid;
1134}
1135
1136//private method meant for internal use by this class only
1137QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
1138{
1139 QString myDatabaseFileName;
1140 QgsCoordinateReferenceSystem::RecordMap myMap;
1141 QString myFieldName;
1142 QString myFieldValue;
1145 int myResult;
1146
1147 // Get the full path name to the sqlite3 spatial reference database.
1148 myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1149 QFileInfo myInfo( myDatabaseFileName );
1150 if ( !myInfo.exists() )
1151 {
1152 QgsDebugError( "failed : " + myDatabaseFileName + " does not exist!" );
1153 return myMap;
1154 }
1155
1156 //check the db is available
1157 myResult = openDatabase( myDatabaseFileName, database );
1158 if ( myResult != SQLITE_OK )
1159 {
1160 return myMap;
1161 }
1162
1163 statement = database.prepare( sql, myResult );
1164 // XXX Need to free memory from the error msg if one is set
1165 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1166 {
1167 int myColumnCount = statement.columnCount();
1168 //loop through each column in the record adding its expression name and value to the map
1169 for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1170 {
1171 myFieldName = statement.columnName( myColNo );
1172 myFieldValue = statement.columnAsText( myColNo );
1173 myMap[myFieldName] = myFieldValue;
1174 }
1175 if ( statement.step() != SQLITE_DONE )
1176 {
1177 QgsDebugMsgLevel( u"Multiple records found in srs.db"_s, 4 );
1178 //be less fussy on proj 6 -- the db has MANY more entries!
1179 }
1180 }
1181 else
1182 {
1183 QgsDebugMsgLevel( "failed : " + sql, 4 );
1184 }
1185
1186 if ( myMap.empty() )
1187 {
1188 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1189 QFileInfo myFileInfo;
1190 myFileInfo.setFile( myDatabaseFileName );
1191 if ( !myFileInfo.exists() )
1192 {
1193 QgsDebugError( u"user qgis.db not found"_s );
1194 return myMap;
1195 }
1196
1197 //check the db is available
1198 myResult = openDatabase( myDatabaseFileName, database );
1199 if ( myResult != SQLITE_OK )
1200 {
1201 return myMap;
1202 }
1203
1204 statement = database.prepare( sql, myResult );
1205 // XXX Need to free memory from the error msg if one is set
1206 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1207 {
1208 int myColumnCount = statement.columnCount();
1209 //loop through each column in the record adding its field name and value to the map
1210 for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1211 {
1212 myFieldName = statement.columnName( myColNo );
1213 myFieldValue = statement.columnAsText( myColNo );
1214 myMap[myFieldName] = myFieldValue;
1215 }
1216
1217 if ( statement.step() != SQLITE_DONE )
1218 {
1219 QgsDebugMsgLevel( u"Multiple records found in srs.db"_s, 4 );
1220 myMap.clear();
1221 }
1222 }
1223 else
1224 {
1225 QgsDebugMsgLevel( "failed : " + sql, 4 );
1226 }
1227 }
1228 return myMap;
1229}
1230
1231// Accessors -----------------------------------
1232
1234{
1235 return d->mSrsId;
1236}
1237
1239{
1240 return d->mSRID;
1241}
1242
1244{
1245 return d->mAuthId;
1246}
1247
1249{
1250 if ( d->mDescription.isNull() )
1251 {
1252 return QString();
1253 }
1254 else
1255 {
1256 return d->mDescription;
1257 }
1258}
1259
1261{
1262 QString id;
1263 if ( !authid().isEmpty() )
1264 {
1265 if ( type != Qgis::CrsIdentifierType::ShortString && !description().isEmpty() )
1266 id = u"%1 - %2"_s.arg( authid(), description() );
1267 else
1268 id = authid();
1269 }
1270 else if ( !description().isEmpty() )
1271 id = description();
1273 id = isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
1274 else if ( !toWkt( Qgis::CrsWktVariant::Preferred ).isEmpty() )
1275 id = QObject::tr( "Custom CRS: %1" )
1276 .arg( type == Qgis::CrsIdentifierType::MediumString ? ( toWkt( Qgis::CrsWktVariant::Preferred ).left( 50 ) + QString( QChar( 0x2026 ) ) ) : toWkt( Qgis::CrsWktVariant::Preferred ) );
1277 else if ( !toProj().isEmpty() )
1278 id = QObject::tr( "Custom CRS: %1" ).arg( type == Qgis::CrsIdentifierType::MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) ) : toProj() );
1279 if ( !id.isEmpty() && !std::isnan( d->mCoordinateEpoch ) )
1280 id += u" @ %1"_s.arg( qgsDoubleToString( d->mCoordinateEpoch, 3 ) );
1281
1282 return id;
1283}
1284
1286{
1287 if ( d->mProjectionAcronym.isNull() )
1288 {
1289 return QString();
1290 }
1291 else
1292 {
1293 return d->mProjectionAcronym;
1294 }
1295}
1296
1298{
1299 if ( d->mEllipsoidAcronym.isNull() )
1300 {
1301 if ( PJ *obj = d->threadLocalProjObject() )
1302 {
1303 QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
1304 if ( ellipsoid )
1305 {
1306 const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1307 const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1308 if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
1309 d->mEllipsoidAcronym = u"%1:%2"_s.arg( ellipsoidAuthName, ellipsoidAuthCode );
1310 else
1311 {
1312 double semiMajor, semiMinor, invFlattening;
1313 int semiMinorComputed = 0;
1314 if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
1315 {
1316 d->mEllipsoidAcronym = u"PARAMETER:%1:%2"_s.arg( qgsDoubleToString( semiMajor ), qgsDoubleToString( semiMinor ) );
1317 }
1318 else
1319 {
1320 d->mEllipsoidAcronym.clear();
1321 }
1322 }
1323 }
1324 }
1325 return d->mEllipsoidAcronym;
1326 }
1327 else
1328 {
1329 return d->mEllipsoidAcronym;
1330 }
1331}
1332
1334{
1335 return toProj();
1336}
1337
1339{
1340 if ( !d->mIsValid )
1341 return QString();
1342
1343 if ( d->mProj4.isEmpty() )
1344 {
1345 if ( PJ *obj = d->threadLocalProjObject() )
1346 {
1347 d->mProj4 = getFullProjString( obj );
1348 }
1349 }
1350 // Stray spaces at the end?
1351 return d->mProj4.trimmed();
1352}
1353
1354std::string QgsCoordinateReferenceSystem::toJsonString( bool multiline, int indentationWidth, const QString &schema ) const
1355{
1356 if ( !d->mIsValid )
1357 return {};
1358
1359 if ( PJ *obj = d->threadLocalProjObject() )
1360 {
1361 const QByteArray multiLineOption = u"MULTILINE=%1"_s.arg( multiline ? u"YES"_s : u"NO"_s ).toLocal8Bit();
1362 const QByteArray indentatationWidthOption = u"INDENTATION_WIDTH=%1"_s.arg( multiline ? QString::number( indentationWidth ) : u"0"_s ).toLocal8Bit();
1363 const QByteArray schemaOption = u"SCHEMA=%1"_s.arg( schema ).toLocal8Bit();
1364 const char *const options[] = { multiLineOption.constData(), indentatationWidthOption.constData(), schemaOption.constData(), nullptr };
1365
1366 const char *json = proj_as_projjson( QgsProjContext::get(), obj, options );
1367 return json ? std::string { json } : std::string {};
1368 }
1369 else
1370 {
1371 return {};
1372 }
1373}
1374
1376{
1377 // NOLINTBEGIN(bugprone-branch-clone)
1378 switch ( d->mProjType )
1379 {
1380 case PJ_TYPE_UNKNOWN:
1382
1383 case PJ_TYPE_ELLIPSOID:
1384 case PJ_TYPE_PRIME_MERIDIAN:
1385 case PJ_TYPE_GEODETIC_REFERENCE_FRAME:
1386 case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME:
1387 case PJ_TYPE_VERTICAL_REFERENCE_FRAME:
1388 case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME:
1389 case PJ_TYPE_DATUM_ENSEMBLE:
1390 case PJ_TYPE_CONVERSION:
1391 case PJ_TYPE_TRANSFORMATION:
1392 case PJ_TYPE_CONCATENATED_OPERATION:
1393 case PJ_TYPE_OTHER_COORDINATE_OPERATION:
1394 case PJ_TYPE_TEMPORAL_DATUM:
1395 case PJ_TYPE_ENGINEERING_DATUM:
1396 case PJ_TYPE_PARAMETRIC_DATUM:
1399 case PJ_TYPE_CRS:
1400 case PJ_TYPE_GEOGRAPHIC_CRS:
1401 //not possible
1402 return Qgis::CrsType::Other;
1403
1404 case PJ_TYPE_GEODETIC_CRS:
1406 case PJ_TYPE_GEOCENTRIC_CRS:
1408 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
1410 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
1412 case PJ_TYPE_VERTICAL_CRS:
1414 case PJ_TYPE_PROJECTED_CRS:
1416 case PJ_TYPE_COMPOUND_CRS:
1418 case PJ_TYPE_TEMPORAL_CRS:
1420 case PJ_TYPE_ENGINEERING_CRS:
1422 case PJ_TYPE_BOUND_CRS:
1423 return Qgis::CrsType::Bound;
1424 case PJ_TYPE_OTHER_CRS:
1425 return Qgis::CrsType::Other;
1426#if PROJ_VERSION_MAJOR > 9 || ( PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 2 )
1427 case PJ_TYPE_DERIVED_PROJECTED_CRS:
1429 case PJ_TYPE_COORDINATE_METADATA:
1430 return Qgis::CrsType::Other;
1431#endif
1432 }
1434 // NOLINTEND(bugprone-branch-clone)
1435}
1436
1438{
1439 const PJ *pj = projObject();
1440 if ( !pj )
1441 return false;
1442
1443 return proj_is_deprecated( pj );
1444}
1445
1447{
1448 return d->mIsGeographic;
1449}
1450
1452{
1453 return d->mIsGeographicCrossingAntimeridian;
1454}
1455
1457{
1458 const PJ *pj = projObject();
1459 if ( !pj )
1460 return false;
1461
1462 return QgsProjUtils::isDynamic( pj );
1463}
1464
1466{
1467 const PJ *pj = projObject();
1468 if ( !pj )
1469 return QString();
1470
1471 PJ_CONTEXT *context = QgsProjContext::get();
1472
1473 return QString( proj_get_celestial_body_name( context, pj ) );
1474}
1475
1477{
1478 if ( d->mCoordinateEpoch == epoch )
1479 return;
1480
1481 // detaching clears the proj object, so we need to clone the existing one first
1483 d.detach();
1484 d->mCoordinateEpoch = epoch;
1485 d->setPj( std::move( clone ) );
1486}
1487
1489{
1490 return d->mCoordinateEpoch;
1491}
1492
1494{
1495 QgsDatumEnsemble res;
1496 res.mValid = false;
1497
1498 const PJ *pj = projObject();
1499 if ( !pj )
1500 return res;
1501
1502 PJ_CONTEXT *context = QgsProjContext::get();
1503
1505 if ( !ensemble )
1506 return res;
1507
1508 res.mValid = true;
1509 res.mName = QString( proj_get_name( ensemble.get() ) );
1510 res.mAuthority = QString( proj_get_id_auth_name( ensemble.get(), 0 ) );
1511 res.mCode = QString( proj_get_id_code( ensemble.get(), 0 ) );
1512 res.mRemarks = QString( proj_get_remarks( ensemble.get() ) );
1513 res.mScope = QString( proj_get_scope( ensemble.get() ) );
1514 res.mAccuracy = proj_datum_ensemble_get_accuracy( context, ensemble.get() );
1515
1516 const int memberCount = proj_datum_ensemble_get_member_count( context, ensemble.get() );
1517 for ( int i = 0; i < memberCount; ++i )
1518 {
1519 QgsProjUtils::proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), i ) );
1520 if ( !member )
1521 continue;
1522
1523 QgsDatumEnsembleMember details;
1524 details.mName = QString( proj_get_name( member.get() ) );
1525 details.mAuthority = QString( proj_get_id_auth_name( member.get(), 0 ) );
1526 details.mCode = QString( proj_get_id_code( member.get(), 0 ) );
1527 details.mRemarks = QString( proj_get_remarks( member.get() ) );
1528 details.mScope = QString( proj_get_scope( member.get() ) );
1529
1530 res.mMembers << details;
1531 }
1532 return res;
1533}
1534
1536{
1538
1539 // we have to make a transformation object corresponding to the crs
1540 QString projString = toProj();
1541 projString.replace( "+type=crs"_L1, QString() );
1542
1543 QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1544 if ( !transformation )
1545 return res;
1546
1547 PJ_COORD coord = proj_coord( 0, 0, 0, HUGE_VAL );
1548 coord.uv.u = point.x() * M_PI / 180.0;
1549 coord.uv.v = point.y() * M_PI / 180.0;
1550
1551 proj_errno_reset( transformation.get() );
1552 const PJ_FACTORS pjFactors = proj_factors( transformation.get(), coord );
1553 if ( proj_errno( transformation.get() ) )
1554 {
1555 return res;
1556 }
1557
1558 res.mIsValid = true;
1559 res.mMeridionalScale = pjFactors.meridional_scale;
1560 res.mParallelScale = pjFactors.parallel_scale;
1561 res.mArealScale = pjFactors.areal_scale;
1562 res.mAngularDistortion = pjFactors.angular_distortion;
1563 res.mMeridianParallelAngle = pjFactors.meridian_parallel_angle * 180 / M_PI;
1564 res.mMeridianConvergence = pjFactors.meridian_convergence * 180 / M_PI;
1565 res.mTissotSemimajor = pjFactors.tissot_semimajor;
1566 res.mTissotSemiminor = pjFactors.tissot_semiminor;
1567 res.mDxDlam = pjFactors.dx_dlam;
1568 res.mDxDphi = pjFactors.dx_dphi;
1569 res.mDyDlam = pjFactors.dy_dlam;
1570 res.mDyDphi = pjFactors.dy_dphi;
1571 return res;
1572}
1573
1575{
1576 if ( !d->mIsValid )
1577 return QgsProjOperation();
1578
1579 QgsProjOperation res;
1580
1581 // we have to make a transformation object corresponding to the crs
1582 QString projString = toProj();
1583 projString.replace( "+type=crs"_L1, QString() );
1584 if ( projString.isEmpty() )
1585 return QgsProjOperation();
1586
1587 QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1588 if ( !transformation )
1589 return res;
1590
1591 PJ_PROJ_INFO info = proj_pj_info( transformation.get() );
1592
1593 if ( info.id )
1594 {
1595 return QgsApplication::coordinateReferenceSystemRegistry()->projOperations().value( QString( info.id ) );
1596 }
1597
1598 return res;
1599}
1600
1602{
1603 if ( !d->mIsValid )
1605
1606 return d->mMapUnits;
1607}
1608
1610{
1611 if ( !d->mIsValid )
1612 return QgsRectangle();
1613
1614 PJ *obj = d->threadLocalProjObject();
1615 if ( !obj )
1616 return QgsRectangle();
1617
1618 double westLon = 0;
1619 double southLat = 0;
1620 double eastLon = 0;
1621 double northLat = 0;
1622
1623 if ( !proj_get_area_of_use( QgsProjContext::get(), obj, &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1624 return QgsRectangle();
1625
1626
1627 // don't use the constructor which normalizes!
1628 QgsRectangle rect;
1629 rect.setXMinimum( westLon );
1630 rect.setYMinimum( southLat );
1631 rect.setXMaximum( eastLon );
1632 rect.setYMaximum( northLat );
1633 return rect;
1634}
1635
1637{
1638 const auto parts { authid().split( ':' ) };
1639 if ( parts.length() == 2 )
1640 {
1641 if ( parts[0] == "EPSG"_L1 )
1642 return u"http://www.opengis.net/def/crs/EPSG/0/%1"_s.arg( parts[1] );
1643 else if ( parts[0] == "OGC"_L1 )
1644 {
1645 return u"http://www.opengis.net/def/crs/OGC/1.3/%1"_s.arg( parts[1] );
1646 }
1647 else
1648 {
1649 QgsMessageLog::logMessage( u"Error converting published CRS to URI %1: (not OGC or EPSG)"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1650 }
1651 }
1652 else
1653 {
1654 QgsMessageLog::logMessage( u"Error converting published CRS to URI: %1"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1655 }
1656 return QString();
1657}
1658
1660{
1661 const auto parts { authid().split( ':' ) };
1662 if ( parts.length() == 2 )
1663 {
1664 if ( parts[0] == "EPSG"_L1 )
1665 return u"urn:ogc:def:crs:EPSG::%1"_s.arg( parts[1] );
1666 else if ( parts[0] == "OGC"_L1 )
1667 {
1668 return u"urn:ogc:def:crs:OGC:1.3:%1"_s.arg( parts[1] );
1669 }
1670 else if ( parts[0].startsWith( "IAU"_L1 ) )
1671 {
1672 if ( parts[0].contains( "_"_L1 ) )
1673 {
1674 const auto subParts = parts[0].split( '_' );
1675 if ( subParts.length() == 2 )
1676 {
1677 return u"urn:ogc:def:crs:%1:%2:%3"_s.arg( subParts[0], subParts[1], parts[1] );
1678 }
1679 }
1680 else
1681 {
1682 return u"urn:ogc:def:crs:%1::%2"_s.arg( parts[0], parts[1] );
1683 }
1684 }
1685 else
1686 {
1687 QgsMessageLog::logMessage( u"Error converting published CRS to URN %1: (not OGC or EPSG)"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1688 }
1689 }
1690 else
1691 {
1692 QgsMessageLog::logMessage( u"Error converting published CRS to URN: %1"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1693 }
1694 return QString();
1695}
1696
1697
1699{
1700 if ( !d->mIsValid )
1701 return;
1702
1703 if ( d->mSrsId >= Qgis::USER_CRS_START_ID )
1704 {
1705 // user CRS, so update to new definition
1706 createFromSrsId( d->mSrsId );
1707 }
1708 else
1709 {
1710 // nothing to do -- only user CRS definitions can be changed
1711 }
1712}
1713
1714void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1715{
1716 d.detach();
1717 d->mProj4 = proj4String;
1718 d->mWktPreferred.clear();
1719
1720 QgsLocaleNumC l;
1721 QString trimmed = proj4String.trimmed();
1722
1723 trimmed += " +type=crs"_L1;
1725
1726 {
1727 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1728 }
1729
1730 if ( !d->hasPj() )
1731 {
1732#ifdef QGISDEBUG
1733 const int errNo = proj_context_errno( ctx );
1734 QgsDebugError( u"proj string rejected: %1"_s.arg( proj_context_errno_string( ctx, errNo ) ) );
1735#endif
1736 d->mIsValid = false;
1737 }
1738 else
1739 {
1740 d->mEllipsoidAcronym.clear();
1741 d->mIsValid = true;
1742 }
1743
1744 setMapUnits();
1745 setExtraProperties();
1746}
1747
1748bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
1749{
1750 bool res = false;
1751 d->mIsValid = false;
1752 d->mWktPreferred.clear();
1753
1754 PROJ_STRING_LIST warnings = nullptr;
1755 PROJ_STRING_LIST grammarErrors = nullptr;
1756 {
1757 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammarErrors ) ) );
1758 }
1759
1760 res = d->hasPj();
1761 if ( !res )
1762 {
1763 QgsDebugMsgLevel( u"\n---------------------------------------------------------------"_s, 2 );
1764 QgsDebugMsgLevel( u"This CRS could *** NOT *** be set from the supplied Wkt "_s, 2 );
1765 QgsDebugMsgLevel( "INPUT: " + wkt, 2 );
1766 for ( auto iter = warnings; iter && *iter; ++iter )
1767 {
1768 QgsDebugMsgLevel( *iter, 2 );
1769 }
1770 for ( auto iter = grammarErrors; iter && *iter; ++iter )
1771 {
1772 QgsDebugMsgLevel( *iter, 2 );
1773 }
1774 QgsDebugMsgLevel( u"---------------------------------------------------------------\n"_s, 2 );
1775 }
1776 proj_string_list_destroy( warnings );
1777 proj_string_list_destroy( grammarErrors );
1778
1779 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1780 if ( !res )
1781 {
1782 locker.changeMode( QgsReadWriteLocker::Write );
1783 if ( !sDisableWktCache )
1784 sWktCache()->insert( wkt, *this );
1785 return d->mIsValid;
1786 }
1787
1788 // try to match to a known authority
1789 if ( d->hasPj() )
1790 {
1791 // try 1 - maybe we can directly grab the auth name and code from the crs already?
1792 QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1793 QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1794
1795 if ( authName.isEmpty() || authCode.isEmpty() )
1796 {
1797 // try 2, use proj's identify method and see if there's a nice candidate we can use
1798 QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1799 }
1800
1801 if ( !authName.isEmpty() && !authCode.isEmpty() )
1802 {
1803 QgsCoordinateReferenceSystem fromAuthCode;
1804 if ( fromAuthCode.loadFromAuthCode( authName, authCode ) )
1805 {
1806 *this = fromAuthCode;
1807 locker.changeMode( QgsReadWriteLocker::Write );
1808 if ( !sDisableWktCache )
1809 sWktCache()->insert( wkt, *this );
1810 return d->mIsValid;
1811 }
1812 }
1813
1814 // Still a valid CRS, just not a known one
1815 d->mIsValid = true;
1816 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1817 setMapUnits();
1818 setExtraProperties();
1819 }
1820
1821 return d->mIsValid;
1822}
1823
1824void QgsCoordinateReferenceSystem::setMapUnits()
1825{
1826 if ( !d->mIsValid )
1827 {
1828 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1829 return;
1830 }
1831
1832 if ( !d->hasPj() )
1833 {
1834 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1835 return;
1836 }
1837
1838 PJ_CONTEXT *context = QgsProjContext::get();
1839 // prefer horizontal CRS units, if present
1840 QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToHorizontalCrs( d->threadLocalProjObject() ) );
1841 if ( !crs )
1842 crs = QgsProjUtils::unboundCrs( d->threadLocalProjObject() );
1843
1844 if ( !crs )
1845 {
1846 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1847 return;
1848 }
1849
1850 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1851 if ( !coordinateSystem )
1852 {
1853 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1854 return;
1855 }
1856
1857 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1858 if ( axisCount > 0 )
1859 {
1860 const char *outUnitName = nullptr;
1861 // Read only first axis
1862 proj_cs_get_axis_info( context, coordinateSystem.get(), 0, nullptr, nullptr, nullptr, nullptr, &outUnitName, nullptr, nullptr );
1863
1864 const QString unitName( outUnitName );
1865
1866 // proj unit names are freeform -- they differ from authority to authority :(
1867 // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1868 if ( unitName.compare( "degree"_L1, Qt::CaseInsensitive ) == 0
1869 || unitName.compare( "degree minute second"_L1, Qt::CaseInsensitive ) == 0
1870 || unitName.compare( "degree minute second hemisphere"_L1, Qt::CaseInsensitive ) == 0
1871 || unitName.compare( "degree minute"_L1, Qt::CaseInsensitive ) == 0
1872 || unitName.compare( "degree hemisphere"_L1, Qt::CaseInsensitive ) == 0
1873 || unitName.compare( "degree minute hemisphere"_L1, Qt::CaseInsensitive ) == 0
1874 || unitName.compare( "hemisphere degree"_L1, Qt::CaseInsensitive ) == 0
1875 || unitName.compare( "hemisphere degree minute"_L1, Qt::CaseInsensitive ) == 0
1876 || unitName.compare( "hemisphere degree minute second"_L1, Qt::CaseInsensitive ) == 0
1877 || unitName.compare( "degree (supplier to define representation)"_L1, Qt::CaseInsensitive ) == 0 )
1878 d->mMapUnits = Qgis::DistanceUnit::Degrees;
1879 else if ( unitName.compare( "metre"_L1, Qt::CaseInsensitive ) == 0 || unitName.compare( 'm'_L1, Qt::CaseInsensitive ) == 0 || unitName.compare( "meter"_L1, Qt::CaseInsensitive ) == 0 )
1880 d->mMapUnits = Qgis::DistanceUnit::Meters;
1881 else if ( unitName.compare( "US survey foot"_L1, Qt::CaseInsensitive ) == 0 )
1882 d->mMapUnits = Qgis::DistanceUnit::FeetUSSurvey;
1883 else if ( unitName.compare( "foot"_L1, Qt::CaseInsensitive ) == 0 )
1884 d->mMapUnits = Qgis::DistanceUnit::Feet;
1885 else if ( unitName.compare( "British yard (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1887 else if ( unitName.compare( "British yard (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1889 else if ( unitName.compare( "British foot (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1891 else if ( unitName.compare( "British foot (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1893 else if ( unitName.compare( "British chain (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1895 else if ( unitName.compare( "British chain (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1897 else if ( unitName.compare( "British link (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1899 else if ( unitName.compare( "British link (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1901 else if ( unitName.compare( "British yard (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1903 else if ( unitName.compare( "British foot (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1905 else if ( unitName.compare( "British chain (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1907 else if ( unitName.compare( "British link (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1909 else if ( unitName.compare( "British yard (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1911 else if ( unitName.compare( "British foot (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1913 else if ( unitName.compare( "British chain (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1915 else if ( unitName.compare( "British link (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1917 else if ( unitName.compare( "British foot (1865)"_L1, Qt::CaseInsensitive ) == 0 )
1919 else if ( unitName.compare( "British foot (1936)"_L1, Qt::CaseInsensitive ) == 0 )
1921 else if ( unitName.compare( "Indian foot"_L1, Qt::CaseInsensitive ) == 0 )
1922 d->mMapUnits = Qgis::DistanceUnit::FeetIndian;
1923 else if ( unitName.compare( "Indian foot (1937)"_L1, Qt::CaseInsensitive ) == 0 )
1925 else if ( unitName.compare( "Indian foot (1962)"_L1, Qt::CaseInsensitive ) == 0 )
1927 else if ( unitName.compare( "Indian foot (1975)"_L1, Qt::CaseInsensitive ) == 0 )
1929 else if ( unitName.compare( "Indian yard"_L1, Qt::CaseInsensitive ) == 0 )
1930 d->mMapUnits = Qgis::DistanceUnit::YardsIndian;
1931 else if ( unitName.compare( "Indian yard (1937)"_L1, Qt::CaseInsensitive ) == 0 )
1933 else if ( unitName.compare( "Indian yard (1962)"_L1, Qt::CaseInsensitive ) == 0 )
1935 else if ( unitName.compare( "Indian yard (1975)"_L1, Qt::CaseInsensitive ) == 0 )
1937 else if ( unitName.compare( "Gold Coast foot"_L1, Qt::CaseInsensitive ) == 0 )
1938 d->mMapUnits = Qgis::DistanceUnit::FeetGoldCoast;
1939 else if ( unitName.compare( "Clarke's foot"_L1, Qt::CaseInsensitive ) == 0 )
1940 d->mMapUnits = Qgis::DistanceUnit::FeetClarkes;
1941 else if ( unitName.compare( "Clarke's yard"_L1, Qt::CaseInsensitive ) == 0 )
1942 d->mMapUnits = Qgis::DistanceUnit::YardsClarkes;
1943 else if ( unitName.compare( "Clarke's chain"_L1, Qt::CaseInsensitive ) == 0 )
1944 d->mMapUnits = Qgis::DistanceUnit::ChainsClarkes;
1945 else if ( unitName.compare( "Clarke's link"_L1, Qt::CaseInsensitive ) == 0 )
1946 d->mMapUnits = Qgis::DistanceUnit::LinksClarkes;
1947 else if ( unitName.compare( "kilometre"_L1, Qt::CaseInsensitive ) == 0 ) //#spellok
1948 d->mMapUnits = Qgis::DistanceUnit::Kilometers;
1949 else if ( unitName.compare( "centimetre"_L1, Qt::CaseInsensitive ) == 0 ) //#spellok
1950 d->mMapUnits = Qgis::DistanceUnit::Centimeters;
1951 else if ( unitName.compare( "millimetre"_L1, Qt::CaseInsensitive ) == 0 ) //#spellok
1952 d->mMapUnits = Qgis::DistanceUnit::Millimeters;
1953 else if ( unitName.compare( "Statute mile"_L1, Qt::CaseInsensitive ) == 0 )
1954 d->mMapUnits = Qgis::DistanceUnit::Miles;
1955 else if ( unitName.compare( "nautical mile"_L1, Qt::CaseInsensitive ) == 0 )
1956 d->mMapUnits = Qgis::DistanceUnit::NauticalMiles;
1957 else if ( unitName.compare( "yard"_L1, Qt::CaseInsensitive ) == 0 )
1958 d->mMapUnits = Qgis::DistanceUnit::Yards;
1959 else if ( unitName.compare( "fathom"_L1, Qt::CaseInsensitive ) == 0 )
1960 d->mMapUnits = Qgis::DistanceUnit::Fathoms;
1961 else if ( unitName.compare( "US survey chain"_L1, Qt::CaseInsensitive ) == 0 )
1963 else if ( unitName.compare( "chain"_L1, Qt::CaseInsensitive ) == 0 )
1965 else if ( unitName.compare( "link"_L1, Qt::CaseInsensitive ) == 0 )
1967 else if ( unitName.compare( "US survey link"_L1, Qt::CaseInsensitive ) == 0 )
1968 d->mMapUnits = Qgis::DistanceUnit::LinksUSSurvey;
1969 else if ( unitName.compare( "US survey mile"_L1, Qt::CaseInsensitive ) == 0 )
1970 d->mMapUnits = Qgis::DistanceUnit::MilesUSSurvey;
1971 else if ( unitName.compare( "German legal metre"_L1, Qt::CaseInsensitive ) == 0 )
1973 // TODO - maybe more values to handle here?
1974 else
1975 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1976 return;
1977 }
1978 else
1979 {
1980 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1981 return;
1982 }
1983}
1984
1985void QgsCoordinateReferenceSystem::setExtraProperties()
1986{
1987 d->mIsGeographicCrossingAntimeridian = false;
1988
1989 // for geographic CRS, determine if the area of use crosses the antimeridian.
1990 // Shortcut this test for commonly used geographic CRS where we know the area does NOT
1991 // cross, to avoid the cost of calling proj_get_area_of_use
1992 if ( d->mIsGeographic && ( d->mAuthId != "EPSG:4326"_L1 ) )
1993 {
1994 if ( PJ *obj = d->threadLocalProjObject() )
1995 {
1996 double westLon = 0;
1997 double southLat = 0;
1998 double eastLon = 0;
1999 double northLat = 0;
2000 if ( proj_get_area_of_use( QgsProjContext::get(), obj, &westLon, &southLat, &eastLon, &northLat, nullptr ) )
2001 {
2002 d->mIsGeographicCrossingAntimeridian = westLon > eastLon;
2003 }
2004 ( void ) southLat;
2005 ( void ) northLat;
2006 }
2007 }
2008}
2009
2011{
2012 if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull() || !d->mIsValid )
2013 {
2015 "QgsCoordinateReferenceSystem::findMatchingProj will only "
2016 "work if prj acr ellipsoid acr and proj4string are set"
2017 " and the current projection is valid!",
2018 4
2019 );
2020 return 0;
2021 }
2022
2025 int myResult;
2026
2027 // Set up the query to retrieve the projection information
2028 // needed to populate the list
2029 QString mySql = QString(
2030 "select srs_id,parameters from tbl_srs where "
2031 "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated"
2032 )
2033 .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ), QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
2034 // Get the full path name to the sqlite3 spatial reference database.
2035 QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2036
2037 //check the db is available
2038 myResult = openDatabase( myDatabaseFileName, database );
2039 if ( myResult != SQLITE_OK )
2040 {
2041 return 0;
2042 }
2043
2044 statement = database.prepare( mySql, myResult );
2045 if ( myResult == SQLITE_OK )
2046 {
2047 while ( statement.step() == SQLITE_ROW )
2048 {
2049 QString mySrsId = statement.columnAsText( 0 );
2050 QString myProj4String = statement.columnAsText( 1 );
2051 if ( toProj() == myProj4String.trimmed() )
2052 {
2053 return mySrsId.toLong();
2054 }
2055 }
2056 }
2057
2058 //
2059 // Try the users db now
2060 //
2061
2062 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2063 //check the db is available
2064 myResult = openDatabase( myDatabaseFileName, database );
2065 if ( myResult != SQLITE_OK )
2066 {
2067 return 0;
2068 }
2069
2070 statement = database.prepare( mySql, myResult );
2071
2072 if ( myResult == SQLITE_OK )
2073 {
2074 while ( statement.step() == SQLITE_ROW )
2075 {
2076 QString mySrsId = statement.columnAsText( 0 );
2077 QString myProj4String = statement.columnAsText( 1 );
2078 if ( toProj() == myProj4String.trimmed() )
2079 {
2080 return mySrsId.toLong();
2081 }
2082 }
2083 }
2084
2085 return 0;
2086}
2087
2089{
2090 // shortcut
2091 if ( d == srs.d )
2092 return true;
2093
2094 if ( !d->mIsValid && !srs.d->mIsValid )
2095 return true;
2096
2097 if ( !d->mIsValid || !srs.d->mIsValid )
2098 return false;
2099
2100 if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
2101 return false;
2102
2103 const bool isUser = d->mSrsId >= Qgis::USER_CRS_START_ID;
2104 const bool otherIsUser = srs.d->mSrsId >= Qgis::USER_CRS_START_ID;
2105 if ( isUser != otherIsUser )
2106 return false;
2107
2108 // we can't directly compare authid for user crses -- the actual definition of these may have changed
2109 if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
2110 return d->mAuthId == srs.d->mAuthId;
2111
2113}
2114
2116{
2117 return !( *this == srs );
2118}
2119
2120QString QgsCoordinateReferenceSystem::toWkt( Qgis::CrsWktVariant variant, bool multiline, int indentationWidth ) const
2121{
2122 if ( PJ *obj = d->threadLocalProjObject() )
2123 {
2124 const bool isDefaultPreferredFormat = variant == Qgis::CrsWktVariant::Preferred && !multiline;
2125 if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
2126 {
2127 // can use cached value
2128 return d->mWktPreferred;
2129 }
2130
2131 PJ_WKT_TYPE type = PJ_WKT1_GDAL;
2132 switch ( variant )
2133 {
2135 type = PJ_WKT1_GDAL;
2136 break;
2138 type = PJ_WKT1_ESRI;
2139 break;
2141 type = PJ_WKT2_2015;
2142 break;
2144 type = PJ_WKT2_2015_SIMPLIFIED;
2145 break;
2147 type = PJ_WKT2_2019;
2148 break;
2150 type = PJ_WKT2_2019_SIMPLIFIED;
2151 break;
2152 }
2153
2154 const QByteArray multiLineOption = u"MULTILINE=%1"_s.arg( multiline ? u"YES"_s : u"NO"_s ).toLocal8Bit();
2155 const QByteArray indentatationWidthOption = u"INDENTATION_WIDTH=%1"_s.arg( multiline ? QString::number( indentationWidth ) : u"0"_s ).toLocal8Bit();
2156 const char *const options[] = { multiLineOption.constData(), indentatationWidthOption.constData(), nullptr };
2157 QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
2158
2159 if ( isDefaultPreferredFormat )
2160 {
2161 // cache result for later use
2162 d->mWktPreferred = res;
2163 }
2164
2165 return res;
2166 }
2167 return QString();
2168}
2169
2170bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
2171{
2172 d.detach();
2173 bool result = true;
2174 QDomNode srsNode = node.namedItem( u"spatialrefsys"_s );
2175
2176 if ( !srsNode.isNull() )
2177 {
2178 bool initialized = false;
2179
2180 bool ok = false;
2181 long srsid = srsNode.namedItem( u"srsid"_s ).toElement().text().toLong( &ok );
2182
2183 QDomNode node;
2184
2185 if ( ok && srsid > 0 && srsid < Qgis::USER_CRS_START_ID )
2186 {
2187 node = srsNode.namedItem( u"authid"_s );
2188 if ( !node.isNull() )
2189 {
2190 createFromOgcWmsCrs( node.toElement().text() );
2191 if ( isValid() )
2192 {
2193 initialized = true;
2194 }
2195 }
2196
2197 if ( !initialized )
2198 {
2199 node = srsNode.namedItem( u"epsg"_s );
2200 if ( !node.isNull() )
2201 {
2202 operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
2203 if ( isValid() )
2204 {
2205 initialized = true;
2206 }
2207 }
2208 }
2209 }
2210
2211 // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
2212 if ( !initialized )
2213 {
2214 // before doing anything, we grab and set the stored CRS name (description).
2215 // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
2216 // or the user's custom CRS list), then we will correctly show the CRS with its original
2217 // name (instead of just "custom crs")
2218 const QString description = srsNode.namedItem( u"description"_s ).toElement().text();
2219
2220 const QString wkt = srsNode.namedItem( u"wkt"_s ).toElement().text();
2221 initialized = createFromWktInternal( wkt, description );
2222 }
2223
2224 if ( !initialized )
2225 {
2226 node = srsNode.namedItem( u"proj4"_s );
2227 const QString proj4 = node.toElement().text();
2228 initialized = createFromProj( proj4 );
2229 }
2230
2231 if ( !initialized )
2232 {
2233 // Setting from elements one by one
2234 node = srsNode.namedItem( u"proj4"_s );
2235 const QString proj4 = node.toElement().text();
2236 if ( !proj4.trimmed().isEmpty() )
2237 setProjString( node.toElement().text() );
2238
2239 node = srsNode.namedItem( u"srsid"_s );
2240 d->mSrsId = node.toElement().text().toLong();
2241
2242 node = srsNode.namedItem( u"srid"_s );
2243 d->mSRID = node.toElement().text().toLong();
2244
2245 node = srsNode.namedItem( u"authid"_s );
2246 d->mAuthId = node.toElement().text();
2247
2248 node = srsNode.namedItem( u"description"_s );
2249 d->mDescription = node.toElement().text();
2250
2251 node = srsNode.namedItem( u"projectionacronym"_s );
2252 d->mProjectionAcronym = node.toElement().text();
2253
2254 node = srsNode.namedItem( u"ellipsoidacronym"_s );
2255 d->mEllipsoidAcronym = node.toElement().text();
2256
2257 node = srsNode.namedItem( u"geographicflag"_s );
2258 d->mIsGeographic = node.toElement().text() == "true"_L1;
2259
2260 d->mWktPreferred.clear();
2261
2262 //make sure the map units have been set
2263 setMapUnits();
2264 setExtraProperties();
2265 }
2266
2267 const QString epoch = srsNode.toElement().attribute( u"coordinateEpoch"_s );
2268 if ( !epoch.isEmpty() )
2269 {
2270 bool epochOk = false;
2271 d->mCoordinateEpoch = epoch.toDouble( &epochOk );
2272 if ( !epochOk )
2273 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
2274 }
2275 else
2276 {
2277 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
2278 }
2279
2280 mNativeFormat = qgsEnumKeyToValue<Qgis::CrsDefinitionFormat>( srsNode.toElement().attribute( u"nativeFormat"_s ), Qgis::CrsDefinitionFormat::Wkt );
2281
2282 const QDomNode topoBaseCrsNode = srsNode.namedItem( u"topocentricBaseCrs"_s );
2283 if ( !topoBaseCrsNode.isNull() )
2284 {
2285 d->mTopocentricBaseCrs = std::make_unique<QgsCoordinateReferenceSystem>();
2286 d->mTopocentricBaseCrs->readXml( topoBaseCrsNode );
2287 }
2288 }
2289 else
2290 {
2291 // Return empty CRS if none was found in the XML.
2292 d = new QgsCoordinateReferenceSystemPrivate();
2293 result = false;
2294 }
2295 return result;
2296}
2297
2298bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
2299{
2300 QDomElement layerNode = node.toElement();
2301 QDomElement srsElement = doc.createElement( u"spatialrefsys"_s );
2302
2303 srsElement.setAttribute( u"nativeFormat"_s, qgsEnumValueToKey<Qgis::CrsDefinitionFormat>( mNativeFormat ) );
2304
2305 if ( std::isfinite( d->mCoordinateEpoch ) )
2306 {
2307 srsElement.setAttribute( u"coordinateEpoch"_s, d->mCoordinateEpoch );
2308 }
2309
2310 QDomElement wktElement = doc.createElement( u"wkt"_s );
2311 wktElement.appendChild( doc.createTextNode( toWkt( Qgis::CrsWktVariant::Preferred ) ) );
2312 srsElement.appendChild( wktElement );
2313
2314 QDomElement proj4Element = doc.createElement( u"proj4"_s );
2315 proj4Element.appendChild( doc.createTextNode( toProj() ) );
2316 srsElement.appendChild( proj4Element );
2317
2318 QDomElement srsIdElement = doc.createElement( u"srsid"_s );
2319 srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
2320 srsElement.appendChild( srsIdElement );
2321
2322 QDomElement sridElement = doc.createElement( u"srid"_s );
2323 sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
2324 srsElement.appendChild( sridElement );
2325
2326 QDomElement authidElement = doc.createElement( u"authid"_s );
2327 authidElement.appendChild( doc.createTextNode( authid() ) );
2328 srsElement.appendChild( authidElement );
2329
2330 QDomElement descriptionElement = doc.createElement( u"description"_s );
2331 descriptionElement.appendChild( doc.createTextNode( description() ) );
2332 srsElement.appendChild( descriptionElement );
2333
2334 QDomElement projectionAcronymElement = doc.createElement( u"projectionacronym"_s );
2335 projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
2336 srsElement.appendChild( projectionAcronymElement );
2337
2338 QDomElement ellipsoidAcronymElement = doc.createElement( u"ellipsoidacronym"_s );
2339 ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
2340 srsElement.appendChild( ellipsoidAcronymElement );
2341
2342 QDomElement geographicFlagElement = doc.createElement( u"geographicflag"_s );
2343 QString geoFlagText = u"false"_s;
2344 if ( isGeographic() )
2345 {
2346 geoFlagText = u"true"_s;
2347 }
2348
2349 geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
2350 srsElement.appendChild( geographicFlagElement );
2351
2352 if ( d->mTopocentricBaseCrs && d->mTopocentricBaseCrs->isValid() )
2353 {
2354 QDomElement topoBaseCrsElement = doc.createElement( u"topocentricBaseCrs"_s );
2355 d->mTopocentricBaseCrs->writeXml( topoBaseCrsElement, doc );
2356 srsElement.appendChild( topoBaseCrsElement );
2357 }
2358
2359 layerNode.appendChild( srsElement );
2360
2361 return true;
2362}
2363
2364//
2365// Static helper methods below this point only please!
2366//
2367
2368
2369// Returns the whole proj4 string for the selected srsid
2370//this is a static method! NOTE I've made it private for now to reduce API clutter TS
2371QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
2372{
2373 QString myDatabaseFileName;
2374 QString myProjString;
2375 QString mySql = u"select parameters from tbl_srs where srs_id = %1 order by deprecated"_s.arg( srsId );
2376
2377 //
2378 // Determine if this is a user projection or a system on
2379 // user projection defs all have srs_id >= 100000
2380 //
2381 if ( srsId >= Qgis::USER_CRS_START_ID )
2382 {
2383 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2384 QFileInfo myFileInfo;
2385 myFileInfo.setFile( myDatabaseFileName );
2386 if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
2387 {
2388 QgsDebugError( u"users qgis.db not found"_s );
2389 return QString();
2390 }
2391 }
2392 else //must be a system projection then
2393 {
2394 myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2395 }
2396
2397 sqlite3_database_unique_ptr database;
2398 sqlite3_statement_unique_ptr statement;
2399
2400 int rc;
2401 rc = openDatabase( myDatabaseFileName, database );
2402 if ( rc )
2403 {
2404 return QString();
2405 }
2406
2407 statement = database.prepare( mySql, rc );
2408
2409 if ( rc == SQLITE_OK )
2410 {
2411 if ( statement.step() == SQLITE_ROW )
2412 {
2413 myProjString = statement.columnAsText( 0 );
2414 }
2415 }
2416
2417 return myProjString;
2418}
2419
2420int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
2421{
2422 int myResult;
2423 if ( readonly )
2424 myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
2425 else
2426 myResult = database.open( path );
2427
2428 if ( myResult != SQLITE_OK )
2429 {
2430 QgsDebugError( "Can't open database: " + database.errorMessage() );
2431 // XXX This will likely never happen since on open, sqlite creates the
2432 // database if it does not exist.
2433 // ... unfortunately it happens on Windows
2434 QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" ).arg( path ).arg( myResult ).arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2435 }
2436 return myResult;
2437}
2438
2440{
2441 sCustomSrsValidation = f;
2442}
2443
2445{
2446 return sCustomSrsValidation;
2447}
2448
2449void QgsCoordinateReferenceSystem::debugPrint()
2450{
2451 QgsDebugMsgLevel( u"***SpatialRefSystem***"_s, 1 );
2452 QgsDebugMsgLevel( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ), 1 );
2453 QgsDebugMsgLevel( "* SrsId : " + QString::number( d->mSrsId ), 1 );
2454 QgsDebugMsgLevel( "* Proj4 : " + toProj(), 1 );
2456 QgsDebugMsgLevel( "* Desc. : " + d->mDescription, 1 );
2458 {
2459 QgsDebugMsgLevel( u"* Units : meters"_s, 1 );
2460 }
2461 else if ( mapUnits() == Qgis::DistanceUnit::Feet )
2462 {
2463 QgsDebugMsgLevel( u"* Units : feet"_s, 1 );
2464 }
2465 else if ( mapUnits() == Qgis::DistanceUnit::Degrees )
2466 {
2467 QgsDebugMsgLevel( u"* Units : degrees"_s, 1 );
2468 }
2469}
2470
2472{
2473 mValidationHint = html;
2474}
2475
2477{
2478 return mValidationHint;
2479}
2480
2485
2487{
2488 mNativeFormat = format;
2489}
2490
2492{
2493 return mNativeFormat;
2494}
2495
2496long QgsCoordinateReferenceSystem::getRecordCount()
2497{
2500 int myResult;
2501 long myRecordCount = 0;
2502 //check the db is available
2503 myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2504 if ( myResult != SQLITE_OK )
2505 {
2506 QgsDebugError( u"Can't open database: %1"_s.arg( database.errorMessage() ) );
2507 return 0;
2508 }
2509 // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2510 QString mySql = u"select count(*) from tbl_srs"_s;
2511 statement = database.prepare( mySql, myResult );
2512 if ( myResult == SQLITE_OK )
2513 {
2514 if ( statement.step() == SQLITE_ROW )
2515 {
2516 QString myRecordCountString = statement.columnAsText( 0 );
2517 myRecordCount = myRecordCountString.toLong();
2518 }
2519 }
2520 return myRecordCount;
2521}
2522
2524{
2525 PJ_CONTEXT *pjContext = QgsProjContext::get();
2526 bool isGeographic = false;
2527
2528 // check horizontal CRS units
2530 if ( !horizontalCrs )
2531 return false;
2532
2533 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, horizontalCrs.get() ) );
2534 if ( coordinateSystem )
2535 {
2536 const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2537 if ( axisCount > 0 )
2538 {
2539 const char *outUnitAuthName = nullptr;
2540 const char *outUnitAuthCode = nullptr;
2541 // Read only first axis
2542 proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0, nullptr, nullptr, nullptr, nullptr, nullptr, &outUnitAuthName, &outUnitAuthCode );
2543
2544 if ( outUnitAuthName && outUnitAuthCode )
2545 {
2546 const char *unitCategory = nullptr;
2547 if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2548 {
2549 isGeographic = QString( unitCategory ).compare( "angular"_L1, Qt::CaseInsensitive ) == 0;
2550 }
2551 }
2552 }
2553 }
2554 return isGeographic;
2555}
2556
2557void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2558{
2559 thread_local const QRegularExpression projRegExp( u"\\+proj=(\\S+)"_s );
2560 const QRegularExpressionMatch projMatch = projRegExp.match( proj );
2561 if ( !projMatch.hasMatch() )
2562 {
2563 QgsDebugMsgLevel( u"no +proj argument found [%2]"_s.arg( proj ), 2 );
2564 return;
2565 }
2566 operation = projMatch.captured( 1 );
2567
2568 const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
2569 if ( ellipseMatch.hasMatch() )
2570 {
2571 ellipsoid = ellipseMatch.captured( 1 );
2572 }
2573 else
2574 {
2575 // satisfy not null constraint on ellipsoid_acronym field
2576 // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2577 // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2578 // set for these CRSes). Better just hack around and make the constraint happy for now,
2579 // and hope that the definitions get corrected in future.
2580 ellipsoid = "";
2581 }
2582}
2583
2584
2585bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2586{
2587 if ( !QgsApplication::coordinateReferenceSystemRegistry()->authorities().contains( auth.toLower() ) )
2588 return false;
2589
2590 d.detach();
2591 d->mIsValid = false;
2592 d->mWktPreferred.clear();
2593
2594 PJ_CONTEXT *pjContext = QgsProjContext::get();
2595 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2596 if ( !crs )
2597 {
2598 return false;
2599 }
2600
2601 crs = QgsProjUtils::unboundCrs( crs.get() );
2602
2603 QString proj4 = getFullProjString( crs.get() );
2604 proj4.replace( "+type=crs"_L1, QString() );
2605 proj4 = proj4.trimmed();
2606
2607 d->mIsValid = true;
2608 d->mProj4 = proj4;
2609 d->mWktPreferred.clear();
2610 d->mDescription = QString( proj_get_name( crs.get() ) );
2611 d->mAuthId = u"%1:%2"_s.arg( auth, code );
2612 d->mIsGeographic = testIsGeographic( crs.get() );
2613 d->mAxisInvertedDirty = true;
2614 QString operation;
2615 QString ellipsoid;
2617 d->mProjectionAcronym = operation;
2618 d->mEllipsoidAcronym.clear();
2619 d->setPj( std::move( crs ) );
2620
2621 const QString dbVals = authIdToQgisSrsIdMap().value( u"%1:%2"_s.arg( auth, code ).toUpper() );
2622 if ( !dbVals.isEmpty() )
2623 {
2624 const QStringList parts = dbVals.split( ',' );
2625 d->mSrsId = parts.at( 0 ).toInt();
2626 d->mSRID = parts.at( 1 ).toInt();
2627 }
2628
2629 setMapUnits();
2630 setExtraProperties();
2631
2632 return true;
2633}
2634
2635QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2636{
2637 QList<long> results;
2638 // check user defined projection database
2639 const QString db = QgsApplication::qgisUserDatabaseFilePath();
2640
2641 QFileInfo myInfo( db );
2642 if ( !myInfo.exists() )
2643 {
2644 QgsDebugError( "failed : " + db + " does not exist!" );
2645 return results;
2646 }
2647
2648 sqlite3_database_unique_ptr database;
2649 sqlite3_statement_unique_ptr statement;
2650
2651 //check the db is available
2652 int result = openDatabase( db, database );
2653 if ( result != SQLITE_OK )
2654 {
2655 QgsDebugError( "failed : " + db + " could not be opened!" );
2656 return results;
2657 }
2658
2659 QString sql = u"select srs_id from tbl_srs where srs_id >= %1"_s.arg( Qgis::USER_CRS_START_ID );
2660 int rc;
2661 statement = database.prepare( sql, rc );
2662 while ( true )
2663 {
2664 int ret = statement.step();
2665
2666 if ( ret == SQLITE_DONE )
2667 {
2668 // there are no more rows to fetch - we can stop looping
2669 break;
2670 }
2671
2672 if ( ret == SQLITE_ROW )
2673 {
2674 results.append( statement.columnAsInt64( 0 ) );
2675 }
2676 else
2677 {
2678 QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2679 break;
2680 }
2681 }
2682
2683 return results;
2684}
2685
2686long QgsCoordinateReferenceSystem::matchToUserCrs() const
2687{
2688 PJ *obj = d->threadLocalProjObject();
2689 if ( !obj )
2690 return 0;
2691
2692 const QList< long > ids = userSrsIds();
2693 for ( long id : ids )
2694 {
2696 if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2697 {
2698 return id;
2699 }
2700 }
2701 return 0;
2702}
2703
2704static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2705{
2706#ifndef QGISDEBUG
2707 Q_UNUSED( message )
2708#endif
2709 if ( level == PJ_LOG_ERROR )
2710 {
2711 QgsDebugMsgLevel( u"PROJ: %1"_s.arg( message ), 2 );
2712 }
2713 else if ( level == PJ_LOG_DEBUG )
2714 {
2715 QgsDebugMsgLevel( u"PROJ: %1"_s.arg( message ), 3 );
2716 }
2717}
2718
2720{
2721 setlocale( LC_ALL, "C" );
2722 QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2723
2724 int inserted = 0, updated = 0, deleted = 0, errors = 0;
2725
2726 QgsDebugMsgLevel( u"Load srs db from: %1"_s.arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2727
2729 if ( database.open( dbFilePath ) != SQLITE_OK )
2730 {
2731 QgsDebugError( u"Could not open database: %1 (%2)\n"_s.arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2732 return -1;
2733 }
2734
2735 if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2736 {
2737 QgsDebugError( u"Could not begin transaction: %1 (%2)\n"_s.arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2738 return -1;
2739 }
2740
2742 int result;
2743 char *errMsg = nullptr;
2744
2745 bool createdTypeColumn = false;
2746 if ( sqlite3_exec( database.get(), "ALTER TABLE tbl_srs ADD COLUMN srs_type text", nullptr, nullptr, nullptr ) == SQLITE_OK )
2747 {
2748 createdTypeColumn = true;
2749 if ( sqlite3_exec( database.get(), "CREATE INDEX srs_type ON tbl_srs(srs_type)", nullptr, nullptr, nullptr ) != SQLITE_OK )
2750 {
2751 QgsDebugError( u"Could not create index for srs_type"_s );
2752 return -1;
2753 }
2754 }
2755
2756 if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2757 {
2758 QString sql = u"INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)"_s
2759 .arg( QString::number( PROJ_VERSION_MAJOR ), QString::number( PROJ_VERSION_MINOR ), QString::number( PROJ_VERSION_PATCH ) );
2760 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2761 {
2762 QgsDebugError( u"Could not execute: %1 [%2/%3]\n"_s.arg( sql, database.errorMessage(), errMsg ? errMsg : "(unknown error)" ) );
2763 if ( errMsg )
2764 sqlite3_free( errMsg );
2765 return -1;
2766 }
2767 }
2768 else
2769 {
2770 // retrieve last update details
2771 QString sql = u"SELECT proj_major, proj_minor, proj_patch FROM tbl_info"_s;
2772 statement = database.prepare( sql, result );
2773 if ( result != SQLITE_OK )
2774 {
2775 QgsDebugError( u"Could not prepare: %1 [%2]\n"_s.arg( sql, database.errorMessage() ) );
2776 return -1;
2777 }
2778 if ( statement.step() == SQLITE_ROW )
2779 {
2780 int major = statement.columnAsInt64( 0 );
2781 int minor = statement.columnAsInt64( 1 );
2782 int patch = statement.columnAsInt64( 2 );
2783 if ( !createdTypeColumn && major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2784 // yay, nothing to do!
2785 return 0;
2786 }
2787 else
2788 {
2789 QgsDebugError( u"Could not retrieve previous CRS sync PROJ version number"_s );
2790 return -1;
2791 }
2792 }
2793
2794 PJ_CONTEXT *pjContext = QgsProjContext::get();
2795 // silence proj warnings
2796 proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2797
2798 PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2799
2800 int nextSrsId = 67218;
2801 int nextSrId = 520007218;
2802 for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2803 {
2804 const QString authority( *authIter );
2805 QgsDebugMsgLevel( u"Loading authority '%1'"_s.arg( authority ), 2 );
2806 PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2807
2808 QStringList allCodes;
2809
2810 for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2811 {
2812 const QString code( *codesIter );
2813 allCodes << QgsSqliteUtils::quotedString( code );
2814 QgsDebugMsgLevel( u"Loading code '%1'"_s.arg( code ), 4 );
2815 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2816 if ( !crs )
2817 {
2818 QgsDebugError( u"Could not load '%1:%2'"_s.arg( authority, code ) );
2819 continue;
2820 }
2821
2822 const PJ_TYPE pjType = proj_get_type( crs.get() );
2823
2824 QString srsTypeString;
2825 // NOLINTBEGIN(bugprone-branch-clone)
2826 switch ( pjType )
2827 {
2828 // don't need these in the CRS db
2829 case PJ_TYPE_ELLIPSOID:
2830 case PJ_TYPE_PRIME_MERIDIAN:
2831 case PJ_TYPE_GEODETIC_REFERENCE_FRAME:
2832 case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME:
2833 case PJ_TYPE_VERTICAL_REFERENCE_FRAME:
2834 case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME:
2835 case PJ_TYPE_DATUM_ENSEMBLE:
2836 case PJ_TYPE_CONVERSION:
2837 case PJ_TYPE_TRANSFORMATION:
2838 case PJ_TYPE_CONCATENATED_OPERATION:
2839 case PJ_TYPE_OTHER_COORDINATE_OPERATION:
2840 case PJ_TYPE_TEMPORAL_DATUM:
2841 case PJ_TYPE_ENGINEERING_DATUM:
2842 case PJ_TYPE_PARAMETRIC_DATUM:
2843 case PJ_TYPE_UNKNOWN:
2844 continue;
2845
2846 case PJ_TYPE_CRS:
2847 case PJ_TYPE_GEOGRAPHIC_CRS:
2848 continue; // not possible
2849
2850 case PJ_TYPE_GEODETIC_CRS:
2851 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Geodetic );
2852 break;
2853
2854 case PJ_TYPE_GEOCENTRIC_CRS:
2856 break;
2857
2858 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
2860 break;
2861
2862 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
2864 break;
2865
2866 case PJ_TYPE_PROJECTED_CRS:
2868 break;
2869
2870 case PJ_TYPE_COMPOUND_CRS:
2871 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Compound );
2872 break;
2873
2874 case PJ_TYPE_TEMPORAL_CRS:
2875 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Temporal );
2876 break;
2877
2878 case PJ_TYPE_ENGINEERING_CRS:
2880 break;
2881
2882 case PJ_TYPE_BOUND_CRS:
2883 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Bound );
2884 break;
2885
2886 case PJ_TYPE_VERTICAL_CRS:
2887 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Vertical );
2888 break;
2889
2890#if PROJ_VERSION_MAJOR > 9 || ( PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 2 )
2891 case PJ_TYPE_DERIVED_PROJECTED_CRS:
2893 break;
2894 case PJ_TYPE_COORDINATE_METADATA:
2895 continue;
2896#endif
2897 case PJ_TYPE_OTHER_CRS:
2898 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Other );
2899 break;
2900 }
2901 // NOLINTEND(bugprone-branch-clone)
2902
2903 crs = QgsProjUtils::unboundCrs( crs.get() );
2904
2905 QString proj4 = getFullProjString( crs.get() );
2906 proj4.replace( "+type=crs"_L1, QString() );
2907 proj4 = proj4.trimmed();
2908
2909 if ( proj4.isEmpty() )
2910 {
2911 QgsDebugMsgLevel( u"No proj4 for '%1:%2'"_s.arg( authority, code ), 2 );
2912 // satisfy not null constraint
2913 proj4 = "";
2914 }
2915
2916 // there's a not-null constraint on these columns, so we must use empty strings instead
2917 QString operation = "";
2918 QString ellps = "";
2920
2921 const QString translatedOperation = QgsCoordinateReferenceSystemUtils::translateProjection( operation );
2922 if ( translatedOperation.isEmpty() && !operation.isEmpty() )
2923 {
2924 std::cout << u"Operation needs translation in QgsCoordinateReferenceSystemUtils::translateProjection: %1"_s.arg( operation ).toLocal8Bit().constData() << std::endl;
2925 qFatal( "aborted" );
2926 }
2927
2928 const bool deprecated = proj_is_deprecated( crs.get() );
2929 const QString name( proj_get_name( crs.get() ) );
2930
2931 QString sql = u"SELECT parameters,description,deprecated,srs_type,projection_acronym FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'"_s.arg( authority, code );
2932 statement = database.prepare( sql, result );
2933 if ( result != SQLITE_OK )
2934 {
2935 QgsDebugError( u"Could not prepare: %1 [%2]\n"_s.arg( sql, database.errorMessage() ) );
2936 continue;
2937 }
2938
2939 QString dbSrsProj4;
2940 QString dbSrsDesc;
2941 QString dbSrsType;
2942 QString dbOperation;
2943 bool dbSrsDeprecated = deprecated;
2944 if ( statement.step() == SQLITE_ROW )
2945 {
2946 dbSrsProj4 = statement.columnAsText( 0 );
2947 dbSrsDesc = statement.columnAsText( 1 );
2948 dbSrsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2949 dbSrsType = statement.columnAsText( 3 );
2950 dbOperation = statement.columnAsText( 4 );
2951 }
2952
2953 if ( !dbSrsProj4.isEmpty() || !dbSrsDesc.isEmpty() )
2954 {
2955 if ( proj4 != dbSrsProj4 || name != dbSrsDesc || deprecated != dbSrsDeprecated || dbSrsType != srsTypeString || dbOperation != operation )
2956 {
2957 errMsg = nullptr;
2958 sql = u"UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3, srs_type=%4,projection_acronym=%5 WHERE auth_name=%6 AND auth_id=%7"_s.arg( QgsSqliteUtils::quotedString( proj4 ) )
2959 .arg( QgsSqliteUtils::quotedString( name ) )
2960 .arg( deprecated ? 1 : 0 )
2962
2963 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2964 {
2965 QgsDebugError( u"Could not execute: %1 [%2/%3]\n"_s.arg( sql, database.errorMessage(), errMsg ? errMsg : "(unknown error)" ) );
2966 if ( errMsg )
2967 sqlite3_free( errMsg );
2968 errors++;
2969 }
2970 else
2971 {
2972 updated++;
2973 }
2974 }
2975 }
2976 else
2977 {
2978 const bool isGeographic = testIsGeographic( crs.get() );
2979
2980 // work out srid and srsid
2981 const QString dbVals = QgsCoordinateReferenceSystem::authIdToQgisSrsIdMap().value( u"%1:%2"_s.arg( authority, code ) );
2982 QString srsId;
2983 QString srId;
2984 if ( !dbVals.isEmpty() )
2985 {
2986 const QStringList parts = dbVals.split( ',' );
2987 srsId = parts.at( 0 );
2988 srId = parts.at( 1 );
2989 }
2990 if ( srId.isEmpty() )
2991 {
2992 srId = QString::number( nextSrId );
2993 nextSrId++;
2994 }
2995 if ( srsId.isEmpty() )
2996 {
2997 srsId = QString::number( nextSrsId );
2998 nextSrsId++;
2999 }
3000
3001 if ( !srsId.isEmpty() )
3002 {
3003 sql = u"INSERT INTO tbl_srs(srs_id, description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated,srs_type) VALUES (%1, %2,%3,%4,%5,%6,%7,%8,%9,%10,%11)"_s
3004 .arg( srsId )
3006 .arg( srId )
3007 .arg( QgsSqliteUtils::quotedString( authority ) )
3008 .arg( QgsSqliteUtils::quotedString( code ) )
3009 .arg( isGeographic ? 1 : 0 )
3010 .arg( deprecated ? 1 : 0 )
3011 .arg( QgsSqliteUtils::quotedString( srsTypeString ) );
3012 }
3013 else
3014 {
3015 sql = u"INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated,srs_type) VALUES (%1,%2,%3,%4,%5,%6,%7,%8,%9,%10)"_s
3017 .arg( srId )
3018 .arg( QgsSqliteUtils::quotedString( authority ) )
3019 .arg( QgsSqliteUtils::quotedString( code ) )
3020 .arg( isGeographic ? 1 : 0 )
3021 .arg( deprecated ? 1 : 0 )
3022 .arg( QgsSqliteUtils::quotedString( srsTypeString ) );
3023 }
3024
3025 errMsg = nullptr;
3026 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
3027 {
3028 inserted++;
3029 }
3030 else
3031 {
3032 qCritical( "Could not execute: %s [%s/%s]\n", sql.toLocal8Bit().constData(), sqlite3_errmsg( database.get() ), errMsg ? errMsg : "(unknown error)" );
3033 errors++;
3034
3035 if ( errMsg )
3036 sqlite3_free( errMsg );
3037 }
3038 }
3039 }
3040
3041 proj_string_list_destroy( codes );
3042
3043 const QString sql = u"DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)"_s.arg( authority, allCodes.join( ',' ) );
3044 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
3045 {
3046 deleted = sqlite3_changes( database.get() );
3047 }
3048 else
3049 {
3050 errors++;
3051 qCritical( "Could not execute: %s [%s]\n", sql.toLocal8Bit().constData(), sqlite3_errmsg( database.get() ) );
3052 }
3053 }
3054 proj_string_list_destroy( authorities );
3055
3056 QString sql = u"UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3"_s.arg( QString::number( PROJ_VERSION_MAJOR ), QString::number( PROJ_VERSION_MINOR ), QString::number( PROJ_VERSION_PATCH ) );
3057 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
3058 {
3059 QgsDebugError( u"Could not execute: %1 [%2/%3]\n"_s.arg( sql, database.errorMessage(), errMsg ? errMsg : "(unknown error)" ) );
3060 if ( errMsg )
3061 sqlite3_free( errMsg );
3062 return -1;
3063 }
3064
3065 if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
3066 {
3067 QgsDebugError( u"Could not commit transaction: %1 [%2]\n"_s.arg( QgsApplication::srsDatabaseFilePath(), sqlite3_errmsg( database.get() ) ) );
3068 return -1;
3069 }
3070
3071#ifdef QGISDEBUG
3072 QgsDebugMsgLevel( u"CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)"_s.arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
3073#else
3074 Q_UNUSED( deleted )
3075#endif
3076
3077 if ( errors > 0 )
3078 return -errors;
3079 else
3080 return updated + inserted;
3081}
3082
3083const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
3084{
3085 return *sStringCache();
3086}
3087
3088const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
3089{
3090 return *sProj4Cache();
3091}
3092
3093const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
3094{
3095 return *sOgcCache();
3096}
3097
3098const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
3099{
3100 return *sWktCache();
3101}
3102
3103const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
3104{
3105 return *sSrIdCache();
3106}
3107
3108const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
3109{
3110 return *sSrsIdCache();
3111}
3112
3114{
3115 if ( isGeographic() )
3116 {
3117 return *this;
3118 }
3119
3120 if ( PJ *obj = d->threadLocalProjObject() )
3121 {
3122 PJ_CONTEXT *pjContext = QgsProjContext::get();
3123 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( pjContext, obj ) );
3124 if ( !geoCrs )
3126
3127 const PJ_TYPE pjType = proj_get_type( geoCrs.get() );
3128 if ( pjType == PJ_TYPE_GEOCENTRIC_CRS )
3129 {
3130 // special case: while a geocentric crs IS a geodetic CRS, this particular QGIS method advertises
3131 // that it will always return a geographic latitude/longitude based CRS. So we build a geographic
3132 // CRS using the same datum as the original CRS
3133 QgsProjUtils::proj_pj_unique_ptr cs( proj_create_ellipsoidal_2D_cs( pjContext, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Degree", 1.0 ) );
3134 QgsProjUtils::proj_pj_unique_ptr datum( proj_crs_get_datum( pjContext, geoCrs.get() ) );
3135 QgsProjUtils::proj_pj_unique_ptr datumEnsemble( proj_crs_get_datum_ensemble( pjContext, geoCrs.get() ) );
3136 QgsProjUtils::proj_pj_unique_ptr geoGraphicCrs( proj_create_geographic_crs_from_datum( pjContext, nullptr, datum ? datum.get() : datumEnsemble.get(), cs.get() ) );
3137 if ( !geoGraphicCrs )
3139 return QgsCoordinateReferenceSystem::fromProjObject( geoGraphicCrs.get() );
3140 }
3141
3142 if ( !testIsGeographic( geoCrs.get() ) )
3144
3145 QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, geoCrs.get() ) );
3146 if ( !normalized )
3148
3149 return QgsCoordinateReferenceSystem::fromProjObject( normalized.get() );
3150 }
3151 else
3152 {
3154 }
3155}
3156
3158{
3160 {
3161 return *this;
3162 }
3163
3164 if ( PJ *obj = d->threadLocalProjObject() )
3165 {
3166 PJ_CONTEXT *pjContext = QgsProjContext::get();
3167
3168 // we need the horizontal, unbound crs in order to extract the datum:
3170 if ( !horizontalCrs )
3171 {
3173 }
3174
3175 QgsProjUtils::proj_pj_unique_ptr datum( proj_crs_get_datum( pjContext, horizontalCrs.get() ) );
3176 QgsProjUtils::proj_pj_unique_ptr datumEnsemble( proj_crs_get_datum_ensemble( pjContext, horizontalCrs.get() ) );
3177 if ( !datum && !datumEnsemble )
3179
3180 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_geocentric_crs_from_datum(
3181 pjContext,
3182 /*crs_name*/ nullptr,
3183 /*datum_or_datum_ensemble*/ datumEnsemble ? datumEnsemble.get() : datum.get(),
3184 /*linear_units*/ nullptr, // "NULL for meter"
3185 /*linear_units_conv*/ 0 // "0 for meter if linear_units == NULL"
3186 ) );
3187 if ( crs )
3189 }
3190
3192}
3193
3195{
3196 switch ( type() )
3197 {
3209 return *this;
3210
3213
3215 break;
3216 }
3217
3218 if ( PJ *obj = d->threadLocalProjObject() )
3219 {
3221 if ( hozCrs )
3222 return QgsCoordinateReferenceSystem::fromProjObject( hozCrs.get() );
3223 }
3225}
3226
3228{
3229 switch ( type() )
3230 {
3243
3245 return *this;
3246
3248 break;
3249 }
3250
3251 if ( PJ *obj = d->threadLocalProjObject() )
3252 {
3254 if ( vertCrs )
3255 return QgsCoordinateReferenceSystem::fromProjObject( vertCrs.get() );
3256 }
3258}
3259
3261{
3262 if ( PJ *obj = d->threadLocalProjObject() )
3263 {
3264 return QgsProjUtils::hasVerticalAxis( obj );
3265 }
3266 return false;
3267}
3268
3270{
3271 if ( isGeographic() )
3272 {
3273 return d->mAuthId;
3274 }
3275 else if ( PJ *obj = d->threadLocalProjObject() )
3276 {
3277 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
3278 return geoCrs ? u"%1:%2"_s.arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
3279 }
3280 else
3281 {
3282 return QString();
3283 }
3284}
3285
3286bool QgsCoordinateReferenceSystem::topocentricOrigin( double &latDeg, double &lonDeg ) const
3287{
3288 if ( !isValid() )
3289 return false;
3290
3292 const PJ *pj = projObject();
3293 if ( !pj )
3294 return false;
3295
3296 if ( !proj_crs_is_derived( ctx, pj ) )
3297 return false;
3298
3299 QgsProjUtils::proj_pj_unique_ptr conversion( proj_crs_get_coordoperation( ctx, pj ) );
3300 if ( !conversion )
3301 return false;
3302
3303 const int paramCount = proj_coordoperation_get_param_count( ctx, conversion.get() );
3304 bool hasLat = false, hasLon = false;
3305
3306 for ( int i = 0; i < paramCount; i++ )
3307 {
3308 const char *paramName = nullptr, *authName = nullptr, *code = nullptr, *valueString = nullptr, *unitName = nullptr, *unitAuthName = nullptr, *unitCode = nullptr, *unitCategory = nullptr;
3309 double value = 0.0;
3310 double unitConvFactor = 1.0;
3311 if ( !proj_coordoperation_get_param( ctx, conversion.get(), i, &paramName, &authName, &code, &value, &valueString, &unitConvFactor, &unitName, &unitAuthName, &unitCode, &unitCategory ) )
3312 continue;
3313
3314 const int paramCode = QString( code ).toInt();
3315
3316 if ( paramCode == EPSG_CODE_PARAMETER_LATITUDE_TOPOGRAPHIC_ORIGIN )
3317 {
3318 latDeg = value * unitConvFactor * 180.0 / M_PI;
3319 hasLat = true;
3320 }
3321 else if ( paramCode == EPSG_CODE_PARAMETER_LONGITUDE_TOPOGRAPHIC_ORIGIN )
3322 {
3323 lonDeg = value * unitConvFactor * 180.0 / M_PI;
3324 hasLon = true;
3325 }
3326 }
3327
3328 return hasLat && hasLon;
3329}
3330
3331
3333{
3334 if ( !isValid() )
3336
3337
3339 const PJ *pj = projObject();
3340 if ( !pj )
3342
3344 double lat = 0.0, lon = 0.0;
3345 const bool isTopocentric = topocentricOrigin( lat, lon );
3346 if ( isTopocentric )
3347 {
3348 baseCrs.reset( proj_get_source_crs( ctx, pj ) );
3349 if ( baseCrs )
3350 pj = baseCrs.get();
3351 }
3352
3353 QgsProjUtils::proj_pj_unique_ptr geodeticCrs( proj_crs_get_geodetic_crs( ctx, pj ) );
3354 if ( !geodeticCrs )
3356
3357 if ( proj_get_type( geodeticCrs.get() ) == PJ_TYPE_GEOGRAPHIC_2D_CRS )
3358 {
3359 QgsProjUtils::proj_pj_unique_ptr geodetic3DCrs( proj_crs_promote_to_3D( ctx, nullptr, geodeticCrs.get() ) );
3360 if ( geodetic3DCrs )
3361 geodeticCrs = std::move( geodetic3DCrs );
3362 }
3363
3364 if ( proj_get_type( geodeticCrs.get() ) == PJ_TYPE_GEOCENTRIC_CRS )
3365 {
3366 QgsProjUtils::proj_pj_unique_ptr cs( proj_create_ellipsoidal_2D_cs( ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Degree", 0 ) );
3367 QgsProjUtils::proj_pj_unique_ptr datum( proj_crs_get_datum( ctx, geodeticCrs.get() ) );
3368 QgsProjUtils::proj_pj_unique_ptr datumEnsemble( proj_crs_get_datum_ensemble( ctx, geodeticCrs.get() ) );
3369 if ( !datum && !datumEnsemble )
3371 geodeticCrs.reset( proj_create_geographic_crs_from_datum( ctx, nullptr, datum ? datum.get() : datumEnsemble.get(), cs.get() ) );
3372 if ( !geodeticCrs )
3374 }
3375
3376 const char *projWkt = proj_as_wkt( ctx, geodeticCrs.get(), PJ_WKT2_2019, nullptr );
3377 if ( !projWkt )
3379
3380 QString baseWkt = QString::fromUtf8( projWkt );
3381 if ( baseWkt.startsWith( "GEOGCRS["_L1 ) )
3382 baseWkt.replace( 0, 8, "BASEGEOGCRS["_L1 );
3383 else
3385
3386 const QString wkt = u"GEODCRS[\"Topocentric\","
3387 "%1,"
3388 "DERIVINGCONVERSION[\"Geographic/topocentric\","
3389 " METHOD[\"Geographic/topocentric conversions\",ID[\"EPSG\",9837]],"
3390 " PARAMETER[\"Latitude of topocentric origin\",%2,"
3391 " ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8834]],"
3392 " PARAMETER[\"Longitude of topocentric origin\",%3,"
3393 " ANGLEUNIT[\"degree\",0.0174532925199433],ID[\"EPSG\",8835]],"
3394 " PARAMETER[\"Ellipsoidal height of topocentric origin\",0,"
3395 " LENGTHUNIT[\"metre\",1],ID[\"EPSG\",8836]]],"
3396 "CS[Cartesian,3],"
3397 "AXIS[\"(X)\",east,ORDER[1],LENGTHUNIT[\"metre\",1]],"
3398 "AXIS[\"(Y)\",north,ORDER[2],LENGTHUNIT[\"metre\",1]],"
3399 "AXIS[\"(Z)\",up,ORDER[3],LENGTHUNIT[\"metre\",1]]]"_s.arg( baseWkt )
3400 .arg( qgsDoubleToString( latitude ) )
3401 .arg( qgsDoubleToString( longitude ) );
3402
3403 QgsProjUtils::proj_pj_unique_ptr topocentric( proj_create_from_wkt( ctx, wkt.toUtf8().constData(), nullptr, nullptr, nullptr ) );
3404 if ( !topocentric )
3406
3408 if ( isTopocentric )
3409 crs.d->mTopocentricBaseCrs = std::make_unique<QgsCoordinateReferenceSystem>( *d->mTopocentricBaseCrs );
3410 else
3411 crs.d->mTopocentricBaseCrs = std::make_unique<QgsCoordinateReferenceSystem>( *this );
3412
3413 return crs;
3414}
3415
3417{
3418 return d->mTopocentricBaseCrs ? *d->mTopocentricBaseCrs : QgsCoordinateReferenceSystem();
3419}
3420
3422{
3423 return d->threadLocalProjObject();
3424}
3425
3432
3434{
3435 d.detach();
3436 d->mIsValid = false;
3437 d->mProj4.clear();
3438 d->mWktPreferred.clear();
3439
3440 if ( !object )
3441 {
3442 return false;
3443 }
3444
3445 switch ( proj_get_type( object ) )
3446 {
3447 case PJ_TYPE_GEODETIC_CRS:
3448 case PJ_TYPE_GEOCENTRIC_CRS:
3449 case PJ_TYPE_GEOGRAPHIC_CRS:
3450 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
3451 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
3452 case PJ_TYPE_VERTICAL_CRS:
3453 case PJ_TYPE_PROJECTED_CRS:
3454 case PJ_TYPE_COMPOUND_CRS:
3455 case PJ_TYPE_TEMPORAL_CRS:
3456 case PJ_TYPE_ENGINEERING_CRS:
3457 case PJ_TYPE_BOUND_CRS:
3458 case PJ_TYPE_OTHER_CRS:
3459 break;
3460
3461 default:
3462 return false;
3463 }
3464
3465 d->setPj( QgsProjUtils::unboundCrs( object ) );
3466
3467 if ( !d->hasPj() )
3468 {
3469 return d->mIsValid;
3470 }
3471 else
3472 {
3473 // maybe we can directly grab the auth name and code from the crs
3474 const QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
3475 const QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
3476 if ( !authName.isEmpty() && !authCode.isEmpty() && createFromOgcWmsCrs( u"%1:%2"_s.arg( authName, authCode ) ) )
3477 {
3478 return d->mIsValid;
3479 }
3480 else
3481 {
3482 // Still a valid CRS, just not a known one
3483 d->mIsValid = true;
3484 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
3485 setMapUnits();
3486 d->mIsGeographic = testIsGeographic( d->threadLocalProjObject() );
3487 setExtraProperties();
3488 }
3489 }
3490
3491 return d->mIsValid;
3492}
3493
3495{
3496 QStringList projections;
3497 const QList<QgsCoordinateReferenceSystem> res = QgsApplication::coordinateReferenceSystemRegistry()->recentCrs();
3498 projections.reserve( res.size() );
3499 for ( const QgsCoordinateReferenceSystem &crs : res )
3500 {
3501 projections << QString::number( crs.srsid() );
3502 }
3503 return projections;
3504}
3505
3510
3515
3520
3525
3527{
3528 sSrIdCacheLock()->lockForWrite();
3529 if ( !sDisableSrIdCache )
3530 {
3531 if ( disableCache )
3532 sDisableSrIdCache = true;
3533 sSrIdCache()->clear();
3534 }
3535 sSrIdCacheLock()->unlock();
3536
3537 sOgcLock()->lockForWrite();
3538 if ( !sDisableOgcCache )
3539 {
3540 if ( disableCache )
3541 sDisableOgcCache = true;
3542 sOgcCache()->clear();
3543 }
3544 sOgcLock()->unlock();
3545
3546 sProj4CacheLock()->lockForWrite();
3547 if ( !sDisableProjCache )
3548 {
3549 if ( disableCache )
3550 sDisableProjCache = true;
3551 sProj4Cache()->clear();
3552 }
3553 sProj4CacheLock()->unlock();
3554
3555 sCRSWktLock()->lockForWrite();
3556 if ( !sDisableWktCache )
3557 {
3558 if ( disableCache )
3559 sDisableWktCache = true;
3560 sWktCache()->clear();
3561 }
3562 sCRSWktLock()->unlock();
3563
3564 sCRSSrsIdLock()->lockForWrite();
3565 if ( !sDisableSrsIdCache )
3566 {
3567 if ( disableCache )
3568 sDisableSrsIdCache = true;
3569 sSrsIdCache()->clear();
3570 }
3571 sCRSSrsIdLock()->unlock();
3572
3573 sCrsStringLock()->lockForWrite();
3574 if ( !sDisableStringCache )
3575 {
3576 if ( disableCache )
3577 sDisableStringCache = true;
3578 sStringCache()->clear();
3579 }
3580 sCrsStringLock()->unlock();
3581}
3582
3584{
3585 return celestialBodyName() == "Earth"_L1;
3586}
3587
3592
3593// invalid < regular < user
3595{
3596 if ( c1.d == c2.d )
3597 return false;
3598
3599 if ( !c1.d->mIsValid && !c2.d->mIsValid )
3600 return false;
3601
3602 if ( !c1.d->mIsValid && c2.d->mIsValid )
3603 return false;
3604
3605 if ( c1.d->mIsValid && !c2.d->mIsValid )
3606 return true;
3607
3608 const bool c1IsUser = c1.d->mSrsId >= Qgis::USER_CRS_START_ID;
3609 const bool c2IsUser = c2.d->mSrsId >= Qgis::USER_CRS_START_ID;
3610
3611 if ( c1IsUser && !c2IsUser )
3612 return true;
3613
3614 if ( !c1IsUser && c2IsUser )
3615 return false;
3616
3617 if ( !c1IsUser && !c2IsUser && !c1.d->mAuthId.isEmpty() && !c2.d->mAuthId.isEmpty() )
3618 {
3619 if ( c1.d->mAuthId != c2.d->mAuthId )
3620 return c1.d->mAuthId > c2.d->mAuthId;
3621 }
3622
3623 const QString wkt1 = c1.toWkt( Qgis::CrsWktVariant::Preferred );
3624 const QString wkt2 = c2.toWkt( Qgis::CrsWktVariant::Preferred );
3625 if ( wkt1 != wkt2 )
3626 return wkt1 > wkt2;
3627
3628 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3629 return false;
3630
3631 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3632 return false;
3633
3634 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3635 return false;
3636
3637 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3638 return true;
3639
3640 return c1.d->mCoordinateEpoch > c2.d->mCoordinateEpoch;
3641}
3642
3644{
3645 if ( c1.d == c2.d )
3646 return false;
3647
3648 if ( !c1.d->mIsValid && !c2.d->mIsValid )
3649 return false;
3650
3651 if ( c1.d->mIsValid && !c2.d->mIsValid )
3652 return false;
3653
3654 if ( !c1.d->mIsValid && c2.d->mIsValid )
3655 return true;
3656
3657 const bool c1IsUser = c1.d->mSrsId >= Qgis::USER_CRS_START_ID;
3658 const bool c2IsUser = c2.d->mSrsId >= Qgis::USER_CRS_START_ID;
3659
3660 if ( !c1IsUser && c2IsUser )
3661 return true;
3662
3663 if ( c1IsUser && !c2IsUser )
3664 return false;
3665
3666 if ( !c1IsUser && !c2IsUser && !c1.d->mAuthId.isEmpty() && !c2.d->mAuthId.isEmpty() )
3667 {
3668 if ( c1.d->mAuthId != c2.d->mAuthId )
3669 return c1.d->mAuthId < c2.d->mAuthId;
3670 }
3671
3672 const QString wkt1 = c1.toWkt( Qgis::CrsWktVariant::Preferred );
3673 const QString wkt2 = c2.toWkt( Qgis::CrsWktVariant::Preferred );
3674 if ( wkt1 != wkt2 )
3675 return wkt1 < wkt2;
3676
3677 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3678 return false;
3679
3680 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3681 return false;
3682
3683 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3684 return false;
3685
3686 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3687 return true;
3688
3689 return c1.d->mCoordinateEpoch < c2.d->mCoordinateEpoch;
3690}
3691
3693{
3694 return !( c1 < c2 );
3695}
3697{
3698 return !( c1 > c2 );
3699}
3700
3701QMap<QString, QString> loadAuthIdToQgisSrsIdMapFromJson()
3702{
3703 QMap<QString, QString> map;
3704
3705 const QString jsonPath = QgsApplication::pkgDataPath() + u"/resources/data/coordinate_reference_system_legacy.json"_s;
3706
3707 QFile file( jsonPath );
3708 if ( !file.open( QIODevice::ReadOnly ) )
3709 {
3710 QgsDebugError( u"Failed to open the coordinate reference legacy map JSON file: %1"_s.arg( jsonPath ) );
3711 return map;
3712 }
3713
3714 const QByteArray jsonContent = file.readAll();
3715 try
3716 {
3717 const json mapJson = json::parse( jsonContent.toStdString() );
3718
3719 if ( !mapJson.is_object() )
3720 {
3721 QgsDebugError( u"Failed to parse coordinate reference legacy map JSON, expected object."_s );
3722 return map;
3723 }
3724
3725 for ( auto &item : mapJson.items() )
3726 {
3727 if ( item.value().is_string() )
3728 {
3729 QString key = QString::fromStdString( item.key() );
3730 QString value = QString::fromStdString( item.value().get<std::string>() );
3731
3732 map.insert( key, value );
3733 }
3734 }
3735 }
3736
3737 catch ( nlohmann::json::exception &ex )
3738 {
3739 QgsDebugError( u"Failed to parse the coordinate reference legacy map JSON: %1"_s.arg( ex.what() ) );
3740 return map;
3741 }
3742
3743 return map;
3744}
3745
3746const QMap<QString, QString> &QgsCoordinateReferenceSystem::authIdToQgisSrsIdMap()
3747{
3748 static const QMap<QString, QString> sAuthIdToQgisSrsIdMap = loadAuthIdToQgisSrsIdMapFromJson();
3749 return sAuthIdToQgisSrsIdMap;
3750}
CrsIdentifierType
Available identifier string types for representing coordinate reference systems.
Definition qgis.h:2603
@ ShortString
A heavily abbreviated string, for use when a compact representation is required.
Definition qgis.h:2604
@ MediumString
A medium-length string, recommended for general purpose use.
Definition qgis.h:2605
DistanceUnit
Units of distance.
Definition qgis.h:5580
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated).
Definition qgis.h:5620
@ Feet
Imperial feet.
Definition qgis.h:5583
@ MilesUSSurvey
US Survey miles.
Definition qgis.h:5627
@ LinksBritishSears1922
British links (Sears 1922).
Definition qgis.h:5615
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A).
Definition qgis.h:5618
@ LinksBritishBenoit1895A
British links (Benoit 1895 A).
Definition qgis.h:5612
@ Centimeters
Centimeters.
Definition qgis.h:5588
@ YardsIndian1975
Indian yards (1975).
Definition qgis.h:5626
@ FeetUSSurvey
US Survey feet.
Definition qgis.h:5610
@ Millimeters
Millimeters.
Definition qgis.h:5589
@ FeetBritishSears1922
British feet (Sears 1922).
Definition qgis.h:5603
@ YardsClarkes
Clarke's yards.
Definition qgis.h:5622
@ YardsIndian
Indian yards.
Definition qgis.h:5623
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B).
Definition qgis.h:5601
@ Miles
Terrestrial miles.
Definition qgis.h:5586
@ LinksUSSurvey
US Survey links.
Definition qgis.h:5617
@ Meters
Meters.
Definition qgis.h:5581
@ ChainsUSSurvey
US Survey chains.
Definition qgis.h:5597
@ FeetClarkes
Clarke's feet.
Definition qgis.h:5604
@ Unknown
Unknown distance unit.
Definition qgis.h:5630
@ Yards
Imperial yards.
Definition qgis.h:5585
@ FeetBritish1936
British feet (1936).
Definition qgis.h:5599
@ FeetIndian1962
Indian feet (1962).
Definition qgis.h:5608
@ YardsBritishSears1922
British yards (Sears 1922).
Definition qgis.h:5621
@ FeetIndian1937
Indian feet (1937).
Definition qgis.h:5607
@ YardsIndian1937
Indian yards (1937).
Definition qgis.h:5624
@ Degrees
Degrees, for planar geographic CRS distance measurements.
Definition qgis.h:5587
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B).
Definition qgis.h:5593
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated).
Definition qgis.h:5614
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A).
Definition qgis.h:5592
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B).
Definition qgis.h:5619
@ FeetBritish1865
British feet (1865).
Definition qgis.h:5598
@ YardsIndian1962
Indian yards (1962).
Definition qgis.h:5625
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated).
Definition qgis.h:5602
@ MetersGermanLegal
German legal meter.
Definition qgis.h:5629
@ LinksBritishBenoit1895B
British links (Benoit 1895 B).
Definition qgis.h:5613
@ ChainsInternational
International chains.
Definition qgis.h:5591
@ Fathoms
Fathoms.
Definition qgis.h:5628
@ LinksInternational
International links.
Definition qgis.h:5611
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated).
Definition qgis.h:5594
@ FeetIndian
Indian (geodetic) feet.
Definition qgis.h:5606
@ NauticalMiles
Nautical miles.
Definition qgis.h:5584
@ ChainsClarkes
Clarke's chains.
Definition qgis.h:5596
@ LinksClarkes
Clarke's links.
Definition qgis.h:5616
@ ChainsBritishSears1922
British chains (Sears 1922).
Definition qgis.h:5595
@ Kilometers
Kilometers.
Definition qgis.h:5582
@ FeetIndian1975
Indian feet (1975).
Definition qgis.h:5609
@ FeetGoldCoast
Gold Coast feet.
Definition qgis.h:5605
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A).
Definition qgis.h:5600
@ Critical
Critical/error message.
Definition qgis.h:163
CrsType
Coordinate reference system types.
Definition qgis.h:2513
@ Vertical
Vertical CRS.
Definition qgis.h:2519
@ Temporal
Temporal CRS.
Definition qgis.h:2522
@ Compound
Compound (horizontal + vertical) CRS.
Definition qgis.h:2521
@ Projected
Projected CRS.
Definition qgis.h:2520
@ Other
Other type.
Definition qgis.h:2525
@ Bound
Bound CRS.
Definition qgis.h:2524
@ DerivedProjected
Derived projected CRS.
Definition qgis.h:2526
@ Unknown
Unknown type.
Definition qgis.h:2514
@ Engineering
Engineering CRS.
Definition qgis.h:2523
@ Geographic3d
3D geopraphic CRS
Definition qgis.h:2518
@ Geodetic
Geodetic CRS.
Definition qgis.h:2515
@ Geographic2d
2D geographic CRS
Definition qgis.h:2517
@ Geocentric
Geocentric CRS.
Definition qgis.h:2516
CrsDefinitionFormat
CRS definition formats.
Definition qgis.h:4166
@ Wkt
WKT format (always recommended over proj string format).
Definition qgis.h:4167
static const int USER_CRS_START_ID
Minimum ID number for a user-defined projection.
Definition qgis.h:7393
CrsAxisDirection
Coordinate reference system axis directions.
Definition qgis.h:2538
@ Starboard
Starboard.
Definition qgis.h:2563
@ ColumnPositive
Column positive.
Definition qgis.h:2566
@ SouthSouthEast
South South East.
Definition qgis.h:2546
@ NorthWest
North West.
Definition qgis.h:2553
@ ColumnNegative
Column negative.
Definition qgis.h:2567
@ RowPositive
Row positive.
Definition qgis.h:2568
@ DisplayDown
Display down.
Definition qgis.h:2573
@ GeocentricZ
Geocentric (Z).
Definition qgis.h:2557
@ DisplayRight
Display right.
Definition qgis.h:2570
@ WestSouthWest
West South West.
Definition qgis.h:2550
@ RowNegative
Row negative.
Definition qgis.h:2569
@ AwayFrom
Away from.
Definition qgis.h:2577
@ NorthNorthEast
North North East.
Definition qgis.h:2540
@ Forward
Forward.
Definition qgis.h:2560
@ EastNorthEast
East North East.
Definition qgis.h:2542
@ Unspecified
Unspecified.
Definition qgis.h:2578
@ NorthEast
North East.
Definition qgis.h:2541
@ NorthNorthWest
North North West.
Definition qgis.h:2554
@ GeocentricY
Geocentric (Y).
Definition qgis.h:2556
@ Towards
Towards.
Definition qgis.h:2576
@ SouthEast
South East.
Definition qgis.h:2545
@ CounterClockwise
Counter clockwise.
Definition qgis.h:2565
@ SouthSouthWest
South South West.
Definition qgis.h:2548
@ DisplayLeft
Display left.
Definition qgis.h:2571
@ WestNorthWest
West North West.
Definition qgis.h:2552
@ EastSouthEast
East South East.
Definition qgis.h:2544
@ Clockwise
Clockwise.
Definition qgis.h:2564
@ SouthWest
South West.
Definition qgis.h:2549
@ DisplayUp
Display up.
Definition qgis.h:2572
@ GeocentricX
Geocentric (X).
Definition qgis.h:2555
CrsWktVariant
Coordinate reference system WKT formatting variants.
Definition qgis.h:2618
@ Wkt2_2019Simplified
WKT2_2019 with the simplification rule of WKT2_SIMPLIFIED.
Definition qgis.h:2630
@ Wkt2_2015Simplified
Same as WKT2_2015 with the following exceptions: UNIT keyword used. ID node only on top element....
Definition qgis.h:2624
@ Wkt1Esri
WKT1 as traditionally output by ESRI software, deriving from OGC 99-049.
Definition qgis.h:2622
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2631
@ Wkt2_2019
Full WKT2 string, conforming to ISO 19162:2019 / OGC 18-010, with all possible nodes and new keyword ...
Definition qgis.h:2627
@ Wkt2_2015
Full WKT2 string, conforming to ISO 19162:2015(E) / OGC 12-063r5 with all possible nodes and new keyw...
Definition qgis.h:2623
@ Wkt1Gdal
WKT1 as traditionally output by GDAL, deriving from OGC 01-009. A notable departure from WKT1_GDAL wi...
Definition qgis.h:2619
static QString pkgDataPath()
Returns the common root path of all application data directories.
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
void removeRecent(const QgsCoordinateReferenceSystem &crs)
Removes a CRS from the list of recently used CRS.
long addUserCrs(const QgsCoordinateReferenceSystem &crs, const QString &name, Qgis::CrsDefinitionFormat nativeFormat=Qgis::CrsDefinitionFormat::Wkt)
Adds a new crs definition as a custom ("USER") CRS.
void clearRecent()
Cleans the list of recently used CRS.
QList< QgsCoordinateReferenceSystem > recentCrs()
Returns a list of recently used CRS.
void pushRecent(const QgsCoordinateReferenceSystem &crs)
Pushes a recently used CRS to the top of the recent CRS list.
QMap< QString, QgsProjOperation > projOperations() const
Returns a map of all valid PROJ operations.
static QString translateProjection(const QString &projection)
Returns a translated string for a projection method.
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool createFromOgcWmsCrs(const QString &crs)
Sets this CRS to the given OGC WMS-format Coordinate Reference Systems.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromWkt(const QString &wkt)
Sets this CRS using a WKT definition.
bool createFromString(const QString &definition)
Set up this CRS from a string definition.
bool hasVerticalAxis() const
Returns true if the CRS has a vertical axis.
void validate()
Perform some validation on this CRS.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
QString toProj() const
Returns a Proj string representation of this CRS.
static CUSTOM_CRS_VALIDATION customCrsValidation()
Gets custom function.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
static QgsCoordinateReferenceSystem createCompoundCrs(const QgsCoordinateReferenceSystem &horizontalCrs, const QgsCoordinateReferenceSystem &verticalCrs, QString &error)
Given a horizontal and vertical CRS, attempts to create a compound CRS from them.
static Q_INVOKABLE QgsCoordinateReferenceSystem fromEpsgId(long epsg)
Creates a CRS from a given EPSG ID.
Q_DECL_DEPRECATED bool createFromProj4(const QString &projString)
Sets this CRS by passing it a PROJ style formatted string.
static Q_DECL_DEPRECATED QStringList recentProjections()
Returns a list of recently used projections.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString toOgcUri() const
Returns the crs as OGC URI (format: http://www.opengis.net/def/crs/OGC/1.3/CRS84) Returns an empty st...
QString toOgcUrn() const
Returns the crs as OGC URN (format: urn:ogc:def:crs:OGC:1.3:CRS84) Returns an empty string on failure...
static void setCustomCrsValidation(CUSTOM_CRS_VALIDATION f)
Sets custom function to force valid CRS.
bool isEarthCrs() const
Returns true if the CRS is associated with the Earth.
static Q_DECL_DEPRECATED void pushRecentCoordinateReferenceSystem(const QgsCoordinateReferenceSystem &crs)
Pushes a recently used CRS to the top of the recent CRS list.
long postgisSrid() const
Returns PostGIS SRID for the CRS.
QgsCoordinateReferenceSystem toTopocentricCrs(double latitude, double longitude) const
Constructs a topocentric CRS derived from this CRS with origin at latitude, longitude in decimal degr...
QgsCoordinateReferenceSystem horizontalCrs() const
Returns the horizontal CRS associated with this CRS object.
Q_DECL_DEPRECATED long findMatchingProj()
Walks the CRS databases (both system and user database) trying to match stored PROJ string to a datab...
static QList< long > validSrsIds()
Returns a list of all valid SRS IDs present in the CRS database.
QgsProjectionFactors factors(const QgsPoint &point) const
Calculate various cartographic properties, such as scale factors, angular distortion and meridian con...
void setValidationHint(const QString &html)
Set user hint for validation.
Q_DECL_DEPRECATED QString toProj4() const
Returns a Proj string representation of this CRS.
bool operator==(const QgsCoordinateReferenceSystem &srs) const
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
QString userFriendlyIdentifier(Qgis::CrsIdentifierType type=Qgis::CrsIdentifierType::MediumString) const
Returns a user friendly identifier for the CRS.
Qgis::CrsDefinitionFormat nativeFormat() const
Returns the native format for the CRS definition.
CrsType
Enumeration of types of IDs accepted in createFromId() method.
@ InternalCrsId
Internal ID used by QGIS in the local SQLite database.
@ PostgisCrsId
SRID used in PostGIS. DEPRECATED – DO NOT USE.
bool createFromUserInput(const QString &definition)
Set up this CRS from various text formats.
QgsCoordinateReferenceSystem()
Constructs an invalid CRS object.
static int syncDatabase()
Update proj.4 parameters in our database from proj.4.
bool operator!=(const QgsCoordinateReferenceSystem &srs) const
void setNativeFormat(Qgis::CrsDefinitionFormat format)
Sets the native format for the CRS definition.
bool createFromProj(const QString &projString, bool identify=true)
Sets this CRS by passing it a PROJ style formatted string.
QgsCoordinateReferenceSystem verticalCrs() const
Returns the vertical CRS associated with this CRS object.
static Q_DECL_DEPRECATED void removeRecentCoordinateReferenceSystem(const QgsCoordinateReferenceSystem &crs)
Removes a CRS from the list of recently used CRS.
QgsDatumEnsemble datumEnsemble() const
Attempts to retrieve datum ensemble details from the CRS.
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
bool isDynamic() const
Returns true if the CRS is a dynamic CRS.
bool createFromSrsId(long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
Q_DECL_DEPRECATED bool createFromId(long id, CrsType type=PostgisCrsId)
Sets this CRS by lookup of the given ID in the CRS database.
static QgsCoordinateReferenceSystem fromProjObject(PJ *object)
Constructs a QgsCoordinateReferenceSystem from a PROJ PJ object.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateReferenceSystem objects.
QgsCoordinateReferenceSystem & operator=(const QgsCoordinateReferenceSystem &srs)
QgsCoordinateReferenceSystem topocentricBaseCrs() const
Returns the base CRS for this topocentric CRS, or an invalid CRS if this is not a topocentric CRS or ...
bool isSameCelestialBody(const QgsCoordinateReferenceSystem &other) const
Returns true if other crs is associated with the same celestial body.
void updateDefinition()
Updates the definition and parameters of the coordinate reference system to their latest values.
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
static Q_DECL_DEPRECATED void setupESRIWktFix()
Make sure that ESRI WKT import is done properly.
static Q_DECL_DEPRECATED QList< QgsCoordinateReferenceSystem > recentCoordinateReferenceSystems()
Returns a list of recently used CRS.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
PJ * projObject() const
Returns the underlying PROJ PJ object corresponding to the CRS, or nullptr if the CRS is invalid.
void setCoordinateEpoch(double epoch)
Sets the coordinate epoch, as a decimal year.
Q_DECL_DEPRECATED bool createFromSrid(long srid)
Sets this CRS by lookup of the given PostGIS SRID in the CRS database.
long saveAsUserCrs(const QString &name, Qgis::CrsDefinitionFormat nativeFormat=Qgis::CrsDefinitionFormat::Wkt)
Saves the CRS as a new custom ("USER") CRS.
std::string toJsonString(bool multiline=false, int indentationWidth=4, const QString &schema=QString()) const
Returns a JSON string representation of this CRS.
static Q_DECL_DEPRECATED void clearRecentCoordinateReferenceSystems()
Cleans the list of recently used CRS.
QString celestialBodyName() const
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
static QgsCoordinateReferenceSystem createGeocentricCrs(const QString &ellipsoid)
Creates a geocentric CRS given an ellipsoid definition.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
double coordinateEpoch() const
Returns the coordinate epoch, as a decimal year.
bool isGeographicCrossingAntimeridian() const
Returns true if the CRS is a geographic CRS (using lat/lon coordinates) where the area of use crosses...
QString geographicCrsAuthId() const
Returns auth id of related geographic CRS.
QString validationHint() const
Gets user hint for validation.
QgsProjOperation operation() const
Returns information about the PROJ operation associated with the coordinate reference system,...
QgsCoordinateReferenceSystem toGeocentricCrs() const
Returns a new geocentric CRS based on this CRS object.
QList< Qgis::CrsAxisDirection > axisOrdering() const
Returns an ordered list of the axis directions reflecting the native axis order for the CRS.
long srsid() const
Returns the internal CRS ID, if available.
Qgis::CrsType type() const
Returns the type of the CRS.
bool topocentricOrigin(double &latitude, double &longitude) const
Returns the topocentric origin of a topocentric compatible CRS.
bool hasAxisInverted() const
Returns whether the axis order is inverted for the CRS compared to the order east/north (longitude/la...
bool createFromProjObject(PJ *object)
Sets this CRS by passing it a PROJ PJ object, corresponding to a PROJ CRS object.
bool isDeprecated() const
Returns true if the CRS is considered deprecated.
static Q_DECL_DEPRECATED QgsCoordinateReferenceSystem fromProj4(const QString &proj4)
Creates a CRS from a proj style formatted string.
Contains information about a member of a datum ensemble.
Definition qgsdatums.h:37
Contains information about a datum ensemble.
Definition qgsdatums.h:100
static EllipsoidParameters ellipsoidParameters(const QString &ellipsoid)
Returns the parameters for the specified ellipsoid.
Sets the current locale to the c locale for the lifetime of the object.
Definition qgslocalec.h:33
static void warning(const QString &msg)
Goes to qWarning.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
Adds a message to the log instance (and creates it if necessary).
CRSFlavor
CRS flavor.
@ UNKNOWN
Unknown/unhandled flavor.
@ AUTH_CODE
E.g EPSG:4326.
static CRSFlavor parseCrsName(const QString &crsName, QString &authority, QString &code)
Parse a CRS name in one of the flavors of OGC services, and decompose it as authority and code.
static QString OGRSpatialReferenceToWkt(OGRSpatialReferenceH srs)
Returns a WKT string corresponding to the specified OGR srs object.
Point geometry type, with support for z-dimension and m-values.
Definition qgspoint.h:53
double x
Definition qgspoint.h:56
double y
Definition qgspoint.h:57
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
Contains information about a PROJ operation.
static proj_pj_unique_ptr crsToHorizontalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), extract the horizontal c...
@ FlagMatchBoundCrsToUnderlyingSourceCrs
Allow matching a BoundCRS object to its underlying SourceCRS.
static proj_pj_unique_ptr createCompoundCrs(const PJ *horizontalCrs, const PJ *verticalCrs, QStringList *errors=nullptr)
Given a PROJ horizontal and vertical CRS, attempt to create a compound CRS from them.
static bool isDynamic(const PJ *crs)
Returns true if the given proj coordinate system is a dynamic CRS.
static proj_pj_unique_ptr unboundCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), ensure that it is not a ...
static bool identifyCrs(const PJ *crs, QString &authName, QString &authCode, IdentifyFlags flags=IdentifyFlags())
Attempts to identify a crs, matching it to a known authority and code within an acceptable level of t...
static bool hasVerticalAxis(const PJ *crs)
Returns true if a PROJ crs has a vertical axis.
static proj_pj_unique_ptr crsToVerticalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound crs, or some other type), extract the vertical crs from it.
static proj_pj_unique_ptr crsToDatumEnsemble(const PJ *crs)
Given a PROJ crs, attempt to retrieve the datum ensemble from it.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
static bool axisOrderIsSwapped(const PJ *crs)
Returns true if the given proj coordinate system uses requires y/x coordinate order instead of x/y.
Contains various cartographic properties, such as scale factors, angular distortion and meridian conv...
A convenience class that simplifies locking and unlocking QReadWriteLocks.
@ Write
Lock for write.
void unlock()
Unlocks the lock.
void changeMode(Mode mode)
Change the mode of the lock to mode.
A rectangle specified with double values.
void setYMinimum(double y)
Set the minimum y value.
void setXMinimum(double x)
Set the minimum x value.
void setYMaximum(double y)
Set the maximum y value.
void setXMaximum(double x)
Set the maximum x value.
static QString quotedString(const QString &value)
Returns a quoted string value, surround by ' characters and with special characters correctly escaped...
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
int open(const QString &path)
Opens the database at the specified file path.
QString errorMessage() const
Returns the most recent error message encountered by the database.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
QString columnName(int column) const
Returns the name of column.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
int columnCount() const
Gets the number of columns that this statement returns.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:8076
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:8419
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:7690
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:8057
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8418
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition qgis.h:7735
QString getFullProjString(PJ *obj)
QMap< QString, QString > loadAuthIdToQgisSrsIdMapFromJson()
bool operator>=(const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2)
bool operator<(const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2)
bool testIsGeographic(PJ *crs)
void getOperationAndEllipsoidFromProjString(const QString &proj, QString &operation, QString &ellipsoid)
QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash
bool operator<=(const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2)
QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash
bool operator>(const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2)
struct pj_ctx PJ_CONTEXT
struct PJconsts PJ
void(* CUSTOM_CRS_VALIDATION)(QgsCoordinateReferenceSystem &)
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:80
#define QgsDebugError(str)
Definition qgslogger.h:71
Contains parameters for an ellipsoid.
double semiMajor
Semi-major axis, in meters.
bool valid
Whether ellipsoid parameters are valid.
double inverseFlattening
Inverse flattening.