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