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