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