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