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