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