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