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