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