QGIS API Documentation 4.1.0-Master (376402f9aeb)
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
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 <proj.h>
22#include <proj_experimental.h>
23#include <sqlite3.h>
24
25#include "qgis.h"
26#include "qgsapplication.h"
31#include "qgsdatums.h"
32#include "qgsellipsoidutils.h"
33#include "qgslocalec.h"
34#include "qgslogger.h"
35#include "qgsmessagelog.h"
36#include "qgsogcutils.h"
37#include "qgsogrutils.h"
39#include "qgsprojoperation.h"
40#include "qgsprojutils.h"
41#include "qgsreadwritelocker.h"
42#include "qgssettings.h"
43
44#include <QDir>
45#include <QDomElement>
46#include <QDomNode>
47#include <QFile>
48#include <QFileInfo>
49#include <QRegularExpression>
50#include <QString>
51#include <QTextStream>
52
53#include "moc_qgscoordinatereferencesystem.cpp"
54
55using namespace Qt::StringLiterals;
56
57//gdal and ogr includes (needed for == operator)
58#include <ogr_srs_api.h>
59#include <cpl_error.h>
60#include <cpl_conv.h>
61#include <cpl_csv.h>
62
63CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
64
65typedef QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash;
66typedef QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash;
67
68Q_GLOBAL_STATIC( QReadWriteLock, sSrIdCacheLock )
70bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
71
72Q_GLOBAL_STATIC( QReadWriteLock, sOgcLock )
74bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
75
76Q_GLOBAL_STATIC( QReadWriteLock, sProj4CacheLock )
78bool QgsCoordinateReferenceSystem::sDisableProjCache = false;
79
80Q_GLOBAL_STATIC( QReadWriteLock, sCRSWktLock )
82bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
83
84Q_GLOBAL_STATIC( QReadWriteLock, sCRSSrsIdLock )
86bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
87
88Q_GLOBAL_STATIC( QReadWriteLock, sCrsStringLock )
90bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
91
92QString getFullProjString( PJ *obj )
93{
94 // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
95 // use proj_as_proj_string
96 QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
97 if ( boundCrs )
98 {
99 if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
100 {
101 return QString( proj4src );
102 }
103 }
104
105 return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
106}
107//--------------------------
108
115
117{
118 d = new QgsCoordinateReferenceSystemPrivate();
119 createFromString( definition );
120}
121
123{
124 d = new QgsCoordinateReferenceSystemPrivate();
126 createFromId( id, type );
128}
129
131 : d( srs.d )
132 , mValidationHint( srs.mValidationHint )
133 , mNativeFormat( srs.mNativeFormat )
134{}
135
137{
138 if ( &srs == this )
139 return *this;
140
141 d = srs.d;
142 mValidationHint = srs.mValidationHint;
143 mNativeFormat = srs.mNativeFormat;
144 return *this;
145}
146
148{
149 QList<long> results;
150 // check both standard & user defined projection databases
152
153 const auto constDbs = dbs;
154 for ( const QString &db : constDbs )
155 {
156 QFileInfo myInfo( db );
157 if ( !myInfo.exists() )
158 {
159 QgsDebugError( "failed : " + db + " does not exist!" );
160 continue;
161 }
162
165
166 //check the db is available
167 int result = openDatabase( db, database );
168 if ( result != SQLITE_OK )
169 {
170 QgsDebugError( "failed : " + db + " could not be opened!" );
171 continue;
172 }
173
174 QString sql = u"select srs_id from tbl_srs"_s;
175 int rc;
176 statement = database.prepare( sql, rc );
177 while ( true )
178 {
179 // this one is an infinitive loop, intended to fetch any row
180 int ret = statement.step();
181
182 if ( ret == SQLITE_DONE )
183 {
184 // there are no more rows to fetch - we can stop looping
185 break;
186 }
187
188 if ( ret == SQLITE_ROW )
189 {
190 results.append( statement.columnAsInt64( 0 ) );
191 }
192 else
193 {
194 QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
195 break;
196 }
197 }
198 }
199 std::sort( results.begin(), results.end() );
200 return results;
201}
202
204{
206 crs.createFromOgcWmsCrs( ogcCrs );
207 return crs;
208}
209
211{
212 QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
213 if ( res.isValid() )
214 return res;
215
216 // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
217 res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
218 if ( res.isValid() )
219 return res;
220
222}
223
225{
226 return fromProj( proj4 );
227}
228
230{
232 crs.createFromProj( proj );
233 return crs;
234}
235
237{
239 crs.createFromWkt( wkt );
240 return crs;
241}
242
249
251{
252 error.clear();
253 PJ *horizontalObj = horizontalCrs.projObject();
254 PJ *verticalObj = verticalCrs.projObject();
255 if ( horizontalObj && verticalObj )
256 {
257 QStringList errors;
258 QgsProjUtils::proj_pj_unique_ptr compoundCrs = QgsProjUtils::createCompoundCrs( horizontalObj, verticalObj, &errors );
259 if ( compoundCrs )
260 return QgsCoordinateReferenceSystem::fromProjObject( compoundCrs.get() );
261
262 QStringList formattedErrorList;
263 for ( const QString &rawError : std::as_const( errors ) )
264 {
265 QString formattedError = rawError;
266 formattedError.replace( "proj_create_compound_crs: "_L1, QString() );
267 formattedErrorList.append( formattedError );
268 }
269 error = formattedErrorList.join( '\n' );
270 }
272}
273
275{
277 if ( !ellipsoidParams.valid )
279
280 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_geocentric_crs(
282 /*crs_name*/ nullptr,
283 /*datum_name*/ nullptr,
284 /*ellps_name*/ nullptr,
285 /*semi_major_metre*/ ellipsoidParams.semiMajor,
286 /*inv_flattening*/ ellipsoidParams.inverseFlattening,
287 /*prime_meridian_name*/ nullptr,
288 /*prime_meridian_offset*/ 0,
289 /*angular_units*/ nullptr, // "NULL for degrees"
290 /*angular_units_conv*/ 0, // "0 for degrees if angular_units == NULL"
291 /*linear_units*/ nullptr, // "NULL for meter"
292 /*linear_units_conv*/ 0 // "0 for Metre if linear_units == NULL"
293 ) );
295}
296
299
301{
302 bool result = false;
303 switch ( type )
304 {
305 case InternalCrsId:
306 result = createFromSrsId( id );
307 break;
308 case PostgisCrsId:
310 result = createFromSrid( id );
312 break;
313 case EpsgCrsId:
314 result = createFromOgcWmsCrs( u"EPSG:%1"_s.arg( id ) );
315 break;
316 default:
317 //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
318 QgsDebugError( u"Unexpected case reached!"_s );
319 };
320 return result;
321}
322
323bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
324{
325 if ( definition.isEmpty() )
326 return false;
327
328 QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Read );
329 if ( !sDisableStringCache )
330 {
331 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache()->constFind( definition );
332 if ( crsIt != sStringCache()->constEnd() )
333 {
334 // found a match in the cache
335 *this = crsIt.value();
336 return d->mIsValid;
337 }
338 }
339 locker.unlock();
340
341 bool result = false;
342 const thread_local QRegularExpression reCrsId( u"^(epsg|esri|osgeo|ignf|ogc|nkg|zangi|iau_2015|iau2000|postgis|internal|user)\\:(\\w+)$"_s, QRegularExpression::CaseInsensitiveOption );
343 QRegularExpressionMatch match = reCrsId.match( definition );
344 if ( match.capturedStart() == 0 )
345 {
346 QString authName = match.captured( 1 ).toLower();
347 if ( authName == "epsg"_L1 )
348 {
349 result = createFromOgcWmsCrs( definition );
350 }
351 else if ( authName == "postgis"_L1 )
352 {
353 const long id = match.captured( 2 ).toLong();
355 result = createFromSrid( id );
357 }
358 else if ( authName == "esri"_L1
359 || authName == "osgeo"_L1
360 || authName == "ignf"_L1
361 || authName == "zangi"_L1
362 || authName == "iau2000"_L1
363 || authName == "ogc"_L1
364 || authName == "nkg"_L1
365 || authName == "iau_2015"_L1 )
366 {
367 result = createFromOgcWmsCrs( definition );
368 }
369 else
370 {
371 const long id = match.captured( 2 ).toLong();
373 result = createFromId( id, InternalCrsId );
375 }
376 }
377 else
378 {
379 const thread_local QRegularExpression reCrsStr( u"^(?:(wkt|proj4|proj)\\:)?(.+)$"_s, QRegularExpression::CaseInsensitiveOption );
380 match = reCrsStr.match( definition );
381 if ( match.capturedStart() == 0 )
382 {
383 if ( match.captured( 1 ).startsWith( "proj"_L1, Qt::CaseInsensitive ) )
384 {
385 result = createFromProj( match.captured( 2 ) );
386 }
387 else
388 {
389 result = createFromWkt( match.captured( 2 ) );
390 }
391 }
392 }
393
395 if ( !sDisableStringCache )
396 sStringCache()->insert( definition, *this );
397 return result;
398}
399
401{
402 if ( definition.isEmpty() )
403 return false;
404
405 QString userWkt;
406 OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
407
408 if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
409 {
411 OSRDestroySpatialReference( crs );
412 }
413 //QgsDebugMsgLevel( "definition: " + definition + " wkt = " + wkt, 2 );
414 return createFromWkt( userWkt );
415}
416
418{
419 // make sure towgs84 parameter is loaded if gdal >= 1.9
420 // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
421 const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
422 const char *configNew = "GEOGCS";
423 // only set if it was not set, to let user change the value if needed
424 if ( strcmp( configOld, "" ) == 0 )
425 {
426 CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
427 if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
428 QgsLogger::warning( u"GDAL_FIX_ESRI_WKT could not be set to %1 : %2"_s.arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
429 QgsDebugMsgLevel( u"set GDAL_FIX_ESRI_WKT : %1"_s.arg( configNew ), 4 );
430 }
431 else
432 {
433 QgsDebugMsgLevel( u"GDAL_FIX_ESRI_WKT was already set : %1"_s.arg( configNew ), 4 );
434 }
435}
436
438{
439 if ( crs.isEmpty() )
440 return false;
441
442 QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Read );
443 if ( !sDisableOgcCache )
444 {
445 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache()->constFind( crs );
446 if ( crsIt != sOgcCache()->constEnd() )
447 {
448 // found a match in the cache
449 *this = crsIt.value();
450 return d->mIsValid;
451 }
452 }
453 locker.unlock();
454
455 QString wmsCrs = crs;
456
457 QString authority;
458 QString code;
459 const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( crs, authority, code );
460 const QString authorityLower = authority.toLower();
461 if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::AUTH_CODE && ( authorityLower == "user"_L1 || authorityLower == "custom"_L1 || authorityLower == "qgis"_L1 ) )
462 {
463 if ( createFromSrsId( code.toInt() ) )
464 {
466 if ( !sDisableOgcCache )
467 sOgcCache()->insert( crs, *this );
468 return d->mIsValid;
469 }
470 }
471 else if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
472 {
473 wmsCrs = authority + ':' + code;
474 }
475
476 // first chance for proj 6 - scan through legacy systems and try to use authid directly
477 const QString legacyKey = wmsCrs.toLower();
478 for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
479 {
480 if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
481 {
482 const QStringList parts = it.key().split( ':' );
483 const QString auth = parts.at( 0 );
484 const QString code = parts.at( 1 );
485 if ( loadFromAuthCode( auth, code ) )
486 {
488 if ( !sDisableOgcCache )
489 sOgcCache()->insert( crs, *this );
490 return d->mIsValid;
491 }
492 }
493 }
494
495 if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), u"lower(auth_name||':'||auth_id)"_s, wmsCrs.toLower() ) )
496 {
498 if ( !sDisableOgcCache )
499 sOgcCache()->insert( crs, *this );
500 return d->mIsValid;
501 }
502
503 // NAD27
504 if ( wmsCrs.compare( "CRS:27"_L1, Qt::CaseInsensitive ) == 0 || wmsCrs.compare( "OGC:CRS27"_L1, Qt::CaseInsensitive ) == 0 )
505 {
506 // TODO: verify same axis orientation
507 return createFromOgcWmsCrs( u"EPSG:4267"_s );
508 }
509
510 // NAD83
511 if ( wmsCrs.compare( "CRS:83"_L1, Qt::CaseInsensitive ) == 0 || wmsCrs.compare( "OGC:CRS83"_L1, Qt::CaseInsensitive ) == 0 )
512 {
513 // TODO: verify same axis orientation
514 return createFromOgcWmsCrs( u"EPSG:4269"_s );
515 }
516
517 // WGS84
518 if ( wmsCrs.compare( "CRS:84"_L1, Qt::CaseInsensitive ) == 0 || wmsCrs.compare( "OGC:CRS84"_L1, Qt::CaseInsensitive ) == 0 )
519 {
520 if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), u"lower(auth_name||':'||auth_id)"_s, u"epsg:4326"_s ) )
521 {
522 d->mAxisInverted = false;
523 d->mAxisInvertedDirty = false;
524 }
525
527 if ( !sDisableOgcCache )
528 sOgcCache()->insert( crs, *this );
529
530 return d->mIsValid;
531 }
532
533 // Try loading from Proj's db using authority and code
534 // While this CRS wasn't found in QGIS' srs db, it may be present in proj's
535 if ( !authority.isEmpty() && !code.isEmpty() && loadFromAuthCode( authority, code ) )
536 {
538 if ( !sDisableOgcCache )
539 sOgcCache()->insert( crs, *this );
540 return d->mIsValid;
541 }
542
544 if ( !sDisableOgcCache )
545 sOgcCache()->insert( crs, QgsCoordinateReferenceSystem() );
546 return d->mIsValid;
547}
548
549// Misc helper functions -----------------------
550
551
553{
554 if ( d->mIsValid || !sCustomSrsValidation )
555 return;
556
557 // try to validate using custom validation routines
558 if ( sCustomSrsValidation )
559 sCustomSrsValidation( *this );
560}
561
563{
564 QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Read );
565 if ( !sDisableSrIdCache )
566 {
567 QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache()->constFind( id );
568 if ( crsIt != sSrIdCache()->constEnd() )
569 {
570 // found a match in the cache
571 *this = crsIt.value();
572 return d->mIsValid;
573 }
574 }
575 locker.unlock();
576
577 // first chance for proj 6 - scan through legacy systems and try to use authid directly
578 for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
579 {
580 if ( it.value().endsWith( u",%1"_s.arg( id ) ) )
581 {
582 const QStringList parts = it.key().split( ':' );
583 const QString auth = parts.at( 0 );
584 const QString code = parts.at( 1 );
585 if ( loadFromAuthCode( auth, code ) )
586 {
588 if ( !sDisableSrIdCache )
589 sSrIdCache()->insert( id, *this );
590
591 return d->mIsValid;
592 }
593 }
594 }
595
596 bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), u"srid"_s, QString::number( id ) );
597
599 if ( !sDisableSrIdCache )
600 sSrIdCache()->insert( id, *this );
601
602 return result;
603}
604
606{
607 QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Read );
608 if ( !sDisableSrsIdCache )
609 {
610 QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache()->constFind( id );
611 if ( crsIt != sSrsIdCache()->constEnd() )
612 {
613 // found a match in the cache
614 *this = crsIt.value();
615 return d->mIsValid;
616 }
617 }
618 locker.unlock();
619
620 // first chance for proj 6 - scan through legacy systems and try to use authid directly
621 for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
622 {
623 if ( it.value().startsWith( QString::number( id ) + ',' ) )
624 {
625 const QStringList parts = it.key().split( ':' );
626 const QString auth = parts.at( 0 );
627 const QString code = parts.at( 1 );
628 if ( loadFromAuthCode( auth, code ) )
629 {
631 if ( !sDisableSrsIdCache )
632 sSrsIdCache()->insert( id, *this );
633 return d->mIsValid;
634 }
635 }
636 }
637
638 bool result = loadFromDatabase( id < Qgis::USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() : QgsApplication::qgisUserDatabaseFilePath(), u"srs_id"_s, QString::number( id ) );
639
641 if ( !sDisableSrsIdCache )
642 sSrsIdCache()->insert( id, *this );
643 return result;
644}
645
646bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
647{
648 d.detach();
649
650 QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
651 d->mIsValid = false;
652 d->mWktPreferred.clear();
653
654 QFileInfo myInfo( db );
655 if ( !myInfo.exists() )
656 {
657 QgsDebugError( "failed : " + db + " does not exist!" );
658 return d->mIsValid;
659 }
660
661 sqlite3_database_unique_ptr database;
662 sqlite3_statement_unique_ptr statement;
663 int myResult;
664 //check the db is available
665 myResult = openDatabase( db, database );
666 if ( myResult != SQLITE_OK )
667 {
668 return d->mIsValid;
669 }
670
671 /*
672 srs_id INTEGER PRIMARY KEY,
673 description text NOT NULL,
674 projection_acronym text NOT NULL,
675 ellipsoid_acronym NOT NULL,
676 parameters text NOT NULL,
677 srid integer NOT NULL,
678 auth_name varchar NOT NULL,
679 auth_id integer NOT NULL,
680 is_geo integer NOT NULL);
681 */
682
683 QString mySql = "select srs_id,description,projection_acronym,"
684 "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo,wkt "
685 "from tbl_srs where "
686 + expression
687 + '='
689 + " order by deprecated";
690 statement = database.prepare( mySql, myResult );
691 QString wkt;
692 // XXX Need to free memory from the error msg if one is set
693 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
694 {
695 d->mSrsId = statement.columnAsText( 0 ).toLong();
696 d->mDescription = statement.columnAsText( 1 );
697 d->mProjectionAcronym = statement.columnAsText( 2 );
698 d->mEllipsoidAcronym.clear();
699 d->mProj4 = statement.columnAsText( 4 );
700 d->mWktPreferred.clear();
701 d->mSRID = statement.columnAsText( 5 ).toLong();
702 d->mAuthId = statement.columnAsText( 6 );
703 d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
704 wkt = statement.columnAsText( 8 );
705 d->mAxisInvertedDirty = true;
706
707 if ( d->mSrsId >= Qgis::USER_CRS_START_ID && ( d->mAuthId.isEmpty() || d->mAuthId == QChar( ':' ) ) )
708 {
709 d->mAuthId = u"USER:%1"_s.arg( d->mSrsId );
710 }
711 else if ( !d->mAuthId.startsWith( "USER:"_L1, Qt::CaseInsensitive ) )
712 {
713 QStringList parts = d->mAuthId.split( ':' );
714 QString auth = parts.at( 0 );
715 QString code = parts.at( 1 );
716
717 {
718 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
719 d->setPj( QgsProjUtils::unboundCrs( crs.get() ) );
720 }
721
722 d->mIsValid = d->hasPj();
723 setMapUnits();
724 }
725
726 if ( !d->mIsValid )
727 {
728 if ( !wkt.isEmpty() )
729 {
730 setWktString( wkt );
731 // set WKT string resets the description to that description embedded in the WKT, so manually overwrite this back to the
732 // value from the user DB
733 d->mDescription = statement.columnAsText( 1 );
734 }
735 else
736 setProjString( d->mProj4 );
737 }
738 }
739 else
740 {
741 QgsDebugMsgLevel( "failed : " + mySql, 4 );
742 }
743 return d->mIsValid;
744}
745
746void QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
747{
748 // Not completely sure about object order destruction after main() has
749 // exited. So it is safer to check sDisableCache before using sCacheLock
750 // in case sCacheLock would have been destroyed before the current TLS
751 // QgsProjContext object that has called us...
752
753 if ( !sDisableSrIdCache )
754 {
755 QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Write );
756 if ( !sDisableSrIdCache )
757 {
758 for ( auto it = sSrIdCache()->begin(); it != sSrIdCache()->end(); )
759 {
760 auto &v = it.value();
761 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
762 it = sSrIdCache()->erase( it );
763 else
764 ++it;
765 }
766 }
767 }
768 if ( !sDisableOgcCache )
769 {
770 QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Write );
771 if ( !sDisableOgcCache )
772 {
773 for ( auto it = sOgcCache()->begin(); it != sOgcCache()->end(); )
774 {
775 auto &v = it.value();
776 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
777 it = sOgcCache()->erase( it );
778 else
779 ++it;
780 }
781 }
782 }
783 if ( !sDisableProjCache )
784 {
785 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Write );
786 if ( !sDisableProjCache )
787 {
788 for ( auto it = sProj4Cache()->begin(); it != sProj4Cache()->end(); )
789 {
790 auto &v = it.value();
791 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
792 it = sProj4Cache()->erase( it );
793 else
794 ++it;
795 }
796 }
797 }
798 if ( !sDisableWktCache )
799 {
800 QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Write );
801 if ( !sDisableWktCache )
802 {
803 for ( auto it = sWktCache()->begin(); it != sWktCache()->end(); )
804 {
805 auto &v = it.value();
806 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
807 it = sWktCache()->erase( it );
808 else
809 ++it;
810 }
811 }
812 }
813 if ( !sDisableSrsIdCache )
814 {
815 QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Write );
816 if ( !sDisableSrsIdCache )
817 {
818 for ( auto it = sSrsIdCache()->begin(); it != sSrsIdCache()->end(); )
819 {
820 auto &v = it.value();
821 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
822 it = sSrsIdCache()->erase( it );
823 else
824 ++it;
825 }
826 }
827 }
828 if ( !sDisableStringCache )
829 {
830 QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Write );
831 if ( !sDisableStringCache )
832 {
833 for ( auto it = sStringCache()->begin(); it != sStringCache()->end(); )
834 {
835 auto &v = it.value();
836 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
837 it = sStringCache()->erase( it );
838 else
839 ++it;
840 }
841 }
842 }
843}
844
846{
847 if ( d->mAxisInvertedDirty )
848 {
849 d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->threadLocalProjObject() );
850 d->mAxisInvertedDirty = false;
851 }
852
853 return d->mAxisInverted;
854}
855
856QList<Qgis::CrsAxisDirection> QgsCoordinateReferenceSystem::axisOrdering() const
857{
858 if ( type() == Qgis::CrsType::Compound )
860
861 const PJ *projObject = d->threadLocalProjObject();
862 if ( !projObject )
863 return {};
864
865 PJ_CONTEXT *context = QgsProjContext::get();
866 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, projObject ) );
867 if ( !pjCs )
868 return {};
869
870 const thread_local QMap< Qgis::CrsAxisDirection, QString > mapping = {
871 { Qgis::CrsAxisDirection::North, u"north"_s },
872 { Qgis::CrsAxisDirection::NorthNorthEast, u"northNorthEast"_s },
873 { Qgis::CrsAxisDirection::NorthEast, u"northEast"_s },
874 { Qgis::CrsAxisDirection::EastNorthEast, u"eastNorthEast"_s },
875 { Qgis::CrsAxisDirection::East, u"east"_s },
876 { Qgis::CrsAxisDirection::EastSouthEast, u"eastSouthEast"_s },
877 { Qgis::CrsAxisDirection::SouthEast, u"southEast"_s },
878 { Qgis::CrsAxisDirection::SouthSouthEast, u"southSouthEast"_s },
879 { Qgis::CrsAxisDirection::South, u"south"_s },
880 { Qgis::CrsAxisDirection::SouthSouthWest, u"southSouthWest"_s },
881 { Qgis::CrsAxisDirection::SouthWest, u"southWest"_s },
882 { Qgis::CrsAxisDirection::WestSouthWest, u"westSouthWest"_s },
883 { Qgis::CrsAxisDirection::West, u"west"_s },
884 { Qgis::CrsAxisDirection::WestNorthWest, u"westNorthWest"_s },
885 { Qgis::CrsAxisDirection::NorthWest, u"northWest"_s },
886 { Qgis::CrsAxisDirection::NorthNorthWest, u"northNorthWest"_s },
887 { Qgis::CrsAxisDirection::GeocentricX, u"geocentricX"_s },
888 { Qgis::CrsAxisDirection::GeocentricY, u"geocentricY"_s },
889 { Qgis::CrsAxisDirection::GeocentricZ, u"geocentricZ"_s },
890 { Qgis::CrsAxisDirection::Up, u"up"_s },
891 { Qgis::CrsAxisDirection::Down, u"down"_s },
892 { Qgis::CrsAxisDirection::Forward, u"forward"_s },
893 { Qgis::CrsAxisDirection::Aft, u"aft"_s },
894 { Qgis::CrsAxisDirection::Port, u"port"_s },
895 { Qgis::CrsAxisDirection::Starboard, u"starboard"_s },
896 { Qgis::CrsAxisDirection::Clockwise, u"clockwise"_s },
897 { Qgis::CrsAxisDirection::CounterClockwise, u"counterClockwise"_s },
898 { Qgis::CrsAxisDirection::ColumnPositive, u"columnPositive"_s },
899 { Qgis::CrsAxisDirection::ColumnNegative, u"columnNegative"_s },
900 { Qgis::CrsAxisDirection::RowPositive, u"rowPositive"_s },
901 { Qgis::CrsAxisDirection::RowNegative, u"rowNegative"_s },
902 { Qgis::CrsAxisDirection::DisplayRight, u"displayRight"_s },
903 { Qgis::CrsAxisDirection::DisplayLeft, u"displayLeft"_s },
904 { Qgis::CrsAxisDirection::DisplayUp, u"displayUp"_s },
905 { Qgis::CrsAxisDirection::DisplayDown, u"displayDown"_s },
906 { Qgis::CrsAxisDirection::Future, u"future"_s },
907 { Qgis::CrsAxisDirection::Past, u"past"_s },
908 { Qgis::CrsAxisDirection::Towards, u"towards"_s },
909 { Qgis::CrsAxisDirection::AwayFrom, u"awayFrom"_s },
910 };
911
912 QList< Qgis::CrsAxisDirection > res;
913 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
914 if ( axisCount > 0 )
915 {
916 res.reserve( axisCount );
917
918 for ( int i = 0; i < axisCount; ++i )
919 {
920 const char *outDirection = nullptr;
921 proj_cs_get_axis_info( context, pjCs.get(), i, nullptr, nullptr, &outDirection, nullptr, nullptr, nullptr, nullptr );
922 // get first word of direction only
923 const thread_local QRegularExpression rx( u"([^\\s]+).*"_s );
924 const QRegularExpressionMatch match = rx.match( QString( outDirection ) );
925 if ( !match.hasMatch() )
926 continue;
927
928 const QString direction = match.captured( 1 );
930 for ( auto it = mapping.constBegin(); it != mapping.constEnd(); ++it )
931 {
932 if ( it.value().compare( direction, Qt::CaseInsensitive ) == 0 )
933 {
934 dir = it.key();
935 break;
936 }
937 }
938
939 res.append( dir );
940 }
941 }
942 return res;
943}
944
946{
947 return createFromWktInternal( wkt, QString() );
948}
949
950bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
951{
952 if ( wkt.isEmpty() )
953 return false;
954
955 d.detach();
956
957 QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
958 if ( !sDisableWktCache )
959 {
960 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
961 if ( crsIt != sWktCache()->constEnd() )
962 {
963 // found a match in the cache
964 *this = crsIt.value();
965
966 if ( !description.isEmpty() && d->mDescription.isEmpty() )
967 {
968 // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
969 d->mDescription = description;
970 locker.changeMode( QgsReadWriteLocker::Write );
971 sWktCache()->insert( wkt, *this );
972 }
973 return d->mIsValid;
974 }
975 }
976 locker.unlock();
977
978 d->mIsValid = false;
979 d->mProj4.clear();
980 d->mWktPreferred.clear();
981 if ( wkt.isEmpty() )
982 {
983 QgsDebugMsgLevel( u"theWkt is uninitialized, operation failed"_s, 4 );
984 return d->mIsValid;
985 }
986
987 // try to match against user crs
988 QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
989 if ( !record.empty() )
990 {
991 long srsId = record[u"srs_id"_s].toLong();
992 if ( srsId > 0 )
993 {
994 createFromSrsId( srsId );
995 }
996 }
997 else
998 {
999 setWktString( wkt );
1000 if ( !description.isEmpty() )
1001 {
1002 d->mDescription = description;
1003 }
1004 if ( d->mSrsId == 0 )
1005 {
1006 // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
1007 long id = matchToUserCrs();
1008 if ( id >= Qgis::USER_CRS_START_ID )
1009 {
1010 createFromSrsId( id );
1011 }
1012 }
1013 }
1014
1015 locker.changeMode( QgsReadWriteLocker::Write );
1016 if ( !sDisableWktCache )
1017 sWktCache()->insert( wkt, *this );
1018
1019 return d->mIsValid;
1020 //setMapunits will be called by createfromproj above
1021}
1022
1024{
1025 return d->mIsValid;
1026}
1027
1028bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
1029{
1030 return createFromProj( proj4String );
1031}
1032
1033bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
1034{
1035 if ( projString.isEmpty() )
1036 return false;
1037
1038 d.detach();
1039
1040 if ( projString.trimmed().isEmpty() )
1041 {
1042 d->mIsValid = false;
1043 d->mProj4.clear();
1044 d->mWktPreferred.clear();
1045 return false;
1046 }
1047
1048 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
1049 if ( !sDisableProjCache )
1050 {
1051 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
1052 if ( crsIt != sProj4Cache()->constEnd() )
1053 {
1054 // found a match in the cache
1055 *this = crsIt.value();
1056 return d->mIsValid;
1057 }
1058 }
1059 locker.unlock();
1060
1061 //
1062 // Examples:
1063 // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
1064 // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
1065 //
1066 // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
1067 // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
1068 //
1069 QString myProj4String = projString.trimmed();
1070 myProj4String.remove( u"+type=crs"_s );
1071 myProj4String = myProj4String.trimmed();
1072
1073 d->mIsValid = false;
1074 d->mWktPreferred.clear();
1075
1076 if ( identify )
1077 {
1078 // first, try to use proj to do this for us...
1079 const QString projCrsString = myProj4String + ( myProj4String.contains( u"+type=crs"_s ) ? QString() : u" +type=crs"_s );
1080 QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
1081 if ( crs )
1082 {
1083 QString authName;
1084 QString authCode;
1086 {
1087 const QString authid = u"%1:%2"_s.arg( authName, authCode );
1088 if ( createFromOgcWmsCrs( authid ) )
1089 {
1091 if ( !sDisableProjCache )
1092 sProj4Cache()->insert( projString, *this );
1093 return d->mIsValid;
1094 }
1095 }
1096 }
1097
1098 // try a direct match against user crses
1099 QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
1100 long id = 0;
1101 if ( !myRecord.empty() )
1102 {
1103 id = myRecord[u"srs_id"_s].toLong();
1104 if ( id >= Qgis::USER_CRS_START_ID )
1105 {
1106 createFromSrsId( id );
1107 }
1108 }
1109 if ( id < Qgis::USER_CRS_START_ID )
1110 {
1111 // no direct matches, so go ahead and create a new proj object based on the proj string alone.
1112 setProjString( myProj4String );
1113
1114 // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
1115 id = matchToUserCrs();
1116 if ( id >= Qgis::USER_CRS_START_ID )
1117 {
1118 createFromSrsId( id );
1119 }
1120 }
1121 }
1122 else
1123 {
1124 setProjString( myProj4String );
1125 }
1126
1128 if ( !sDisableProjCache )
1129 sProj4Cache()->insert( projString, *this );
1130
1131 return d->mIsValid;
1132}
1133
1134//private method meant for internal use by this class only
1135QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
1136{
1137 QString myDatabaseFileName;
1138 QgsCoordinateReferenceSystem::RecordMap myMap;
1139 QString myFieldName;
1140 QString myFieldValue;
1143 int myResult;
1144
1145 // Get the full path name to the sqlite3 spatial reference database.
1146 myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1147 QFileInfo myInfo( myDatabaseFileName );
1148 if ( !myInfo.exists() )
1149 {
1150 QgsDebugError( "failed : " + myDatabaseFileName + " does not exist!" );
1151 return myMap;
1152 }
1153
1154 //check the db is available
1155 myResult = openDatabase( myDatabaseFileName, database );
1156 if ( myResult != SQLITE_OK )
1157 {
1158 return myMap;
1159 }
1160
1161 statement = database.prepare( sql, myResult );
1162 // XXX Need to free memory from the error msg if one is set
1163 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1164 {
1165 int myColumnCount = statement.columnCount();
1166 //loop through each column in the record adding its expression name and value to the map
1167 for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1168 {
1169 myFieldName = statement.columnName( myColNo );
1170 myFieldValue = statement.columnAsText( myColNo );
1171 myMap[myFieldName] = myFieldValue;
1172 }
1173 if ( statement.step() != SQLITE_DONE )
1174 {
1175 QgsDebugMsgLevel( u"Multiple records found in srs.db"_s, 4 );
1176 //be less fussy on proj 6 -- the db has MANY more entries!
1177 }
1178 }
1179 else
1180 {
1181 QgsDebugMsgLevel( "failed : " + sql, 4 );
1182 }
1183
1184 if ( myMap.empty() )
1185 {
1186 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1187 QFileInfo myFileInfo;
1188 myFileInfo.setFile( myDatabaseFileName );
1189 if ( !myFileInfo.exists() )
1190 {
1191 QgsDebugError( u"user qgis.db not found"_s );
1192 return myMap;
1193 }
1194
1195 //check the db is available
1196 myResult = openDatabase( myDatabaseFileName, database );
1197 if ( myResult != SQLITE_OK )
1198 {
1199 return myMap;
1200 }
1201
1202 statement = database.prepare( sql, myResult );
1203 // XXX Need to free memory from the error msg if one is set
1204 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1205 {
1206 int myColumnCount = statement.columnCount();
1207 //loop through each column in the record adding its field name and value to the map
1208 for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1209 {
1210 myFieldName = statement.columnName( myColNo );
1211 myFieldValue = statement.columnAsText( myColNo );
1212 myMap[myFieldName] = myFieldValue;
1213 }
1214
1215 if ( statement.step() != SQLITE_DONE )
1216 {
1217 QgsDebugMsgLevel( u"Multiple records found in srs.db"_s, 4 );
1218 myMap.clear();
1219 }
1220 }
1221 else
1222 {
1223 QgsDebugMsgLevel( "failed : " + sql, 4 );
1224 }
1225 }
1226 return myMap;
1227}
1228
1229// Accessors -----------------------------------
1230
1232{
1233 return d->mSrsId;
1234}
1235
1237{
1238 return d->mSRID;
1239}
1240
1242{
1243 return d->mAuthId;
1244}
1245
1247{
1248 if ( d->mDescription.isNull() )
1249 {
1250 return QString();
1251 }
1252 else
1253 {
1254 return d->mDescription;
1255 }
1256}
1257
1259{
1260 QString id;
1261 if ( !authid().isEmpty() )
1262 {
1263 if ( type != Qgis::CrsIdentifierType::ShortString && !description().isEmpty() )
1264 id = u"%1 - %2"_s.arg( authid(), description() );
1265 else
1266 id = authid();
1267 }
1268 else if ( !description().isEmpty() )
1269 id = description();
1271 id = isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
1272 else if ( !toWkt( Qgis::CrsWktVariant::Preferred ).isEmpty() )
1273 id = QObject::tr( "Custom CRS: %1" )
1274 .arg( type == Qgis::CrsIdentifierType::MediumString ? ( toWkt( Qgis::CrsWktVariant::Preferred ).left( 50 ) + QString( QChar( 0x2026 ) ) ) : toWkt( Qgis::CrsWktVariant::Preferred ) );
1275 else if ( !toProj().isEmpty() )
1276 id = QObject::tr( "Custom CRS: %1" ).arg( type == Qgis::CrsIdentifierType::MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) ) : toProj() );
1277 if ( !id.isEmpty() && !std::isnan( d->mCoordinateEpoch ) )
1278 id += u" @ %1"_s.arg( qgsDoubleToString( d->mCoordinateEpoch, 3 ) );
1279
1280 return id;
1281}
1282
1284{
1285 if ( d->mProjectionAcronym.isNull() )
1286 {
1287 return QString();
1288 }
1289 else
1290 {
1291 return d->mProjectionAcronym;
1292 }
1293}
1294
1296{
1297 if ( d->mEllipsoidAcronym.isNull() )
1298 {
1299 if ( PJ *obj = d->threadLocalProjObject() )
1300 {
1301 QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
1302 if ( ellipsoid )
1303 {
1304 const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1305 const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1306 if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
1307 d->mEllipsoidAcronym = u"%1:%2"_s.arg( ellipsoidAuthName, ellipsoidAuthCode );
1308 else
1309 {
1310 double semiMajor, semiMinor, invFlattening;
1311 int semiMinorComputed = 0;
1312 if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
1313 {
1314 d->mEllipsoidAcronym = u"PARAMETER:%1:%2"_s.arg( qgsDoubleToString( semiMajor ), qgsDoubleToString( semiMinor ) );
1315 }
1316 else
1317 {
1318 d->mEllipsoidAcronym.clear();
1319 }
1320 }
1321 }
1322 }
1323 return d->mEllipsoidAcronym;
1324 }
1325 else
1326 {
1327 return d->mEllipsoidAcronym;
1328 }
1329}
1330
1332{
1333 return toProj();
1334}
1335
1337{
1338 if ( !d->mIsValid )
1339 return QString();
1340
1341 if ( d->mProj4.isEmpty() )
1342 {
1343 if ( PJ *obj = d->threadLocalProjObject() )
1344 {
1345 d->mProj4 = getFullProjString( obj );
1346 }
1347 }
1348 // Stray spaces at the end?
1349 return d->mProj4.trimmed();
1350}
1351
1352std::string QgsCoordinateReferenceSystem::toJsonString( bool multiline, int indentationWidth, const QString &schema ) const
1354 if ( !d->mIsValid )
1355 return {};
1357 if ( PJ *obj = d->threadLocalProjObject() )
1358 {
1359 const QByteArray multiLineOption = u"MULTILINE=%1"_s.arg( multiline ? u"YES"_s : u"NO"_s ).toLocal8Bit();
1360 const QByteArray indentatationWidthOption = u"INDENTATION_WIDTH=%1"_s.arg( multiline ? QString::number( indentationWidth ) : u"0"_s ).toLocal8Bit();
1361 const QByteArray schemaOption = u"SCHEMA=%1"_s.arg( schema ).toLocal8Bit();
1362 const char *const options[] = { multiLineOption.constData(), indentatationWidthOption.constData(), schemaOption.constData(), nullptr };
1363
1364 const char *json = proj_as_projjson( QgsProjContext::get(), obj, options );
1365 return json ? std::string { json } : std::string {};
1366 }
1367 else
1368 {
1369 return {};
1370 }
1371}
1372
1374{
1375 // NOLINTBEGIN(bugprone-branch-clone)
1376 switch ( d->mProjType )
1377 {
1378 case PJ_TYPE_UNKNOWN:
1380
1381 case PJ_TYPE_ELLIPSOID:
1382 case PJ_TYPE_PRIME_MERIDIAN:
1383 case PJ_TYPE_GEODETIC_REFERENCE_FRAME:
1384 case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME:
1385 case PJ_TYPE_VERTICAL_REFERENCE_FRAME:
1386 case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME:
1387 case PJ_TYPE_DATUM_ENSEMBLE:
1388 case PJ_TYPE_CONVERSION:
1389 case PJ_TYPE_TRANSFORMATION:
1390 case PJ_TYPE_CONCATENATED_OPERATION:
1391 case PJ_TYPE_OTHER_COORDINATE_OPERATION:
1392 case PJ_TYPE_TEMPORAL_DATUM:
1393 case PJ_TYPE_ENGINEERING_DATUM:
1394 case PJ_TYPE_PARAMETRIC_DATUM:
1395 return Qgis::CrsType::Other;
1396
1397 case PJ_TYPE_CRS:
1398 case PJ_TYPE_GEOGRAPHIC_CRS:
1399 //not possible
1400 return Qgis::CrsType::Other;
1401
1402 case PJ_TYPE_GEODETIC_CRS:
1404 case PJ_TYPE_GEOCENTRIC_CRS:
1406 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
1408 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
1410 case PJ_TYPE_VERTICAL_CRS:
1412 case PJ_TYPE_PROJECTED_CRS:
1414 case PJ_TYPE_COMPOUND_CRS:
1416 case PJ_TYPE_TEMPORAL_CRS:
1418 case PJ_TYPE_ENGINEERING_CRS:
1420 case PJ_TYPE_BOUND_CRS:
1421 return Qgis::CrsType::Bound;
1422 case PJ_TYPE_OTHER_CRS:
1423 return Qgis::CrsType::Other;
1424#if PROJ_VERSION_MAJOR > 9 || ( PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 2 )
1425 case PJ_TYPE_DERIVED_PROJECTED_CRS:
1427 case PJ_TYPE_COORDINATE_METADATA:
1428 return Qgis::CrsType::Other;
1429#endif
1430 }
1432 // NOLINTEND(bugprone-branch-clone)
1433}
1434
1436{
1437 const PJ *pj = projObject();
1438 if ( !pj )
1439 return false;
1440
1441 return proj_is_deprecated( pj );
1442}
1443
1445{
1446 return d->mIsGeographic;
1447}
1448
1450{
1451 const PJ *pj = projObject();
1452 if ( !pj )
1453 return false;
1454
1455 return QgsProjUtils::isDynamic( pj );
1456}
1457
1459{
1460 const PJ *pj = projObject();
1461 if ( !pj )
1462 return QString();
1463
1464 PJ_CONTEXT *context = QgsProjContext::get();
1465
1466 return QString( proj_get_celestial_body_name( context, pj ) );
1467}
1468
1470{
1471 if ( d->mCoordinateEpoch == epoch )
1472 return;
1473
1474 // detaching clears the proj object, so we need to clone the existing one first
1476 d.detach();
1477 d->mCoordinateEpoch = epoch;
1478 d->setPj( std::move( clone ) );
1479}
1480
1482{
1483 return d->mCoordinateEpoch;
1484}
1485
1487{
1488 QgsDatumEnsemble res;
1489 res.mValid = false;
1490
1491 const PJ *pj = projObject();
1492 if ( !pj )
1493 return res;
1494
1495 PJ_CONTEXT *context = QgsProjContext::get();
1496
1498 if ( !ensemble )
1499 return res;
1500
1501 res.mValid = true;
1502 res.mName = QString( proj_get_name( ensemble.get() ) );
1503 res.mAuthority = QString( proj_get_id_auth_name( ensemble.get(), 0 ) );
1504 res.mCode = QString( proj_get_id_code( ensemble.get(), 0 ) );
1505 res.mRemarks = QString( proj_get_remarks( ensemble.get() ) );
1506 res.mScope = QString( proj_get_scope( ensemble.get() ) );
1507 res.mAccuracy = proj_datum_ensemble_get_accuracy( context, ensemble.get() );
1508
1509 const int memberCount = proj_datum_ensemble_get_member_count( context, ensemble.get() );
1510 for ( int i = 0; i < memberCount; ++i )
1511 {
1512 QgsProjUtils::proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), i ) );
1513 if ( !member )
1514 continue;
1515
1516 QgsDatumEnsembleMember details;
1517 details.mName = QString( proj_get_name( member.get() ) );
1518 details.mAuthority = QString( proj_get_id_auth_name( member.get(), 0 ) );
1519 details.mCode = QString( proj_get_id_code( member.get(), 0 ) );
1520 details.mRemarks = QString( proj_get_remarks( member.get() ) );
1521 details.mScope = QString( proj_get_scope( member.get() ) );
1522
1523 res.mMembers << details;
1524 }
1525 return res;
1526}
1527
1529{
1531
1532 // we have to make a transformation object corresponding to the crs
1533 QString projString = toProj();
1534 projString.replace( "+type=crs"_L1, QString() );
1535
1536 QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1537 if ( !transformation )
1538 return res;
1539
1540 PJ_COORD coord = proj_coord( 0, 0, 0, HUGE_VAL );
1541 coord.uv.u = point.x() * M_PI / 180.0;
1542 coord.uv.v = point.y() * M_PI / 180.0;
1543
1544 proj_errno_reset( transformation.get() );
1545 const PJ_FACTORS pjFactors = proj_factors( transformation.get(), coord );
1546 if ( proj_errno( transformation.get() ) )
1547 {
1548 return res;
1549 }
1550
1551 res.mIsValid = true;
1552 res.mMeridionalScale = pjFactors.meridional_scale;
1553 res.mParallelScale = pjFactors.parallel_scale;
1554 res.mArealScale = pjFactors.areal_scale;
1555 res.mAngularDistortion = pjFactors.angular_distortion;
1556 res.mMeridianParallelAngle = pjFactors.meridian_parallel_angle * 180 / M_PI;
1557 res.mMeridianConvergence = pjFactors.meridian_convergence * 180 / M_PI;
1558 res.mTissotSemimajor = pjFactors.tissot_semimajor;
1559 res.mTissotSemiminor = pjFactors.tissot_semiminor;
1560 res.mDxDlam = pjFactors.dx_dlam;
1561 res.mDxDphi = pjFactors.dx_dphi;
1562 res.mDyDlam = pjFactors.dy_dlam;
1563 res.mDyDphi = pjFactors.dy_dphi;
1564 return res;
1565}
1566
1568{
1569 if ( !d->mIsValid )
1570 return QgsProjOperation();
1571
1572 QgsProjOperation res;
1573
1574 // we have to make a transformation object corresponding to the crs
1575 QString projString = toProj();
1576 projString.replace( "+type=crs"_L1, QString() );
1577 if ( projString.isEmpty() )
1578 return QgsProjOperation();
1579
1580 QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1581 if ( !transformation )
1582 return res;
1583
1584 PJ_PROJ_INFO info = proj_pj_info( transformation.get() );
1585
1586 if ( info.id )
1587 {
1588 return QgsApplication::coordinateReferenceSystemRegistry()->projOperations().value( QString( info.id ) );
1589 }
1590
1591 return res;
1592}
1593
1595{
1596 if ( !d->mIsValid )
1598
1599 return d->mMapUnits;
1600}
1601
1603{
1604 if ( !d->mIsValid )
1605 return QgsRectangle();
1606
1607 PJ *obj = d->threadLocalProjObject();
1608 if ( !obj )
1609 return QgsRectangle();
1610
1611 double westLon = 0;
1612 double southLat = 0;
1613 double eastLon = 0;
1614 double northLat = 0;
1615
1616 if ( !proj_get_area_of_use( QgsProjContext::get(), obj, &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1617 return QgsRectangle();
1618
1619
1620 // don't use the constructor which normalizes!
1621 QgsRectangle rect;
1622 rect.setXMinimum( westLon );
1623 rect.setYMinimum( southLat );
1624 rect.setXMaximum( eastLon );
1625 rect.setYMaximum( northLat );
1626 return rect;
1627}
1628
1630{
1631 const auto parts { authid().split( ':' ) };
1632 if ( parts.length() == 2 )
1633 {
1634 if ( parts[0] == "EPSG"_L1 )
1635 return u"http://www.opengis.net/def/crs/EPSG/0/%1"_s.arg( parts[1] );
1636 else if ( parts[0] == "OGC"_L1 )
1637 {
1638 return u"http://www.opengis.net/def/crs/OGC/1.3/%1"_s.arg( parts[1] );
1639 }
1640 else
1641 {
1642 QgsMessageLog::logMessage( u"Error converting published CRS to URI %1: (not OGC or EPSG)"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1643 }
1644 }
1645 else
1646 {
1647 QgsMessageLog::logMessage( u"Error converting published CRS to URI: %1"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1648 }
1649 return QString();
1650}
1651
1653{
1654 const auto parts { authid().split( ':' ) };
1655 if ( parts.length() == 2 )
1656 {
1657 if ( parts[0] == "EPSG"_L1 )
1658 return u"urn:ogc:def:crs:EPSG::%1"_s.arg( parts[1] );
1659 else if ( parts[0] == "OGC"_L1 )
1660 {
1661 return u"urn:ogc:def:crs:OGC:1.3:%1"_s.arg( parts[1] );
1662 }
1663 else if ( parts[0].startsWith( "IAU"_L1 ) )
1664 {
1665 if ( parts[0].contains( "_"_L1 ) )
1666 {
1667 const auto subParts = parts[0].split( '_' );
1668 if ( subParts.length() == 2 )
1669 {
1670 return u"urn:ogc:def:crs:%1:%2:%3"_s.arg( subParts[0], subParts[1], parts[1] );
1671 }
1672 }
1673 else
1674 {
1675 return u"urn:ogc:def:crs:%1::%2"_s.arg( parts[0], parts[1] );
1676 }
1677 }
1678 else
1679 {
1680 QgsMessageLog::logMessage( u"Error converting published CRS to URN %1: (not OGC or EPSG)"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1681 }
1682 }
1683 else
1684 {
1685 QgsMessageLog::logMessage( u"Error converting published CRS to URN: %1"_s.arg( authid() ), u"CRS"_s, Qgis::MessageLevel::Critical );
1686 }
1687 return QString();
1688}
1689
1690
1692{
1693 if ( !d->mIsValid )
1694 return;
1695
1696 if ( d->mSrsId >= Qgis::USER_CRS_START_ID )
1697 {
1698 // user CRS, so update to new definition
1699 createFromSrsId( d->mSrsId );
1700 }
1701 else
1702 {
1703 // nothing to do -- only user CRS definitions can be changed
1704 }
1705}
1706
1707void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1708{
1709 d.detach();
1710 d->mProj4 = proj4String;
1711 d->mWktPreferred.clear();
1712
1713 QgsLocaleNumC l;
1714 QString trimmed = proj4String.trimmed();
1715
1716 trimmed += " +type=crs"_L1;
1718
1719 {
1720 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1721 }
1722
1723 if ( !d->hasPj() )
1724 {
1725#ifdef QGISDEBUG
1726 const int errNo = proj_context_errno( ctx );
1727 QgsDebugError( u"proj string rejected: %1"_s.arg( proj_context_errno_string( ctx, errNo ) ) );
1728#endif
1729 d->mIsValid = false;
1730 }
1731 else
1732 {
1733 d->mEllipsoidAcronym.clear();
1734 d->mIsValid = true;
1735 }
1736
1737 setMapUnits();
1738}
1739
1740bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
1741{
1742 bool res = false;
1743 d->mIsValid = false;
1744 d->mWktPreferred.clear();
1745
1746 PROJ_STRING_LIST warnings = nullptr;
1747 PROJ_STRING_LIST grammarErrors = nullptr;
1748 {
1749 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammarErrors ) ) );
1750 }
1751
1752 res = d->hasPj();
1753 if ( !res )
1754 {
1755 QgsDebugMsgLevel( u"\n---------------------------------------------------------------"_s, 2 );
1756 QgsDebugMsgLevel( u"This CRS could *** NOT *** be set from the supplied Wkt "_s, 2 );
1757 QgsDebugMsgLevel( "INPUT: " + wkt, 2 );
1758 for ( auto iter = warnings; iter && *iter; ++iter )
1759 {
1760 QgsDebugMsgLevel( *iter, 2 );
1761 }
1762 for ( auto iter = grammarErrors; iter && *iter; ++iter )
1763 {
1764 QgsDebugMsgLevel( *iter, 2 );
1765 }
1766 QgsDebugMsgLevel( u"---------------------------------------------------------------\n"_s, 2 );
1767 }
1768 proj_string_list_destroy( warnings );
1769 proj_string_list_destroy( grammarErrors );
1770
1771 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1772 if ( !res )
1773 {
1774 locker.changeMode( QgsReadWriteLocker::Write );
1775 if ( !sDisableWktCache )
1776 sWktCache()->insert( wkt, *this );
1777 return d->mIsValid;
1778 }
1779
1780 // try to match to a known authority
1781 if ( d->hasPj() )
1782 {
1783 // try 1 - maybe we can directly grab the auth name and code from the crs already?
1784 QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1785 QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1786
1787 if ( authName.isEmpty() || authCode.isEmpty() )
1788 {
1789 // try 2, use proj's identify method and see if there's a nice candidate we can use
1790 QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1791 }
1792
1793 if ( !authName.isEmpty() && !authCode.isEmpty() )
1794 {
1795 QgsCoordinateReferenceSystem fromAuthCode;
1796 if ( fromAuthCode.loadFromAuthCode( authName, authCode ) )
1797 {
1798 *this = fromAuthCode;
1799 locker.changeMode( QgsReadWriteLocker::Write );
1800 if ( !sDisableWktCache )
1801 sWktCache()->insert( wkt, *this );
1802 return d->mIsValid;
1803 }
1804 }
1805
1806 // Still a valid CRS, just not a known one
1807 d->mIsValid = true;
1808 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1809 setMapUnits();
1810 }
1811
1812 return d->mIsValid;
1813}
1814
1815void QgsCoordinateReferenceSystem::setMapUnits()
1816{
1817 if ( !d->mIsValid )
1818 {
1819 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1820 return;
1821 }
1822
1823 if ( !d->hasPj() )
1824 {
1825 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1826 return;
1827 }
1828
1829 PJ_CONTEXT *context = QgsProjContext::get();
1830 // prefer horizontal CRS units, if present
1831 QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToHorizontalCrs( d->threadLocalProjObject() ) );
1832 if ( !crs )
1833 crs = QgsProjUtils::unboundCrs( d->threadLocalProjObject() );
1834
1835 if ( !crs )
1836 {
1837 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1838 return;
1839 }
1840
1841 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1842 if ( !coordinateSystem )
1843 {
1844 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1845 return;
1846 }
1847
1848 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1849 if ( axisCount > 0 )
1850 {
1851 const char *outUnitName = nullptr;
1852 // Read only first axis
1853 proj_cs_get_axis_info( context, coordinateSystem.get(), 0, nullptr, nullptr, nullptr, nullptr, &outUnitName, nullptr, nullptr );
1854
1855 const QString unitName( outUnitName );
1856
1857 // proj unit names are freeform -- they differ from authority to authority :(
1858 // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1859 if ( unitName.compare( "degree"_L1, Qt::CaseInsensitive ) == 0
1860 || unitName.compare( "degree minute second"_L1, Qt::CaseInsensitive ) == 0
1861 || unitName.compare( "degree minute second hemisphere"_L1, Qt::CaseInsensitive ) == 0
1862 || unitName.compare( "degree minute"_L1, Qt::CaseInsensitive ) == 0
1863 || unitName.compare( "degree hemisphere"_L1, Qt::CaseInsensitive ) == 0
1864 || unitName.compare( "degree minute hemisphere"_L1, Qt::CaseInsensitive ) == 0
1865 || unitName.compare( "hemisphere degree"_L1, Qt::CaseInsensitive ) == 0
1866 || unitName.compare( "hemisphere degree minute"_L1, Qt::CaseInsensitive ) == 0
1867 || unitName.compare( "hemisphere degree minute second"_L1, Qt::CaseInsensitive ) == 0
1868 || unitName.compare( "degree (supplier to define representation)"_L1, Qt::CaseInsensitive ) == 0 )
1869 d->mMapUnits = Qgis::DistanceUnit::Degrees;
1870 else if ( unitName.compare( "metre"_L1, Qt::CaseInsensitive ) == 0 || unitName.compare( 'm'_L1, Qt::CaseInsensitive ) == 0 || unitName.compare( "meter"_L1, Qt::CaseInsensitive ) == 0 )
1871 d->mMapUnits = Qgis::DistanceUnit::Meters;
1872 else if ( unitName.compare( "US survey foot"_L1, Qt::CaseInsensitive ) == 0 )
1873 d->mMapUnits = Qgis::DistanceUnit::FeetUSSurvey;
1874 else if ( unitName.compare( "foot"_L1, Qt::CaseInsensitive ) == 0 )
1875 d->mMapUnits = Qgis::DistanceUnit::Feet;
1876 else if ( unitName.compare( "British yard (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1878 else if ( unitName.compare( "British yard (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1880 else if ( unitName.compare( "British foot (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1882 else if ( unitName.compare( "British foot (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1884 else if ( unitName.compare( "British chain (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1886 else if ( unitName.compare( "British chain (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1888 else if ( unitName.compare( "British link (Sears 1922)"_L1, Qt::CaseInsensitive ) == 0 )
1890 else if ( unitName.compare( "British link (Sears 1922 truncated)"_L1, Qt::CaseInsensitive ) == 0 )
1892 else if ( unitName.compare( "British yard (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1894 else if ( unitName.compare( "British foot (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1896 else if ( unitName.compare( "British chain (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1898 else if ( unitName.compare( "British link (Benoit 1895 A)"_L1, Qt::CaseInsensitive ) == 0 )
1900 else if ( unitName.compare( "British yard (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1902 else if ( unitName.compare( "British foot (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1904 else if ( unitName.compare( "British chain (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1906 else if ( unitName.compare( "British link (Benoit 1895 B)"_L1, Qt::CaseInsensitive ) == 0 )
1908 else if ( unitName.compare( "British foot (1865)"_L1, Qt::CaseInsensitive ) == 0 )
1910 else if ( unitName.compare( "British foot (1936)"_L1, Qt::CaseInsensitive ) == 0 )
1912 else if ( unitName.compare( "Indian foot"_L1, Qt::CaseInsensitive ) == 0 )
1913 d->mMapUnits = Qgis::DistanceUnit::FeetIndian;
1914 else if ( unitName.compare( "Indian foot (1937)"_L1, Qt::CaseInsensitive ) == 0 )
1916 else if ( unitName.compare( "Indian foot (1962)"_L1, Qt::CaseInsensitive ) == 0 )
1918 else if ( unitName.compare( "Indian foot (1975)"_L1, Qt::CaseInsensitive ) == 0 )
1920 else if ( unitName.compare( "Indian yard"_L1, Qt::CaseInsensitive ) == 0 )
1921 d->mMapUnits = Qgis::DistanceUnit::YardsIndian;
1922 else if ( unitName.compare( "Indian yard (1937)"_L1, Qt::CaseInsensitive ) == 0 )
1924 else if ( unitName.compare( "Indian yard (1962)"_L1, Qt::CaseInsensitive ) == 0 )
1926 else if ( unitName.compare( "Indian yard (1975)"_L1, Qt::CaseInsensitive ) == 0 )
1928 else if ( unitName.compare( "Gold Coast foot"_L1, Qt::CaseInsensitive ) == 0 )
1929 d->mMapUnits = Qgis::DistanceUnit::FeetGoldCoast;
1930 else if ( unitName.compare( "Clarke's foot"_L1, Qt::CaseInsensitive ) == 0 )
1931 d->mMapUnits = Qgis::DistanceUnit::FeetClarkes;
1932 else if ( unitName.compare( "Clarke's yard"_L1, Qt::CaseInsensitive ) == 0 )
1933 d->mMapUnits = Qgis::DistanceUnit::YardsClarkes;
1934 else if ( unitName.compare( "Clarke's chain"_L1, Qt::CaseInsensitive ) == 0 )
1935 d->mMapUnits = Qgis::DistanceUnit::ChainsClarkes;
1936 else if ( unitName.compare( "Clarke's link"_L1, Qt::CaseInsensitive ) == 0 )
1937 d->mMapUnits = Qgis::DistanceUnit::LinksClarkes;
1938 else if ( unitName.compare( "kilometre"_L1, Qt::CaseInsensitive ) == 0 ) //#spellok
1939 d->mMapUnits = Qgis::DistanceUnit::Kilometers;
1940 else if ( unitName.compare( "centimetre"_L1, Qt::CaseInsensitive ) == 0 ) //#spellok
1941 d->mMapUnits = Qgis::DistanceUnit::Centimeters;
1942 else if ( unitName.compare( "millimetre"_L1, Qt::CaseInsensitive ) == 0 ) //#spellok
1943 d->mMapUnits = Qgis::DistanceUnit::Millimeters;
1944 else if ( unitName.compare( "Statute mile"_L1, Qt::CaseInsensitive ) == 0 )
1945 d->mMapUnits = Qgis::DistanceUnit::Miles;
1946 else if ( unitName.compare( "nautical mile"_L1, Qt::CaseInsensitive ) == 0 )
1947 d->mMapUnits = Qgis::DistanceUnit::NauticalMiles;
1948 else if ( unitName.compare( "yard"_L1, Qt::CaseInsensitive ) == 0 )
1949 d->mMapUnits = Qgis::DistanceUnit::Yards;
1950 else if ( unitName.compare( "fathom"_L1, Qt::CaseInsensitive ) == 0 )
1951 d->mMapUnits = Qgis::DistanceUnit::Fathoms;
1952 else if ( unitName.compare( "US survey chain"_L1, Qt::CaseInsensitive ) == 0 )
1954 else if ( unitName.compare( "chain"_L1, Qt::CaseInsensitive ) == 0 )
1956 else if ( unitName.compare( "link"_L1, Qt::CaseInsensitive ) == 0 )
1958 else if ( unitName.compare( "US survey link"_L1, Qt::CaseInsensitive ) == 0 )
1959 d->mMapUnits = Qgis::DistanceUnit::LinksUSSurvey;
1960 else if ( unitName.compare( "US survey mile"_L1, Qt::CaseInsensitive ) == 0 )
1961 d->mMapUnits = Qgis::DistanceUnit::MilesUSSurvey;
1962 else if ( unitName.compare( "German legal metre"_L1, Qt::CaseInsensitive ) == 0 )
1964 // TODO - maybe more values to handle here?
1965 else
1966 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1967 return;
1968 }
1969 else
1970 {
1971 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1972 return;
1973 }
1974}
1975
1976
1978{
1979 if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull() || !d->mIsValid )
1980 {
1982 "QgsCoordinateReferenceSystem::findMatchingProj will only "
1983 "work if prj acr ellipsoid acr and proj4string are set"
1984 " and the current projection is valid!",
1985 4
1986 );
1987 return 0;
1988 }
1989
1992 int myResult;
1993
1994 // Set up the query to retrieve the projection information
1995 // needed to populate the list
1996 QString mySql = QString(
1997 "select srs_id,parameters from tbl_srs where "
1998 "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated"
1999 )
2000 .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ), QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
2001 // Get the full path name to the sqlite3 spatial reference database.
2002 QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2003
2004 //check the db is available
2005 myResult = openDatabase( myDatabaseFileName, database );
2006 if ( myResult != SQLITE_OK )
2007 {
2008 return 0;
2009 }
2010
2011 statement = database.prepare( mySql, myResult );
2012 if ( myResult == SQLITE_OK )
2013 {
2014 while ( statement.step() == SQLITE_ROW )
2015 {
2016 QString mySrsId = statement.columnAsText( 0 );
2017 QString myProj4String = statement.columnAsText( 1 );
2018 if ( toProj() == myProj4String.trimmed() )
2019 {
2020 return mySrsId.toLong();
2021 }
2022 }
2023 }
2024
2025 //
2026 // Try the users db now
2027 //
2028
2029 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2030 //check the db is available
2031 myResult = openDatabase( myDatabaseFileName, database );
2032 if ( myResult != SQLITE_OK )
2033 {
2034 return 0;
2035 }
2036
2037 statement = database.prepare( mySql, myResult );
2038
2039 if ( myResult == SQLITE_OK )
2040 {
2041 while ( statement.step() == SQLITE_ROW )
2042 {
2043 QString mySrsId = statement.columnAsText( 0 );
2044 QString myProj4String = statement.columnAsText( 1 );
2045 if ( toProj() == myProj4String.trimmed() )
2046 {
2047 return mySrsId.toLong();
2048 }
2049 }
2050 }
2051
2052 return 0;
2053}
2054
2056{
2057 // shortcut
2058 if ( d == srs.d )
2059 return true;
2060
2061 if ( !d->mIsValid && !srs.d->mIsValid )
2062 return true;
2063
2064 if ( !d->mIsValid || !srs.d->mIsValid )
2065 return false;
2066
2067 if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
2068 return false;
2069
2070 const bool isUser = d->mSrsId >= Qgis::USER_CRS_START_ID;
2071 const bool otherIsUser = srs.d->mSrsId >= Qgis::USER_CRS_START_ID;
2072 if ( isUser != otherIsUser )
2073 return false;
2074
2075 // we can't directly compare authid for user crses -- the actual definition of these may have changed
2076 if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
2077 return d->mAuthId == srs.d->mAuthId;
2078
2080}
2081
2083{
2084 return !( *this == srs );
2085}
2086
2087QString QgsCoordinateReferenceSystem::toWkt( Qgis::CrsWktVariant variant, bool multiline, int indentationWidth ) const
2088{
2089 if ( PJ *obj = d->threadLocalProjObject() )
2090 {
2091 const bool isDefaultPreferredFormat = variant == Qgis::CrsWktVariant::Preferred && !multiline;
2092 if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
2093 {
2094 // can use cached value
2095 return d->mWktPreferred;
2096 }
2097
2098 PJ_WKT_TYPE type = PJ_WKT1_GDAL;
2099 switch ( variant )
2100 {
2102 type = PJ_WKT1_GDAL;
2103 break;
2105 type = PJ_WKT1_ESRI;
2106 break;
2108 type = PJ_WKT2_2015;
2109 break;
2111 type = PJ_WKT2_2015_SIMPLIFIED;
2112 break;
2114 type = PJ_WKT2_2019;
2115 break;
2117 type = PJ_WKT2_2019_SIMPLIFIED;
2118 break;
2119 }
2120
2121 const QByteArray multiLineOption = u"MULTILINE=%1"_s.arg( multiline ? u"YES"_s : u"NO"_s ).toLocal8Bit();
2122 const QByteArray indentatationWidthOption = u"INDENTATION_WIDTH=%1"_s.arg( multiline ? QString::number( indentationWidth ) : u"0"_s ).toLocal8Bit();
2123 const char *const options[] = { multiLineOption.constData(), indentatationWidthOption.constData(), nullptr };
2124 QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
2125
2126 if ( isDefaultPreferredFormat )
2127 {
2128 // cache result for later use
2129 d->mWktPreferred = res;
2130 }
2131
2132 return res;
2133 }
2134 return QString();
2135}
2136
2137bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
2138{
2139 d.detach();
2140 bool result = true;
2141 QDomNode srsNode = node.namedItem( u"spatialrefsys"_s );
2142
2143 if ( !srsNode.isNull() )
2144 {
2145 bool initialized = false;
2146
2147 bool ok = false;
2148 long srsid = srsNode.namedItem( u"srsid"_s ).toElement().text().toLong( &ok );
2149
2150 QDomNode node;
2151
2152 if ( ok && srsid > 0 && srsid < Qgis::USER_CRS_START_ID )
2153 {
2154 node = srsNode.namedItem( u"authid"_s );
2155 if ( !node.isNull() )
2156 {
2157 createFromOgcWmsCrs( node.toElement().text() );
2158 if ( isValid() )
2159 {
2160 initialized = true;
2161 }
2162 }
2163
2164 if ( !initialized )
2165 {
2166 node = srsNode.namedItem( u"epsg"_s );
2167 if ( !node.isNull() )
2168 {
2169 operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
2170 if ( isValid() )
2171 {
2172 initialized = true;
2173 }
2174 }
2175 }
2176 }
2177
2178 // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
2179 if ( !initialized )
2180 {
2181 // before doing anything, we grab and set the stored CRS name (description).
2182 // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
2183 // or the user's custom CRS list), then we will correctly show the CRS with its original
2184 // name (instead of just "custom crs")
2185 const QString description = srsNode.namedItem( u"description"_s ).toElement().text();
2186
2187 const QString wkt = srsNode.namedItem( u"wkt"_s ).toElement().text();
2188 initialized = createFromWktInternal( wkt, description );
2189 }
2190
2191 if ( !initialized )
2192 {
2193 node = srsNode.namedItem( u"proj4"_s );
2194 const QString proj4 = node.toElement().text();
2195 initialized = createFromProj( proj4 );
2196 }
2197
2198 if ( !initialized )
2199 {
2200 // Setting from elements one by one
2201 node = srsNode.namedItem( u"proj4"_s );
2202 const QString proj4 = node.toElement().text();
2203 if ( !proj4.trimmed().isEmpty() )
2204 setProjString( node.toElement().text() );
2205
2206 node = srsNode.namedItem( u"srsid"_s );
2207 d->mSrsId = node.toElement().text().toLong();
2208
2209 node = srsNode.namedItem( u"srid"_s );
2210 d->mSRID = node.toElement().text().toLong();
2211
2212 node = srsNode.namedItem( u"authid"_s );
2213 d->mAuthId = node.toElement().text();
2214
2215 node = srsNode.namedItem( u"description"_s );
2216 d->mDescription = node.toElement().text();
2217
2218 node = srsNode.namedItem( u"projectionacronym"_s );
2219 d->mProjectionAcronym = node.toElement().text();
2220
2221 node = srsNode.namedItem( u"ellipsoidacronym"_s );
2222 d->mEllipsoidAcronym = node.toElement().text();
2223
2224 node = srsNode.namedItem( u"geographicflag"_s );
2225 d->mIsGeographic = node.toElement().text() == "true"_L1;
2226
2227 d->mWktPreferred.clear();
2228
2229 //make sure the map units have been set
2230 setMapUnits();
2231 }
2232
2233 const QString epoch = srsNode.toElement().attribute( u"coordinateEpoch"_s );
2234 if ( !epoch.isEmpty() )
2235 {
2236 bool epochOk = false;
2237 d->mCoordinateEpoch = epoch.toDouble( &epochOk );
2238 if ( !epochOk )
2239 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
2240 }
2241 else
2242 {
2243 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
2244 }
2245
2246 mNativeFormat = qgsEnumKeyToValue<Qgis::CrsDefinitionFormat>( srsNode.toElement().attribute( u"nativeFormat"_s ), Qgis::CrsDefinitionFormat::Wkt );
2247 }
2248 else
2249 {
2250 // Return empty CRS if none was found in the XML.
2251 d = new QgsCoordinateReferenceSystemPrivate();
2252 result = false;
2253 }
2254 return result;
2255}
2256
2257bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
2258{
2259 QDomElement layerNode = node.toElement();
2260 QDomElement srsElement = doc.createElement( u"spatialrefsys"_s );
2261
2262 srsElement.setAttribute( u"nativeFormat"_s, qgsEnumValueToKey<Qgis::CrsDefinitionFormat>( mNativeFormat ) );
2263
2264 if ( std::isfinite( d->mCoordinateEpoch ) )
2265 {
2266 srsElement.setAttribute( u"coordinateEpoch"_s, d->mCoordinateEpoch );
2267 }
2268
2269 QDomElement wktElement = doc.createElement( u"wkt"_s );
2270 wktElement.appendChild( doc.createTextNode( toWkt( Qgis::CrsWktVariant::Preferred ) ) );
2271 srsElement.appendChild( wktElement );
2272
2273 QDomElement proj4Element = doc.createElement( u"proj4"_s );
2274 proj4Element.appendChild( doc.createTextNode( toProj() ) );
2275 srsElement.appendChild( proj4Element );
2276
2277 QDomElement srsIdElement = doc.createElement( u"srsid"_s );
2278 srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
2279 srsElement.appendChild( srsIdElement );
2280
2281 QDomElement sridElement = doc.createElement( u"srid"_s );
2282 sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
2283 srsElement.appendChild( sridElement );
2284
2285 QDomElement authidElement = doc.createElement( u"authid"_s );
2286 authidElement.appendChild( doc.createTextNode( authid() ) );
2287 srsElement.appendChild( authidElement );
2288
2289 QDomElement descriptionElement = doc.createElement( u"description"_s );
2290 descriptionElement.appendChild( doc.createTextNode( description() ) );
2291 srsElement.appendChild( descriptionElement );
2292
2293 QDomElement projectionAcronymElement = doc.createElement( u"projectionacronym"_s );
2294 projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
2295 srsElement.appendChild( projectionAcronymElement );
2296
2297 QDomElement ellipsoidAcronymElement = doc.createElement( u"ellipsoidacronym"_s );
2298 ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
2299 srsElement.appendChild( ellipsoidAcronymElement );
2300
2301 QDomElement geographicFlagElement = doc.createElement( u"geographicflag"_s );
2302 QString geoFlagText = u"false"_s;
2303 if ( isGeographic() )
2304 {
2305 geoFlagText = u"true"_s;
2306 }
2307
2308 geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
2309 srsElement.appendChild( geographicFlagElement );
2310
2311 layerNode.appendChild( srsElement );
2312
2313 return true;
2314}
2315
2316//
2317// Static helper methods below this point only please!
2318//
2319
2320
2321// Returns the whole proj4 string for the selected srsid
2322//this is a static method! NOTE I've made it private for now to reduce API clutter TS
2323QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
2324{
2325 QString myDatabaseFileName;
2326 QString myProjString;
2327 QString mySql = u"select parameters from tbl_srs where srs_id = %1 order by deprecated"_s.arg( srsId );
2328
2329 //
2330 // Determine if this is a user projection or a system on
2331 // user projection defs all have srs_id >= 100000
2332 //
2333 if ( srsId >= Qgis::USER_CRS_START_ID )
2334 {
2335 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2336 QFileInfo myFileInfo;
2337 myFileInfo.setFile( myDatabaseFileName );
2338 if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
2339 {
2340 QgsDebugError( u"users qgis.db not found"_s );
2341 return QString();
2342 }
2343 }
2344 else //must be a system projection then
2345 {
2346 myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2347 }
2348
2349 sqlite3_database_unique_ptr database;
2350 sqlite3_statement_unique_ptr statement;
2351
2352 int rc;
2353 rc = openDatabase( myDatabaseFileName, database );
2354 if ( rc )
2355 {
2356 return QString();
2357 }
2358
2359 statement = database.prepare( mySql, rc );
2360
2361 if ( rc == SQLITE_OK )
2362 {
2363 if ( statement.step() == SQLITE_ROW )
2364 {
2365 myProjString = statement.columnAsText( 0 );
2366 }
2367 }
2368
2369 return myProjString;
2370}
2371
2372int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
2373{
2374 int myResult;
2375 if ( readonly )
2376 myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
2377 else
2378 myResult = database.open( path );
2379
2380 if ( myResult != SQLITE_OK )
2381 {
2382 QgsDebugError( "Can't open database: " + database.errorMessage() );
2383 // XXX This will likely never happen since on open, sqlite creates the
2384 // database if it does not exist.
2385 // ... unfortunately it happens on Windows
2386 QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" ).arg( path ).arg( myResult ).arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2387 }
2388 return myResult;
2389}
2390
2392{
2393 sCustomSrsValidation = f;
2394}
2395
2397{
2398 return sCustomSrsValidation;
2399}
2400
2401void QgsCoordinateReferenceSystem::debugPrint()
2402{
2403 QgsDebugMsgLevel( u"***SpatialRefSystem***"_s, 1 );
2404 QgsDebugMsgLevel( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ), 1 );
2405 QgsDebugMsgLevel( "* SrsId : " + QString::number( d->mSrsId ), 1 );
2406 QgsDebugMsgLevel( "* Proj4 : " + toProj(), 1 );
2408 QgsDebugMsgLevel( "* Desc. : " + d->mDescription, 1 );
2410 {
2411 QgsDebugMsgLevel( u"* Units : meters"_s, 1 );
2412 }
2413 else if ( mapUnits() == Qgis::DistanceUnit::Feet )
2414 {
2415 QgsDebugMsgLevel( u"* Units : feet"_s, 1 );
2416 }
2417 else if ( mapUnits() == Qgis::DistanceUnit::Degrees )
2418 {
2419 QgsDebugMsgLevel( u"* Units : degrees"_s, 1 );
2420 }
2421}
2422
2424{
2425 mValidationHint = html;
2426}
2427
2429{
2430 return mValidationHint;
2431}
2432
2437
2439{
2440 mNativeFormat = format;
2441}
2442
2444{
2445 return mNativeFormat;
2446}
2447
2448long QgsCoordinateReferenceSystem::getRecordCount()
2449{
2452 int myResult;
2453 long myRecordCount = 0;
2454 //check the db is available
2455 myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2456 if ( myResult != SQLITE_OK )
2457 {
2458 QgsDebugError( u"Can't open database: %1"_s.arg( database.errorMessage() ) );
2459 return 0;
2460 }
2461 // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2462 QString mySql = u"select count(*) from tbl_srs"_s;
2463 statement = database.prepare( mySql, myResult );
2464 if ( myResult == SQLITE_OK )
2465 {
2466 if ( statement.step() == SQLITE_ROW )
2467 {
2468 QString myRecordCountString = statement.columnAsText( 0 );
2469 myRecordCount = myRecordCountString.toLong();
2470 }
2471 }
2472 return myRecordCount;
2473}
2474
2476{
2477 PJ_CONTEXT *pjContext = QgsProjContext::get();
2478 bool isGeographic = false;
2479
2480 // check horizontal CRS units
2482 if ( !horizontalCrs )
2483 return false;
2484
2485 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, horizontalCrs.get() ) );
2486 if ( coordinateSystem )
2487 {
2488 const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2489 if ( axisCount > 0 )
2490 {
2491 const char *outUnitAuthName = nullptr;
2492 const char *outUnitAuthCode = nullptr;
2493 // Read only first axis
2494 proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0, nullptr, nullptr, nullptr, nullptr, nullptr, &outUnitAuthName, &outUnitAuthCode );
2495
2496 if ( outUnitAuthName && outUnitAuthCode )
2497 {
2498 const char *unitCategory = nullptr;
2499 if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2500 {
2501 isGeographic = QString( unitCategory ).compare( "angular"_L1, Qt::CaseInsensitive ) == 0;
2502 }
2503 }
2504 }
2505 }
2506 return isGeographic;
2507}
2508
2509void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2510{
2511 thread_local const QRegularExpression projRegExp( u"\\+proj=(\\S+)"_s );
2512 const QRegularExpressionMatch projMatch = projRegExp.match( proj );
2513 if ( !projMatch.hasMatch() )
2514 {
2515 QgsDebugMsgLevel( u"no +proj argument found [%2]"_s.arg( proj ), 2 );
2516 return;
2517 }
2518 operation = projMatch.captured( 1 );
2519
2520 const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
2521 if ( ellipseMatch.hasMatch() )
2522 {
2523 ellipsoid = ellipseMatch.captured( 1 );
2524 }
2525 else
2526 {
2527 // satisfy not null constraint on ellipsoid_acronym field
2528 // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2529 // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2530 // set for these CRSes). Better just hack around and make the constraint happy for now,
2531 // and hope that the definitions get corrected in future.
2532 ellipsoid = "";
2533 }
2534}
2535
2536
2537bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2538{
2539 if ( !QgsApplication::coordinateReferenceSystemRegistry()->authorities().contains( auth.toLower() ) )
2540 return false;
2541
2542 d.detach();
2543 d->mIsValid = false;
2544 d->mWktPreferred.clear();
2545
2546 PJ_CONTEXT *pjContext = QgsProjContext::get();
2547 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2548 if ( !crs )
2549 {
2550 return false;
2551 }
2552
2553 crs = QgsProjUtils::unboundCrs( crs.get() );
2554
2555 QString proj4 = getFullProjString( crs.get() );
2556 proj4.replace( "+type=crs"_L1, QString() );
2557 proj4 = proj4.trimmed();
2558
2559 d->mIsValid = true;
2560 d->mProj4 = proj4;
2561 d->mWktPreferred.clear();
2562 d->mDescription = QString( proj_get_name( crs.get() ) );
2563 d->mAuthId = u"%1:%2"_s.arg( auth, code );
2564 d->mIsGeographic = testIsGeographic( crs.get() );
2565 d->mAxisInvertedDirty = true;
2566 QString operation;
2567 QString ellipsoid;
2569 d->mProjectionAcronym = operation;
2570 d->mEllipsoidAcronym.clear();
2571 d->setPj( std::move( crs ) );
2572
2573 const QString dbVals = sAuthIdToQgisSrsIdMap.value( u"%1:%2"_s.arg( auth, code ).toUpper() );
2574 if ( !dbVals.isEmpty() )
2575 {
2576 const QStringList parts = dbVals.split( ',' );
2577 d->mSrsId = parts.at( 0 ).toInt();
2578 d->mSRID = parts.at( 1 ).toInt();
2579 }
2580
2581 setMapUnits();
2582
2583 return true;
2584}
2585
2586QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2587{
2588 QList<long> results;
2589 // check user defined projection database
2590 const QString db = QgsApplication::qgisUserDatabaseFilePath();
2591
2592 QFileInfo myInfo( db );
2593 if ( !myInfo.exists() )
2594 {
2595 QgsDebugError( "failed : " + db + " does not exist!" );
2596 return results;
2597 }
2598
2599 sqlite3_database_unique_ptr database;
2600 sqlite3_statement_unique_ptr statement;
2601
2602 //check the db is available
2603 int result = openDatabase( db, database );
2604 if ( result != SQLITE_OK )
2605 {
2606 QgsDebugError( "failed : " + db + " could not be opened!" );
2607 return results;
2608 }
2609
2610 QString sql = u"select srs_id from tbl_srs where srs_id >= %1"_s.arg( Qgis::USER_CRS_START_ID );
2611 int rc;
2612 statement = database.prepare( sql, rc );
2613 while ( true )
2614 {
2615 int ret = statement.step();
2616
2617 if ( ret == SQLITE_DONE )
2618 {
2619 // there are no more rows to fetch - we can stop looping
2620 break;
2621 }
2622
2623 if ( ret == SQLITE_ROW )
2624 {
2625 results.append( statement.columnAsInt64( 0 ) );
2626 }
2627 else
2628 {
2629 QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2630 break;
2631 }
2632 }
2633
2634 return results;
2635}
2636
2637long QgsCoordinateReferenceSystem::matchToUserCrs() const
2638{
2639 PJ *obj = d->threadLocalProjObject();
2640 if ( !obj )
2641 return 0;
2642
2643 const QList< long > ids = userSrsIds();
2644 for ( long id : ids )
2645 {
2647 if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2648 {
2649 return id;
2650 }
2651 }
2652 return 0;
2653}
2654
2655static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2656{
2657#ifndef QGISDEBUG
2658 Q_UNUSED( message )
2659#endif
2660 if ( level == PJ_LOG_ERROR )
2661 {
2662 QgsDebugMsgLevel( u"PROJ: %1"_s.arg( message ), 2 );
2663 }
2664 else if ( level == PJ_LOG_DEBUG )
2665 {
2666 QgsDebugMsgLevel( u"PROJ: %1"_s.arg( message ), 3 );
2667 }
2668}
2669
2671{
2672 setlocale( LC_ALL, "C" );
2673 QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2674
2675 int inserted = 0, updated = 0, deleted = 0, errors = 0;
2676
2677 QgsDebugMsgLevel( u"Load srs db from: %1"_s.arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2678
2680 if ( database.open( dbFilePath ) != SQLITE_OK )
2681 {
2682 QgsDebugError( u"Could not open database: %1 (%2)\n"_s.arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2683 return -1;
2684 }
2685
2686 if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2687 {
2688 QgsDebugError( u"Could not begin transaction: %1 (%2)\n"_s.arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2689 return -1;
2690 }
2691
2693 int result;
2694 char *errMsg = nullptr;
2695
2696 bool createdTypeColumn = false;
2697 if ( sqlite3_exec( database.get(), "ALTER TABLE tbl_srs ADD COLUMN srs_type text", nullptr, nullptr, nullptr ) == SQLITE_OK )
2698 {
2699 createdTypeColumn = true;
2700 if ( sqlite3_exec( database.get(), "CREATE INDEX srs_type ON tbl_srs(srs_type)", nullptr, nullptr, nullptr ) != SQLITE_OK )
2701 {
2702 QgsDebugError( u"Could not create index for srs_type"_s );
2703 return -1;
2704 }
2705 }
2706
2707 if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2708 {
2709 QString sql = u"INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)"_s
2710 .arg( QString::number( PROJ_VERSION_MAJOR ), QString::number( PROJ_VERSION_MINOR ), QString::number( PROJ_VERSION_PATCH ) );
2711 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2712 {
2713 QgsDebugError( u"Could not execute: %1 [%2/%3]\n"_s.arg( sql, database.errorMessage(), errMsg ? errMsg : "(unknown error)" ) );
2714 if ( errMsg )
2715 sqlite3_free( errMsg );
2716 return -1;
2717 }
2718 }
2719 else
2720 {
2721 // retrieve last update details
2722 QString sql = u"SELECT proj_major, proj_minor, proj_patch FROM tbl_info"_s;
2723 statement = database.prepare( sql, result );
2724 if ( result != SQLITE_OK )
2725 {
2726 QgsDebugError( u"Could not prepare: %1 [%2]\n"_s.arg( sql, database.errorMessage() ) );
2727 return -1;
2728 }
2729 if ( statement.step() == SQLITE_ROW )
2730 {
2731 int major = statement.columnAsInt64( 0 );
2732 int minor = statement.columnAsInt64( 1 );
2733 int patch = statement.columnAsInt64( 2 );
2734 if ( !createdTypeColumn && major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2735 // yay, nothing to do!
2736 return 0;
2737 }
2738 else
2739 {
2740 QgsDebugError( u"Could not retrieve previous CRS sync PROJ version number"_s );
2741 return -1;
2742 }
2743 }
2744
2745 PJ_CONTEXT *pjContext = QgsProjContext::get();
2746 // silence proj warnings
2747 proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2748
2749 PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2750
2751 int nextSrsId = 67218;
2752 int nextSrId = 520007218;
2753 for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2754 {
2755 const QString authority( *authIter );
2756 QgsDebugMsgLevel( u"Loading authority '%1'"_s.arg( authority ), 2 );
2757 PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2758
2759 QStringList allCodes;
2760
2761 for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2762 {
2763 const QString code( *codesIter );
2764 allCodes << QgsSqliteUtils::quotedString( code );
2765 QgsDebugMsgLevel( u"Loading code '%1'"_s.arg( code ), 4 );
2766 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2767 if ( !crs )
2768 {
2769 QgsDebugError( u"Could not load '%1:%2'"_s.arg( authority, code ) );
2770 continue;
2771 }
2772
2773 const PJ_TYPE pjType = proj_get_type( crs.get() );
2774
2775 QString srsTypeString;
2776 // NOLINTBEGIN(bugprone-branch-clone)
2777 switch ( pjType )
2778 {
2779 // don't need these in the CRS db
2780 case PJ_TYPE_ELLIPSOID:
2781 case PJ_TYPE_PRIME_MERIDIAN:
2782 case PJ_TYPE_GEODETIC_REFERENCE_FRAME:
2783 case PJ_TYPE_DYNAMIC_GEODETIC_REFERENCE_FRAME:
2784 case PJ_TYPE_VERTICAL_REFERENCE_FRAME:
2785 case PJ_TYPE_DYNAMIC_VERTICAL_REFERENCE_FRAME:
2786 case PJ_TYPE_DATUM_ENSEMBLE:
2787 case PJ_TYPE_CONVERSION:
2788 case PJ_TYPE_TRANSFORMATION:
2789 case PJ_TYPE_CONCATENATED_OPERATION:
2790 case PJ_TYPE_OTHER_COORDINATE_OPERATION:
2791 case PJ_TYPE_TEMPORAL_DATUM:
2792 case PJ_TYPE_ENGINEERING_DATUM:
2793 case PJ_TYPE_PARAMETRIC_DATUM:
2794 case PJ_TYPE_UNKNOWN:
2795 continue;
2796
2797 case PJ_TYPE_CRS:
2798 case PJ_TYPE_GEOGRAPHIC_CRS:
2799 continue; // not possible
2800
2801 case PJ_TYPE_GEODETIC_CRS:
2802 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Geodetic );
2803 break;
2804
2805 case PJ_TYPE_GEOCENTRIC_CRS:
2807 break;
2808
2809 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
2811 break;
2812
2813 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
2815 break;
2816
2817 case PJ_TYPE_PROJECTED_CRS:
2819 break;
2820
2821 case PJ_TYPE_COMPOUND_CRS:
2822 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Compound );
2823 break;
2824
2825 case PJ_TYPE_TEMPORAL_CRS:
2826 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Temporal );
2827 break;
2828
2829 case PJ_TYPE_ENGINEERING_CRS:
2831 break;
2832
2833 case PJ_TYPE_BOUND_CRS:
2834 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Bound );
2835 break;
2836
2837 case PJ_TYPE_VERTICAL_CRS:
2838 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Vertical );
2839 break;
2840
2841#if PROJ_VERSION_MAJOR > 9 || ( PROJ_VERSION_MAJOR == 9 && PROJ_VERSION_MINOR >= 2 )
2842 case PJ_TYPE_DERIVED_PROJECTED_CRS:
2844 break;
2845 case PJ_TYPE_COORDINATE_METADATA:
2846 continue;
2847#endif
2848 case PJ_TYPE_OTHER_CRS:
2849 srsTypeString = qgsEnumValueToKey( Qgis::CrsType::Other );
2850 break;
2851 }
2852 // NOLINTEND(bugprone-branch-clone)
2853
2854 crs = QgsProjUtils::unboundCrs( crs.get() );
2855
2856 QString proj4 = getFullProjString( crs.get() );
2857 proj4.replace( "+type=crs"_L1, QString() );
2858 proj4 = proj4.trimmed();
2859
2860 if ( proj4.isEmpty() )
2861 {
2862 QgsDebugMsgLevel( u"No proj4 for '%1:%2'"_s.arg( authority, code ), 2 );
2863 // satisfy not null constraint
2864 proj4 = "";
2865 }
2866
2867 // there's a not-null constraint on these columns, so we must use empty strings instead
2868 QString operation = "";
2869 QString ellps = "";
2871
2872 const QString translatedOperation = QgsCoordinateReferenceSystemUtils::translateProjection( operation );
2873 if ( translatedOperation.isEmpty() && !operation.isEmpty() )
2874 {
2875 std::cout << u"Operation needs translation in QgsCoordinateReferenceSystemUtils::translateProjection: %1"_s.arg( operation ).toLocal8Bit().constData() << std::endl;
2876 qFatal( "aborted" );
2877 }
2878
2879 const bool deprecated = proj_is_deprecated( crs.get() );
2880 const QString name( proj_get_name( crs.get() ) );
2881
2882 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 );
2883 statement = database.prepare( sql, result );
2884 if ( result != SQLITE_OK )
2885 {
2886 QgsDebugError( u"Could not prepare: %1 [%2]\n"_s.arg( sql, database.errorMessage() ) );
2887 continue;
2888 }
2889
2890 QString dbSrsProj4;
2891 QString dbSrsDesc;
2892 QString dbSrsType;
2893 QString dbOperation;
2894 bool dbSrsDeprecated = deprecated;
2895 if ( statement.step() == SQLITE_ROW )
2896 {
2897 dbSrsProj4 = statement.columnAsText( 0 );
2898 dbSrsDesc = statement.columnAsText( 1 );
2899 dbSrsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2900 dbSrsType = statement.columnAsText( 3 );
2901 dbOperation = statement.columnAsText( 4 );
2902 }
2903
2904 if ( !dbSrsProj4.isEmpty() || !dbSrsDesc.isEmpty() )
2905 {
2906 if ( proj4 != dbSrsProj4 || name != dbSrsDesc || deprecated != dbSrsDeprecated || dbSrsType != srsTypeString || dbOperation != operation )
2907 {
2908 errMsg = nullptr;
2909 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 ) )
2910 .arg( QgsSqliteUtils::quotedString( name ) )
2911 .arg( deprecated ? 1 : 0 )
2913
2914 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2915 {
2916 QgsDebugError( u"Could not execute: %1 [%2/%3]\n"_s.arg( sql, database.errorMessage(), errMsg ? errMsg : "(unknown error)" ) );
2917 if ( errMsg )
2918 sqlite3_free( errMsg );
2919 errors++;
2920 }
2921 else
2922 {
2923 updated++;
2924 }
2925 }
2926 }
2927 else
2928 {
2929 const bool isGeographic = testIsGeographic( crs.get() );
2930
2931 // work out srid and srsid
2932 const QString dbVals = sAuthIdToQgisSrsIdMap.value( u"%1:%2"_s.arg( authority, code ) );
2933 QString srsId;
2934 QString srId;
2935 if ( !dbVals.isEmpty() )
2936 {
2937 const QStringList parts = dbVals.split( ',' );
2938 srsId = parts.at( 0 );
2939 srId = parts.at( 1 );
2940 }
2941 if ( srId.isEmpty() )
2942 {
2943 srId = QString::number( nextSrId );
2944 nextSrId++;
2945 }
2946 if ( srsId.isEmpty() )
2947 {
2948 srsId = QString::number( nextSrsId );
2949 nextSrsId++;
2950 }
2951
2952 if ( !srsId.isEmpty() )
2953 {
2954 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
2955 .arg( srsId )
2957 .arg( srId )
2958 .arg( QgsSqliteUtils::quotedString( authority ) )
2959 .arg( QgsSqliteUtils::quotedString( code ) )
2960 .arg( isGeographic ? 1 : 0 )
2961 .arg( deprecated ? 1 : 0 )
2962 .arg( QgsSqliteUtils::quotedString( srsTypeString ) );
2963 }
2964 else
2965 {
2966 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
2968 .arg( srId )
2969 .arg( QgsSqliteUtils::quotedString( authority ) )
2970 .arg( QgsSqliteUtils::quotedString( code ) )
2971 .arg( isGeographic ? 1 : 0 )
2972 .arg( deprecated ? 1 : 0 )
2973 .arg( QgsSqliteUtils::quotedString( srsTypeString ) );
2974 }
2975
2976 errMsg = nullptr;
2977 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2978 {
2979 inserted++;
2980 }
2981 else
2982 {
2983 qCritical( "Could not execute: %s [%s/%s]\n", sql.toLocal8Bit().constData(), sqlite3_errmsg( database.get() ), errMsg ? errMsg : "(unknown error)" );
2984 errors++;
2985
2986 if ( errMsg )
2987 sqlite3_free( errMsg );
2988 }
2989 }
2990 }
2991
2992 proj_string_list_destroy( codes );
2993
2994 const QString sql = u"DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)"_s.arg( authority, allCodes.join( ',' ) );
2995 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2996 {
2997 deleted = sqlite3_changes( database.get() );
2998 }
2999 else
3000 {
3001 errors++;
3002 qCritical( "Could not execute: %s [%s]\n", sql.toLocal8Bit().constData(), sqlite3_errmsg( database.get() ) );
3003 }
3004 }
3005 proj_string_list_destroy( authorities );
3006
3007 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 ) );
3008 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
3009 {
3010 QgsDebugError( u"Could not execute: %1 [%2/%3]\n"_s.arg( sql, database.errorMessage(), errMsg ? errMsg : "(unknown error)" ) );
3011 if ( errMsg )
3012 sqlite3_free( errMsg );
3013 return -1;
3014 }
3015
3016 if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
3017 {
3018 QgsDebugError( u"Could not commit transaction: %1 [%2]\n"_s.arg( QgsApplication::srsDatabaseFilePath(), sqlite3_errmsg( database.get() ) ) );
3019 return -1;
3020 }
3021
3022#ifdef QGISDEBUG
3023 QgsDebugMsgLevel( u"CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)"_s.arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
3024#else
3025 Q_UNUSED( deleted )
3026#endif
3027
3028 if ( errors > 0 )
3029 return -errors;
3030 else
3031 return updated + inserted;
3032}
3033
3034const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
3035{
3036 return *sStringCache();
3037}
3038
3039const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
3040{
3041 return *sProj4Cache();
3042}
3043
3044const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
3045{
3046 return *sOgcCache();
3047}
3048
3049const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
3050{
3051 return *sWktCache();
3052}
3053
3054const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
3055{
3056 return *sSrIdCache();
3057}
3058
3059const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
3060{
3061 return *sSrsIdCache();
3062}
3063
3065{
3066 if ( isGeographic() )
3067 {
3068 return *this;
3069 }
3070
3071 if ( PJ *obj = d->threadLocalProjObject() )
3072 {
3073 PJ_CONTEXT *pjContext = QgsProjContext::get();
3074 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( pjContext, obj ) );
3075 if ( !geoCrs )
3077
3078 const PJ_TYPE pjType = proj_get_type( geoCrs.get() );
3079 if ( pjType == PJ_TYPE_GEOCENTRIC_CRS )
3080 {
3081 // special case: while a geocentric crs IS a geodetic CRS, this particular QGIS method advertises
3082 // that it will always return a geographic latitude/longitude based CRS. So we build a geographic
3083 // CRS using the same datum as the original CRS
3084 QgsProjUtils::proj_pj_unique_ptr cs( proj_create_ellipsoidal_2D_cs( pjContext, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Degree", 1.0 ) );
3085 QgsProjUtils::proj_pj_unique_ptr datum( proj_crs_get_datum( pjContext, geoCrs.get() ) );
3086 QgsProjUtils::proj_pj_unique_ptr datumEnsemble( proj_crs_get_datum_ensemble( pjContext, geoCrs.get() ) );
3087 QgsProjUtils::proj_pj_unique_ptr geoGraphicCrs( proj_create_geographic_crs_from_datum( pjContext, nullptr, datum ? datum.get() : datumEnsemble.get(), cs.get() ) );
3088 if ( !geoGraphicCrs )
3090 return QgsCoordinateReferenceSystem::fromProjObject( geoGraphicCrs.get() );
3091 }
3092
3093 if ( !testIsGeographic( geoCrs.get() ) )
3095
3096 QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, geoCrs.get() ) );
3097 if ( !normalized )
3099
3100 return QgsCoordinateReferenceSystem::fromProjObject( normalized.get() );
3101 }
3102 else
3103 {
3105 }
3106}
3107
3109{
3111 {
3112 return *this;
3113 }
3114
3115 if ( PJ *obj = d->threadLocalProjObject() )
3116 {
3117 PJ_CONTEXT *pjContext = QgsProjContext::get();
3118
3119 // we need the horizontal, unbound crs in order to extract the datum:
3121 if ( !horizontalCrs )
3122 {
3124 }
3125
3126 QgsProjUtils::proj_pj_unique_ptr datum( proj_crs_get_datum( pjContext, horizontalCrs.get() ) );
3127 QgsProjUtils::proj_pj_unique_ptr datumEnsemble( proj_crs_get_datum_ensemble( pjContext, horizontalCrs.get() ) );
3128 if ( !datum && !datumEnsemble )
3130
3131 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_geocentric_crs_from_datum(
3132 pjContext,
3133 /*crs_name*/ nullptr,
3134 /*datum_or_datum_ensemble*/ datumEnsemble ? datumEnsemble.get() : datum.get(),
3135 /*linear_units*/ nullptr, // "NULL for meter"
3136 /*linear_units_conv*/ 0 // "0 for meter if linear_units == NULL"
3137 ) );
3138 if ( crs )
3140 }
3141
3143}
3144
3146{
3147 switch ( type() )
3148 {
3160 return *this;
3161
3164
3166 break;
3167 }
3168
3169 if ( PJ *obj = d->threadLocalProjObject() )
3170 {
3172 if ( hozCrs )
3173 return QgsCoordinateReferenceSystem::fromProjObject( hozCrs.get() );
3174 }
3176}
3177
3179{
3180 switch ( type() )
3181 {
3194
3196 return *this;
3197
3199 break;
3200 }
3201
3202 if ( PJ *obj = d->threadLocalProjObject() )
3203 {
3205 if ( vertCrs )
3206 return QgsCoordinateReferenceSystem::fromProjObject( vertCrs.get() );
3207 }
3209}
3210
3212{
3213 if ( PJ *obj = d->threadLocalProjObject() )
3214 {
3215 return QgsProjUtils::hasVerticalAxis( obj );
3216 }
3217 return false;
3218}
3219
3221{
3222 if ( isGeographic() )
3223 {
3224 return d->mAuthId;
3225 }
3226 else if ( PJ *obj = d->threadLocalProjObject() )
3227 {
3228 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
3229 return geoCrs ? u"%1:%2"_s.arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
3230 }
3231 else
3232 {
3233 return QString();
3234 }
3235}
3236
3238{
3239 return d->threadLocalProjObject();
3240}
3241
3248
3250{
3251 d.detach();
3252 d->mIsValid = false;
3253 d->mProj4.clear();
3254 d->mWktPreferred.clear();
3255
3256 if ( !object )
3257 {
3258 return false;
3259 }
3260
3261 switch ( proj_get_type( object ) )
3262 {
3263 case PJ_TYPE_GEODETIC_CRS:
3264 case PJ_TYPE_GEOCENTRIC_CRS:
3265 case PJ_TYPE_GEOGRAPHIC_CRS:
3266 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
3267 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
3268 case PJ_TYPE_VERTICAL_CRS:
3269 case PJ_TYPE_PROJECTED_CRS:
3270 case PJ_TYPE_COMPOUND_CRS:
3271 case PJ_TYPE_TEMPORAL_CRS:
3272 case PJ_TYPE_ENGINEERING_CRS:
3273 case PJ_TYPE_BOUND_CRS:
3274 case PJ_TYPE_OTHER_CRS:
3275 break;
3276
3277 default:
3278 return false;
3279 }
3280
3281 d->setPj( QgsProjUtils::unboundCrs( object ) );
3282
3283 if ( !d->hasPj() )
3284 {
3285 return d->mIsValid;
3286 }
3287 else
3288 {
3289 // maybe we can directly grab the auth name and code from the crs
3290 const QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
3291 const QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
3292 if ( !authName.isEmpty() && !authCode.isEmpty() && createFromOgcWmsCrs( u"%1:%2"_s.arg( authName, authCode ) ) )
3293 {
3294 return d->mIsValid;
3295 }
3296 else
3297 {
3298 // Still a valid CRS, just not a known one
3299 d->mIsValid = true;
3300 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
3301 setMapUnits();
3302 d->mIsGeographic = testIsGeographic( d->threadLocalProjObject() );
3303 }
3304 }
3305
3306 return d->mIsValid;
3307}
3308
3310{
3311 QStringList projections;
3312 const QList<QgsCoordinateReferenceSystem> res = QgsApplication::coordinateReferenceSystemRegistry()->recentCrs();
3313 projections.reserve( res.size() );
3314 for ( const QgsCoordinateReferenceSystem &crs : res )
3315 {
3316 projections << QString::number( crs.srsid() );
3317 }
3318 return projections;
3319}
3320
3325
3330
3335
3340
3342{
3343 sSrIdCacheLock()->lockForWrite();
3344 if ( !sDisableSrIdCache )
3345 {
3346 if ( disableCache )
3347 sDisableSrIdCache = true;
3348 sSrIdCache()->clear();
3349 }
3350 sSrIdCacheLock()->unlock();
3351
3352 sOgcLock()->lockForWrite();
3353 if ( !sDisableOgcCache )
3354 {
3355 if ( disableCache )
3356 sDisableOgcCache = true;
3357 sOgcCache()->clear();
3358 }
3359 sOgcLock()->unlock();
3360
3361 sProj4CacheLock()->lockForWrite();
3362 if ( !sDisableProjCache )
3363 {
3364 if ( disableCache )
3365 sDisableProjCache = true;
3366 sProj4Cache()->clear();
3367 }
3368 sProj4CacheLock()->unlock();
3369
3370 sCRSWktLock()->lockForWrite();
3371 if ( !sDisableWktCache )
3372 {
3373 if ( disableCache )
3374 sDisableWktCache = true;
3375 sWktCache()->clear();
3376 }
3377 sCRSWktLock()->unlock();
3378
3379 sCRSSrsIdLock()->lockForWrite();
3380 if ( !sDisableSrsIdCache )
3381 {
3382 if ( disableCache )
3383 sDisableSrsIdCache = true;
3384 sSrsIdCache()->clear();
3385 }
3386 sCRSSrsIdLock()->unlock();
3387
3388 sCrsStringLock()->lockForWrite();
3389 if ( !sDisableStringCache )
3390 {
3391 if ( disableCache )
3392 sDisableStringCache = true;
3393 sStringCache()->clear();
3394 }
3395 sCrsStringLock()->unlock();
3396}
3397
3399{
3400 return celestialBodyName() == "Earth"_L1;
3401}
3402
3407
3408// invalid < regular < user
3410{
3411 if ( c1.d == c2.d )
3412 return false;
3413
3414 if ( !c1.d->mIsValid && !c2.d->mIsValid )
3415 return false;
3416
3417 if ( !c1.d->mIsValid && c2.d->mIsValid )
3418 return false;
3419
3420 if ( c1.d->mIsValid && !c2.d->mIsValid )
3421 return true;
3422
3423 const bool c1IsUser = c1.d->mSrsId >= Qgis::USER_CRS_START_ID;
3424 const bool c2IsUser = c2.d->mSrsId >= Qgis::USER_CRS_START_ID;
3425
3426 if ( c1IsUser && !c2IsUser )
3427 return true;
3428
3429 if ( !c1IsUser && c2IsUser )
3430 return false;
3431
3432 if ( !c1IsUser && !c2IsUser && !c1.d->mAuthId.isEmpty() && !c2.d->mAuthId.isEmpty() )
3433 {
3434 if ( c1.d->mAuthId != c2.d->mAuthId )
3435 return c1.d->mAuthId > c2.d->mAuthId;
3436 }
3437
3438 const QString wkt1 = c1.toWkt( Qgis::CrsWktVariant::Preferred );
3439 const QString wkt2 = c2.toWkt( Qgis::CrsWktVariant::Preferred );
3440 if ( wkt1 != wkt2 )
3441 return wkt1 > wkt2;
3442
3443 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3444 return false;
3445
3446 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3447 return false;
3448
3449 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3450 return false;
3451
3452 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3453 return true;
3454
3455 return c1.d->mCoordinateEpoch > c2.d->mCoordinateEpoch;
3456}
3457
3459{
3460 if ( c1.d == c2.d )
3461 return false;
3462
3463 if ( !c1.d->mIsValid && !c2.d->mIsValid )
3464 return false;
3465
3466 if ( c1.d->mIsValid && !c2.d->mIsValid )
3467 return false;
3468
3469 if ( !c1.d->mIsValid && c2.d->mIsValid )
3470 return true;
3471
3472 const bool c1IsUser = c1.d->mSrsId >= Qgis::USER_CRS_START_ID;
3473 const bool c2IsUser = c2.d->mSrsId >= Qgis::USER_CRS_START_ID;
3474
3475 if ( !c1IsUser && c2IsUser )
3476 return true;
3477
3478 if ( c1IsUser && !c2IsUser )
3479 return false;
3480
3481 if ( !c1IsUser && !c2IsUser && !c1.d->mAuthId.isEmpty() && !c2.d->mAuthId.isEmpty() )
3482 {
3483 if ( c1.d->mAuthId != c2.d->mAuthId )
3484 return c1.d->mAuthId < c2.d->mAuthId;
3485 }
3486
3487 const QString wkt1 = c1.toWkt( Qgis::CrsWktVariant::Preferred );
3488 const QString wkt2 = c2.toWkt( Qgis::CrsWktVariant::Preferred );
3489 if ( wkt1 != wkt2 )
3490 return wkt1 < wkt2;
3491
3492 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3493 return false;
3494
3495 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3496 return false;
3497
3498 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3499 return false;
3500
3501 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3502 return true;
3503
3504 return c1.d->mCoordinateEpoch < c2.d->mCoordinateEpoch;
3505}
3506
3508{
3509 return !( c1 < c2 );
3510}
3512{
3513 return !( c1 > c2 );
3514}
CrsIdentifierType
Available identifier string types for representing coordinate reference systems.
Definition qgis.h:2552
@ ShortString
A heavily abbreviated string, for use when a compact representation is required.
Definition qgis.h:2553
@ MediumString
A medium-length string, recommended for general purpose use.
Definition qgis.h:2554
DistanceUnit
Units of distance.
Definition qgis.h:5326
@ YardsBritishSears1922Truncated
British yards (Sears 1922 truncated).
Definition qgis.h:5366
@ Feet
Imperial feet.
Definition qgis.h:5329
@ MilesUSSurvey
US Survey miles.
Definition qgis.h:5373
@ LinksBritishSears1922
British links (Sears 1922).
Definition qgis.h:5361
@ YardsBritishBenoit1895A
British yards (Benoit 1895 A).
Definition qgis.h:5364
@ LinksBritishBenoit1895A
British links (Benoit 1895 A).
Definition qgis.h:5358
@ Centimeters
Centimeters.
Definition qgis.h:5334
@ YardsIndian1975
Indian yards (1975).
Definition qgis.h:5372
@ FeetUSSurvey
US Survey feet.
Definition qgis.h:5356
@ Millimeters
Millimeters.
Definition qgis.h:5335
@ FeetBritishSears1922
British feet (Sears 1922).
Definition qgis.h:5349
@ YardsClarkes
Clarke's yards.
Definition qgis.h:5368
@ YardsIndian
Indian yards.
Definition qgis.h:5369
@ FeetBritishBenoit1895B
British feet (Benoit 1895 B).
Definition qgis.h:5347
@ Miles
Terrestrial miles.
Definition qgis.h:5332
@ LinksUSSurvey
US Survey links.
Definition qgis.h:5363
@ Meters
Meters.
Definition qgis.h:5327
@ ChainsUSSurvey
US Survey chains.
Definition qgis.h:5343
@ FeetClarkes
Clarke's feet.
Definition qgis.h:5350
@ Unknown
Unknown distance unit.
Definition qgis.h:5376
@ Yards
Imperial yards.
Definition qgis.h:5331
@ FeetBritish1936
British feet (1936).
Definition qgis.h:5345
@ FeetIndian1962
Indian feet (1962).
Definition qgis.h:5354
@ YardsBritishSears1922
British yards (Sears 1922).
Definition qgis.h:5367
@ FeetIndian1937
Indian feet (1937).
Definition qgis.h:5353
@ YardsIndian1937
Indian yards (1937).
Definition qgis.h:5370
@ Degrees
Degrees, for planar geographic CRS distance measurements.
Definition qgis.h:5333
@ ChainsBritishBenoit1895B
British chains (Benoit 1895 B).
Definition qgis.h:5339
@ LinksBritishSears1922Truncated
British links (Sears 1922 truncated).
Definition qgis.h:5360
@ ChainsBritishBenoit1895A
British chains (Benoit 1895 A).
Definition qgis.h:5338
@ YardsBritishBenoit1895B
British yards (Benoit 1895 B).
Definition qgis.h:5365
@ FeetBritish1865
British feet (1865).
Definition qgis.h:5344
@ YardsIndian1962
Indian yards (1962).
Definition qgis.h:5371
@ FeetBritishSears1922Truncated
British feet (Sears 1922 truncated).
Definition qgis.h:5348
@ MetersGermanLegal
German legal meter.
Definition qgis.h:5375
@ LinksBritishBenoit1895B
British links (Benoit 1895 B).
Definition qgis.h:5359
@ ChainsInternational
International chains.
Definition qgis.h:5337
@ Fathoms
Fathoms.
Definition qgis.h:5374
@ LinksInternational
International links.
Definition qgis.h:5357
@ ChainsBritishSears1922Truncated
British chains (Sears 1922 truncated).
Definition qgis.h:5340
@ FeetIndian
Indian (geodetic) feet.
Definition qgis.h:5352
@ NauticalMiles
Nautical miles.
Definition qgis.h:5330
@ ChainsClarkes
Clarke's chains.
Definition qgis.h:5342
@ LinksClarkes
Clarke's links.
Definition qgis.h:5362
@ ChainsBritishSears1922
British chains (Sears 1922).
Definition qgis.h:5341
@ Kilometers
Kilometers.
Definition qgis.h:5328
@ FeetIndian1975
Indian feet (1975).
Definition qgis.h:5355
@ FeetGoldCoast
Gold Coast feet.
Definition qgis.h:5351
@ FeetBritishBenoit1895A
British feet (Benoit 1895 A).
Definition qgis.h:5346
@ Critical
Critical/error message.
Definition qgis.h:163
CrsType
Coordinate reference system types.
Definition qgis.h:2462
@ Vertical
Vertical CRS.
Definition qgis.h:2468
@ Temporal
Temporal CRS.
Definition qgis.h:2471
@ Compound
Compound (horizontal + vertical) CRS.
Definition qgis.h:2470
@ Projected
Projected CRS.
Definition qgis.h:2469
@ Other
Other type.
Definition qgis.h:2474
@ Bound
Bound CRS.
Definition qgis.h:2473
@ DerivedProjected
Derived projected CRS.
Definition qgis.h:2475
@ Unknown
Unknown type.
Definition qgis.h:2463
@ Engineering
Engineering CRS.
Definition qgis.h:2472
@ Geographic3d
3D geopraphic CRS
Definition qgis.h:2467
@ Geodetic
Geodetic CRS.
Definition qgis.h:2464
@ Geographic2d
2D geographic CRS
Definition qgis.h:2466
@ Geocentric
Geocentric CRS.
Definition qgis.h:2465
CrsDefinitionFormat
CRS definition formats.
Definition qgis.h:4075
@ Wkt
WKT format (always recommended over proj string format).
Definition qgis.h:4076
static const int USER_CRS_START_ID
Minimum ID number for a user-defined projection.
Definition qgis.h:6762
CrsAxisDirection
Coordinate reference system axis directions.
Definition qgis.h:2487
@ Starboard
Starboard.
Definition qgis.h:2512
@ ColumnPositive
Column positive.
Definition qgis.h:2515
@ SouthSouthEast
South South East.
Definition qgis.h:2495
@ NorthWest
North West.
Definition qgis.h:2502
@ ColumnNegative
Column negative.
Definition qgis.h:2516
@ RowPositive
Row positive.
Definition qgis.h:2517
@ DisplayDown
Display down.
Definition qgis.h:2522
@ GeocentricZ
Geocentric (Z).
Definition qgis.h:2506
@ DisplayRight
Display right.
Definition qgis.h:2519
@ WestSouthWest
West South West.
Definition qgis.h:2499
@ RowNegative
Row negative.
Definition qgis.h:2518
@ AwayFrom
Away from.
Definition qgis.h:2526
@ NorthNorthEast
North North East.
Definition qgis.h:2489
@ Forward
Forward.
Definition qgis.h:2509
@ EastNorthEast
East North East.
Definition qgis.h:2491
@ Unspecified
Unspecified.
Definition qgis.h:2527
@ NorthEast
North East.
Definition qgis.h:2490
@ NorthNorthWest
North North West.
Definition qgis.h:2503
@ GeocentricY
Geocentric (Y).
Definition qgis.h:2505
@ Towards
Towards.
Definition qgis.h:2525
@ SouthEast
South East.
Definition qgis.h:2494
@ CounterClockwise
Counter clockwise.
Definition qgis.h:2514
@ SouthSouthWest
South South West.
Definition qgis.h:2497
@ DisplayLeft
Display left.
Definition qgis.h:2520
@ WestNorthWest
West North West.
Definition qgis.h:2501
@ EastSouthEast
East South East.
Definition qgis.h:2493
@ Clockwise
Clockwise.
Definition qgis.h:2513
@ SouthWest
South West.
Definition qgis.h:2498
@ DisplayUp
Display up.
Definition qgis.h:2521
@ GeocentricX
Geocentric (X).
Definition qgis.h:2504
CrsWktVariant
Coordinate reference system WKT formatting variants.
Definition qgis.h:2567
@ Wkt2_2019Simplified
WKT2_2019 with the simplification rule of WKT2_SIMPLIFIED.
Definition qgis.h:2579
@ Wkt2_2015Simplified
Same as WKT2_2015 with the following exceptions: UNIT keyword used. ID node only on top element....
Definition qgis.h:2573
@ Wkt1Esri
WKT1 as traditionally output by ESRI software, deriving from OGC 99-049.
Definition qgis.h:2571
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
Definition qgis.h:2580
@ Wkt2_2019
Full WKT2 string, conforming to ISO 19162:2019 / OGC 18-010, with all possible nodes and new keyword ...
Definition qgis.h:2576
@ Wkt2_2015
Full WKT2 string, conforming to ISO 19162:2015(E) / OGC 12-063r5 with all possible nodes and new keyw...
Definition qgis.h:2572
@ Wkt1Gdal
WKT1 as traditionally output by GDAL, deriving from OGC 01-009. A notable departure from WKT1_GDAL wi...
Definition qgis.h:2568
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 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)
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.
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 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:7335
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:7678
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:7052
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7316
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7677
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition qgis.h:7097
QString getFullProjString(PJ *obj)
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 &)
const QMap< QString, QString > sAuthIdToQgisSrsIdMap
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
Contains parameters for an ellipsoid.
double semiMajor
Semi-major axis, in meters.
bool valid
Whether ellipsoid parameters are valid.
double inverseFlattening
Inverse flattening.