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