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