QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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 QgsDebugError( "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 QgsDebugError( "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 QgsDebugError( 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 //QgsDebugMsgLevel( "definition: " + definition + " wkt = " + wkt, 2 );
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 QgsDebugError( "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 QgsDebugError( "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 QgsDebugError( 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;
1193}
1194
1196{
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();
1217 else if ( !description().isEmpty() )
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 )
1465 return Qgis::DistanceUnit::Unknown;
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 const auto parts { authid().split( ':' ) };
1501 if ( parts.length() == 2 )
1502 {
1503 if ( parts[0] == QLatin1String( "EPSG" ) )
1504 return QStringLiteral( "http://www.opengis.net/def/crs/EPSG/0/%1" ).arg( parts[1] ) ;
1505 else if ( parts[0] == QLatin1String( "OGC" ) )
1506 {
1507 return QStringLiteral( "http://www.opengis.net/def/crs/OGC/1.3/%1" ).arg( parts[1] ) ;
1508 }
1509 else
1510 {
1511 QgsMessageLog::logMessage( QStringLiteral( "Error converting published CRS to URI %1: (not OGC or EPSG)" ).arg( authid() ), QStringLiteral( "CRS" ), Qgis::MessageLevel::Critical );
1512 }
1513 }
1514 else
1515 {
1516 QgsMessageLog::logMessage( QStringLiteral( "Error converting published CRS to URI: %1" ).arg( authid() ), QStringLiteral( "CRS" ), Qgis::MessageLevel::Critical );
1517 }
1518 return QString();
1519}
1520
1522{
1523 if ( !d->mIsValid )
1524 return;
1525
1526 if ( d->mSrsId >= USER_CRS_START_ID )
1527 {
1528 // user CRS, so update to new definition
1529 createFromSrsId( d->mSrsId );
1530 }
1531 else
1532 {
1533 // nothing to do -- only user CRS definitions can be changed
1534 }
1535}
1536
1537void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1538{
1539 d.detach();
1540 d->mProj4 = proj4String;
1541 d->mWktPreferred.clear();
1542
1543 QgsLocaleNumC l;
1544 QString trimmed = proj4String.trimmed();
1545
1546 trimmed += QLatin1String( " +type=crs" );
1548
1549 {
1550 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1551 }
1552
1553 if ( !d->hasPj() )
1554 {
1555#ifdef QGISDEBUG
1556 const int errNo = proj_context_errno( ctx );
1557 QgsDebugError( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
1558#endif
1559 d->mIsValid = false;
1560 }
1561 else
1562 {
1563 d->mEllipsoidAcronym.clear();
1564 d->mIsValid = true;
1565 }
1566
1567 setMapUnits();
1568}
1569
1570bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
1571{
1572 bool res = false;
1573 d->mIsValid = false;
1574 d->mWktPreferred.clear();
1575
1576 PROJ_STRING_LIST warnings = nullptr;
1577 PROJ_STRING_LIST grammerErrors = nullptr;
1578 {
1579 d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammerErrors ) ) );
1580 }
1581
1582 res = d->hasPj();
1583 if ( !res )
1584 {
1585 QgsDebugMsgLevel( QStringLiteral( "\n---------------------------------------------------------------" ), 2 );
1586 QgsDebugMsgLevel( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ), 2 );
1587 QgsDebugMsgLevel( "INPUT: " + wkt, 2 );
1588 for ( auto iter = warnings; iter && *iter; ++iter )
1589 QgsDebugMsgLevel( *iter, 2 );
1590 for ( auto iter = grammerErrors; iter && *iter; ++iter )
1591 QgsDebugMsgLevel( *iter, 2 );
1592 QgsDebugMsgLevel( QStringLiteral( "---------------------------------------------------------------\n" ), 2 );
1593 }
1594 proj_string_list_destroy( warnings );
1595 proj_string_list_destroy( grammerErrors );
1596
1597 QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1598 if ( !res )
1599 {
1600 locker.changeMode( QgsReadWriteLocker::Write );
1601 if ( !sDisableWktCache )
1602 sWktCache()->insert( wkt, *this );
1603 return d->mIsValid;
1604 }
1605
1606 if ( d->hasPj() )
1607 {
1608 // try 1 - maybe we can directly grab the auth name and code from the crs already?
1609 QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1610 QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1611
1612 if ( authName.isEmpty() || authCode.isEmpty() )
1613 {
1614 // try 2, use proj's identify method and see if there's a nice candidate we can use
1615 QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1616 }
1617
1618 if ( !authName.isEmpty() && !authCode.isEmpty() )
1619 {
1620 if ( loadFromAuthCode( authName, authCode ) )
1621 {
1622 locker.changeMode( QgsReadWriteLocker::Write );
1623 if ( !sDisableWktCache )
1624 sWktCache()->insert( wkt, *this );
1625 return d->mIsValid;
1626 }
1627 }
1628 else
1629 {
1630 // Still a valid CRS, just not a known one
1631 d->mIsValid = true;
1632 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1633 }
1634 setMapUnits();
1635 }
1636
1637 return d->mIsValid;
1638}
1639
1640void QgsCoordinateReferenceSystem::setMapUnits()
1641{
1642 if ( !d->mIsValid )
1643 {
1644 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1645 return;
1646 }
1647
1648 if ( !d->hasPj() )
1649 {
1650 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1651 return;
1652 }
1653
1654 PJ_CONTEXT *context = QgsProjContext::get();
1655 QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToSingleCrs( d->threadLocalProjObject() ) );
1656 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1657 if ( !coordinateSystem )
1658 {
1659 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1660 return;
1661 }
1662
1663 const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1664 if ( axisCount > 0 )
1665 {
1666 const char *outUnitName = nullptr;
1667 // Read only first axis
1668 proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
1669 nullptr,
1670 nullptr,
1671 nullptr,
1672 nullptr,
1673 &outUnitName,
1674 nullptr,
1675 nullptr );
1676
1677 const QString unitName( outUnitName );
1678
1679 // proj unit names are freeform -- they differ from authority to authority :(
1680 // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1681 if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
1682 unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1683 unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1684 unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
1685 unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1686 unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1687 unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
1688 unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
1689 unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1690 unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
1691 d->mMapUnits = Qgis::DistanceUnit::Degrees;
1692 else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0
1693 || unitName.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0
1694 || unitName.compare( QLatin1String( "meter" ), Qt::CaseInsensitive ) == 0 )
1695 d->mMapUnits = Qgis::DistanceUnit::Meters;
1696 // we don't differentiate between these, suck it imperial users!
1697 else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
1698 unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
1699 d->mMapUnits = Qgis::DistanceUnit::Feet;
1700 else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1701 d->mMapUnits = Qgis::DistanceUnit::Kilometers;
1702 else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1703 d->mMapUnits = Qgis::DistanceUnit::Centimeters;
1704 else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1705 d->mMapUnits = Qgis::DistanceUnit::Millimeters;
1706 else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
1707 d->mMapUnits = Qgis::DistanceUnit::Miles;
1708 else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
1709 d->mMapUnits = Qgis::DistanceUnit::NauticalMiles;
1710 else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
1711 d->mMapUnits = Qgis::DistanceUnit::Yards;
1712 // TODO - maybe more values to handle here?
1713 else
1714 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1715 return;
1716 }
1717 else
1718 {
1719 d->mMapUnits = Qgis::DistanceUnit::Unknown;
1720 return;
1721 }
1722}
1723
1724
1726{
1727 if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1728 || !d->mIsValid )
1729 {
1730 QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1731 "work if prj acr ellipsoid acr and proj4string are set"
1732 " and the current projection is valid!", 4 );
1733 return 0;
1734 }
1735
1738 int myResult;
1739
1740 // Set up the query to retrieve the projection information
1741 // needed to populate the list
1742 QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1743 "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1744 .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1745 QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1746 // Get the full path name to the sqlite3 spatial reference database.
1747 QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1748
1749 //check the db is available
1750 myResult = openDatabase( myDatabaseFileName, database );
1751 if ( myResult != SQLITE_OK )
1752 {
1753 return 0;
1754 }
1755
1756 statement = database.prepare( mySql, myResult );
1757 if ( myResult == SQLITE_OK )
1758 {
1759
1760 while ( statement.step() == SQLITE_ROW )
1761 {
1762 QString mySrsId = statement.columnAsText( 0 );
1763 QString myProj4String = statement.columnAsText( 1 );
1764 if ( toProj() == myProj4String.trimmed() )
1765 {
1766 return mySrsId.toLong();
1767 }
1768 }
1769 }
1770
1771 //
1772 // Try the users db now
1773 //
1774
1775 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1776 //check the db is available
1777 myResult = openDatabase( myDatabaseFileName, database );
1778 if ( myResult != SQLITE_OK )
1779 {
1780 return 0;
1781 }
1782
1783 statement = database.prepare( mySql, myResult );
1784
1785 if ( myResult == SQLITE_OK )
1786 {
1787 while ( statement.step() == SQLITE_ROW )
1788 {
1789 QString mySrsId = statement.columnAsText( 0 );
1790 QString myProj4String = statement.columnAsText( 1 );
1791 if ( toProj() == myProj4String.trimmed() )
1792 {
1793 return mySrsId.toLong();
1794 }
1795 }
1796 }
1797
1798 return 0;
1799}
1800
1802{
1803 // shortcut
1804 if ( d == srs.d )
1805 return true;
1806
1807 if ( !d->mIsValid && !srs.d->mIsValid )
1808 return true;
1809
1810 if ( !d->mIsValid || !srs.d->mIsValid )
1811 return false;
1812
1813 if ( !qgsNanCompatibleEquals( d->mCoordinateEpoch, srs.d->mCoordinateEpoch ) )
1814 return false;
1815
1816 const bool isUser = d->mSrsId >= USER_CRS_START_ID;
1817 const bool otherIsUser = srs.d->mSrsId >= USER_CRS_START_ID;
1818 if ( isUser != otherIsUser )
1819 return false;
1820
1821 // we can't directly compare authid for user crses -- the actual definition of these may have changed
1822 if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
1823 return d->mAuthId == srs.d->mAuthId;
1824
1825 return toWkt( WKT_PREFERRED ) == srs.toWkt( WKT_PREFERRED );
1826}
1827
1829{
1830 return !( *this == srs );
1831}
1832
1833QString QgsCoordinateReferenceSystem::toWkt( WktVariant variant, bool multiline, int indentationWidth ) const
1834{
1835 if ( PJ *obj = d->threadLocalProjObject() )
1836 {
1837 const bool isDefaultPreferredFormat = variant == WKT_PREFERRED && !multiline;
1838 if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
1839 {
1840 // can use cached value
1841 return d->mWktPreferred;
1842 }
1843
1844 PJ_WKT_TYPE type = PJ_WKT1_GDAL;
1845 switch ( variant )
1846 {
1847 case WKT1_GDAL:
1848 type = PJ_WKT1_GDAL;
1849 break;
1850 case WKT1_ESRI:
1851 type = PJ_WKT1_ESRI;
1852 break;
1853 case WKT2_2015:
1854 type = PJ_WKT2_2015;
1855 break;
1857 type = PJ_WKT2_2015_SIMPLIFIED;
1858 break;
1859 case WKT2_2019:
1860 type = PJ_WKT2_2019;
1861 break;
1863 type = PJ_WKT2_2019_SIMPLIFIED;
1864 break;
1865 }
1866
1867 const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
1868 const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
1869 const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
1870 QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
1871
1872 if ( isDefaultPreferredFormat )
1873 {
1874 // cache result for later use
1875 d->mWktPreferred = res;
1876 }
1877
1878 return res;
1879 }
1880 return QString();
1881}
1882
1883bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
1884{
1885 d.detach();
1886 bool result = true;
1887 QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
1888
1889 if ( ! srsNode.isNull() )
1890 {
1891 bool initialized = false;
1892
1893 bool ok = false;
1894 long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
1895
1896 QDomNode node;
1897
1898 if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
1899 {
1900 node = srsNode.namedItem( QStringLiteral( "authid" ) );
1901 if ( !node.isNull() )
1902 {
1903 createFromOgcWmsCrs( node.toElement().text() );
1904 if ( isValid() )
1905 {
1906 initialized = true;
1907 }
1908 }
1909
1910 if ( !initialized )
1911 {
1912 node = srsNode.namedItem( QStringLiteral( "epsg" ) );
1913 if ( !node.isNull() )
1914 {
1915 operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
1916 if ( isValid() )
1917 {
1918 initialized = true;
1919 }
1920 }
1921 }
1922 }
1923
1924 // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
1925 if ( !initialized )
1926 {
1927 // before doing anything, we grab and set the stored CRS name (description).
1928 // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
1929 // or the user's custom CRS list), then we will correctly show the CRS with its original
1930 // name (instead of just "custom crs")
1931 const QString description = srsNode.namedItem( QStringLiteral( "description" ) ).toElement().text();
1932
1933 const QString wkt = srsNode.namedItem( QStringLiteral( "wkt" ) ).toElement().text();
1934 initialized = createFromWktInternal( wkt, description );
1935 }
1936
1937 if ( !initialized )
1938 {
1939 node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1940 const QString proj4 = node.toElement().text();
1941 initialized = createFromProj( proj4 );
1942 }
1943
1944 if ( !initialized )
1945 {
1946 // Setting from elements one by one
1947 node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1948 const QString proj4 = node.toElement().text();
1949 if ( !proj4.trimmed().isEmpty() )
1950 setProjString( node.toElement().text() );
1951
1952 node = srsNode.namedItem( QStringLiteral( "srsid" ) );
1953 d->mSrsId = node.toElement().text().toLong();
1954
1955 node = srsNode.namedItem( QStringLiteral( "srid" ) );
1956 d->mSRID = node.toElement().text().toLong();
1957
1958 node = srsNode.namedItem( QStringLiteral( "authid" ) );
1959 d->mAuthId = node.toElement().text();
1960
1961 node = srsNode.namedItem( QStringLiteral( "description" ) );
1962 d->mDescription = node.toElement().text();
1963
1964 node = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
1965 d->mProjectionAcronym = node.toElement().text();
1966
1967 node = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
1968 d->mEllipsoidAcronym = node.toElement().text();
1969
1970 node = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
1971 d->mIsGeographic = node.toElement().text() == QLatin1String( "true" );
1972
1973 d->mWktPreferred.clear();
1974
1975 //make sure the map units have been set
1976 setMapUnits();
1977 }
1978
1979 const QString epoch = srsNode.toElement().attribute( QStringLiteral( "coordinateEpoch" ) );
1980 if ( !epoch.isEmpty() )
1981 {
1982 bool epochOk = false;
1983 d->mCoordinateEpoch = epoch.toDouble( &epochOk );
1984 if ( !epochOk )
1985 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
1986 }
1987 else
1988 {
1989 d->mCoordinateEpoch = std::numeric_limits< double >::quiet_NaN();
1990 }
1991
1992 mNativeFormat = qgsEnumKeyToValue<Qgis::CrsDefinitionFormat>( srsNode.toElement().attribute( QStringLiteral( "nativeFormat" ) ), Qgis::CrsDefinitionFormat::Wkt );
1993 }
1994 else
1995 {
1996 // Return empty CRS if none was found in the XML.
1997 d = new QgsCoordinateReferenceSystemPrivate();
1998 result = false;
1999 }
2000 return result;
2001}
2002
2003bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
2004{
2005 QDomElement layerNode = node.toElement();
2006 QDomElement srsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
2007
2008 srsElement.setAttribute( QStringLiteral( "nativeFormat" ), qgsEnumValueToKey<Qgis::CrsDefinitionFormat>( mNativeFormat ) );
2009
2010 if ( std::isfinite( d->mCoordinateEpoch ) )
2011 {
2012 srsElement.setAttribute( QStringLiteral( "coordinateEpoch" ), d->mCoordinateEpoch );
2013 }
2014
2015 QDomElement wktElement = doc.createElement( QStringLiteral( "wkt" ) );
2016 wktElement.appendChild( doc.createTextNode( toWkt( WKT_PREFERRED ) ) );
2017 srsElement.appendChild( wktElement );
2018
2019 QDomElement proj4Element = doc.createElement( QStringLiteral( "proj4" ) );
2020 proj4Element.appendChild( doc.createTextNode( toProj() ) );
2021 srsElement.appendChild( proj4Element );
2022
2023 QDomElement srsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
2024 srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
2025 srsElement.appendChild( srsIdElement );
2026
2027 QDomElement sridElement = doc.createElement( QStringLiteral( "srid" ) );
2028 sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
2029 srsElement.appendChild( sridElement );
2030
2031 QDomElement authidElement = doc.createElement( QStringLiteral( "authid" ) );
2032 authidElement.appendChild( doc.createTextNode( authid() ) );
2033 srsElement.appendChild( authidElement );
2034
2035 QDomElement descriptionElement = doc.createElement( QStringLiteral( "description" ) );
2036 descriptionElement.appendChild( doc.createTextNode( description() ) );
2037 srsElement.appendChild( descriptionElement );
2038
2039 QDomElement projectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
2040 projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
2041 srsElement.appendChild( projectionAcronymElement );
2042
2043 QDomElement ellipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
2044 ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
2045 srsElement.appendChild( ellipsoidAcronymElement );
2046
2047 QDomElement geographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
2048 QString geoFlagText = QStringLiteral( "false" );
2049 if ( isGeographic() )
2050 {
2051 geoFlagText = QStringLiteral( "true" );
2052 }
2053
2054 geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
2055 srsElement.appendChild( geographicFlagElement );
2056
2057 layerNode.appendChild( srsElement );
2058
2059 return true;
2060}
2061
2062//
2063// Static helper methods below this point only please!
2064//
2065
2066
2067// Returns the whole proj4 string for the selected srsid
2068//this is a static method! NOTE I've made it private for now to reduce API clutter TS
2069QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
2070{
2071 QString myDatabaseFileName;
2072 QString myProjString;
2073 QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
2074
2075 //
2076 // Determine if this is a user projection or a system on
2077 // user projection defs all have srs_id >= 100000
2078 //
2079 if ( srsId >= USER_CRS_START_ID )
2080 {
2081 myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
2082 QFileInfo myFileInfo;
2083 myFileInfo.setFile( myDatabaseFileName );
2084 if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
2085 {
2086 QgsDebugError( QStringLiteral( "users qgis.db not found" ) );
2087 return QString();
2088 }
2089 }
2090 else //must be a system projection then
2091 {
2092 myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
2093 }
2094
2097
2098 int rc;
2099 rc = openDatabase( myDatabaseFileName, database );
2100 if ( rc )
2101 {
2102 return QString();
2103 }
2104
2105 statement = database.prepare( mySql, rc );
2106
2107 if ( rc == SQLITE_OK )
2108 {
2109 if ( statement.step() == SQLITE_ROW )
2110 {
2111 myProjString = statement.columnAsText( 0 );
2112 }
2113 }
2114
2115 return myProjString;
2116}
2117
2118int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
2119{
2120 int myResult;
2121 if ( readonly )
2122 myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
2123 else
2124 myResult = database.open( path );
2125
2126 if ( myResult != SQLITE_OK )
2127 {
2128 QgsDebugError( "Can't open database: " + database.errorMessage() );
2129 // XXX This will likely never happen since on open, sqlite creates the
2130 // database if it does not exist.
2131 // ... unfortunately it happens on Windows
2132 QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
2133 .arg( path )
2134 .arg( myResult )
2135 .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2136 }
2137 return myResult;
2138}
2139
2141{
2142 sCustomSrsValidation = f;
2143}
2144
2146{
2147 return sCustomSrsValidation;
2148}
2149
2150void QgsCoordinateReferenceSystem::debugPrint()
2151{
2152 QgsDebugMsgLevel( QStringLiteral( "***SpatialRefSystem***" ), 1 );
2153 QgsDebugMsgLevel( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ), 1 );
2154 QgsDebugMsgLevel( "* SrsId : " + QString::number( d->mSrsId ), 1 );
2155 QgsDebugMsgLevel( "* Proj4 : " + toProj(), 1 );
2156 QgsDebugMsgLevel( "* WKT : " + toWkt( WKT_PREFERRED ), 1 );
2157 QgsDebugMsgLevel( "* Desc. : " + d->mDescription, 1 );
2158 if ( mapUnits() == Qgis::DistanceUnit::Meters )
2159 {
2160 QgsDebugMsgLevel( QStringLiteral( "* Units : meters" ), 1 );
2161 }
2162 else if ( mapUnits() == Qgis::DistanceUnit::Feet )
2163 {
2164 QgsDebugMsgLevel( QStringLiteral( "* Units : feet" ), 1 );
2165 }
2166 else if ( mapUnits() == Qgis::DistanceUnit::Degrees )
2167 {
2168 QgsDebugMsgLevel( QStringLiteral( "* Units : degrees" ), 1 );
2169 }
2170}
2171
2173{
2174 mValidationHint = html;
2175}
2176
2178{
2179 return mValidationHint;
2180}
2181
2183{
2185}
2186
2188{
2189 mNativeFormat = format;
2190}
2191
2193{
2194 return mNativeFormat;
2195}
2196
2197long QgsCoordinateReferenceSystem::getRecordCount()
2198{
2201 int myResult;
2202 long myRecordCount = 0;
2203 //check the db is available
2204 myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2205 if ( myResult != SQLITE_OK )
2206 {
2207 QgsDebugError( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
2208 return 0;
2209 }
2210 // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2211 QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
2212 statement = database.prepare( mySql, myResult );
2213 if ( myResult == SQLITE_OK )
2214 {
2215 if ( statement.step() == SQLITE_ROW )
2216 {
2217 QString myRecordCountString = statement.columnAsText( 0 );
2218 myRecordCount = myRecordCountString.toLong();
2219 }
2220 }
2221 return myRecordCount;
2222}
2223
2225{
2226 PJ_CONTEXT *pjContext = QgsProjContext::get();
2227 bool isGeographic = false;
2228 QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, crs ) );
2229 if ( coordinateSystem )
2230 {
2231 const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2232 if ( axisCount > 0 )
2233 {
2234 const char *outUnitAuthName = nullptr;
2235 const char *outUnitAuthCode = nullptr;
2236 // Read only first axis
2237 proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
2238 nullptr,
2239 nullptr,
2240 nullptr,
2241 nullptr,
2242 nullptr,
2243 &outUnitAuthName,
2244 &outUnitAuthCode );
2245
2246 if ( outUnitAuthName && outUnitAuthCode )
2247 {
2248 const char *unitCategory = nullptr;
2249 if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2250 {
2251 isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
2252 }
2253 }
2254 }
2255 }
2256 return isGeographic;
2257}
2258
2259void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2260{
2261 thread_local const QRegularExpression projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
2262 const QRegularExpressionMatch projMatch = projRegExp.match( proj );
2263 if ( !projMatch.hasMatch() )
2264 {
2265 QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
2266 return;
2267 }
2268 operation = projMatch.captured( 1 );
2269
2270 const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
2271 if ( ellipseMatch.hasMatch() )
2272 {
2273 ellipsoid = ellipseMatch.captured( 1 );
2274 }
2275 else
2276 {
2277 // satisfy not null constraint on ellipsoid_acronym field
2278 // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2279 // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2280 // set for these CRSes). Better just hack around and make the constraint happy for now,
2281 // and hope that the definitions get corrected in future.
2282 ellipsoid = "";
2283 }
2284}
2285
2286
2287bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2288{
2289 d.detach();
2290 d->mIsValid = false;
2291 d->mWktPreferred.clear();
2292
2293 PJ_CONTEXT *pjContext = QgsProjContext::get();
2294 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2295 if ( !crs )
2296 {
2297 return false;
2298 }
2299
2300 switch ( proj_get_type( crs.get() ) )
2301 {
2302 case PJ_TYPE_VERTICAL_CRS:
2303 return false;
2304
2305 default:
2306 break;
2307 }
2308
2310
2311 QString proj4 = getFullProjString( crs.get() );
2312 proj4.replace( QLatin1String( "+type=crs" ), QString() );
2313 proj4 = proj4.trimmed();
2314
2315 d->mIsValid = true;
2316 d->mProj4 = proj4;
2317 d->mWktPreferred.clear();
2318 d->mDescription = QString( proj_get_name( crs.get() ) );
2319 d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
2320 d->mIsGeographic = testIsGeographic( crs.get() );
2321 d->mAxisInvertedDirty = true;
2322 QString operation;
2323 QString ellipsoid;
2325 d->mProjectionAcronym = operation;
2326 d->mEllipsoidAcronym.clear();
2327 d->setPj( std::move( crs ) );
2328
2329 const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
2330 if ( !dbVals.isEmpty() )
2331 {
2332 const QStringList parts = dbVals.split( ',' );
2333 d->mSrsId = parts.at( 0 ).toInt();
2334 d->mSRID = parts.at( 1 ).toInt();
2335 }
2336
2337 setMapUnits();
2338
2339 return true;
2340}
2341
2342QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2343{
2344 QList<long> results;
2345 // check user defined projection database
2346 const QString db = QgsApplication::qgisUserDatabaseFilePath();
2347
2348 QFileInfo myInfo( db );
2349 if ( !myInfo.exists() )
2350 {
2351 QgsDebugError( "failed : " + db + " does not exist!" );
2352 return results;
2353 }
2354
2357
2358 //check the db is available
2359 int result = openDatabase( db, database );
2360 if ( result != SQLITE_OK )
2361 {
2362 QgsDebugError( "failed : " + db + " could not be opened!" );
2363 return results;
2364 }
2365
2366 QString sql = QStringLiteral( "select srs_id from tbl_srs where srs_id >= %1" ).arg( USER_CRS_START_ID );
2367 int rc;
2368 statement = database.prepare( sql, rc );
2369 while ( true )
2370 {
2371 int ret = statement.step();
2372
2373 if ( ret == SQLITE_DONE )
2374 {
2375 // there are no more rows to fetch - we can stop looping
2376 break;
2377 }
2378
2379 if ( ret == SQLITE_ROW )
2380 {
2381 results.append( statement.columnAsInt64( 0 ) );
2382 }
2383 else
2384 {
2385 QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2386 break;
2387 }
2388 }
2389
2390 return results;
2391}
2392
2393long QgsCoordinateReferenceSystem::matchToUserCrs() const
2394{
2395 PJ *obj = d->threadLocalProjObject();
2396 if ( !obj )
2397 return 0;
2398
2399 const QList< long > ids = userSrsIds();
2400 for ( long id : ids )
2401 {
2403 if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2404 {
2405 return id;
2406 }
2407 }
2408 return 0;
2409}
2410
2411static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2412{
2413#ifndef QGISDEBUG
2414 Q_UNUSED( message )
2415#endif
2416 if ( level == PJ_LOG_ERROR )
2417 {
2418 QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
2419 }
2420 else if ( level == PJ_LOG_DEBUG )
2421 {
2422 QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
2423 }
2424}
2425
2427{
2428 setlocale( LC_ALL, "C" );
2429 QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2430
2431 int inserted = 0, updated = 0, deleted = 0, errors = 0;
2432
2433 QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2434
2436 if ( database.open( dbFilePath ) != SQLITE_OK )
2437 {
2438 QgsDebugError( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2439 return -1;
2440 }
2441
2442 if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2443 {
2444 QgsDebugError( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2445 return -1;
2446 }
2447
2449 int result;
2450 char *errMsg = nullptr;
2451
2452 if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2453 {
2454 QString sql = QStringLiteral( "INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)" )
2455 .arg( QString::number( PROJ_VERSION_MAJOR ),
2456 QString::number( PROJ_VERSION_MINOR ),
2457 QString::number( PROJ_VERSION_PATCH ) );
2458 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2459 {
2460 QgsDebugError( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2461 sql,
2462 database.errorMessage(),
2463 errMsg ? errMsg : "(unknown error)" ) );
2464 if ( errMsg )
2465 sqlite3_free( errMsg );
2466 return -1;
2467 }
2468 }
2469 else
2470 {
2471 // retrieve last update details
2472 QString sql = QStringLiteral( "SELECT proj_major, proj_minor, proj_patch FROM tbl_info" );
2473 statement = database.prepare( sql, result );
2474 if ( result != SQLITE_OK )
2475 {
2476 QgsDebugError( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2477 return -1;
2478 }
2479 if ( statement.step() == SQLITE_ROW )
2480 {
2481 int major = statement.columnAsInt64( 0 );
2482 int minor = statement.columnAsInt64( 1 );
2483 int patch = statement.columnAsInt64( 2 );
2484 if ( major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2485 // yay, nothing to do!
2486 return 0;
2487 }
2488 else
2489 {
2490 QgsDebugError( QStringLiteral( "Could not retrieve previous CRS sync PROJ version number" ) );
2491 return -1;
2492 }
2493 }
2494
2495 PJ_CONTEXT *pjContext = QgsProjContext::get();
2496 // silence proj warnings
2497 proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2498
2499 PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2500
2501 int nextSrsId = 63561;
2502 int nextSrId = 520003561;
2503 for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2504 {
2505 const QString authority( *authIter );
2506 QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
2507 PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2508
2509 QStringList allCodes;
2510
2511 for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2512 {
2513 const QString code( *codesIter );
2514 allCodes << QgsSqliteUtils::quotedString( code );
2515 QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
2516 QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2517 if ( !crs )
2518 {
2519 QgsDebugError( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
2520 continue;
2521 }
2522
2523 switch ( proj_get_type( crs.get() ) )
2524 {
2525 case PJ_TYPE_VERTICAL_CRS: // don't need these in the CRS db
2526 continue;
2527
2528 default:
2529 break;
2530 }
2531
2533
2534 QString proj4 = getFullProjString( crs.get() );
2535 proj4.replace( QLatin1String( "+type=crs" ), QString() );
2536 proj4 = proj4.trimmed();
2537
2538 if ( proj4.isEmpty() )
2539 {
2540 QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
2541 // satisfy not null constraint
2542 proj4 = "";
2543 }
2544
2545 const bool deprecated = proj_is_deprecated( crs.get() );
2546 const QString name( proj_get_name( crs.get() ) );
2547
2548 QString sql = QStringLiteral( "SELECT parameters,description,deprecated FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
2549 statement = database.prepare( sql, result );
2550 if ( result != SQLITE_OK )
2551 {
2552 QgsDebugError( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2553 continue;
2554 }
2555
2556 QString srsProj4;
2557 QString srsDesc;
2558 bool srsDeprecated = deprecated;
2559 if ( statement.step() == SQLITE_ROW )
2560 {
2561 srsProj4 = statement.columnAsText( 0 );
2562 srsDesc = statement.columnAsText( 1 );
2563 srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2564 }
2565
2566 if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
2567 {
2568 if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
2569 {
2570 errMsg = nullptr;
2571 sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name=%4 AND auth_id=%5" )
2572 .arg( QgsSqliteUtils::quotedString( proj4 ) )
2573 .arg( QgsSqliteUtils::quotedString( name ) )
2574 .arg( deprecated ? 1 : 0 )
2575 .arg( QgsSqliteUtils::quotedString( authority ), QgsSqliteUtils::quotedString( code ) );
2576
2577 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2578 {
2579 QgsDebugError( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2580 sql,
2581 database.errorMessage(),
2582 errMsg ? errMsg : "(unknown error)" ) );
2583 if ( errMsg )
2584 sqlite3_free( errMsg );
2585 errors++;
2586 }
2587 else
2588 {
2589 updated++;
2590 }
2591 }
2592 }
2593 else
2594 {
2595 // there's a not-null contraint on these columns, so we must use empty strings instead
2596 QString operation = "";
2597 QString ellps = "";
2599 const bool isGeographic = testIsGeographic( crs.get() );
2600
2601 // work out srid and srsid
2602 const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
2603 QString srsId;
2604 QString srId;
2605 if ( !dbVals.isEmpty() )
2606 {
2607 const QStringList parts = dbVals.split( ',' );
2608 srsId = parts.at( 0 );
2609 srId = parts.at( 1 );
2610 }
2611 if ( srId.isEmpty() )
2612 {
2613 srId = QString::number( nextSrId );
2614 nextSrId++;
2615 }
2616 if ( srsId.isEmpty() )
2617 {
2618 srsId = QString::number( nextSrsId );
2619 nextSrsId++;
2620 }
2621
2622 if ( !srsId.isEmpty() )
2623 {
2624 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)" )
2625 .arg( srsId )
2626 .arg( QgsSqliteUtils::quotedString( name ),
2630 .arg( srId )
2631 .arg( QgsSqliteUtils::quotedString( authority ) )
2632 .arg( QgsSqliteUtils::quotedString( code ) )
2633 .arg( isGeographic ? 1 : 0 )
2634 .arg( deprecated ? 1 : 0 );
2635 }
2636 else
2637 {
2638 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)" )
2639 .arg( QgsSqliteUtils::quotedString( name ),
2643 .arg( srId )
2644 .arg( QgsSqliteUtils::quotedString( authority ) )
2645 .arg( QgsSqliteUtils::quotedString( code ) )
2646 .arg( isGeographic ? 1 : 0 )
2647 .arg( deprecated ? 1 : 0 );
2648 }
2649
2650 errMsg = nullptr;
2651 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2652 {
2653 inserted++;
2654 }
2655 else
2656 {
2657 qCritical( "Could not execute: %s [%s/%s]\n",
2658 sql.toLocal8Bit().constData(),
2659 sqlite3_errmsg( database.get() ),
2660 errMsg ? errMsg : "(unknown error)" );
2661 errors++;
2662
2663 if ( errMsg )
2664 sqlite3_free( errMsg );
2665 }
2666 }
2667 }
2668
2669 proj_string_list_destroy( codes );
2670
2671 const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
2672 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2673 {
2674 deleted = sqlite3_changes( database.get() );
2675 }
2676 else
2677 {
2678 errors++;
2679 qCritical( "Could not execute: %s [%s]\n",
2680 sql.toLocal8Bit().constData(),
2681 sqlite3_errmsg( database.get() ) );
2682 }
2683
2684 }
2685 proj_string_list_destroy( authorities );
2686
2687 QString sql = QStringLiteral( "UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3" )
2688 .arg( QString::number( PROJ_VERSION_MAJOR ),
2689 QString::number( PROJ_VERSION_MINOR ),
2690 QString::number( PROJ_VERSION_PATCH ) );
2691 if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2692 {
2693 QgsDebugError( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2694 sql,
2695 database.errorMessage(),
2696 errMsg ? errMsg : "(unknown error)" ) );
2697 if ( errMsg )
2698 sqlite3_free( errMsg );
2699 return -1;
2700 }
2701
2702 if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2703 {
2704 QgsDebugError( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2706 sqlite3_errmsg( database.get() ) )
2707 );
2708 return -1;
2709 }
2710
2711#ifdef QGISDEBUG
2712 QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
2713#else
2714 Q_UNUSED( deleted )
2715#endif
2716
2717 if ( errors > 0 )
2718 return -errors;
2719 else
2720 return updated + inserted;
2721}
2722
2723const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
2724{
2725 return *sStringCache();
2726}
2727
2728const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
2729{
2730 return *sProj4Cache();
2731}
2732
2733const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
2734{
2735 return *sOgcCache();
2736}
2737
2738const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
2739{
2740 return *sWktCache();
2741}
2742
2743const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
2744{
2745 return *sSrIdCache();
2746}
2747
2748const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
2749{
2750 return *sSrsIdCache();
2751}
2752
2754{
2755 if ( isGeographic() )
2756 {
2757 return *this;
2758 }
2759
2760 if ( PJ *obj = d->threadLocalProjObject() )
2761 {
2762 PJ_CONTEXT *pjContext = QgsProjContext::get();
2763 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( pjContext, obj ) );
2764 if ( !geoCrs )
2766
2767 if ( !testIsGeographic( geoCrs.get() ) )
2769
2770 QgsProjUtils::proj_pj_unique_ptr normalized( proj_normalize_for_visualization( pjContext, geoCrs.get() ) );
2771 if ( !normalized )
2773
2774 return QgsCoordinateReferenceSystem::fromProjObject( normalized.get() );
2775 }
2776 else
2777 {
2779 }
2780}
2781
2783{
2784 if ( isGeographic() )
2785 {
2786 return d->mAuthId;
2787 }
2788 else if ( PJ *obj = d->threadLocalProjObject() )
2789 {
2790 QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
2791 return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
2792 }
2793 else
2794 {
2795 return QString();
2796 }
2797}
2798
2800{
2801 return d->threadLocalProjObject();
2802}
2803
2805{
2807 crs.createFromProjObject( object );
2808 return crs;
2809}
2810
2812{
2813 d.detach();
2814 d->mIsValid = false;
2815 d->mProj4.clear();
2816 d->mWktPreferred.clear();
2817
2818 if ( !object )
2819 {
2820 return false;
2821 }
2822
2823 switch ( proj_get_type( object ) )
2824 {
2825 case PJ_TYPE_GEODETIC_CRS:
2826 case PJ_TYPE_GEOCENTRIC_CRS:
2827 case PJ_TYPE_GEOGRAPHIC_CRS:
2828 case PJ_TYPE_GEOGRAPHIC_2D_CRS:
2829 case PJ_TYPE_GEOGRAPHIC_3D_CRS:
2830 case PJ_TYPE_VERTICAL_CRS:
2831 case PJ_TYPE_PROJECTED_CRS:
2832 case PJ_TYPE_COMPOUND_CRS:
2833 case PJ_TYPE_TEMPORAL_CRS:
2834 case PJ_TYPE_ENGINEERING_CRS:
2835 case PJ_TYPE_BOUND_CRS:
2836 case PJ_TYPE_OTHER_CRS:
2837 break;
2838
2839 default:
2840 return false;
2841 }
2842
2843 d->setPj( QgsProjUtils::crsToSingleCrs( object ) );
2844
2845 if ( !d->hasPj() )
2846 {
2847 return d->mIsValid;
2848 }
2849 else
2850 {
2851 // maybe we can directly grab the auth name and code from the crs
2852 const QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
2853 const QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
2854 if ( !authName.isEmpty() && !authCode.isEmpty() && loadFromAuthCode( authName, authCode ) )
2855 {
2856 return d->mIsValid;
2857 }
2858 else
2859 {
2860 // Still a valid CRS, just not a known one
2861 d->mIsValid = true;
2862 d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
2863 setMapUnits();
2864 d->mIsGeographic = testIsGeographic( d->threadLocalProjObject() );
2865 }
2866 }
2867
2868 return d->mIsValid;
2869}
2870
2872{
2873 QStringList projections;
2874 const QList<QgsCoordinateReferenceSystem> res = recentCoordinateReferenceSystems();
2875 projections.reserve( res.size() );
2876 for ( const QgsCoordinateReferenceSystem &crs : res )
2877 {
2878 projections << QString::number( crs.srsid() );
2879 }
2880 return projections;
2881}
2882
2884{
2885 QList<QgsCoordinateReferenceSystem> res;
2886
2887 // Read settings from persistent storage
2888 QgsSettings settings;
2889 QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
2890 QStringList projectionsWkt = settings.value( QStringLiteral( "UI/recentProjectionsWkt" ) ).toStringList();
2891 QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
2892 int max = std::max( projectionsAuthId.size(), std::max( projectionsProj4.size(), projectionsWkt.size() ) );
2893 res.reserve( max );
2894 for ( int i = 0; i < max; ++i )
2895 {
2896 const QString proj = projectionsProj4.value( i );
2897 const QString wkt = projectionsWkt.value( i );
2898 const QString authid = projectionsAuthId.value( i );
2899
2901 if ( !authid.isEmpty() )
2903 if ( !crs.isValid() && !wkt.isEmpty() )
2904 crs.createFromWkt( wkt );
2905 if ( !crs.isValid() && !proj.isEmpty() )
2906 crs.createFromProj( wkt );
2907
2908 if ( crs.isValid() )
2909 res << crs;
2910 }
2911 return res;
2912}
2913
2915{
2916 // we only want saved and standard CRSes in the recent list
2917 if ( crs.srsid() == 0 || !crs.isValid() )
2918 return;
2919
2920 QList<QgsCoordinateReferenceSystem> recent = recentCoordinateReferenceSystems();
2921 recent.removeAll( crs );
2922 recent.insert( 0, crs );
2923
2924 // trim to max 30 items
2925 recent = recent.mid( 0, 30 );
2926 QStringList authids;
2927 authids.reserve( recent.size() );
2928 QStringList proj;
2929 proj.reserve( recent.size() );
2930 QStringList wkt;
2931 wkt.reserve( recent.size() );
2932 for ( const QgsCoordinateReferenceSystem &c : std::as_const( recent ) )
2933 {
2934 authids << c.authid();
2935 proj << c.toProj();
2936 wkt << c.toWkt( WKT_PREFERRED );
2937 }
2938
2939 QgsSettings settings;
2940 settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids );
2941 settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt );
2942 settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj );
2943}
2944
2945
2947{
2948 if ( crs.srsid() == 0 || !crs.isValid() )
2949 return;
2950
2951 QList<QgsCoordinateReferenceSystem> recent = recentCoordinateReferenceSystems();
2952 recent.removeAll( crs );
2953 QStringList authids;
2954 authids.reserve( recent.size() );
2955 QStringList proj;
2956 proj.reserve( recent.size() );
2957 QStringList wkt;
2958 wkt.reserve( recent.size() );
2959 for ( const QgsCoordinateReferenceSystem &c : std::as_const( recent ) )
2960 {
2961 authids << c.authid();
2962 proj << c.toProj();
2963 wkt << c.toWkt( WKT_PREFERRED );
2964 }
2965 QgsSettings settings;
2966 settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids );
2967 settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt );
2968 settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj );
2969}
2970
2972{
2973 QgsSettings settings;
2974 settings.remove( QStringLiteral( "UI/recentProjectionsAuthId" ) );
2975 settings.remove( QStringLiteral( "UI/recentProjectionsWkt" ) );
2976 settings.remove( QStringLiteral( "UI/recentProjectionsProj4" ) );
2977}
2978
2980{
2981 sSrIdCacheLock()->lockForWrite();
2982 if ( !sDisableSrIdCache )
2983 {
2984 if ( disableCache )
2985 sDisableSrIdCache = true;
2986 sSrIdCache()->clear();
2987 }
2988 sSrIdCacheLock()->unlock();
2989
2990 sOgcLock()->lockForWrite();
2991 if ( !sDisableOgcCache )
2992 {
2993 if ( disableCache )
2994 sDisableOgcCache = true;
2995 sOgcCache()->clear();
2996 }
2997 sOgcLock()->unlock();
2998
2999 sProj4CacheLock()->lockForWrite();
3000 if ( !sDisableProjCache )
3001 {
3002 if ( disableCache )
3003 sDisableProjCache = true;
3004 sProj4Cache()->clear();
3005 }
3006 sProj4CacheLock()->unlock();
3007
3008 sCRSWktLock()->lockForWrite();
3009 if ( !sDisableWktCache )
3010 {
3011 if ( disableCache )
3012 sDisableWktCache = true;
3013 sWktCache()->clear();
3014 }
3015 sCRSWktLock()->unlock();
3016
3017 sCRSSrsIdLock()->lockForWrite();
3018 if ( !sDisableSrsIdCache )
3019 {
3020 if ( disableCache )
3021 sDisableSrsIdCache = true;
3022 sSrsIdCache()->clear();
3023 }
3024 sCRSSrsIdLock()->unlock();
3025
3026 sCrsStringLock()->lockForWrite();
3027 if ( !sDisableStringCache )
3028 {
3029 if ( disableCache )
3030 sDisableStringCache = true;
3031 sStringCache()->clear();
3032 }
3033 sCrsStringLock()->unlock();
3034}
3035
3036// invalid < regular < user
3038{
3039 if ( c1.d == c2.d )
3040 return false;
3041
3042 if ( !c1.d->mIsValid && !c2.d->mIsValid )
3043 return false;
3044
3045 if ( !c1.d->mIsValid && c2.d->mIsValid )
3046 return false;
3047
3048 if ( c1.d->mIsValid && !c2.d->mIsValid )
3049 return true;
3050
3051 const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
3052 const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
3053
3054 if ( c1IsUser && !c2IsUser )
3055 return true;
3056
3057 if ( !c1IsUser && c2IsUser )
3058 return false;
3059
3060 if ( !c1IsUser && !c2IsUser )
3061 {
3062 if ( c1.d->mAuthId != c2.d->mAuthId )
3063 return c1.d->mAuthId > c2.d->mAuthId;
3064 }
3065 else
3066 {
3067 const QString wkt1 = c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3068 const QString wkt2 = c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3069 if ( wkt1 != wkt2 )
3070 return wkt1 > wkt2;
3071 }
3072
3073 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3074 return false;
3075
3076 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3077 return false;
3078
3079 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3080 return false;
3081
3082 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3083 return true;
3084
3085 return c1.d->mCoordinateEpoch > c2.d->mCoordinateEpoch;
3086}
3087
3089{
3090 if ( c1.d == c2.d )
3091 return false;
3092
3093 if ( !c1.d->mIsValid && !c2.d->mIsValid )
3094 return false;
3095
3096 if ( c1.d->mIsValid && !c2.d->mIsValid )
3097 return false;
3098
3099 if ( !c1.d->mIsValid && c2.d->mIsValid )
3100 return true;
3101
3102 const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
3103 const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
3104
3105 if ( !c1IsUser && c2IsUser )
3106 return true;
3107
3108 if ( c1IsUser && !c2IsUser )
3109 return false;
3110
3111 if ( !c1IsUser && !c2IsUser )
3112 {
3113 if ( c1.d->mAuthId != c2.d->mAuthId )
3114 return c1.d->mAuthId < c2.d->mAuthId;
3115 }
3116 else
3117 {
3118 const QString wkt1 = c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3119 const QString wkt2 = c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
3120 if ( wkt1 != wkt2 )
3121 return wkt1 < wkt2;
3122 }
3123
3124 if ( c1.d->mCoordinateEpoch == c2.d->mCoordinateEpoch )
3125 return false;
3126
3127 if ( std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3128 return false;
3129
3130 if ( !std::isnan( c1.d->mCoordinateEpoch ) && std::isnan( c2.d->mCoordinateEpoch ) )
3131 return false;
3132
3133 if ( std::isnan( c1.d->mCoordinateEpoch ) && !std::isnan( c2.d->mCoordinateEpoch ) )
3134 return true;
3135
3136 return c1.d->mCoordinateEpoch < c2.d->mCoordinateEpoch;
3137}
3138
3140{
3141 return !( c1 < c2 );
3142}
3144{
3145 return !( c1 > c2 );
3146}
DistanceUnit
Units of distance.
Definition: qgis.h:3310
CrsDefinitionFormat
CRS definition formats.
Definition: qgis.h:2431
CrsAxisDirection
Data provider flags.
Definition: qgis.h:1586
@ 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.
Q_GADGET Qgis::DistanceUnit mapUnits
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.
QString toOgcUri() const
Returns the crs as OGC URI (format: http://www.opengis.net/def/crs/OGC/1.3/CRS84) Returns an empty st...
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.
static void removeRecentCoordinateReferenceSystem(const QgsCoordinateReferenceSystem &crs)
Removes a CRS from the list of recently used CRS.
QgsCoordinateReferenceSystem toGeographicCrs() const
Returns the geographic CRS associated with this CRS object.
bool isDynamic() const
Returns true if the CRS is a dynamic CRS.
bool createFromSrsId(long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
Q_DECL_DEPRECATED bool createFromId(long id, CrsType type=PostgisCrsId)
Sets this CRS by lookup of the given ID in the CRS database.
static QgsCoordinateReferenceSystem fromProjObject(PJ *object)
Constructs a QgsCoordinateReferenceSystem from a PROJ PJ object.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateReferenceSystem objects.
QgsCoordinateReferenceSystem & operator=(const QgsCoordinateReferenceSystem &srs)
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.
static void clearRecentCoordinateReferenceSystems()
Cleans the list of recently used 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,...
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 the axis order is inverted for the CRS compared to the order east/north (longitude/la...
bool createFromProjObject(PJ *object)
Sets this CRS by passing it a PROJ PJ object, corresponding to a PROJ CRS object.
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:131
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:119
CRSFlavor
CRS flavor.
Definition: qgsogcutils.h:596
@ 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:63
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void remove(const QString &key, QgsSettings::Section section=QgsSettings::NoSection)
Removes the setting key and any sub-settings of key in a section.
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...
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:4572
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:3927
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:4571
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:3972
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:4520
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 QgsDebugError(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs