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