QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 ***************************************************************************/
20
23#include "qgsreadwritelocker.h"
24
25#include <cmath>
26
27#include <QDir>
28#include <QDomNode>
29#include <QDomElement>
30#include <QFileInfo>
31#include <QRegularExpression>
32#include <QTextStream>
33#include <QFile>
34
35#include "qgsapplication.h"
36#include "qgslogger.h"
37#include "qgsmessagelog.h"
38#include "qgis.h" //const vals declared here
39#include "qgslocalec.h"
40#include "qgssettings.h"
41#include "qgsogrutils.h"
42#include "qgsdatums.h"
43#include "qgsogcutils.h"
45#include "qgsprojoperation.h"
46
47#include <sqlite3.h>
48#include "qgsprojutils.h"
49#include <proj.h>
50#include <proj_experimental.h>
51
52//gdal and ogr includes (needed for == operator)
53#include <ogr_srs_api.h>
54#include <cpl_error.h>
55#include <cpl_conv.h>
56#include <cpl_csv.h>
57
58CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
59
60typedef QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash;
61typedef QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash;
62
63Q_GLOBAL_STATIC( QReadWriteLock, sSrIdCacheLock )
65bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
66
67Q_GLOBAL_STATIC( QReadWriteLock, sOgcLock )
69bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
70
71Q_GLOBAL_STATIC( QReadWriteLock, sProj4CacheLock )
73bool QgsCoordinateReferenceSystem::sDisableProjCache = false;
74
75Q_GLOBAL_STATIC( QReadWriteLock, sCRSWktLock )
77bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
78
79Q_GLOBAL_STATIC( QReadWriteLock, sCRSSrsIdLock )
81bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
82
83Q_GLOBAL_STATIC( QReadWriteLock, sCrsStringLock )
85bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
86
87QString getFullProjString( PJ *obj )
88{
89 // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
90 // use proj_as_proj_string
91 QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
92 if ( boundCrs )
93 {
94 if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
95 {
96 return QString( proj4src );
97 }
98 }
99
100 return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
101}
102//--------------------------
103
105{
107
108 d = nullCrs.d;
109}
110
112{
113 d = new QgsCoordinateReferenceSystemPrivate();
114 createFromString( definition );
115}
116
118{
119 d = new QgsCoordinateReferenceSystemPrivate();
121 createFromId( id, type );
123}
124
126 : d( srs.d )
127 , mValidationHint( srs.mValidationHint )
128 , mNativeFormat( srs.mNativeFormat )
129{
130}
131
133{
134 d = srs.d;
135 mValidationHint = srs.mValidationHint;
136 mNativeFormat = srs.mNativeFormat;
137 return *this;
138}
139
141{
142 QList<long> results;
143 // check both standard & user defined projection databases
145
146 const auto constDbs = dbs;
147 for ( const QString &db : constDbs )
148 {
149 QFileInfo myInfo( db );
150 if ( !myInfo.exists() )
151 {
152 QgsDebugMsg( "failed : " + db + " does not exist!" );
153 continue;
154 }
155
158
159 //check the db is available
160 int result = openDatabase( db, database );
161 if ( result != SQLITE_OK )
162 {
163 QgsDebugMsg( "failed : " + db + " could not be opened!" );
164 continue;
165 }
166
167 QString sql = QStringLiteral( "select srs_id from tbl_srs" );
168 int rc;
169 statement = database.prepare( sql, rc );
170 while ( true )
171 {
172 // this one is an infinitive loop, intended to fetch any row
173 int ret = statement.step();
174
175 if ( ret == SQLITE_DONE )
176 {
177 // there are no more rows to fetch - we can stop looping
178 break;
179 }
180
181 if ( ret == SQLITE_ROW )
182 {
183 results.append( statement.columnAsInt64( 0 ) );
184 }
185 else
186 {
187 QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
188 break;
189 }
190 }
191 }
192 std::sort( results.begin(), results.end() );
193 return results;
194}
195
197{
199 crs.createFromOgcWmsCrs( ogcCrs );
200 return crs;
201}
202
204{
205 QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
206 if ( res.isValid() )
207 return res;
208
209 // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
210 res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
211 if ( res.isValid() )
212 return res;
213
215}
216
218{
219 return fromProj( proj4 );
220}
221
223{
225 crs.createFromProj( proj );
226 return crs;
227}
228
230{
232 crs.createFromWkt( wkt );
233 return crs;
234}
235
237{
239 crs.createFromSrsId( srsId );
240 return crs;
241}
242
244{
245}
246
248{
249 bool result = false;
250 switch ( type )
251 {
252 case InternalCrsId:
253 result = createFromSrsId( id );
254 break;
255 case PostgisCrsId:
257 result = createFromSrid( id );
259 break;
260 case EpsgCrsId:
261 result = createFromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( id ) );
262 break;
263 default:
264 //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
265 QgsDebugMsg( QStringLiteral( "Unexpected case reached!" ) );
266 };
267 return result;
268}
269
270bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
271{
272 if ( definition.isEmpty() )
273 return false;
274
275 QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Read );
276 if ( !sDisableStringCache )
277 {
278 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache()->constFind( definition );
279 if ( crsIt != sStringCache()->constEnd() )
280 {
281 // found a match in the cache
282 *this = crsIt.value();
283 return d->mIsValid;
284 }
285 }
286 locker.unlock();
287
288 bool result = false;
289 const thread_local QRegularExpression reCrsId( QStringLiteral( "^(epsg|esri|osgeo|ignf|ogc|nkg|zangi|iau_2015|iau2000|postgis|internal|user)\\:(\\w+)$" ), QRegularExpression::CaseInsensitiveOption );
290 QRegularExpressionMatch match = reCrsId.match( definition );
291 if ( match.capturedStart() == 0 )
292 {
293 QString authName = match.captured( 1 ).toLower();
294 if ( authName == QLatin1String( "epsg" ) )
295 {
296 result = createFromOgcWmsCrs( definition );
297 }
298 else if ( authName == QLatin1String( "postgis" ) )
299 {
300 const long id = match.captured( 2 ).toLong();
302 result = createFromSrid( id );
304 }
305 else if ( authName == QLatin1String( "esri" )
306 || authName == QLatin1String( "osgeo" )
307 || authName == QLatin1String( "ignf" )
308 || authName == QLatin1String( "zangi" )
309 || authName == QLatin1String( "iau2000" )
310 || authName == QLatin1String( "ogc" )
311 || authName == QLatin1String( "nkg" )
312 || authName == QLatin1String( "iau_2015" )
313 )
314 {
315 result = createFromOgcWmsCrs( definition );
316 }
317 else
318 {
319 const long id = match.captured( 2 ).toLong();
321 result = createFromId( id, InternalCrsId );
323 }
324 }
325 else
326 {
327 const thread_local QRegularExpression reCrsStr( QStringLiteral( "^(?:(wkt|proj4|proj)\\:)?(.+)$" ), QRegularExpression::CaseInsensitiveOption );
328 match = reCrsStr.match( definition );
329 if ( match.capturedStart() == 0 )
330 {
331 if ( match.captured( 1 ).startsWith( QLatin1String( "proj" ), Qt::CaseInsensitive ) )
332 {
333 result = createFromProj( match.captured( 2 ) );
334 }
335 else
336 {
337 result = createFromWkt( match.captured( 2 ) );
338 }
339 }
340 }
341
343 if ( !sDisableStringCache )
344 sStringCache()->insert( definition, *this );
345 return result;
346}
347
349{
350 if ( definition.isEmpty() )
351 return false;
352
353 QString userWkt;
354 OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
355
356 if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
357 {
359 OSRDestroySpatialReference( crs );
360 }
361 //QgsDebugMsg( "definition: " + definition + " wkt = " + wkt );
362 return createFromWkt( userWkt );
363}
364
366{
367 // make sure towgs84 parameter is loaded if gdal >= 1.9
368 // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
369 const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
370 const char *configNew = "GEOGCS";
371 // only set if it was not set, to let user change the value if needed
372 if ( strcmp( configOld, "" ) == 0 )
373 {
374 CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
375 if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
376 QgsLogger::warning( QStringLiteral( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2" )
377 .arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
378 QgsDebugMsgLevel( QStringLiteral( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ), 4 );
379 }
380 else
381 {
382 QgsDebugMsgLevel( QStringLiteral( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ), 4 );
383 }
384}
385
387{
388 if ( crs.isEmpty() )
389 return false;
390
391 QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Read );
392 if ( !sDisableOgcCache )
393 {
394 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache()->constFind( crs );
395 if ( crsIt != sOgcCache()->constEnd() )
396 {
397 // found a match in the cache
398 *this = crsIt.value();
399 return d->mIsValid;
400 }
401 }
402 locker.unlock();
403
404 QString wmsCrs = crs;
405
406 QString authority;
407 QString code;
408 const QgsOgcCrsUtils::CRSFlavor crsFlavor = QgsOgcCrsUtils::parseCrsName( crs, authority, code );
409 const QString authorityLower = authority.toLower();
410 if ( crsFlavor == QgsOgcCrsUtils::CRSFlavor::AUTH_CODE &&
411 ( authorityLower == QLatin1String( "user" ) ||
412 authorityLower == QLatin1String( "custom" ) ||
413 authorityLower == QLatin1String( "qgis" ) ) )
414 {
415 if ( createFromSrsId( code.toInt() ) )
416 {
418 if ( !sDisableOgcCache )
419 sOgcCache()->insert( crs, *this );
420 return d->mIsValid;
421 }
422 }
423 else if ( crsFlavor != QgsOgcCrsUtils::CRSFlavor::UNKNOWN )
424 {
425 wmsCrs = authority + ':' + code;
426 }
427
428 // first chance for proj 6 - scan through legacy systems and try to use authid directly
429 const QString legacyKey = wmsCrs.toLower();
430 for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
431 {
432 if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
433 {
434 const QStringList parts = it.key().split( ':' );
435 const QString auth = parts.at( 0 );
436 const QString code = parts.at( 1 );
437 if ( loadFromAuthCode( auth, code ) )
438 {
440 if ( !sDisableOgcCache )
441 sOgcCache()->insert( crs, *this );
442 return d->mIsValid;
443 }
444 }
445 }
446
447 if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), wmsCrs.toLower() ) )
448 {
450 if ( !sDisableOgcCache )
451 sOgcCache()->insert( crs, *this );
452 return d->mIsValid;
453 }
454
455 // NAD27
456 if ( wmsCrs.compare( QLatin1String( "CRS:27" ), Qt::CaseInsensitive ) == 0 ||
457 wmsCrs.compare( QLatin1String( "OGC:CRS27" ), Qt::CaseInsensitive ) == 0 )
458 {
459 // TODO: verify same axis orientation
460 return createFromOgcWmsCrs( QStringLiteral( "EPSG:4267" ) );
461 }
462
463 // NAD83
464 if ( wmsCrs.compare( QLatin1String( "CRS:83" ), Qt::CaseInsensitive ) == 0 ||
465 wmsCrs.compare( QLatin1String( "OGC:CRS83" ), Qt::CaseInsensitive ) == 0 )
466 {
467 // TODO: verify same axis orientation
468 return createFromOgcWmsCrs( QStringLiteral( "EPSG:4269" ) );
469 }
470
471 // WGS84
472 if ( wmsCrs.compare( QLatin1String( "CRS:84" ), Qt::CaseInsensitive ) == 0 ||
473 wmsCrs.compare( QLatin1String( "OGC:CRS84" ), Qt::CaseInsensitive ) == 0 )
474 {
475 if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), QStringLiteral( "epsg:4326" ) ) )
476 {
477 d->mAxisInverted = false;
478 d->mAxisInvertedDirty = false;
479 }
480
482 if ( !sDisableOgcCache )
483 sOgcCache()->insert( crs, *this );
484
485 return d->mIsValid;
486 }
487
489 if ( !sDisableOgcCache )
490 sOgcCache()->insert( crs, QgsCoordinateReferenceSystem() );
491 return d->mIsValid;
492}
493
494// Misc helper functions -----------------------
495
496
498{
499 if ( d->mIsValid || !sCustomSrsValidation )
500 return;
501
502 // try to validate using custom validation routines
503 if ( sCustomSrsValidation )
504 sCustomSrsValidation( *this );
505}
506
508{
509 QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Read );
510 if ( !sDisableSrIdCache )
511 {
512 QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache()->constFind( id );
513 if ( crsIt != sSrIdCache()->constEnd() )
514 {
515 // found a match in the cache
516 *this = crsIt.value();
517 return d->mIsValid;
518 }
519 }
520 locker.unlock();
521
522 // first chance for proj 6 - scan through legacy systems and try to use authid directly
523 for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
524 {
525 if ( it.value().endsWith( QStringLiteral( ",%1" ).arg( id ) ) )
526 {
527 const QStringList parts = it.key().split( ':' );
528 const QString auth = parts.at( 0 );
529 const QString code = parts.at( 1 );
530 if ( loadFromAuthCode( auth, code ) )
531 {
533 if ( !sDisableSrIdCache )
534 sSrIdCache()->insert( id, *this );
535
536 return d->mIsValid;
537 }
538 }
539 }
540
541 bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "srid" ), QString::number( id ) );
542
544 if ( !sDisableSrIdCache )
545 sSrIdCache()->insert( id, *this );
546
547 return result;
548}
549
551{
552 QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Read );
553 if ( !sDisableSrsIdCache )
554 {
555 QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache()->constFind( id );
556 if ( crsIt != sSrsIdCache()->constEnd() )
557 {
558 // found a match in the cache
559 *this = crsIt.value();
560 return d->mIsValid;
561 }
562 }
563 locker.unlock();
564
565 // first chance for proj 6 - scan through legacy systems and try to use authid directly
566 for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
567 {
568 if ( it.value().startsWith( QString::number( id ) + ',' ) )
569 {
570 const QStringList parts = it.key().split( ':' );
571 const QString auth = parts.at( 0 );
572 const QString code = parts.at( 1 );
573 if ( loadFromAuthCode( auth, code ) )
574 {
576 if ( !sDisableSrsIdCache )
577 sSrsIdCache()->insert( id, *this );
578 return d->mIsValid;
579 }
580 }
581 }
582
583 bool result = loadFromDatabase( id < USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() :
585 QStringLiteral( "srs_id" ), QString::number( id ) );
586
588 if ( !sDisableSrsIdCache )
589 sSrsIdCache()->insert( id, *this );
590 return result;
591}
592
593bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
594{
595 d.detach();
596
597 QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
598 d->mIsValid = false;
599 d->mWktPreferred.clear();
600
601 QFileInfo myInfo( db );
602 if ( !myInfo.exists() )
603 {
604 QgsDebugMsg( "failed : " + db + " does not exist!" );
605 return d->mIsValid;
606 }
607
610 int myResult;
611 //check the db is available
612 myResult = openDatabase( db, database );
613 if ( myResult != SQLITE_OK )
614 {
615 return d->mIsValid;
616 }
617
618 /*
619 srs_id INTEGER PRIMARY KEY,
620 description text NOT NULL,
621 projection_acronym text NOT NULL,
622 ellipsoid_acronym NOT NULL,
623 parameters text NOT NULL,
624 srid integer NOT NULL,
625 auth_name varchar NOT NULL,
626 auth_id integer NOT NULL,
627 is_geo integer NOT NULL);
628 */
629
630 QString mySql = "select srs_id,description,projection_acronym,"
631 "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo,wkt "
632 "from tbl_srs where " + expression + '=' + QgsSqliteUtils::quotedString( value ) + " order by deprecated";
633 statement = database.prepare( mySql, myResult );
634 QString wkt;
635 // XXX Need to free memory from the error msg if one is set
636 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
637 {
638 d->mSrsId = statement.columnAsText( 0 ).toLong();
639 d->mDescription = statement.columnAsText( 1 );
640 d->mProjectionAcronym = statement.columnAsText( 2 );
641 d->mEllipsoidAcronym.clear();
642 d->mProj4 = statement.columnAsText( 4 );
643 d->mWktPreferred.clear();
644 d->mSRID = statement.columnAsText( 5 ).toLong();
645 d->mAuthId = statement.columnAsText( 6 );
646 d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
647 wkt = statement.columnAsText( 8 );
648 d->mAxisInvertedDirty = true;
649
650 if ( d->mSrsId >= USER_CRS_START_ID && ( d->mAuthId.isEmpty() || d->mAuthId == QChar( ':' ) ) )
651 {
652 d->mAuthId = QStringLiteral( "USER:%1" ).arg( d->mSrsId );
653 }
654 else if ( !d->mAuthId.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive ) )
655 {
656 QStringList parts = d->mAuthId.split( ':' );
657 QString auth = parts.at( 0 );
658 QString code = parts.at( 1 );
659
660 {
661 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
662 d->setPj( QgsProjUtils::crsToSingleCrs( crs.get() ) );
663 }
664
665 d->mIsValid = d->hasPj();
666 setMapUnits();
667 }
668
669 if ( !d->mIsValid )
670 {
671 if ( !wkt.isEmpty() )
672 {
673 setWktString( wkt );
674 // set WKT string resets the description to that description embedded in the WKT, so manually overwrite this back to the
675 // value from the user DB
676 d->mDescription = statement.columnAsText( 1 );
677 }
678 else
679 setProjString( d->mProj4 );
680 }
681 }
682 else
683 {
684 QgsDebugMsgLevel( "failed : " + mySql, 4 );
685 }
686 return d->mIsValid;
687}
688
689void QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
690{
691 // Not completely sure about object order destruction after main() has
692 // exited. So it is safer to check sDisableCache before using sCacheLock
693 // in case sCacheLock would have been destroyed before the current TLS
694 // QgsProjContext object that has called us...
695
696 if ( !sDisableSrIdCache )
697 {
698 QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Write );
699 if ( !sDisableSrIdCache )
700 {
701 for ( auto it = sSrIdCache()->begin(); it != sSrIdCache()->end(); )
702 {
703 auto &v = it.value();
704 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
705 it = sSrIdCache()->erase( it );
706 else
707 ++it;
708 }
709 }
710 }
711 if ( !sDisableOgcCache )
712 {
713 QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Write );
714 if ( !sDisableOgcCache )
715 {
716 for ( auto it = sOgcCache()->begin(); it != sOgcCache()->end(); )
717 {
718 auto &v = it.value();
719 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
720 it = sOgcCache()->erase( it );
721 else
722 ++it;
723 }
724 }
725 }
726 if ( !sDisableProjCache )
727 {
728 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Write );
729 if ( !sDisableProjCache )
730 {
731 for ( auto it = sProj4Cache()->begin(); it != sProj4Cache()->end(); )
732 {
733 auto &v = it.value();
734 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
735 it = sProj4Cache()->erase( it );
736 else
737 ++it;
738 }
739 }
740 }
741 if ( !sDisableWktCache )
742 {
743 QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Write );
744 if ( !sDisableWktCache )
745 {
746 for ( auto it = sWktCache()->begin(); it != sWktCache()->end(); )
747 {
748 auto &v = it.value();
749 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
750 it = sWktCache()->erase( it );
751 else
752 ++it;
753 }
754 }
755 }
756 if ( !sDisableSrsIdCache )
757 {
758 QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Write );
759 if ( !sDisableSrsIdCache )
760 {
761 for ( auto it = sSrsIdCache()->begin(); it != sSrsIdCache()->end(); )
762 {
763 auto &v = it.value();
764 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
765 it = sSrsIdCache()->erase( it );
766 else
767 ++it;
768 }
769 }
770 }
771 if ( !sDisableStringCache )
772 {
773 QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Write );
774 if ( !sDisableStringCache )
775 {
776 for ( auto it = sStringCache()->begin(); it != sStringCache()->end(); )
777 {
778 auto &v = it.value();
779 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
780 it = sStringCache()->erase( it );
781 else
782 ++it;
783 }
784 }
785 }
786}
787
789{
790 if ( d->mAxisInvertedDirty )
791 {
792 d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->threadLocalProjObject() );
793 d->mAxisInvertedDirty = false;
794 }
795
796 return d->mAxisInverted;
797}
798
799QList<Qgis::CrsAxisDirection> QgsCoordinateReferenceSystem::axisOrdering() const
800{
801 const PJ *projObject = d->threadLocalProjObject();
802 if ( !projObject )
803 return {};
804
805 PJ_CONTEXT *context = QgsProjContext::get();
806 QgsProjUtils::proj_pj_unique_ptr pjCs( proj_crs_get_coordinate_system( context, projObject ) );
807 if ( !pjCs )
808 return {};
809
810 const thread_local QMap< Qgis::CrsAxisDirection, QString > mapping =
811 {
812 { Qgis::CrsAxisDirection::North, QStringLiteral( "north" ) },
813 { Qgis::CrsAxisDirection::NorthNorthEast, QStringLiteral( "northNorthEast" ) },
814 { Qgis::CrsAxisDirection::NorthEast, QStringLiteral( "northEast" ) },
815 { Qgis::CrsAxisDirection::EastNorthEast, QStringLiteral( "eastNorthEast" ) },
816 { Qgis::CrsAxisDirection::East, QStringLiteral( "east" ) },
817 { Qgis::CrsAxisDirection::EastSouthEast, QStringLiteral( "eastSouthEast" ) },
818 { Qgis::CrsAxisDirection::SouthEast, QStringLiteral( "southEast" ) },
819 { Qgis::CrsAxisDirection::SouthSouthEast, QStringLiteral( "southSouthEast" ) },
820 { Qgis::CrsAxisDirection::South, QStringLiteral( "south" ) },
821 { Qgis::CrsAxisDirection::SouthSouthWest, QStringLiteral( "southSouthWest" ) },
822 { Qgis::CrsAxisDirection::SouthWest, QStringLiteral( "southWest" ) },
823 { Qgis::CrsAxisDirection::WestSouthWest, QStringLiteral( "westSouthWest" ) },
824 { Qgis::CrsAxisDirection::West, QStringLiteral( "west" ) },
825 { Qgis::CrsAxisDirection::WestNorthWest, QStringLiteral( "westNorthWest" ) },
826 { Qgis::CrsAxisDirection::NorthWest, QStringLiteral( "northWest" ) },
827 { Qgis::CrsAxisDirection::NorthNorthWest, QStringLiteral( "northNorthWest" ) },
828 { Qgis::CrsAxisDirection::GeocentricX, QStringLiteral( "geocentricX" ) },
829 { Qgis::CrsAxisDirection::GeocentricY, QStringLiteral( "geocentricY" ) },
830 { Qgis::CrsAxisDirection::GeocentricZ, QStringLiteral( "geocentricZ" ) },
831 { Qgis::CrsAxisDirection::Up, QStringLiteral( "up" ) },
832 { Qgis::CrsAxisDirection::Down, QStringLiteral( "down" ) },
833 { Qgis::CrsAxisDirection::Forward, QStringLiteral( "forward" ) },
834 { Qgis::CrsAxisDirection::Aft, QStringLiteral( "aft" ) },
835 { Qgis::CrsAxisDirection::Port, QStringLiteral( "port" ) },
836 { Qgis::CrsAxisDirection::Starboard, QStringLiteral( "starboard" ) },
837 { Qgis::CrsAxisDirection::Clockwise, QStringLiteral( "clockwise" ) },
838 { Qgis::CrsAxisDirection::CounterClockwise, QStringLiteral( "counterClockwise" ) },
839 { Qgis::CrsAxisDirection::ColumnPositive, QStringLiteral( "columnPositive" ) },
840 { Qgis::CrsAxisDirection::ColumnNegative, QStringLiteral( "columnNegative" ) },
841 { Qgis::CrsAxisDirection::RowPositive, QStringLiteral( "rowPositive" ) },
842 { Qgis::CrsAxisDirection::RowNegative, QStringLiteral( "rowNegative" ) },
843 { Qgis::CrsAxisDirection::DisplayRight, QStringLiteral( "displayRight" ) },
844 { Qgis::CrsAxisDirection::DisplayLeft, QStringLiteral( "displayLeft" ) },
845 { Qgis::CrsAxisDirection::DisplayUp, QStringLiteral( "displayUp" ) },
846 { Qgis::CrsAxisDirection::DisplayDown, QStringLiteral( "displayDown" ) },
847 { Qgis::CrsAxisDirection::Future, QStringLiteral( "future" ) },
848 { Qgis::CrsAxisDirection::Past, QStringLiteral( "past" ) },
849 { Qgis::CrsAxisDirection::Towards, QStringLiteral( "towards" ) },
850 { Qgis::CrsAxisDirection::AwayFrom, QStringLiteral( "awayFrom" ) },
851 };
852
853 QList< Qgis::CrsAxisDirection > res;
854 const int axisCount = proj_cs_get_axis_count( context, pjCs.get() );
855 if ( axisCount > 0 )
856 {
857 res.reserve( axisCount );
858
859 for ( int i = 0; i < axisCount; ++i )
860 {
861 const char *outDirection = nullptr;
862 proj_cs_get_axis_info( context, pjCs.get(), i,
863 nullptr,
864 nullptr,
865 &outDirection,
866 nullptr,
867 nullptr,
868 nullptr,
869 nullptr
870 );
871 // get first word of direction only
872 const thread_local QRegularExpression rx( QStringLiteral( "([^\\s]+).*" ) );
873 const QRegularExpressionMatch match = rx.match( QString( outDirection ) );
874 if ( !match.hasMatch() )
875 continue;
876
877 const QString direction = match.captured( 1 );
879 for ( auto it = mapping.constBegin(); it != mapping.constEnd(); ++it )
880 {
881 if ( it.value().compare( direction, Qt::CaseInsensitive ) == 0 )
882 {
883 dir = it.key();
884 break;
885 }
886 }
887
888 res.append( dir );
889 }
890 }
891 return res;
892}
893
895{
896 return createFromWktInternal( wkt, QString() );
897}
898
899bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
900{
901 if ( wkt.isEmpty() )
902 return false;
903
904 d.detach();
905
906 QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
907 if ( !sDisableWktCache )
908 {
909 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
910 if ( crsIt != sWktCache()->constEnd() )
911 {
912 // found a match in the cache
913 *this = crsIt.value();
914
915 if ( !description.isEmpty() && d->mDescription.isEmpty() )
916 {
917 // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
918 d->mDescription = description;
919 locker.changeMode( QgsReadWriteLocker::Write );
920 sWktCache()->insert( wkt, *this );
921 }
922 return d->mIsValid;
923 }
924 }
925 locker.unlock();
926
927 d->mIsValid = false;
928 d->mProj4.clear();
929 d->mWktPreferred.clear();
930 if ( wkt.isEmpty() )
931 {
932 QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
933 return d->mIsValid;
934 }
935
936 // try to match against user crs
937 QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
938 if ( !record.empty() )
939 {
940 long srsId = record[QStringLiteral( "srs_id" )].toLong();
941 if ( srsId > 0 )
942 {
943 createFromSrsId( srsId );
944 }
945 }
946 else
947 {
948 setWktString( wkt );
949 if ( !description.isEmpty() )
950 {
951 d->mDescription = description;
952 }
953 if ( d->mSrsId == 0 )
954 {
955 // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
956 long id = matchToUserCrs();
957 if ( id >= USER_CRS_START_ID )
958 {
959 createFromSrsId( id );
960 }
961 }
962 }
963
964 locker.changeMode( QgsReadWriteLocker::Write );
965 if ( !sDisableWktCache )
966 sWktCache()->insert( wkt, *this );
967
968 return d->mIsValid;
969 //setMapunits will be called by createfromproj above
970}
971
973{
974 return d->mIsValid;
975}
976
977bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
978{
979 return createFromProj( proj4String );
980}
981
982bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
983{
984 if ( projString.isEmpty() )
985 return false;
986
987 d.detach();
988
989 if ( projString.trimmed().isEmpty() )
990 {
991 d->mIsValid = false;
992 d->mProj4.clear();
993 d->mWktPreferred.clear();
994 return false;
995 }
996
997 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
998 if ( !sDisableProjCache )
999 {
1000 QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
1001 if ( crsIt != sProj4Cache()->constEnd() )
1002 {
1003 // found a match in the cache
1004 *this = crsIt.value();
1005 return d->mIsValid;
1006 }
1007 }
1008 locker.unlock();
1009
1010 //
1011 // Examples:
1012 // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
1013 // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
1014 //
1015 // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
1016 // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
1017 //
1018 QString myProj4String = projString.trimmed();
1019 myProj4String.remove( QStringLiteral( "+type=crs" ) );
1020 myProj4String = myProj4String.trimmed();
1021
1022 d->mIsValid = false;
1023 d->mWktPreferred.clear();
1024
1025 if ( identify )
1026 {
1027 // first, try to use proj to do this for us...
1028 const QString projCrsString = myProj4String + ( myProj4String.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
1029 QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
1030 if ( crs )
1031 {
1032 QString authName;
1033 QString authCode;
1035 {
1036 const QString authid = QStringLiteral( "%1:%2" ).arg( authName, authCode );
1037 if ( createFromOgcWmsCrs( authid ) )
1038 {
1040 if ( !sDisableProjCache )
1041 sProj4Cache()->insert( projString, *this );
1042 return d->mIsValid;
1043 }
1044 }
1045 }
1046
1047 // try a direct match against user crses
1048 QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
1049 long id = 0;
1050 if ( !myRecord.empty() )
1051 {
1052 id = myRecord[QStringLiteral( "srs_id" )].toLong();
1053 if ( id >= USER_CRS_START_ID )
1054 {
1055 createFromSrsId( id );
1056 }
1057 }
1058 if ( id < USER_CRS_START_ID )
1059 {
1060 // no direct matches, so go ahead and create a new proj object based on the proj string alone.
1061 setProjString( myProj4String );
1062
1063 // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
1064 id = matchToUserCrs();
1065 if ( id >= USER_CRS_START_ID )
1066 {
1067 createFromSrsId( id );
1068 }
1069 }
1070 }
1071 else
1072 {
1073 setProjString( myProj4String );
1074 }
1075
1077 if ( !sDisableProjCache )
1078 sProj4Cache()->insert( projString, *this );
1079
1080 return d->mIsValid;
1081}
1082
1083//private method meant for internal use by this class only
1084QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
1085{
1086 QString myDatabaseFileName;
1087 QgsCoordinateReferenceSystem::RecordMap myMap;
1088 QString myFieldName;
1089 QString myFieldValue;
1092 int myResult;
1093
1094 // Get the full path name to the sqlite3 spatial reference database.
1095 myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1096 QFileInfo myInfo( myDatabaseFileName );
1097 if ( !myInfo.exists() )
1098 {
1099 QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
1100 return myMap;
1101 }
1102
1103 //check the db is available
1104 myResult = openDatabase( myDatabaseFileName, database );
1105 if ( myResult != SQLITE_OK )
1106 {
1107 return myMap;
1108 }
1109
1110 statement = database.prepare( sql, myResult );
1111 // XXX Need to free memory from the error msg if one is set
1112 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1113 {
1114 int myColumnCount = statement.columnCount();
1115 //loop through each column in the record adding its expression name and value to the map
1116 for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1117 {
1118 myFieldName = statement.columnName( myColNo );
1119 myFieldValue = statement.columnAsText( myColNo );
1120 myMap[myFieldName] = myFieldValue;
1121 }
1122 if ( statement.step() != SQLITE_DONE )
1123 {
1124 QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1125 //be less fussy on proj 6 -- the db has MANY more entries!
1126 }
1127 }
1128 else
1129 {
1130 QgsDebugMsgLevel( "failed : " + sql, 4 );
1131 }
1132
1133 if ( myMap.empty() )
1134 {
1135 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1136 QFileInfo myFileInfo;
1137 myFileInfo.setFile( myDatabaseFileName );
1138 if ( !myFileInfo.exists() )
1139 {
1140 QgsDebugMsg( QStringLiteral( "user qgis.db not found" ) );
1141 return myMap;
1142 }
1143
1144 //check the db is available
1145 myResult = openDatabase( myDatabaseFileName, database );
1146 if ( myResult != SQLITE_OK )
1147 {
1148 return myMap;
1149 }
1150
1151 statement = database.prepare( sql, myResult );
1152 // XXX Need to free memory from the error msg if one is set
1153 if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1154 {
1155 int myColumnCount = statement.columnCount();
1156 //loop through each column in the record adding its field name and value to the map
1157 for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1158 {
1159 myFieldName = statement.columnName( myColNo );
1160 myFieldValue = statement.columnAsText( myColNo );
1161 myMap[myFieldName] = myFieldValue;
1162 }
1163
1164 if ( statement.step() != SQLITE_DONE )
1165 {
1166 QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1167 myMap.clear();
1168 }
1169 }
1170 else
1171 {
1172 QgsDebugMsgLevel( "failed : " + sql, 4 );
1173 }
1174 }
1175 return myMap;
1176}
1177
1178// Accessors -----------------------------------
1179
1181{
1182 return d->mSrsId;
1183}
1184
1186{
1187 return d->mSRID;
1188}
1189
1191{
1192 return d->mAuthId;
1197 if ( d->mDescription.isNull() )
1198 {
1199 return QString();
1200 }
1201 else
1202 {
1203 return d->mDescription;
1204 }
1205}
1206
1208{
1209 QString id;
1210 if ( !authid().isEmpty() )
1211 {
1212 if ( type != ShortString && !description().isEmpty() )
1213 id = QStringLiteral( "%1 - %2" ).arg( authid(), description() );
1214 else
1215 id = authid();
1216 }
1217 else if ( !description().isEmpty() )
1218 id = description();
1219 else if ( type == ShortString )
1220 id = isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
1221 else if ( !toWkt( WKT_PREFERRED ).isEmpty() )
1222 id = QObject::tr( "Custom CRS: %1" ).arg(
1223 type == MediumString ? ( toWkt( WKT_PREFERRED ).left( 50 ) + QString( QChar( 0x2026 ) ) )
1224 : toWkt( WKT_PREFERRED ) );
1225 else if ( !toProj().isEmpty() )
1226 id = QObject::tr( "Custom CRS: %1" ).arg( type == MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) )
1227 : toProj() );
1228 if ( !id.isEmpty() && !std::isnan( d->mCoordinateEpoch ) )
1229 id += QStringLiteral( " @ %1" ).arg( d->mCoordinateEpoch );
1230
1231 return id;
1232}
1233
1235{
1236 if ( d->mProjectionAcronym.isNull() )
1237 {
1238 return QString();
1239 }
1240 else
1241 {
1242 return d->mProjectionAcronym;
1243 }
1244}
1245
1247{
1248 if ( d->mEllipsoidAcronym.isNull() )
1249 {
1250 if ( PJ *obj = d->threadLocalProjObject() )
1251 {
1252 QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
1253 if ( ellipsoid )
1254 {
1255 const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1256 const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1257 if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
1258 d->mEllipsoidAcronym = QStringLiteral( "%1:%2" ).arg( ellipsoidAuthName, ellipsoidAuthCode );
1259 else
1260 {
1261 double semiMajor, semiMinor, invFlattening;
1262 int semiMinorComputed = 0;
1263 if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
1264 {
1265 d->mEllipsoidAcronym = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ),
1266 qgsDoubleToString( semiMinor ) );
1267 }
1268 else
1269 {
1270 d->mEllipsoidAcronym.clear();
1271 }
1272 }
1273 }
1274 }
1275 return d->mEllipsoidAcronym;
1276 }
1277 else
1278 {
1279 return d->mEllipsoidAcronym;
1280 }
1281}
1282
1284{
1285 return toProj();
1286}
1287
1289{
1290 if ( !d->mIsValid )
1291 return QString();
1292
1293 if ( d->mProj4.isEmpty() )
1294 {
1295 if ( PJ *obj = d->threadLocalProjObject() )
1296 {
1297 d->mProj4 = getFullProjString( obj );
1298 }
1299 }
1300 // Stray spaces at the end?
1301 return d->mProj4.trimmed();
1302}
1303
1305{
1306 return d->mIsGeographic;
1307}
1308
1310{
1311 const PJ *pj = projObject();
1312 if ( !pj )
1313 return false;
1314
1315 return QgsProjUtils::isDynamic( pj );
1316}
1317
1319{
1320 const PJ *pj = projObject();
1321 if ( !pj )
1322 return QString();
1323
1324#if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=1)
1325 PJ_CONTEXT *context = QgsProjContext::get();
1326
1327 return QString( proj_get_celestial_body_name( context, pj ) );
1328#else
1329 throw QgsNotSupportedException( QObject::tr( "Retrieving celestial body requires a QGIS build based on PROJ 8.1 or later" ) );
1330#endif
1331}
1332
1334{
1335 if ( d->mCoordinateEpoch == epoch )
1336 return;
1337
1338 // detaching clears the proj object, so we need to clone the existing one first
1340 d.detach();
1341 d->mCoordinateEpoch = epoch;
1342 d->setPj( std::move( clone ) );
1343}
1344
1346{
1347 return d->mCoordinateEpoch;
1348}
1349
1351{
1352 QgsDatumEnsemble res;
1353 res.mValid = false;
1354
1355 const PJ *pj = projObject();
1356 if ( !pj )
1357 return res;
1358
1359#if PROJ_VERSION_MAJOR>=8
1360 PJ_CONTEXT *context = QgsProjContext::get();
1361
1363 if ( !ensemble )
1364 return res;
1365
1366 res.mValid = true;
1367 res.mName = QString( proj_get_name( ensemble.get() ) );
1368 res.mAuthority = QString( proj_get_id_auth_name( ensemble.get(), 0 ) );
1369 res.mCode = QString( proj_get_id_code( ensemble.get(), 0 ) );
1370 res.mRemarks = QString( proj_get_remarks( ensemble.get() ) );
1371 res.mScope = QString( proj_get_scope( ensemble.get() ) );
1372 res.mAccuracy = proj_datum_ensemble_get_accuracy( context, ensemble.get() );
1373
1374 const int memberCount = proj_datum_ensemble_get_member_count( context, ensemble.get() );
1375 for ( int i = 0; i < memberCount; ++i )
1376 {
1377 QgsProjUtils::proj_pj_unique_ptr member( proj_datum_ensemble_get_member( context, ensemble.get(), i ) );
1378 if ( !member )
1379 continue;
1380
1381 QgsDatumEnsembleMember details;
1382 details.mName = QString( proj_get_name( member.get() ) );
1383 details.mAuthority = QString( proj_get_id_auth_name( member.get(), 0 ) );
1384 details.mCode = QString( proj_get_id_code( member.get(), 0 ) );
1385 details.mRemarks = QString( proj_get_remarks( member.get() ) );
1386 details.mScope = QString( proj_get_scope( member.get() ) );
1387
1388 res.mMembers << details;
1389 }
1390 return res;
1391#else
1392 throw QgsNotSupportedException( QObject::tr( "Calculating datum ensembles requires a QGIS build based on PROJ 8.0 or later" ) );
1393#endif
1394}
1395
1397{
1399
1400 // we have to make a transformation object corresponding to the crs
1401 QString projString = toProj();
1402 projString.replace( QLatin1String( "+type=crs" ), QString() );
1403
1404 QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1405 if ( !transformation )
1406 return res;
1407
1408 PJ_COORD coord = proj_coord( 0, 0, 0, HUGE_VAL );
1409 coord.uv.u = point.x() * M_PI / 180.0;
1410 coord.uv.v = point.y() * M_PI / 180.0;
1411
1412 proj_errno_reset( transformation.get() );
1413 const PJ_FACTORS pjFactors = proj_factors( transformation.get(), coord );
1414 if ( proj_errno( transformation.get() ) )
1415 {
1416 return res;
1417 }
1418
1419 res.mIsValid = true;
1420 res.mMeridionalScale = pjFactors.meridional_scale;
1421 res.mParallelScale = pjFactors.parallel_scale;
1422 res.mArealScale = pjFactors.areal_scale;
1423 res.mAngularDistortion = pjFactors.angular_distortion;
1424 res.mMeridianParallelAngle = pjFactors.meridian_parallel_angle * 180 / M_PI;
1425 res.mMeridianConvergence = pjFactors.meridian_convergence * 180 / M_PI;
1426 res.mTissotSemimajor = pjFactors.tissot_semimajor;
1427 res.mTissotSemiminor = pjFactors.tissot_semiminor;
1428 res.mDxDlam = pjFactors.dx_dlam;
1429 res.mDxDphi = pjFactors.dx_dphi;
1430 res.mDyDlam = pjFactors.dy_dlam;
1431 res.mDyDphi = pjFactors.dy_dphi;
1432 return res;
1433}
1434
1436{
1437 if ( !d->mIsValid )
1438 return QgsProjOperation();
1439
1440 QgsProjOperation res;
1441
1442 // we have to make a transformation object corresponding to the crs
1443 QString projString = toProj();
1444 projString.replace( QLatin1String( "+type=crs" ), QString() );
1445 if ( projString.isEmpty() )
1446 return QgsProjOperation();
1447
1448 QgsProjUtils::proj_pj_unique_ptr transformation( proj_create( QgsProjContext::get(), projString.toUtf8().constData() ) );
1449 if ( !transformation )
1450 return res;
1451
1452 PJ_PROJ_INFO info = proj_pj_info( transformation.get() );
1453
1454 if ( info.id )
1455 {
1456 return QgsApplication::coordinateReferenceSystemRegistry()->projOperations().value( QString( info.id ) );
1457 }
1458
1459 return res;
1460}
1461
1463{
1464 if ( !d->mIsValid )
1466
1467 return d->mMapUnits;
1468}
1469
1471{
1472 if ( !d->mIsValid )
1473 return QgsRectangle();
1474
1475 PJ *obj = d->threadLocalProjObject();
1476 if ( !obj )
1477 return QgsRectangle();
1478
1479 double westLon = 0;
1480 double southLat = 0;
1481 double eastLon = 0;
1482 double northLat = 0;
1483
1484 if ( !proj_get_area_of_use( QgsProjContext::get(), obj,
1485 &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1486 return QgsRectangle();
1487
1488
1489 // don't use the constructor which normalizes!
1490 QgsRectangle rect;
1491 rect.setXMinimum( westLon );
1492 rect.setYMinimum( southLat );
1493 rect.setXMaximum( eastLon );
1494 rect.setYMaximum( northLat );
1495 return rect;
1496}
1497
1499{
1500 if ( !d->mIsValid )
1501 return;
1502
1503 if ( d->mSrsId >= USER_CRS_START_ID )
1504 {
1505 // user CRS, so update to new definition
1506 createFromSrsId( d->mSrsId );
1507 }
1508 else
1509 {
1510 // nothing to do -- only user CRS definitions can be changed
1511 }
1512}
1513
1514void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1515{
1516 d.detach();
1517 d->mProj4 = proj4String;
1518 d->mWktPreferred.clear();
1519
1520 QgsLocaleNumC l;
1521 QString trimmed = proj4String.trimmed();
1522
1523 trimmed += QLatin1String( " +type=crs" );
1525
1526 {
1527 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1528 }
1529
1530 if ( !d->hasPj() )
1531 {
1532#ifdef QGISDEBUG
1533 const int errNo = proj_context_errno( ctx );
1534 QgsDebugMsg( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
1535#endif
1536 d->mIsValid = false;
1537 }
1538 else
1539 {
1540 d->mEllipsoidAcronym.clear();
1541 d->mIsValid = true;
1542 }
1543
1544 setMapUnits();
1545}
1546
1547bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
1548{
1549 bool res = false;
1550 d->mIsValid = false;
1551 d->mWktPreferred.clear();
1552
1553 PROJ_STRING_LIST warnings = nullptr;
1554 PROJ_STRING_LIST grammerErrors = nullptr;
1555 {
1556 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammerErrors ) ) );
1557 }
1558
1559 res = d->hasPj();
1560 if ( !res )
1561 {
1562 QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
1563 QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
1564 QgsDebugMsg( "INPUT: " + wkt );
1565 for ( auto iter = warnings; iter && *iter; ++iter )
1566 QgsDebugMsg( *iter );
1567 for ( auto iter = grammerErrors; iter && *iter; ++iter )
1568 QgsDebugMsg( *iter );
1569 QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
1570 }
1571 proj_string_list_destroy( warnings );
1572 proj_string_list_destroy( grammerErrors );
1573
1574 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1575 if ( !res )
1576 {
1577 locker.changeMode( QgsReadWriteLocker::Write );
1578 if ( !sDisableWktCache )
1579 sWktCache()->insert( wkt, *this );
1580 return d->mIsValid;
1581 }
1582
1583 if ( d->hasPj() )
1584 {
1585 // try 1 - maybe we can directly grab the auth name and code from the crs already?
1586 QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1587 QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1588
1589 if ( authName.isEmpty() || authCode.isEmpty() )
1590 {
1591 // try 2, use proj's identify method and see if there's a nice candidate we can use
1592 QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1593 }
1594
1595 if ( !authName.isEmpty() && !authCode.isEmpty() )
1596 {
1597 if ( loadFromAuthCode( authName, authCode ) )
1598 {
1599 locker.changeMode( QgsReadWriteLocker::Write );
1600 if ( !sDisableWktCache )
1601 sWktCache()->insert( wkt, *this );
1602 return d->mIsValid;
1603 }
1604 }
1605 else
1606 {
1607 // Still a valid CRS, just not a known one
1608 d->mIsValid = true;
1609 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1610 }
1611 setMapUnits();
1612 }
1613
1614 return d->mIsValid;
1615}
1616
1617void QgsCoordinateReferenceSystem::setMapUnits()
1618{
1619 if ( !d->mIsValid )
1620 {
1621 d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1622 return;
1623 }
1624
1625 if ( !d->hasPj() )
1626 {
1627 d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1628 return;
1629 }
1630
1631 PJ_CONTEXT *context = QgsProjContext::get();
1632 QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToSingleCrs( d->threadLocalProjObject() ) );
1633 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1634 if ( !coordinateSystem )
1635 {
1636 d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1637 return;
1638 }
1639
1640 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1641 if ( axisCount > 0 )
1642 {
1643 const char *outUnitName = nullptr;
1644 // Read only first axis
1645 proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
1646 nullptr,
1647 nullptr,
1648 nullptr,
1649 nullptr,
1650 &outUnitName,
1651 nullptr,
1652 nullptr );
1653
1654 const QString unitName( outUnitName );
1655
1656 // proj unit names are freeform -- they differ from authority to authority :(
1657 // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1658 if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
1659 unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1660 unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1661 unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
1662 unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1663 unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1664 unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
1665 unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
1666 unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1667 unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
1668 d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1669 else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0
1670 || unitName.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0
1671 || unitName.compare( QLatin1String( "meter" ), Qt::CaseInsensitive ) == 0 )
1672 d->mMapUnits = QgsUnitTypes::DistanceMeters;
1673 // we don't differentiate between these, suck it imperial users!
1674 else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
1675 unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
1676 d->mMapUnits = QgsUnitTypes::DistanceFeet;
1677 else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1678 d->mMapUnits = QgsUnitTypes::DistanceKilometers;
1679 else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1680 d->mMapUnits = QgsUnitTypes::DistanceCentimeters;
1681 else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1682 d->mMapUnits = QgsUnitTypes::DistanceMillimeters;
1683 else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
1684 d->mMapUnits = QgsUnitTypes::DistanceMiles;
1685 else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
1687 else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
1688 d->mMapUnits = QgsUnitTypes::DistanceYards;
1689 // TODO - maybe more values to handle here?
1690 else
1691 d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1692 return;
1693 }
1694 else
1695 {
1696 d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1697 return;
1698 }
1699}
1700
1701
1703{
1704 if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1705 || !d->mIsValid )
1706 {
1707 QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1708 "work if prj acr ellipsoid acr and proj4string are set"
1709 " and the current projection is valid!", 4 );
1710 return 0;
1711 }
1712
1715 int myResult;
1716
1717 // Set up the query to retrieve the projection information
1718 // needed to populate the list
1719 QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1720 "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1721 .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1722 QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1723 // Get the full path name to the sqlite3 spatial reference database.
1724 QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1725
1726 //check the db is available
1727 myResult = openDatabase( myDatabaseFileName, database );
1728 if ( myResult != SQLITE_OK )
1729 {
1730 return 0;
1731 }
1732
1733 statement = database.prepare( mySql, myResult );
1734 if ( myResult == SQLITE_OK )
1735 {
1736
1737 while ( statement.step() == SQLITE_ROW )
1738 {
1739 QString mySrsId = statement.columnAsText( 0 );
1740 QString myProj4String = statement.columnAsText( 1 );
1741 if ( toProj() == myProj4String.trimmed() )
1742 {
1743 return mySrsId.toLong();
1744 }
1745 }
1746 }
1747
1748 //
1749 // Try the users db now
1750 //
1751
1752 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1753 //check the db is available
1754 myResult = openDatabase( myDatabaseFileName, database );
1755 if ( myResult != SQLITE_OK )
1756 {
1757 return 0;
1758 }
1759
1760 statement = database.prepare( mySql, myResult );
1761
1762 if ( myResult == SQLITE_OK )
1763 {
1764 while ( statement.step() == SQLITE_ROW )
1765 {
1766 QString mySrsId = statement.columnAsText( 0 );
1767 QString myProj4String = statement.columnAsText( 1 );
1768 if ( toProj() == myProj4String.trimmed() )
1769 {
1770 return mySrsId.toLong();
1771 }
1772 }
1773 }
1774
1775 return 0;
1776}
1777
1779{
1780 // shortcut
1781 if ( d == srs.d )
1782 return true;
1783
1784 if ( !d->mIsValid && !srs.d->mIsValid )
1785 return true;
1786
1787 if ( !d->mIsValid || !srs.d->mIsValid )
1788 return false;
1789
1790 if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
1791 return false;
1792
1793 const bool isUser = d->mSrsId >= USER_CRS_START_ID;
1794 const bool otherIsUser = srs.d->mSrsId >= USER_CRS_START_ID;
1795 if ( isUser != otherIsUser )
1796 return false;
1797
1798 // we can't directly compare authid for user crses -- the actual definition of these may have changed
1799 if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
1800 return d->mAuthId == srs.d->mAuthId;
1801
1802 return toWkt( WKT_PREFERRED ) == srs.toWkt( WKT_PREFERRED );
1803}
1804
1806{
1807 return !( *this == srs );
1808}
1809
1810QString QgsCoordinateReferenceSystem::toWkt( WktVariant variant, bool multiline, int indentationWidth ) const
1811{
1812 if ( PJ *obj = d->threadLocalProjObject() )
1813 {
1814 const bool isDefaultPreferredFormat = variant == WKT_PREFERRED && !multiline;
1815 if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
1816 {
1817 // can use cached value
1818 return d->mWktPreferred;
1819 }
1820
1821 PJ_WKT_TYPE type = PJ_WKT1_GDAL;
1822 switch ( variant )
1823 {
1824 case WKT1_GDAL:
1825 type = PJ_WKT1_GDAL;
1826 break;
1827 case WKT1_ESRI:
1828 type = PJ_WKT1_ESRI;
1829 break;
1830 case WKT2_2015:
1831 type = PJ_WKT2_2015;
1832 break;
1834 type = PJ_WKT2_2015_SIMPLIFIED;
1835 break;
1836 case WKT2_2019:
1837 type = PJ_WKT2_2019;
1838 break;
1840 type = PJ_WKT2_2019_SIMPLIFIED;
1841 break;
1842 }
1843
1844 const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
1845 const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
1846 const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
1847 QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
1848
1849 if ( isDefaultPreferredFormat )
1850 {
1851 // cache result for later use
1852 d->mWktPreferred = res;
1853 }
1854
1855 return res;
1856 }
1857 return QString();
1858}
1859
1860bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
1861{
1862 d.detach();
1863 bool result = true;
1864 QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
1865
1866 if ( ! srsNode.isNull() )
1867 {
1868 bool initialized = false;
1869
1870 bool ok = false;
1871 long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
1872
1873 QDomNode node;
1874
1875 if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
1876 {
1877 node = srsNode.namedItem( QStringLiteral( "authid" ) );
1878 if ( !node.isNull() )
1879 {
1880 createFromOgcWmsCrs( node.toElement().text() );
1881 if ( isValid() )
1882 {
1883 initialized = true;
1884 }
1885 }
1886
1887 if ( !initialized )
1888 {
1889 node = srsNode.namedItem( QStringLiteral( "epsg" ) );
1890 if ( !node.isNull() )
1891 {
1892 operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
1893 if ( isValid() )
1894 {
1895 initialized = true;
1896 }
1897 }
1898 }
1899 }
1900
1901 // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
1902 if ( !initialized )
1903 {
1904 // before doing anything, we grab and set the stored CRS name (description).
1905 // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
1906 // or the user's custom CRS list), then we will correctly show the CRS with its original
1907 // name (instead of just "custom crs")
1908 const QString description = srsNode.namedItem( QStringLiteral( "description" ) ).toElement().text();
1909
1910 const QString wkt = srsNode.namedItem( QStringLiteral( "wkt" ) ).toElement().text();
1911 initialized = createFromWktInternal( wkt, description );
1912 }
1913
1914 if ( !initialized )
1915 {
1916 node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1917 const QString proj4 = node.toElement().text();
1918 initialized = createFromProj( proj4 );
1919 }
1920
1921 if ( !initialized )
1922 {
1923 // Setting from elements one by one
1924 node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1925 const QString proj4 = node.toElement().text();
1926 if ( !proj4.trimmed().isEmpty() )
1927 setProjString( node.toElement().text() );
1928
1929 node = srsNode.namedItem( QStringLiteral( "srsid" ) );
1930 d->mSrsId = node.toElement().text().toLong();
1931
1932 node = srsNode.namedItem( QStringLiteral( "srid" ) );
1933 d->mSRID = node.toElement().text().toLong();
1934
1935 node = srsNode.namedItem( QStringLiteral( "authid" ) );
1936 d->mAuthId = node.toElement().text();
1937
1938 node = srsNode.namedItem( QStringLiteral( "description" ) );
1939 d->mDescription = node.toElement().text();
1940
1941 node = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
1942 d->mProjectionAcronym = node.toElement().text();
1943
1944 node = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
1945 d->mEllipsoidAcronym = node.toElement().text();
1946
1947 node = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
1948 d->mIsGeographic = node.toElement().text() == QLatin1String( "true" );
1949
1950 d->mWktPreferred.clear();
1951
1952 //make sure the map units have been set
1953 setMapUnits();
1954 }
1955
1956 const QString epoch = srsNode.toElement().attribute( QStringLiteral( "coordinateEpoch" ) );
1957 if ( !epoch.isEmpty() )
1958 {
1959 bool epochOk = false;
1960 d->mCoordinateEpoch = epoch.toDouble( &epochOk );
1961 if ( !epochOk )
1962 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
1963 }
1964 else
1965 {
1966 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
1967 }
1968
1969 mNativeFormat = qgsEnumKeyToValue<Qgis::CrsDefinitionFormat>( srsNode.toElement().attribute( QStringLiteral( "nativeFormat" ) ), Qgis::CrsDefinitionFormat::Wkt );
1970 }
1971 else
1972 {
1973 // Return empty CRS if none was found in the XML.
1974 d = new QgsCoordinateReferenceSystemPrivate();
1975 result = false;
1976 }
1977 return result;
1978}
1979
1980bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
1981{
1982 QDomElement layerNode = node.toElement();
1983 QDomElement srsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
1984
1985 srsElement.setAttribute( QStringLiteral( "nativeFormat" ), qgsEnumValueToKey<Qgis::CrsDefinitionFormat>( mNativeFormat ) );
1986
1987 if ( std::isfinite( d->mCoordinateEpoch ) )
1988 {
1989 srsElement.setAttribute( QStringLiteral( "coordinateEpoch" ), d->mCoordinateEpoch );
1990 }
1991
1992 QDomElement wktElement = doc.createElement( QStringLiteral( "wkt" ) );
1993 wktElement.appendChild( doc.createTextNode( toWkt( WKT_PREFERRED ) ) );
1994 srsElement.appendChild( wktElement );
1995
1996 QDomElement proj4Element = doc.createElement( QStringLiteral( "proj4" ) );
1997 proj4Element.appendChild( doc.createTextNode( toProj() ) );
1998 srsElement.appendChild( proj4Element );
1999
2000 QDomElement srsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
2001 srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
2002 srsElement.appendChild( srsIdElement );
2003
2004 QDomElement sridElement = doc.createElement( QStringLiteral( "srid" ) );
2005 sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
2006 srsElement.appendChild( sridElement );
2007
2008 QDomElement authidElement = doc.createElement( QStringLiteral( "authid" ) );
2009 authidElement.appendChild( doc.createTextNode( authid() ) );
2010 srsElement.appendChild( authidElement );
2011
2012 QDomElement descriptionElement = doc.createElement( QStringLiteral( "description" ) );
2013 descriptionElement.appendChild( doc.createTextNode( description() ) );
2014 srsElement.appendChild( descriptionElement );
2015
2016 QDomElement projectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
2017 projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
2018 srsElement.appendChild( projectionAcronymElement );
2019
2020 QDomElement ellipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
2021 ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
2022 srsElement.appendChild( ellipsoidAcronymElement );
2023
2024 QDomElement geographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
2025 QString geoFlagText = QStringLiteral( "false" );
2026 if ( isGeographic() )
2027 {
2028 geoFlagText = QStringLiteral( "true" );
2029 }
2030
2031 geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
2032 srsElement.appendChild( geographicFlagElement );
2033
2034 layerNode.appendChild( srsElement );
2035
2036 return true;
2037}
2038
2039//
2040// Static helper methods below this point only please!
2041//
2042
2043
2044// Returns the whole proj4 string for the selected srsid
2045//this is a static method! NOTE I've made it private for now to reduce API clutter TS
2046QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
2047{
2048 QString myDatabaseFileName;
2049 QString myProjString;
2050 QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
2051
2052 //
2053 // Determine if this is a user projection or a system on
2054 // user projection defs all have srs_id >= 100000
2055 //
2056 if ( srsId >= USER_CRS_START_ID )
2057 {
2058 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2059 QFileInfo myFileInfo;
2060 myFileInfo.setFile( myDatabaseFileName );
2061 if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
2062 {
2063 QgsDebugMsg( QStringLiteral( "users qgis.db not found" ) );
2064 return QString();
2065 }
2066 }
2067 else //must be a system projection then
2068 {
2069 myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2070 }
2071
2074
2075 int rc;
2076 rc = openDatabase( myDatabaseFileName, database );
2077 if ( rc )
2078 {
2079 return QString();
2080 }
2081
2082 statement = database.prepare( mySql, rc );
2083
2084 if ( rc == SQLITE_OK )
2085 {
2086 if ( statement.step() == SQLITE_ROW )
2087 {
2088 myProjString = statement.columnAsText( 0 );
2089 }
2090 }
2091
2092 return myProjString;
2093}
2094
2095int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
2096{
2097 int myResult;
2098 if ( readonly )
2099 myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
2100 else
2101 myResult = database.open( path );
2102
2103 if ( myResult != SQLITE_OK )
2104 {
2105 QgsDebugMsg( "Can't open database: " + database.errorMessage() );
2106 // XXX This will likely never happen since on open, sqlite creates the
2107 // database if it does not exist.
2108 // ... unfortunately it happens on Windows
2109 QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
2110 .arg( path )
2111 .arg( myResult )
2112 .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2113 }
2114 return myResult;
2115}
2116
2118{
2119 sCustomSrsValidation = f;
2120}
2121
2123{
2124 return sCustomSrsValidation;
2125}
2126
2127void QgsCoordinateReferenceSystem::debugPrint()
2128{
2129 QgsDebugMsg( QStringLiteral( "***SpatialRefSystem***" ) );
2130 QgsDebugMsg( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ) );
2131 QgsDebugMsg( "* SrsId : " + QString::number( d->mSrsId ) );
2132 QgsDebugMsg( "* Proj4 : " + toProj() );
2133 QgsDebugMsg( "* WKT : " + toWkt( WKT_PREFERRED ) );
2134 QgsDebugMsg( "* Desc. : " + d->mDescription );
2136 {
2137 QgsDebugMsg( QStringLiteral( "* Units : meters" ) );
2138 }
2139 else if ( mapUnits() == QgsUnitTypes::DistanceFeet )
2140 {
2141 QgsDebugMsg( QStringLiteral( "* Units : feet" ) );
2142 }
2144 {
2145 QgsDebugMsg( QStringLiteral( "* Units : degrees" ) );
2146 }
2147}
2148
2150{
2151 mValidationHint = html;
2152}
2153
2155{
2156 return mValidationHint;
2157}
2158
2160{
2162}
2163
2165{
2166 mNativeFormat = format;
2167}
2168
2170{
2171 return mNativeFormat;
2172}
2173
2174long QgsCoordinateReferenceSystem::getRecordCount()
2175{
2178 int myResult;
2179 long myRecordCount = 0;
2180 //check the db is available
2181 myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2182 if ( myResult != SQLITE_OK )
2183 {
2184 QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
2185 return 0;
2186 }
2187 // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2188 QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
2189 statement = database.prepare( mySql, myResult );
2190 if ( myResult == SQLITE_OK )
2191 {
2192 if ( statement.step() == SQLITE_ROW )
2193 {
2194 QString myRecordCountString = statement.columnAsText( 0 );
2195 myRecordCount = myRecordCountString.toLong();
2196 }
2197 }
2198 return myRecordCount;
2199}
2200
2202{
2203 PJ_CONTEXT *pjContext = QgsProjContext::get();
2204 bool isGeographic = false;
2205 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, crs ) );
2206 if ( coordinateSystem )
2207 {
2208 const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2209 if ( axisCount > 0 )
2210 {
2211 const char *outUnitAuthName = nullptr;
2212 const char *outUnitAuthCode = nullptr;
2213 // Read only first axis
2214 proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
2215 nullptr,
2216 nullptr,
2217 nullptr,
2218 nullptr,
2219 nullptr,
2220 &outUnitAuthName,
2221 &outUnitAuthCode );
2222
2223 if ( outUnitAuthName && outUnitAuthCode )
2224 {
2225 const char *unitCategory = nullptr;
2226 if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2227 {
2228 isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
2229 }
2230 }
2231 }
2232 }
2233 return isGeographic;
2234}
2235
2236void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2237{
2238 thread_local const QRegularExpression projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
2239 const QRegularExpressionMatch projMatch = projRegExp.match( proj );
2240 if ( !projMatch.hasMatch() )
2241 {
2242 QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
2243 return;
2244 }
2245 operation = projMatch.captured( 1 );
2246
2247 const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
2248 if ( ellipseMatch.hasMatch() )
2249 {
2250 ellipsoid = ellipseMatch.captured( 1 );
2251 }
2252 else
2253 {
2254 // satisfy not null constraint on ellipsoid_acronym field
2255 // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2256 // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2257 // set for these CRSes). Better just hack around and make the constraint happy for now,
2258 // and hope that the definitions get corrected in future.
2259 ellipsoid = "";
2260 }
2261}
2262
2263
2264bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2265{
2266 d.detach();
2267 d->mIsValid = false;
2268 d->mWktPreferred.clear();
2269
2270 PJ_CONTEXT *pjContext = QgsProjContext::get();
2271 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2272 if ( !crs )
2273 {
2274 return false;
2275 }
2276
2277 switch ( proj_get_type( crs.get() ) )
2278 {
2279 case PJ_TYPE_VERTICAL_CRS:
2280 return false;
2281
2282 default:
2283 break;
2284 }
2285
2287
2288 QString proj4 = getFullProjString( crs.get() );
2289 proj4.replace( QLatin1String( "+type=crs" ), QString() );
2290 proj4 = proj4.trimmed();
2291
2292 d->mIsValid = true;
2293 d->mProj4 = proj4;
2294 d->mWktPreferred.clear();
2295 d->mDescription = QString( proj_get_name( crs.get() ) );
2296 d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
2297 d->mIsGeographic = testIsGeographic( crs.get() );
2298 d->mAxisInvertedDirty = true;
2299 QString operation;
2300 QString ellipsoid;
2302 d->mProjectionAcronym = operation;
2303 d->mEllipsoidAcronym.clear();
2304 d->setPj( std::move( crs ) );
2305
2306 const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
2307 if ( !dbVals.isEmpty() )
2308 {
2309 const QStringList parts = dbVals.split( ',' );
2310 d->mSrsId = parts.at( 0 ).toInt();
2311 d->mSRID = parts.at( 1 ).toInt();
2312 }
2313
2314 setMapUnits();
2315
2316 return true;
2317}
2318
2319QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2320{
2321 QList<long> results;
2322 // check user defined projection database
2323 const QString db = QgsApplication::qgisUserDatabaseFilePath();
2324
2325 QFileInfo myInfo( db );
2326 if ( !myInfo.exists() )
2327 {
2328 QgsDebugMsg( "failed : " + db + " does not exist!" );
2329 return results;
2330 }
2331
2334
2335 //check the db is available
2336 int result = openDatabase( db, database );
2337 if ( result != SQLITE_OK )
2338 {
2339 QgsDebugMsg( "failed : " + db + " could not be opened!" );
2340 return results;
2341 }
2342
2343 QString sql = QStringLiteral( "select srs_id from tbl_srs where srs_id >= %1" ).arg( USER_CRS_START_ID );
2344 int rc;
2345 statement = database.prepare( sql, rc );
2346 while ( true )
2347 {
2348 int ret = statement.step();
2349
2350 if ( ret == SQLITE_DONE )
2351 {
2352 // there are no more rows to fetch - we can stop looping
2353 break;
2354 }
2355
2356 if ( ret == SQLITE_ROW )
2357 {
2358 results.append( statement.columnAsInt64( 0 ) );
2359 }
2360 else
2361 {
2362 QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2363 break;
2364 }
2365 }
2366
2367 return results;
2368}
2369
2370long QgsCoordinateReferenceSystem::matchToUserCrs() const
2371{
2372 PJ *obj = d->threadLocalProjObject();
2373 if ( !obj )
2374 return 0;
2375
2376 const QList< long > ids = userSrsIds();
2377 for ( long id : ids )
2378 {
2380 if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2381 {
2382 return id;
2383 }
2384 }
2385 return 0;
2386}
2387
2388static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2389{
2390#ifndef QGISDEBUG
2391 Q_UNUSED( message )
2392#endif
2393 if ( level == PJ_LOG_ERROR )
2394 {
2395 QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
2396 }
2397 else if ( level == PJ_LOG_DEBUG )
2398 {
2399 QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
2400 }
2401}
2402
2404{
2405 setlocale( LC_ALL, "C" );
2406 QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2407
2408 int inserted = 0, updated = 0, deleted = 0, errors = 0;
2409
2410 QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2411
2413 if ( database.open( dbFilePath ) != SQLITE_OK )
2414 {
2415 QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2416 return -1;
2417 }
2418
2419 if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2420 {
2421 QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2422 return -1;
2423 }
2424
2426 int result;
2427 char *errMsg = nullptr;
2428
2429 if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2430 {
2431 QString sql = QStringLiteral( "INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)" )
2432 .arg( QString::number( PROJ_VERSION_MAJOR ),
2433 QString::number( PROJ_VERSION_MINOR ),
2434 QString::number( PROJ_VERSION_PATCH ) );
2435 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2436 {
2437 QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2438 sql,
2439 database.errorMessage(),
2440 errMsg ? errMsg : "(unknown error)" ) );
2441 if ( errMsg )
2442 sqlite3_free( errMsg );
2443 return -1;
2444 }
2445 }
2446 else
2447 {
2448 // retrieve last update details
2449 QString sql = QStringLiteral( "SELECT proj_major, proj_minor, proj_patch FROM tbl_info" );
2450 statement = database.prepare( sql, result );
2451 if ( result != SQLITE_OK )
2452 {
2453 QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2454 return -1;
2455 }
2456 if ( statement.step() == SQLITE_ROW )
2457 {
2458 int major = statement.columnAsInt64( 0 );
2459 int minor = statement.columnAsInt64( 1 );
2460 int patch = statement.columnAsInt64( 2 );
2461 if ( major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2462 // yay, nothing to do!
2463 return 0;
2464 }
2465 else
2466 {
2467 QgsDebugMsg( QStringLiteral( "Could not retrieve previous CRS sync PROJ version number" ) );
2468 return -1;
2469 }
2470 }
2471
2472 PJ_CONTEXT *pjContext = QgsProjContext::get();
2473 // silence proj warnings
2474 proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2475
2476 PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2477
2478 int nextSrsId = 63561;
2479 int nextSrId = 520003561;
2480 for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2481 {
2482 const QString authority( *authIter );
2483 QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
2484 PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2485
2486 QStringList allCodes;
2487
2488 for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2489 {
2490 const QString code( *codesIter );
2491 allCodes << QgsSqliteUtils::quotedString( code );
2492 QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
2493 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2494 if ( !crs )
2495 {
2496 QgsDebugMsg( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
2497 continue;
2498 }
2499
2500 switch ( proj_get_type( crs.get() ) )
2501 {
2502 case PJ_TYPE_VERTICAL_CRS: // don't need these in the CRS db
2503 continue;
2504
2505 default:
2506 break;
2507 }
2508
2510
2511 QString proj4 = getFullProjString( crs.get() );
2512 proj4.replace( QLatin1String( "+type=crs" ), QString() );
2513 proj4 = proj4.trimmed();
2514
2515 if ( proj4.isEmpty() )
2516 {
2517 QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
2518 // satisfy not null constraint
2519 proj4 = "";
2520 }
2521
2522 const bool deprecated = proj_is_deprecated( crs.get() );
2523 const QString name( proj_get_name( crs.get() ) );
2524
2525 QString sql = QStringLiteral( "SELECT parameters,description,deprecated FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
2526 statement = database.prepare( sql, result );
2527 if ( result != SQLITE_OK )
2528 {
2529 QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2530 continue;
2531 }
2532
2533 QString srsProj4;
2534 QString srsDesc;
2535 bool srsDeprecated = deprecated;
2536 if ( statement.step() == SQLITE_ROW )
2537 {
2538 srsProj4 = statement.columnAsText( 0 );
2539 srsDesc = statement.columnAsText( 1 );
2540 srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2541 }
2542
2543 if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
2544 {
2545 if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
2546 {
2547 errMsg = nullptr;
2548 sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name=%4 AND auth_id=%5" )
2549 .arg( QgsSqliteUtils::quotedString( proj4 ) )
2550 .arg( QgsSqliteUtils::quotedString( name ) )
2551 .arg( deprecated ? 1 : 0 )
2552 .arg( QgsSqliteUtils::quotedString( authority ), QgsSqliteUtils::quotedString( code ) );
2553
2554 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2555 {
2556 QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2557 sql,
2558 database.errorMessage(),
2559 errMsg ? errMsg : "(unknown error)" ) );
2560 if ( errMsg )
2561 sqlite3_free( errMsg );
2562 errors++;
2563 }
2564 else
2565 {
2566 updated++;
2567 }
2568 }
2569 }
2570 else
2571 {
2572 // there's a not-null contraint on these columns, so we must use empty strings instead
2573 QString operation = "";
2574 QString ellps = "";
2576 const bool isGeographic = testIsGeographic( crs.get() );
2577
2578 // work out srid and srsid
2579 const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
2580 QString srsId;
2581 QString srId;
2582 if ( !dbVals.isEmpty() )
2583 {
2584 const QStringList parts = dbVals.split( ',' );
2585 srsId = parts.at( 0 );
2586 srId = parts.at( 1 );
2587 }
2588 if ( srId.isEmpty() )
2589 {
2590 srId = QString::number( nextSrId );
2591 nextSrId++;
2592 }
2593 if ( srsId.isEmpty() )
2594 {
2595 srsId = QString::number( nextSrsId );
2596 nextSrsId++;
2597 }
2598
2599 if ( !srsId.isEmpty() )
2600 {
2601 sql = QStringLiteral( "INSERT INTO tbl_srs(srs_id, description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1, %2,%3,%4,%5,%6,%7,%8,%9,%10)" )
2602 .arg( srsId )
2603 .arg( QgsSqliteUtils::quotedString( name ),
2607 .arg( srId )
2608 .arg( QgsSqliteUtils::quotedString( authority ) )
2609 .arg( QgsSqliteUtils::quotedString( code ) )
2610 .arg( isGeographic ? 1 : 0 )
2611 .arg( deprecated ? 1 : 0 );
2612 }
2613 else
2614 {
2615 sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%2,%3,%4,%5,%6,%7,%8,%9,%10)" )
2616 .arg( QgsSqliteUtils::quotedString( name ),
2620 .arg( srId )
2621 .arg( QgsSqliteUtils::quotedString( authority ) )
2622 .arg( QgsSqliteUtils::quotedString( code ) )
2623 .arg( isGeographic ? 1 : 0 )
2624 .arg( deprecated ? 1 : 0 );
2625 }
2626
2627 errMsg = nullptr;
2628 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2629 {
2630 inserted++;
2631 }
2632 else
2633 {
2634 qCritical( "Could not execute: %s [%s/%s]\n",
2635 sql.toLocal8Bit().constData(),
2636 sqlite3_errmsg( database.get() ),
2637 errMsg ? errMsg : "(unknown error)" );
2638 errors++;
2639
2640 if ( errMsg )
2641 sqlite3_free( errMsg );
2642 }
2643 }
2644 }
2645
2646 proj_string_list_destroy( codes );
2647
2648 const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
2649 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2650 {
2651 deleted = sqlite3_changes( database.get() );
2652 }
2653 else
2654 {
2655 errors++;
2656 qCritical( "Could not execute: %s [%s]\n",
2657 sql.toLocal8Bit().constData(),
2658 sqlite3_errmsg( database.get() ) );
2659 }
2660
2661 }
2662 proj_string_list_destroy( authorities );
2663
2664 QString sql = QStringLiteral( "UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3" )
2665 .arg( QString::number( PROJ_VERSION_MAJOR ),
2666 QString::number( PROJ_VERSION_MINOR ),
2667 QString::number( PROJ_VERSION_PATCH ) );
2668 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2669 {
2670 QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2671 sql,
2672 database.errorMessage(),
2673 errMsg ? errMsg : "(unknown error)" ) );
2674 if ( errMsg )
2675 sqlite3_free( errMsg );
2676 return -1;
2677 }
2678
2679 if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2680 {
2681 QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2683 sqlite3_errmsg( database.get() ) )
2684 );
2685 return -1;
2686 }
2687
2688#ifdef QGISDEBUG
2689 QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
2690#else
2691 Q_UNUSED( deleted )
2692#endif
2693
2694 if ( errors > 0 )
2695 return -errors;
2696 else
2697 return updated + inserted;
2698}
2699
2700const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
2701{
2702 return *sStringCache();
2703}
2704
2705const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
2706{
2707 return *sProj4Cache();
2708}
2709
2710const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
2711{
2712 return *sOgcCache();
2713}
2714
2715const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
2716{
2717 return *sWktCache();
2718}
2719
2720const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
2721{
2722 return *sSrIdCache();
2723}
2724
2725const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
2726{
2727 return *sSrsIdCache();
2728}
2729
2731{
2732 if ( isGeographic() )
2733 {
2734 return *this;
2735 }
2736
2737 if ( PJ *obj = d->threadLocalProjObject() )
2738 {
2739 PJ_CONTEXT *pjContext = QgsProjContext::get();
2740 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( pjContext, obj ) );
2741 if ( !geoCrs )
2743
2744 if ( !testIsGeographic( geoCrs.get() ) )
2746
2747 QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, geoCrs.get() ) );
2748 if ( !normalized )
2750
2751 return QgsCoordinateReferenceSystem::fromProjObject( normalized.get() );
2752 }
2753 else
2754 {
2756 }
2757}
2758
2760{
2761 if ( isGeographic() )
2762 {
2763 return d->mAuthId;
2764 }
2765 else if ( PJ *obj = d->threadLocalProjObject() )
2766 {
2767 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
2768 return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
2769 }
2770 else
2771 {
2772 return QString();
2773 }
2774}
2775
2777{
2778 return d->threadLocalProjObject();
2779}
2780
2782{
2784 crs.createFromProjObject( object );
2785 return crs;
2786}
2787
2789{
2790 d.detach();
2791 d->mIsValid = false;
2792 d->mProj4.clear();
2793 d->mWktPreferred.clear();
2794
2795 if ( !object )
2796 {
2797 return false;
2798 }
2799
2800 switch ( proj_get_type( object ) )
2801 {
2802 case PJ_TYPE_GEODETIC_CRS:
2803 case PJ_TYPE_GEOCENTRIC_CRS:
2804 case PJ_TYPE_GEOGRAPHIC_CRS:
2805 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
2806 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
2807 case PJ_TYPE_VERTICAL_CRS:
2808 case PJ_TYPE_PROJECTED_CRS:
2809 case PJ_TYPE_COMPOUND_CRS:
2810 case PJ_TYPE_TEMPORAL_CRS:
2811 case PJ_TYPE_ENGINEERING_CRS:
2812 case PJ_TYPE_BOUND_CRS:
2813 case PJ_TYPE_OTHER_CRS:
2814 break;
2815
2816 default:
2817 return false;
2818 }
2819
2820 d->setPj( QgsProjUtils::crsToSingleCrs( object ) );
2821
2822 if ( !d->hasPj() )
2823 {
2824 return d->mIsValid;
2825 }
2826 else
2827 {
2828 // maybe we can directly grab the auth name and code from the crs
2829 const QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
2830 const QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
2831 if ( !authName.isEmpty() && !authCode.isEmpty() && loadFromAuthCode( authName, authCode ) )
2832 {
2833 return d->mIsValid;
2834 }
2835 else
2836 {
2837 // Still a valid CRS, just not a known one
2838 d->mIsValid = true;
2839 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
2840 setMapUnits();
2841 }
2842 }
2843
2844 return d->mIsValid;
2845}
2846
2848{
2849 QStringList projections;
2850 const QList<QgsCoordinateReferenceSystem> res = recentCoordinateReferenceSystems();
2851 projections.reserve( res.size() );
2852 for ( const QgsCoordinateReferenceSystem &crs : res )
2853 {
2854 projections << QString::number( crs.srsid() );
2855 }
2856 return projections;
2857}
2858
2860{
2861 QList<QgsCoordinateReferenceSystem> res;
2862
2863 // Read settings from persistent storage
2864 QgsSettings settings;
2865 QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
2866 QStringList projectionsWkt = settings.value( QStringLiteral( "UI/recentProjectionsWkt" ) ).toStringList();
2867 QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
2868 int max = std::max( projectionsAuthId.size(), std::max( projectionsProj4.size(), projectionsWkt.size() ) );
2869 res.reserve( max );
2870 for ( int i = 0; i < max; ++i )
2871 {
2872 const QString proj = projectionsProj4.value( i );
2873 const QString wkt = projectionsWkt.value( i );
2874 const QString authid = projectionsAuthId.value( i );
2875
2877 if ( !authid.isEmpty() )
2879 if ( !crs.isValid() && !wkt.isEmpty() )
2880 crs.createFromWkt( wkt );
2881 if ( !crs.isValid() && !proj.isEmpty() )
2882 crs.createFromProj( wkt );
2883
2884 if ( crs.isValid() )
2885 res << crs;
2886 }
2887 return res;
2888}
2889
2891{
2892 // we only want saved and standard CRSes in the recent list
2893 if ( crs.srsid() == 0 || !crs.isValid() )
2894 return;
2895
2896 QList<QgsCoordinateReferenceSystem> recent = recentCoordinateReferenceSystems();
2897 recent.removeAll( crs );
2898 recent.insert( 0, crs );
2899
2900 // trim to max 30 items
2901 recent = recent.mid( 0, 30 );
2902 QStringList authids;
2903 authids.reserve( recent.size() );
2904 QStringList proj;
2905 proj.reserve( recent.size() );
2906 QStringList wkt;
2907 wkt.reserve( recent.size() );
2908 for ( const QgsCoordinateReferenceSystem &c : std::as_const( recent ) )
2909 {
2910 authids << c.authid();
2911 proj << c.toProj();
2912 wkt << c.toWkt( WKT_PREFERRED );
2913 }
2914
2915 QgsSettings settings;
2916 settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids );
2917 settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt );
2918 settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj );
2919}
2920
2922{
2923 sSrIdCacheLock()->lockForWrite();
2924 if ( !sDisableSrIdCache )
2925 {
2926 if ( disableCache )
2927 sDisableSrIdCache = true;
2928 sSrIdCache()->clear();
2929 }
2930 sSrIdCacheLock()->unlock();
2931
2932 sOgcLock()->lockForWrite();
2933 if ( !sDisableOgcCache )
2934 {
2935 if ( disableCache )
2936 sDisableOgcCache = true;
2937 sOgcCache()->clear();
2938 }
2939 sOgcLock()->unlock();
2940
2941 sProj4CacheLock()->lockForWrite();
2942 if ( !sDisableProjCache )
2943 {
2944 if ( disableCache )
2945 sDisableProjCache = true;
2946 sProj4Cache()->clear();
2947 }
2948 sProj4CacheLock()->unlock();
2949
2950 sCRSWktLock()->lockForWrite();
2951 if ( !sDisableWktCache )
2952 {
2953 if ( disableCache )
2954 sDisableWktCache = true;
2955 sWktCache()->clear();
2956 }
2957 sCRSWktLock()->unlock();
2958
2959 sCRSSrsIdLock()->lockForWrite();
2960 if ( !sDisableSrsIdCache )
2961 {
2962 if ( disableCache )
2963 sDisableSrsIdCache = true;
2964 sSrsIdCache()->clear();
2965 }
2966 sCRSSrsIdLock()->unlock();
2967
2968 sCrsStringLock()->lockForWrite();
2969 if ( !sDisableStringCache )
2970 {
2971 if ( disableCache )
2972 sDisableStringCache = true;
2973 sStringCache()->clear();
2974 }
2975 sCrsStringLock()->unlock();
2976}
2977
2978// invalid < regular < user
2980{
2981 if ( c1.d == c2.d )
2982 return false;
2983
2984 if ( !c1.d->mIsValid && !c2.d->mIsValid )
2985 return false;
2986
2987 if ( !c1.d->mIsValid && c2.d->mIsValid )
2988 return false;
2989
2990 if ( c1.d->mIsValid && !c2.d->mIsValid )
2991 return true;
2992
2993 const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
2994 const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
2995
2996 if ( c1IsUser && !c2IsUser )
2997 return true;
2998
2999 if ( !c1IsUser && c2IsUser )
3000 return false;
3001
3002 if ( !c1IsUser && !c2IsUser )
3003 {
3004 if ( c1.d->mAuthId != c2.d->mAuthId )
3005 return c1.d->mAuthId > c2.d->mAuthId;
3006 }
3007 else
3008 {
3009 const QString wkt1 = c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3010 const QString wkt2 = c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3011 if ( wkt1 != wkt2 )
3012 return wkt1 > wkt2;
3013 }
3014
3015 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3016 return false;
3017
3018 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3019 return false;
3020
3021 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3022 return false;
3023
3024 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3025 return true;
3026
3027 return c1.d->mCoordinateEpoch > c2.d->mCoordinateEpoch;
3028}
3029
3031{
3032 if ( c1.d == c2.d )
3033 return false;
3034
3035 if ( !c1.d->mIsValid && !c2.d->mIsValid )
3036 return false;
3037
3038 if ( c1.d->mIsValid && !c2.d->mIsValid )
3039 return false;
3040
3041 if ( !c1.d->mIsValid && c2.d->mIsValid )
3042 return true;
3043
3044 const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
3045 const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
3046
3047 if ( !c1IsUser && c2IsUser )
3048 return true;
3049
3050 if ( c1IsUser && !c2IsUser )
3051 return false;
3052
3053 if ( !c1IsUser && !c2IsUser )
3054 {
3055 if ( c1.d->mAuthId != c2.d->mAuthId )
3056 return c1.d->mAuthId < c2.d->mAuthId;
3057 }
3058 else
3059 {
3060 const QString wkt1 = c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3061 const QString wkt2 = c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3062 if ( wkt1 != wkt2 )
3063 return wkt1 < wkt2;
3064 }
3065
3066 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3067 return false;
3068
3069 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3070 return false;
3071
3072 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3073 return false;
3074
3075 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3076 return true;
3077
3078 return c1.d->mCoordinateEpoch < c2.d->mCoordinateEpoch;
3079}
3080
3082{
3083 return !( c1 < c2 );
3084}
3086{
3087 return !( c1 > c2 );
3088}
CrsDefinitionFormat
CRS definition formats.
Definition: qgis.h:1857
CrsAxisDirection
Data provider flags.
Definition: qgis.h:1117
@ ColumnPositive
Column positive.
@ SouthSouthEast
South South East.
@ NorthWest
North West.
@ ColumnNegative
Column negative.
@ RowPositive
Row positive.
@ DisplayDown
Display down.
@ GeocentricZ
Geocentric (Z)
@ DisplayRight
Display right.
@ WestSouthWest
West South West.
@ RowNegative
Row negative.
@ NorthNorthEast
North North East.
@ EastNorthEast
East North East.
@ Unspecified
Unspecified.
@ NorthEast
North East.
@ NorthNorthWest
North North West.
@ GeocentricY
Geocentric (Y)
@ SouthEast
South East.
@ CounterClockwise
Counter clockwise.
@ SouthSouthWest
South South West.
@ DisplayLeft
Display left.
@ WestNorthWest
West North West.
@ EastSouthEast
East South East.
@ SouthWest
South West.
@ DisplayUp
Display up.
@ GeocentricX
Geocentric (X)
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.
long addUserCrs(const QgsCoordinateReferenceSystem &crs, const QString &name, Qgis::CrsDefinitionFormat nativeFormat=Qgis::CrsDefinitionFormat::Wkt)
Adds a new crs definition as a custom ("USER") CRS.
QMap< QString, QgsProjOperation > projOperations() const
Returns a map of all valid PROJ operations.
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool createFromOgcWmsCrs(const QString &crs)
Sets this CRS to the given OGC WMS-format Coordinate Reference Systems.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromWkt(const QString &wkt)
Sets this CRS using a WKT definition.
bool createFromString(const QString &definition)
Set up this CRS from a string definition.
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 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.
static void setCustomCrsValidation(CUSTOM_CRS_VALIDATION f)
Sets custom function to force valid CRS.
static 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.
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
Overloaded == operator used to compare to CRS's.
QString projectionAcronym() const
Returns the projection acronym for the projection used by 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
Overloaded != operator used to compare to CRS's.
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.
QgsDatumEnsemble datumEnsemble() const SIP_THROW(QgsNotSupportedException)
Attempts to retrieve datum ensemble details from the CRS.
IdentifierType
Type of identifier string to create.
@ MediumString
A medium-length string, recommended for general purpose use.
@ ShortString
A heavily abbreviated string, for use when a compact representation is required.
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)
Assignment operator.
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 QList< QgsCoordinateReferenceSystem > recentCoordinateReferenceSystems()
Returns a list of recently used CRS.
PJ * projObject() const
Returns the underlying PROJ PJ object corresponding to the CRS, or nullptr if the CRS is invalid.
QString userFriendlyIdentifier(IdentifierType type=MediumString) const
Returns a user friendly identifier for the CRS.
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.
WktVariant
WKT formatting variants, only used for builds based on Proj >= 6.
@ WKT1_GDAL
WKT1 as traditionally output by GDAL, deriving from OGC 01-009. A notable departure from WKT1_GDAL wi...
@ WKT2_2019_SIMPLIFIED
WKT2_2019 with the simplification rule of WKT2_SIMPLIFIED.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
@ WKT2_2015
Full WKT2 string, conforming to ISO 19162:2015(E) / OGC 12-063r5 with all possible nodes and new keyw...
@ WKT2_2019
Full WKT2 string, conforming to ISO 19162:2019 / OGC 18-010, with all possible nodes and new keyword ...
@ WKT1_ESRI
WKT1 as traditionally output by ESRI software, deriving from OGC 99-049.
@ WKT2_2015_SIMPLIFIED
Same as WKT2_2015 with the following exceptions: UNIT keyword used. ID node only on top element....
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 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.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QgsProjOperation operation() const
Returns information about the PROJ operation associated with the coordinate reference system,...
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
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.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
bool createFromProjObject(PJ *object)
Sets this CRS by passing it a PROJ PJ object, corresponding to a PROJ CRS object.
QString celestialBodyName() const SIP_THROW(QgsNotSupportedException)
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
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 void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:122
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Custom exception class which is raised when an operation is not supported.
Definition: qgsexception.h:118
CRSFlavor
CRS flavor.
Definition: qgsogcutils.h:587
@ AUTH_CODE
unknown/unhandled flavor
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
Q_GADGET 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.
@ FlagMatchBoundCrsToUnderlyingSourceCrs
Allow matching a BoundCRS object to its underlying SourceCRS.
Definition: qgsprojutils.h:121
static bool isDynamic(const PJ *crs)
Returns true if the given proj coordinate system is a dynamic CRS.
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 proj_pj_unique_ptr crsToSingleCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), extract a single crs fro...
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.
Definition: qgsprojutils.h:141
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...
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
@ Write
Lock for write.
@ Read
Lock for read.
void unlock()
Unlocks the lock.
void changeMode(Mode mode)
Change the mode of the lock to mode.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
void setYMinimum(double y) SIP_HOLDGIL
Set the minimum y value.
Definition: qgsrectangle.h:161
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void setYMaximum(double y) SIP_HOLDGIL
Set the maximum y value.
Definition: qgsrectangle.h:166
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static QString quotedString(const QString &value)
Returns a quoted string value, surround by ' characters and with special characters correctly escaped...
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:68
@ DistanceMeters
Meters.
Definition: qgsunittypes.h:69
@ DistanceDegrees
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:75
@ DistanceKilometers
Kilometers.
Definition: qgsunittypes.h:70
@ DistanceMiles
Terrestrial miles.
Definition: qgsunittypes.h:74
@ DistanceUnknownUnit
Unknown distance unit.
Definition: qgsunittypes.h:78
@ DistanceMillimeters
Millimeters.
Definition: qgsunittypes.h:77
@ DistanceYards
Imperial yards.
Definition: qgsunittypes.h:73
@ DistanceFeet
Imperial feet.
Definition: qgsunittypes.h:71
@ DistanceNauticalMiles
Nautical miles.
Definition: qgsunittypes.h:72
@ DistanceCentimeters
Centimeters.
Definition: qgsunittypes.h:76
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.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:3061
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:2466
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:3060
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:2511
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:3009
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)
void * OGRSpatialReferenceH
struct PJconsts PJ
struct projCtx_t PJ_CONTEXT
void(* CUSTOM_CRS_VALIDATION)(QgsCoordinateReferenceSystem &)
const QMap< QString, QString > sAuthIdToQgisSrsIdMap
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs