Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgscoordinatereferencesystem.cpp
3 : :
4 : : -------------------
5 : : begin : 2007
6 : : copyright : (C) 2007 by Gary E. Sherman
7 : : email : sherman@mrcc.com
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 : : ***************************************************************************/
18 : : #include "qgscoordinatereferencesystem.h"
19 : : #include "qgscoordinatereferencesystem_p.h"
20 : :
21 : : #include "qgscoordinatereferencesystem_legacy.h"
22 : : #include "qgscoordinatereferencesystemregistry.h"
23 : : #include "qgsreadwritelocker.h"
24 : :
25 : : #include <cmath>
26 : :
27 : : #include <QDir>
28 : : #include <QDomNode>
29 : : #include <QDomElement>
30 : : #include <QFileInfo>
31 : : #include <QRegExp>
32 : : #include <QTextStream>
33 : : #include <QFile>
34 : : #include <QRegularExpression>
35 : :
36 : : #include "qgsapplication.h"
37 : : #include "qgslogger.h"
38 : : #include "qgsmessagelog.h"
39 : : #include "qgis.h" //const vals declared here
40 : : #include "qgslocalec.h"
41 : : #include "qgssettings.h"
42 : : #include "qgsogrutils.h"
43 : :
44 : : #include <sqlite3.h>
45 : : #include "qgsprojutils.h"
46 : : #include <proj.h>
47 : : #include <proj_experimental.h>
48 : :
49 : : //gdal and ogr includes (needed for == operator)
50 : : #include <ogr_srs_api.h>
51 : : #include <cpl_error.h>
52 : : #include <cpl_conv.h>
53 : : #include <cpl_csv.h>
54 : :
55 : : CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
56 : :
57 : : typedef QHash< long, QgsCoordinateReferenceSystem > SrIdCrsCacheHash;
58 : : typedef QHash< QString, QgsCoordinateReferenceSystem > StringCrsCacheHash;
59 : :
60 : 31 : Q_GLOBAL_STATIC( QReadWriteLock, sSrIdCacheLock )
61 : 20 : Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrIdCache )
62 : : bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
63 : :
64 : 45 : Q_GLOBAL_STATIC( QReadWriteLock, sOgcLock )
65 : 57 : Q_GLOBAL_STATIC( StringCrsCacheHash, sOgcCache )
66 : : bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
67 : :
68 : 32 : Q_GLOBAL_STATIC( QReadWriteLock, sProj4CacheLock )
69 : 20 : Q_GLOBAL_STATIC( StringCrsCacheHash, sProj4Cache )
70 : : bool QgsCoordinateReferenceSystem::sDisableProjCache = false;
71 : :
72 : 34 : Q_GLOBAL_STATIC( QReadWriteLock, sCRSWktLock )
73 : 28 : Q_GLOBAL_STATIC( StringCrsCacheHash, sWktCache )
74 : : bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
75 : :
76 : 31 : Q_GLOBAL_STATIC( QReadWriteLock, sCRSSrsIdLock )
77 : 20 : Q_GLOBAL_STATIC( SrIdCrsCacheHash, sSrsIdCache )
78 : : bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
79 : :
80 : 85 : Q_GLOBAL_STATIC( QReadWriteLock, sCrsStringLock )
81 : 136 : Q_GLOBAL_STATIC( StringCrsCacheHash, sStringCache )
82 : : bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
83 : :
84 : 10 : QString getFullProjString( PJ *obj )
85 : : {
86 : : // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
87 : : // use proj_as_proj_string
88 : 10 : QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
89 : 10 : if ( boundCrs )
90 : : {
91 : 10 : if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
92 : : {
93 : 10 : return QString( proj4src );
94 : : }
95 : 0 : }
96 : :
97 : 0 : return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
98 : 10 : }
99 : : //--------------------------
100 : :
101 : 2262 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem()
102 : : {
103 : 2262 : static QgsCoordinateReferenceSystem nullCrs = QgsCoordinateReferenceSystem( QString() );
104 : :
105 : 2262 : d = nullCrs.d;
106 : 2262 : }
107 : :
108 : 58 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem( const QString &definition )
109 : : {
110 : 58 : d = new QgsCoordinateReferenceSystemPrivate();
111 : 58 : createFromString( definition );
112 : 58 : }
113 : :
114 : 0 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem( const long id, CrsType type )
115 : : {
116 : 0 : d = new QgsCoordinateReferenceSystemPrivate();
117 : : Q_NOWARN_DEPRECATED_PUSH
118 : 0 : createFromId( id, type );
119 : : Q_NOWARN_DEPRECATED_POP
120 : 0 : }
121 : :
122 : 6169 : QgsCoordinateReferenceSystem::QgsCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &srs ) //NOLINT
123 : 6169 : : d( srs.d )
124 : 6169 : , mValidationHint( srs.mValidationHint )
125 : : {
126 : 6169 : }
127 : :
128 : 978 : QgsCoordinateReferenceSystem &QgsCoordinateReferenceSystem::operator=( const QgsCoordinateReferenceSystem &srs ) //NOLINT
129 : : {
130 : 978 : d = srs.d;
131 : 978 : mValidationHint = srs.mValidationHint;
132 : 978 : return *this;
133 : : }
134 : :
135 : 0 : QList<long> QgsCoordinateReferenceSystem::validSrsIds()
136 : : {
137 : 0 : QList<long> results;
138 : : // check both standard & user defined projection databases
139 : 0 : QStringList dbs = QStringList() << QgsApplication::srsDatabaseFilePath() << QgsApplication::qgisUserDatabaseFilePath();
140 : :
141 : 0 : const auto constDbs = dbs;
142 : 0 : for ( const QString &db : constDbs )
143 : : {
144 : 0 : QFileInfo myInfo( db );
145 : 0 : if ( !myInfo.exists() )
146 : : {
147 : 0 : QgsDebugMsg( "failed : " + db + " does not exist!" );
148 : 0 : continue;
149 : : }
150 : :
151 : 0 : sqlite3_database_unique_ptr database;
152 : 0 : sqlite3_statement_unique_ptr statement;
153 : :
154 : : //check the db is available
155 : 0 : int result = openDatabase( db, database );
156 : 0 : if ( result != SQLITE_OK )
157 : : {
158 : 0 : QgsDebugMsg( "failed : " + db + " could not be opened!" );
159 : 0 : continue;
160 : : }
161 : :
162 : 0 : QString sql = QStringLiteral( "select srs_id from tbl_srs" );
163 : : int rc;
164 : 0 : statement = database.prepare( sql, rc );
165 : 0 : while ( true )
166 : : {
167 : : // this one is an infinitive loop, intended to fetch any row
168 : 0 : int ret = statement.step();
169 : :
170 : 0 : if ( ret == SQLITE_DONE )
171 : : {
172 : : // there are no more rows to fetch - we can stop looping
173 : 0 : break;
174 : : }
175 : :
176 : 0 : if ( ret == SQLITE_ROW )
177 : : {
178 : 0 : results.append( statement.columnAsInt64( 0 ) );
179 : 0 : }
180 : : else
181 : : {
182 : 0 : QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
183 : 0 : break;
184 : : }
185 : : }
186 : 0 : }
187 : 0 : std::sort( results.begin(), results.end() );
188 : 0 : return results;
189 : 0 : }
190 : :
191 : 6 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromOgcWmsCrs( const QString &ogcCrs )
192 : : {
193 : 6 : QgsCoordinateReferenceSystem crs;
194 : 6 : crs.createFromOgcWmsCrs( ogcCrs );
195 : 6 : return crs;
196 : 6 : }
197 : :
198 : 2 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromEpsgId( long epsg )
199 : : {
200 : 2 : QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
201 : 2 : if ( res.isValid() )
202 : 2 : return res;
203 : :
204 : : // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
205 : 0 : res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
206 : 0 : if ( res.isValid() )
207 : 0 : return res;
208 : :
209 : 0 : return QgsCoordinateReferenceSystem();
210 : 2 : }
211 : :
212 : 0 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromProj4( const QString &proj4 )
213 : : {
214 : 0 : return fromProj( proj4 );
215 : : }
216 : :
217 : 0 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromProj( const QString &proj )
218 : : {
219 : 0 : QgsCoordinateReferenceSystem crs;
220 : 0 : crs.createFromProj( proj );
221 : 0 : return crs;
222 : 0 : }
223 : :
224 : 3 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromWkt( const QString &wkt )
225 : : {
226 : 3 : QgsCoordinateReferenceSystem crs;
227 : 3 : crs.createFromWkt( wkt );
228 : 3 : return crs;
229 : 3 : }
230 : :
231 : 0 : QgsCoordinateReferenceSystem QgsCoordinateReferenceSystem::fromSrsId( long srsId )
232 : : {
233 : 0 : QgsCoordinateReferenceSystem crs;
234 : 0 : crs.createFromSrsId( srsId );
235 : 0 : return crs;
236 : 0 : }
237 : :
238 : 8421 : QgsCoordinateReferenceSystem::~QgsCoordinateReferenceSystem() //NOLINT
239 : : {
240 : 8421 : }
241 : :
242 : 0 : bool QgsCoordinateReferenceSystem::createFromId( const long id, CrsType type )
243 : : {
244 : 0 : bool result = false;
245 : 0 : switch ( type )
246 : : {
247 : : case InternalCrsId:
248 : 0 : result = createFromSrsId( id );
249 : 0 : break;
250 : : case PostgisCrsId:
251 : 0 : result = createFromPostgisSrid( id );
252 : 0 : break;
253 : : case EpsgCrsId:
254 : 0 : result = createFromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( id ) );
255 : 0 : break;
256 : : default:
257 : : //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
258 : 0 : QgsDebugMsg( QStringLiteral( "Unexpected case reached!" ) );
259 : 0 : };
260 : 0 : return result;
261 : 0 : }
262 : :
263 : 59 : bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
264 : : {
265 : 59 : if ( definition.isEmpty() )
266 : 5 : return false;
267 : :
268 : 54 : QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Read );
269 : 54 : if ( !sDisableStringCache )
270 : : {
271 : 54 : QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache()->constFind( definition );
272 : 54 : if ( crsIt != sStringCache()->constEnd() )
273 : : {
274 : : // found a match in the cache
275 : 46 : *this = crsIt.value();
276 : 46 : return d->mIsValid;
277 : : }
278 : 8 : }
279 : 8 : locker.unlock();
280 : :
281 : 8 : bool result = false;
282 : 11 : const thread_local QRegularExpression reCrsId( QStringLiteral( "^(epsg|esri|osgeo|ignf|zangi|iau2000|postgis|internal|user)\\:(\\w+)$" ), QRegularExpression::CaseInsensitiveOption );
283 : 8 : QRegularExpressionMatch match = reCrsId.match( definition );
284 : 8 : if ( match.capturedStart() == 0 )
285 : : {
286 : 8 : QString authName = match.captured( 1 ).toLower();
287 : 8 : if ( authName == QLatin1String( "epsg" ) )
288 : : {
289 : 8 : result = createFromOgcWmsCrs( definition );
290 : 8 : }
291 : 0 : else if ( authName == QLatin1String( "postgis" ) )
292 : : {
293 : 0 : const long id = match.captured( 2 ).toLong();
294 : 0 : result = createFromPostgisSrid( id );
295 : 0 : }
296 : 0 : else if ( authName == QLatin1String( "esri" ) || authName == QLatin1String( "osgeo" ) || authName == QLatin1String( "ignf" ) || authName == QLatin1String( "zangi" ) || authName == QLatin1String( "iau2000" ) )
297 : : {
298 : 0 : result = createFromOgcWmsCrs( definition );
299 : 0 : }
300 : : else
301 : : {
302 : 0 : const long id = match.captured( 2 ).toLong();
303 : : Q_NOWARN_DEPRECATED_PUSH
304 : 0 : result = createFromId( id, InternalCrsId );
305 : : Q_NOWARN_DEPRECATED_POP
306 : : }
307 : 8 : }
308 : : else
309 : : {
310 : 0 : const thread_local QRegularExpression reCrsStr( QStringLiteral( "^(?:(wkt|proj4|proj)\\:)?(.+)$" ), QRegularExpression::CaseInsensitiveOption );
311 : 0 : match = reCrsStr.match( definition );
312 : 0 : if ( match.capturedStart() == 0 )
313 : : {
314 : 0 : if ( match.captured( 1 ).startsWith( QLatin1String( "proj" ), Qt::CaseInsensitive ) )
315 : : {
316 : 0 : result = createFromProj( match.captured( 2 ) );
317 : 0 : }
318 : : else
319 : : {
320 : 0 : result = createFromWkt( match.captured( 2 ) );
321 : : }
322 : 0 : }
323 : : }
324 : :
325 : 8 : locker.changeMode( QgsReadWriteLocker::Write );
326 : 8 : if ( !sDisableStringCache )
327 : 8 : sStringCache()->insert( definition, *this );
328 : 8 : return result;
329 : 59 : }
330 : :
331 : 0 : bool QgsCoordinateReferenceSystem::createFromUserInput( const QString &definition )
332 : : {
333 : 0 : if ( definition.isEmpty() )
334 : 0 : return false;
335 : :
336 : 0 : QString userWkt;
337 : 0 : OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
338 : :
339 : 0 : if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
340 : : {
341 : 0 : userWkt = QgsOgrUtils::OGRSpatialReferenceToWkt( crs );
342 : 0 : OSRDestroySpatialReference( crs );
343 : 0 : }
344 : : //QgsDebugMsg( "definition: " + definition + " wkt = " + wkt );
345 : 0 : return createFromWkt( userWkt );
346 : 0 : }
347 : :
348 : 0 : void QgsCoordinateReferenceSystem::setupESRIWktFix()
349 : : {
350 : : // make sure towgs84 parameter is loaded if gdal >= 1.9
351 : : // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
352 : 0 : const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
353 : 0 : const char *configNew = "GEOGCS";
354 : : // only set if it was not set, to let user change the value if needed
355 : 0 : if ( strcmp( configOld, "" ) == 0 )
356 : : {
357 : 0 : CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
358 : 0 : if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
359 : 0 : QgsLogger::warning( QStringLiteral( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2" )
360 : 0 : .arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
361 : 0 : QgsDebugMsgLevel( QStringLiteral( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ), 4 );
362 : 0 : }
363 : : else
364 : : {
365 : 0 : QgsDebugMsgLevel( QStringLiteral( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ), 4 );
366 : : }
367 : 0 : }
368 : :
369 : 14 : bool QgsCoordinateReferenceSystem::createFromOgcWmsCrs( const QString &crs )
370 : : {
371 : 14 : if ( crs.isEmpty() )
372 : 0 : return false;
373 : :
374 : 14 : QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Read );
375 : 14 : if ( !sDisableOgcCache )
376 : : {
377 : 14 : QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache()->constFind( crs );
378 : 14 : if ( crsIt != sOgcCache()->constEnd() )
379 : : {
380 : : // found a match in the cache
381 : 5 : *this = crsIt.value();
382 : 5 : return d->mIsValid;
383 : : }
384 : 9 : }
385 : 9 : locker.unlock();
386 : :
387 : 9 : QString wmsCrs = crs;
388 : :
389 : 12 : thread_local const QRegExp re_uri( QStringLiteral( "http://www\\.opengis\\.net/def/crs/([^/]+).+/([^/]+)" ), Qt::CaseInsensitive );
390 : 12 : thread_local const QRegExp re_urn( QStringLiteral( "urn:ogc:def:crs:([^:]+).+([^:]+)" ), Qt::CaseInsensitive );
391 : 9 : if ( re_uri.exactMatch( wmsCrs ) )
392 : : {
393 : 0 : wmsCrs = re_uri.cap( 1 ) + ':' + re_uri.cap( 2 );
394 : 0 : }
395 : 9 : else if ( re_urn.exactMatch( wmsCrs ) )
396 : : {
397 : 0 : wmsCrs = re_urn.cap( 1 ) + ':' + re_urn.cap( 2 );
398 : 0 : }
399 : : else
400 : : {
401 : 12 : thread_local const QRegExp re_urn_custom( QStringLiteral( "(user|custom|qgis):(\\d+)" ), Qt::CaseInsensitive );
402 : 9 : if ( re_urn_custom.exactMatch( wmsCrs ) && createFromSrsId( re_urn_custom.cap( 2 ).toInt() ) )
403 : : {
404 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
405 : 0 : if ( !sDisableOgcCache )
406 : 0 : sOgcCache()->insert( crs, *this );
407 : 0 : return d->mIsValid;
408 : : }
409 : : }
410 : :
411 : : // first chance for proj 6 - scan through legacy systems and try to use authid directly
412 : 9 : const QString legacyKey = wmsCrs.toLower();
413 : 24698 : for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
414 : : {
415 : 24698 : if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
416 : : {
417 : 9 : const QStringList parts = it.key().split( ':' );
418 : 9 : const QString auth = parts.at( 0 );
419 : 9 : const QString code = parts.at( 1 );
420 : 9 : if ( loadFromAuthCode( auth, code ) )
421 : : {
422 : 9 : locker.changeMode( QgsReadWriteLocker::Write );
423 : 9 : if ( !sDisableOgcCache )
424 : 9 : sOgcCache()->insert( crs, *this );
425 : 9 : return d->mIsValid;
426 : : }
427 : 9 : }
428 : 24689 : }
429 : :
430 : 0 : if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), wmsCrs.toLower() ) )
431 : : {
432 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
433 : 0 : if ( !sDisableOgcCache )
434 : 0 : sOgcCache()->insert( crs, *this );
435 : 0 : return d->mIsValid;
436 : : }
437 : :
438 : : // NAD27
439 : 0 : if ( wmsCrs.compare( QLatin1String( "CRS:27" ), Qt::CaseInsensitive ) == 0 ||
440 : 0 : wmsCrs.compare( QLatin1String( "OGC:CRS27" ), Qt::CaseInsensitive ) == 0 )
441 : : {
442 : : // TODO: verify same axis orientation
443 : 0 : return createFromOgcWmsCrs( QStringLiteral( "EPSG:4267" ) );
444 : : }
445 : :
446 : : // NAD83
447 : 0 : if ( wmsCrs.compare( QLatin1String( "CRS:83" ), Qt::CaseInsensitive ) == 0 ||
448 : 0 : wmsCrs.compare( QLatin1String( "OGC:CRS83" ), Qt::CaseInsensitive ) == 0 )
449 : : {
450 : : // TODO: verify same axis orientation
451 : 0 : return createFromOgcWmsCrs( QStringLiteral( "EPSG:4269" ) );
452 : : }
453 : :
454 : : // WGS84
455 : 0 : if ( wmsCrs.compare( QLatin1String( "CRS:84" ), Qt::CaseInsensitive ) == 0 ||
456 : 0 : wmsCrs.compare( QLatin1String( "OGC:CRS84" ), Qt::CaseInsensitive ) == 0 )
457 : : {
458 : 0 : if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), QStringLiteral( "epsg:4326" ) ) )
459 : : {
460 : 0 : d->mAxisInverted = false;
461 : 0 : d->mAxisInvertedDirty = false;
462 : 0 : }
463 : :
464 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
465 : 0 : if ( !sDisableOgcCache )
466 : 0 : sOgcCache()->insert( crs, *this );
467 : :
468 : 0 : return d->mIsValid;
469 : : }
470 : :
471 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
472 : 0 : if ( !sDisableOgcCache )
473 : 0 : sOgcCache()->insert( crs, QgsCoordinateReferenceSystem() );
474 : 0 : return d->mIsValid;
475 : 14 : }
476 : :
477 : : // Misc helper functions -----------------------
478 : :
479 : :
480 : 63 : void QgsCoordinateReferenceSystem::validate()
481 : : {
482 : 63 : if ( d->mIsValid || !sCustomSrsValidation )
483 : 63 : return;
484 : :
485 : : // try to validate using custom validation routines
486 : 0 : if ( sCustomSrsValidation )
487 : 0 : sCustomSrsValidation( *this );
488 : 63 : }
489 : :
490 : 0 : bool QgsCoordinateReferenceSystem::createFromSrid( const long id )
491 : : {
492 : 0 : return createFromPostgisSrid( id );
493 : : }
494 : :
495 : 0 : bool QgsCoordinateReferenceSystem::createFromPostgisSrid( const long id )
496 : : {
497 : 0 : QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Read );
498 : 0 : if ( !sDisableSrIdCache )
499 : : {
500 : 0 : QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache()->constFind( id );
501 : 0 : if ( crsIt != sSrIdCache()->constEnd() )
502 : : {
503 : : // found a match in the cache
504 : 0 : *this = crsIt.value();
505 : 0 : return d->mIsValid;
506 : : }
507 : 0 : }
508 : 0 : locker.unlock();
509 : :
510 : : // first chance for proj 6 - scan through legacy systems and try to use authid directly
511 : 0 : for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
512 : : {
513 : 0 : if ( it.value().endsWith( QStringLiteral( ",%1" ).arg( id ) ) )
514 : : {
515 : 0 : const QStringList parts = it.key().split( ':' );
516 : 0 : const QString auth = parts.at( 0 );
517 : 0 : const QString code = parts.at( 1 );
518 : 0 : if ( loadFromAuthCode( auth, code ) )
519 : : {
520 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
521 : 0 : if ( !sDisableSrIdCache )
522 : 0 : sSrIdCache()->insert( id, *this );
523 : :
524 : 0 : return d->mIsValid;
525 : : }
526 : 0 : }
527 : 0 : }
528 : :
529 : 0 : bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "srid" ), QString::number( id ) );
530 : :
531 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
532 : 0 : if ( !sDisableSrIdCache )
533 : 0 : sSrIdCache()->insert( id, *this );
534 : :
535 : 0 : return result;
536 : 0 : }
537 : :
538 : 0 : bool QgsCoordinateReferenceSystem::createFromSrsId( const long id )
539 : : {
540 : 0 : QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Read );
541 : 0 : if ( !sDisableSrsIdCache )
542 : : {
543 : 0 : QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache()->constFind( id );
544 : 0 : if ( crsIt != sSrsIdCache()->constEnd() )
545 : : {
546 : : // found a match in the cache
547 : 0 : *this = crsIt.value();
548 : 0 : return d->mIsValid;
549 : : }
550 : 0 : }
551 : 0 : locker.unlock();
552 : :
553 : : // first chance for proj 6 - scan through legacy systems and try to use authid directly
554 : 0 : for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
555 : : {
556 : 0 : if ( it.value().startsWith( QString::number( id ) + ',' ) )
557 : : {
558 : 0 : const QStringList parts = it.key().split( ':' );
559 : 0 : const QString auth = parts.at( 0 );
560 : 0 : const QString code = parts.at( 1 );
561 : 0 : if ( loadFromAuthCode( auth, code ) )
562 : : {
563 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
564 : 0 : if ( !sDisableSrsIdCache )
565 : 0 : sSrsIdCache()->insert( id, *this );
566 : 0 : return d->mIsValid;
567 : : }
568 : 0 : }
569 : 0 : }
570 : :
571 : 0 : bool result = loadFromDatabase( id < USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() :
572 : 0 : QgsApplication::qgisUserDatabaseFilePath(),
573 : 0 : QStringLiteral( "srs_id" ), QString::number( id ) );
574 : :
575 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
576 : 0 : if ( !sDisableSrsIdCache )
577 : 0 : sSrsIdCache()->insert( id, *this );
578 : 0 : return result;
579 : 0 : }
580 : :
581 : 0 : bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
582 : : {
583 : 0 : d.detach();
584 : :
585 : 0 : QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
586 : 0 : d->mIsValid = false;
587 : 0 : d->mWktPreferred.clear();
588 : :
589 : 0 : QFileInfo myInfo( db );
590 : 0 : if ( !myInfo.exists() )
591 : : {
592 : 0 : QgsDebugMsg( "failed : " + db + " does not exist!" );
593 : 0 : return d->mIsValid;
594 : : }
595 : :
596 : 0 : sqlite3_database_unique_ptr database;
597 : 0 : sqlite3_statement_unique_ptr statement;
598 : : int myResult;
599 : : //check the db is available
600 : 0 : myResult = openDatabase( db, database );
601 : 0 : if ( myResult != SQLITE_OK )
602 : : {
603 : 0 : return d->mIsValid;
604 : : }
605 : :
606 : : /*
607 : : srs_id INTEGER PRIMARY KEY,
608 : : description text NOT NULL,
609 : : projection_acronym text NOT NULL,
610 : : ellipsoid_acronym NOT NULL,
611 : : parameters text NOT NULL,
612 : : srid integer NOT NULL,
613 : : auth_name varchar NOT NULL,
614 : : auth_id integer NOT NULL,
615 : : is_geo integer NOT NULL);
616 : : */
617 : :
618 : 0 : QString mySql = "select srs_id,description,projection_acronym,"
619 : : "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo,wkt "
620 : 0 : "from tbl_srs where " + expression + '=' + QgsSqliteUtils::quotedString( value ) + " order by deprecated";
621 : 0 : statement = database.prepare( mySql, myResult );
622 : 0 : QString wkt;
623 : : // XXX Need to free memory from the error msg if one is set
624 : 0 : if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
625 : : {
626 : 0 : d->mSrsId = statement.columnAsText( 0 ).toLong();
627 : 0 : d->mDescription = statement.columnAsText( 1 );
628 : 0 : d->mProjectionAcronym = statement.columnAsText( 2 );
629 : 0 : d->mEllipsoidAcronym.clear();
630 : 0 : d->mProj4 = statement.columnAsText( 4 );
631 : 0 : d->mWktPreferred.clear();
632 : 0 : d->mSRID = statement.columnAsText( 5 ).toLong();
633 : 0 : d->mAuthId = statement.columnAsText( 6 );
634 : 0 : d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
635 : 0 : wkt = statement.columnAsText( 8 );
636 : 0 : d->mAxisInvertedDirty = true;
637 : :
638 : 0 : if ( d->mSrsId >= USER_CRS_START_ID && ( d->mAuthId.isEmpty() || d->mAuthId == QChar( ':' ) ) )
639 : : {
640 : 0 : d->mAuthId = QStringLiteral( "USER:%1" ).arg( d->mSrsId );
641 : 0 : }
642 : 0 : else if ( !d->mAuthId.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive ) )
643 : : {
644 : 0 : QStringList parts = d->mAuthId.split( ':' );
645 : 0 : QString auth = parts.at( 0 );
646 : 0 : QString code = parts.at( 1 );
647 : :
648 : : {
649 : 0 : QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
650 : 0 : d->setPj( QgsProjUtils::crsToSingleCrs( crs.get() ) );
651 : 0 : }
652 : :
653 : 0 : d->mIsValid = d->hasPj();
654 : 0 : setMapUnits();
655 : 0 : }
656 : :
657 : 0 : if ( !d->mIsValid )
658 : : {
659 : 0 : if ( !wkt.isEmpty() )
660 : : {
661 : 0 : setWktString( wkt );
662 : : // set WKT string resets the description to that description embedded in the WKT, so manually overwrite this back to the
663 : : // value from the user DB
664 : 0 : d->mDescription = statement.columnAsText( 1 );
665 : 0 : }
666 : : else
667 : 0 : setProjString( d->mProj4 );
668 : 0 : }
669 : 0 : }
670 : : else
671 : : {
672 : 0 : QgsDebugMsgLevel( "failed : " + mySql, 4 );
673 : : }
674 : 0 : return d->mIsValid;
675 : 0 : }
676 : :
677 : 3 : void QgsCoordinateReferenceSystem::removeFromCacheObjectsBelongingToCurrentThread( PJ_CONTEXT *pj_context )
678 : : {
679 : : // Not completely sure about object order destruction after main() has
680 : : // exited. So it is safer to check sDisableCache before using sCacheLock
681 : : // in case sCacheLock would have been destroyed before the current TLS
682 : : // QgsProjContext object that has called us...
683 : :
684 : 3 : if ( !sDisableSrIdCache )
685 : : {
686 : 0 : QgsReadWriteLocker locker( *sSrIdCacheLock(), QgsReadWriteLocker::Write );
687 : 0 : if ( !sDisableSrIdCache )
688 : : {
689 : 0 : for ( auto it = sSrIdCache()->begin(); it != sSrIdCache()->end(); )
690 : : {
691 : 0 : auto &v = it.value();
692 : 0 : if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
693 : 0 : it = sSrIdCache()->erase( it );
694 : : else
695 : 0 : ++it;
696 : : }
697 : 0 : }
698 : 0 : }
699 : 3 : if ( !sDisableOgcCache )
700 : : {
701 : 0 : QgsReadWriteLocker locker( *sOgcLock(), QgsReadWriteLocker::Write );
702 : 0 : if ( !sDisableOgcCache )
703 : : {
704 : 0 : for ( auto it = sOgcCache()->begin(); it != sOgcCache()->end(); )
705 : : {
706 : 0 : auto &v = it.value();
707 : 0 : if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
708 : 0 : it = sOgcCache()->erase( it );
709 : : else
710 : 0 : ++it;
711 : : }
712 : 0 : }
713 : 0 : }
714 : 3 : if ( !sDisableProjCache )
715 : : {
716 : 0 : QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Write );
717 : 0 : if ( !sDisableProjCache )
718 : : {
719 : 0 : for ( auto it = sProj4Cache()->begin(); it != sProj4Cache()->end(); )
720 : : {
721 : 0 : auto &v = it.value();
722 : 0 : if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
723 : 0 : it = sProj4Cache()->erase( it );
724 : : else
725 : 0 : ++it;
726 : : }
727 : 0 : }
728 : 0 : }
729 : 3 : if ( !sDisableWktCache )
730 : : {
731 : 0 : QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Write );
732 : 0 : if ( !sDisableWktCache )
733 : : {
734 : 0 : for ( auto it = sWktCache()->begin(); it != sWktCache()->end(); )
735 : : {
736 : 0 : auto &v = it.value();
737 : 0 : if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
738 : 0 : it = sWktCache()->erase( it );
739 : : else
740 : 0 : ++it;
741 : : }
742 : 0 : }
743 : 0 : }
744 : 3 : if ( !sDisableSrsIdCache )
745 : : {
746 : 0 : QgsReadWriteLocker locker( *sCRSSrsIdLock(), QgsReadWriteLocker::Write );
747 : 0 : if ( !sDisableSrsIdCache )
748 : : {
749 : 0 : for ( auto it = sSrsIdCache()->begin(); it != sSrsIdCache()->end(); )
750 : : {
751 : 0 : auto &v = it.value();
752 : 0 : if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
753 : 0 : it = sSrsIdCache()->erase( it );
754 : : else
755 : 0 : ++it;
756 : : }
757 : 0 : }
758 : 0 : }
759 : 3 : if ( !sDisableStringCache )
760 : : {
761 : 0 : QgsReadWriteLocker locker( *sCrsStringLock(), QgsReadWriteLocker::Write );
762 : 0 : if ( !sDisableStringCache )
763 : : {
764 : 0 : for ( auto it = sStringCache()->begin(); it != sStringCache()->end(); )
765 : : {
766 : 0 : auto &v = it.value();
767 : 0 : if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
768 : 0 : it = sStringCache()->erase( it );
769 : : else
770 : 0 : ++it;
771 : : }
772 : 0 : }
773 : 0 : }
774 : 3 : }
775 : :
776 : 0 : bool QgsCoordinateReferenceSystem::hasAxisInverted() const
777 : : {
778 : 0 : if ( d->mAxisInvertedDirty )
779 : : {
780 : 0 : d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->threadLocalProjObject() );
781 : 0 : d->mAxisInvertedDirty = false;
782 : 0 : }
783 : :
784 : 0 : return d->mAxisInverted;
785 : : }
786 : :
787 : 3 : bool QgsCoordinateReferenceSystem::createFromWkt( const QString &wkt )
788 : : {
789 : 3 : return createFromWktInternal( wkt, QString() );
790 : 0 : }
791 : :
792 : 3 : bool QgsCoordinateReferenceSystem::createFromWktInternal( const QString &wkt, const QString &description )
793 : : {
794 : 3 : if ( wkt.isEmpty() )
795 : 0 : return false;
796 : :
797 : 3 : d.detach();
798 : :
799 : 3 : QgsReadWriteLocker locker( *sCRSWktLock(), QgsReadWriteLocker::Read );
800 : 3 : if ( !sDisableWktCache )
801 : : {
802 : 3 : QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache()->constFind( wkt );
803 : 3 : if ( crsIt != sWktCache()->constEnd() )
804 : : {
805 : : // found a match in the cache
806 : 2 : *this = crsIt.value();
807 : :
808 : 2 : if ( !description.isEmpty() && d->mDescription.isEmpty() )
809 : : {
810 : : // now we have a name for a previously unknown CRS! Update the cached CRS accordingly, so that we use the name from now on...
811 : 0 : d->mDescription = description;
812 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
813 : 0 : sWktCache()->insert( wkt, *this );
814 : 0 : }
815 : 2 : return d->mIsValid;
816 : : }
817 : 1 : }
818 : 1 : locker.unlock();
819 : :
820 : 1 : d->mIsValid = false;
821 : 1 : d->mProj4.clear();
822 : 1 : d->mWktPreferred.clear();
823 : 1 : if ( wkt.isEmpty() )
824 : : {
825 : 0 : QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
826 : 0 : return d->mIsValid;
827 : : }
828 : :
829 : : // try to match against user crs
830 : 1 : QgsCoordinateReferenceSystem::RecordMap record = getRecord( "select * from tbl_srs where wkt=" + QgsSqliteUtils::quotedString( wkt ) + " order by deprecated" );
831 : 1 : if ( !record.empty() )
832 : : {
833 : 0 : long srsId = record[QStringLiteral( "srs_id" )].toLong();
834 : 0 : if ( srsId > 0 )
835 : : {
836 : 0 : createFromSrsId( srsId );
837 : 0 : }
838 : 0 : }
839 : : else
840 : : {
841 : 1 : setWktString( wkt );
842 : 1 : if ( !description.isEmpty() )
843 : : {
844 : 0 : d->mDescription = description;
845 : 0 : }
846 : 1 : if ( d->mSrsId == 0 )
847 : : {
848 : : // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
849 : 0 : long id = matchToUserCrs();
850 : 0 : if ( id >= USER_CRS_START_ID )
851 : : {
852 : 0 : createFromSrsId( id );
853 : 0 : }
854 : 0 : }
855 : : }
856 : :
857 : 1 : locker.changeMode( QgsReadWriteLocker::Write );
858 : 1 : if ( !sDisableWktCache )
859 : 1 : sWktCache()->insert( wkt, *this );
860 : :
861 : 1 : return d->mIsValid;
862 : : //setMapunits will be called by createfromproj above
863 : 3 : }
864 : :
865 : 4547 : bool QgsCoordinateReferenceSystem::isValid() const
866 : : {
867 : 4547 : return d->mIsValid;
868 : : }
869 : :
870 : 0 : bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
871 : : {
872 : 0 : return createFromProj( proj4String );
873 : : }
874 : :
875 : 0 : bool QgsCoordinateReferenceSystem::createFromProj( const QString &projString, const bool identify )
876 : : {
877 : 0 : if ( projString.isEmpty() )
878 : 0 : return false;
879 : :
880 : 0 : d.detach();
881 : :
882 : 0 : if ( projString.trimmed().isEmpty() )
883 : : {
884 : 0 : d->mIsValid = false;
885 : 0 : d->mProj4.clear();
886 : 0 : d->mWktPreferred.clear();
887 : 0 : return false;
888 : : }
889 : :
890 : 0 : QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Read );
891 : 0 : if ( !sDisableProjCache )
892 : : {
893 : 0 : QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache()->constFind( projString );
894 : 0 : if ( crsIt != sProj4Cache()->constEnd() )
895 : : {
896 : : // found a match in the cache
897 : 0 : *this = crsIt.value();
898 : 0 : return d->mIsValid;
899 : : }
900 : 0 : }
901 : 0 : locker.unlock();
902 : :
903 : : //
904 : : // Examples:
905 : : // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
906 : : // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
907 : : //
908 : : // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
909 : : // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
910 : : //
911 : 0 : QString myProj4String = projString.trimmed();
912 : 0 : myProj4String.remove( QStringLiteral( "+type=crs" ) );
913 : 0 : myProj4String = myProj4String.trimmed();
914 : :
915 : 0 : d->mIsValid = false;
916 : 0 : d->mWktPreferred.clear();
917 : :
918 : 0 : if ( identify )
919 : : {
920 : : // first, try to use proj to do this for us...
921 : 0 : const QString projCrsString = myProj4String + ( myProj4String.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
922 : 0 : QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
923 : 0 : if ( crs )
924 : : {
925 : 0 : QString authName;
926 : 0 : QString authCode;
927 : 0 : if ( QgsProjUtils::identifyCrs( crs.get(), authName, authCode, QgsProjUtils::FlagMatchBoundCrsToUnderlyingSourceCrs ) )
928 : : {
929 : 0 : const QString authid = QStringLiteral( "%1:%2" ).arg( authName, authCode );
930 : 0 : if ( createFromOgcWmsCrs( authid ) )
931 : : {
932 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
933 : 0 : if ( !sDisableProjCache )
934 : 0 : sProj4Cache()->insert( projString, *this );
935 : 0 : return d->mIsValid;
936 : : }
937 : 0 : }
938 : 0 : }
939 : :
940 : : // try a direct match against user crses
941 : 0 : QgsCoordinateReferenceSystem::RecordMap myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
942 : 0 : long id = 0;
943 : 0 : if ( !myRecord.empty() )
944 : : {
945 : 0 : id = myRecord[QStringLiteral( "srs_id" )].toLong();
946 : 0 : if ( id >= USER_CRS_START_ID )
947 : : {
948 : 0 : createFromSrsId( id );
949 : 0 : }
950 : 0 : }
951 : 0 : if ( id < USER_CRS_START_ID )
952 : : {
953 : : // no direct matches, so go ahead and create a new proj object based on the proj string alone.
954 : 0 : setProjString( myProj4String );
955 : :
956 : : // lastly, try a tolerant match of the created proj object against all user CRSes (allowing differences in parameter order during the comparison)
957 : 0 : id = matchToUserCrs();
958 : 0 : if ( id >= USER_CRS_START_ID )
959 : : {
960 : 0 : createFromSrsId( id );
961 : 0 : }
962 : 0 : }
963 : 0 : }
964 : : else
965 : : {
966 : 0 : setProjString( myProj4String );
967 : : }
968 : :
969 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
970 : 0 : if ( !sDisableProjCache )
971 : 0 : sProj4Cache()->insert( projString, *this );
972 : :
973 : 0 : return d->mIsValid;
974 : 0 : }
975 : :
976 : : //private method meant for internal use by this class only
977 : 1 : QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
978 : : {
979 : 1 : QString myDatabaseFileName;
980 : 1 : QgsCoordinateReferenceSystem::RecordMap myMap;
981 : 1 : QString myFieldName;
982 : 1 : QString myFieldValue;
983 : 1 : sqlite3_database_unique_ptr database;
984 : 1 : sqlite3_statement_unique_ptr statement;
985 : : int myResult;
986 : :
987 : : // Get the full path name to the sqlite3 spatial reference database.
988 : 1 : myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
989 : 1 : QFileInfo myInfo( myDatabaseFileName );
990 : 1 : if ( !myInfo.exists() )
991 : : {
992 : 0 : QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
993 : 0 : return myMap;
994 : : }
995 : :
996 : : //check the db is available
997 : 1 : myResult = openDatabase( myDatabaseFileName, database );
998 : 1 : if ( myResult != SQLITE_OK )
999 : : {
1000 : 0 : return myMap;
1001 : : }
1002 : :
1003 : 1 : statement = database.prepare( sql, myResult );
1004 : : // XXX Need to free memory from the error msg if one is set
1005 : 1 : if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1006 : : {
1007 : 0 : int myColumnCount = statement.columnCount();
1008 : : //loop through each column in the record adding its expression name and value to the map
1009 : 0 : for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1010 : : {
1011 : 0 : myFieldName = statement.columnName( myColNo );
1012 : 0 : myFieldValue = statement.columnAsText( myColNo );
1013 : 0 : myMap[myFieldName] = myFieldValue;
1014 : 0 : }
1015 : 0 : if ( statement.step() != SQLITE_DONE )
1016 : : {
1017 : 0 : QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1018 : : //be less fussy on proj 6 -- the db has MANY more entries!
1019 : 0 : }
1020 : 0 : }
1021 : : else
1022 : : {
1023 : 1 : QgsDebugMsgLevel( "failed : " + sql, 4 );
1024 : : }
1025 : :
1026 : 1 : if ( myMap.empty() )
1027 : : {
1028 : 1 : myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1029 : 1 : QFileInfo myFileInfo;
1030 : 1 : myFileInfo.setFile( myDatabaseFileName );
1031 : 1 : if ( !myFileInfo.exists() )
1032 : : {
1033 : 0 : QgsDebugMsg( QStringLiteral( "user qgis.db not found" ) );
1034 : 0 : return myMap;
1035 : : }
1036 : :
1037 : : //check the db is available
1038 : 1 : myResult = openDatabase( myDatabaseFileName, database );
1039 : 1 : if ( myResult != SQLITE_OK )
1040 : : {
1041 : 0 : return myMap;
1042 : : }
1043 : :
1044 : 1 : statement = database.prepare( sql, myResult );
1045 : : // XXX Need to free memory from the error msg if one is set
1046 : 1 : if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1047 : : {
1048 : 0 : int myColumnCount = statement.columnCount();
1049 : : //loop through each column in the record adding its field name and value to the map
1050 : 0 : for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1051 : : {
1052 : 0 : myFieldName = statement.columnName( myColNo );
1053 : 0 : myFieldValue = statement.columnAsText( myColNo );
1054 : 0 : myMap[myFieldName] = myFieldValue;
1055 : 0 : }
1056 : :
1057 : 0 : if ( statement.step() != SQLITE_DONE )
1058 : : {
1059 : 0 : QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1060 : 0 : myMap.clear();
1061 : 0 : }
1062 : 0 : }
1063 : : else
1064 : : {
1065 : 1 : QgsDebugMsgLevel( "failed : " + sql, 4 );
1066 : : }
1067 : 1 : }
1068 : 1 : return myMap;
1069 : 1 : }
1070 : :
1071 : : // Accessors -----------------------------------
1072 : :
1073 : 0 : long QgsCoordinateReferenceSystem::srsid() const
1074 : : {
1075 : 0 : return d->mSrsId;
1076 : : }
1077 : :
1078 : 0 : long QgsCoordinateReferenceSystem::postgisSrid() const
1079 : : {
1080 : 0 : return d->mSRID;
1081 : : }
1082 : :
1083 : 260 : QString QgsCoordinateReferenceSystem::authid() const
1084 : : {
1085 : 260 : return d->mAuthId;
1086 : : }
1087 : :
1088 : 1 : QString QgsCoordinateReferenceSystem::description() const
1089 : : {
1090 : 1 : if ( d->mDescription.isNull() )
1091 : : {
1092 : 1 : return QString();
1093 : : }
1094 : : else
1095 : : {
1096 : 0 : return d->mDescription;
1097 : : }
1098 : 1 : }
1099 : :
1100 : 0 : QString QgsCoordinateReferenceSystem::userFriendlyIdentifier( IdentifierType type ) const
1101 : : {
1102 : 0 : if ( !authid().isEmpty() )
1103 : : {
1104 : 0 : if ( type != ShortString && !description().isEmpty() )
1105 : 0 : return QStringLiteral( "%1 - %2" ).arg( authid(), description() );
1106 : 0 : return authid();
1107 : : }
1108 : 0 : else if ( !description().isEmpty() )
1109 : 0 : return description();
1110 : 0 : else if ( type == ShortString )
1111 : 0 : return isValid() ? QObject::tr( "Custom CRS" ) : QObject::tr( "Unknown CRS" );
1112 : 0 : else if ( !toWkt( WKT_PREFERRED ).isEmpty() )
1113 : 0 : return QObject::tr( "Custom CRS: %1" ).arg(
1114 : 0 : type == MediumString ? ( toWkt( WKT_PREFERRED ).left( 50 ) + QString( QChar( 0x2026 ) ) )
1115 : 0 : : toWkt( WKT_PREFERRED ) );
1116 : 0 : else if ( !toProj().isEmpty() )
1117 : 0 : return QObject::tr( "Custom CRS: %1" ).arg( type == MediumString ? ( toProj().left( 50 ) + QString( QChar( 0x2026 ) ) )
1118 : 0 : : toProj() );
1119 : : else
1120 : 0 : return QString();
1121 : 0 : }
1122 : :
1123 : 1 : QString QgsCoordinateReferenceSystem::projectionAcronym() const
1124 : : {
1125 : 1 : if ( d->mProjectionAcronym.isNull() )
1126 : : {
1127 : 1 : return QString();
1128 : : }
1129 : : else
1130 : : {
1131 : 0 : return d->mProjectionAcronym;
1132 : : }
1133 : 1 : }
1134 : :
1135 : 1 : QString QgsCoordinateReferenceSystem::ellipsoidAcronym() const
1136 : : {
1137 : 1 : if ( d->mEllipsoidAcronym.isNull() )
1138 : : {
1139 : 1 : if ( PJ *obj = d->threadLocalProjObject() )
1140 : : {
1141 : 0 : QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( QgsProjContext::get(), obj ) );
1142 : 0 : if ( ellipsoid )
1143 : : {
1144 : 0 : const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1145 : 0 : const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1146 : 0 : if ( !ellipsoidAuthName.isEmpty() && !ellipsoidAuthCode.isEmpty() )
1147 : 0 : d->mEllipsoidAcronym = QStringLiteral( "%1:%2" ).arg( ellipsoidAuthName, ellipsoidAuthCode );
1148 : : else
1149 : : {
1150 : : double semiMajor, semiMinor, invFlattening;
1151 : 0 : int semiMinorComputed = 0;
1152 : 0 : if ( proj_ellipsoid_get_parameters( QgsProjContext::get(), ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
1153 : : {
1154 : 0 : d->mEllipsoidAcronym = QStringLiteral( "PARAMETER:%1:%2" ).arg( qgsDoubleToString( semiMajor ),
1155 : 0 : qgsDoubleToString( semiMinor ) );
1156 : 0 : }
1157 : : else
1158 : : {
1159 : 0 : d->mEllipsoidAcronym.clear();
1160 : : }
1161 : : }
1162 : 0 : }
1163 : 0 : }
1164 : 1 : return d->mEllipsoidAcronym;
1165 : : }
1166 : : else
1167 : : {
1168 : 0 : return d->mEllipsoidAcronym;
1169 : : }
1170 : 1 : }
1171 : :
1172 : 0 : QString QgsCoordinateReferenceSystem::toProj4() const
1173 : : {
1174 : 0 : return toProj();
1175 : : }
1176 : :
1177 : 2 : QString QgsCoordinateReferenceSystem::toProj() const
1178 : : {
1179 : 2 : if ( !d->mIsValid )
1180 : 2 : return QString();
1181 : :
1182 : 0 : if ( d->mProj4.isEmpty() )
1183 : : {
1184 : 0 : if ( PJ *obj = d->threadLocalProjObject() )
1185 : : {
1186 : 0 : d->mProj4 = getFullProjString( obj );
1187 : 0 : }
1188 : 0 : }
1189 : : // Stray spaces at the end?
1190 : 0 : return d->mProj4.trimmed();
1191 : 2 : }
1192 : :
1193 : 0 : bool QgsCoordinateReferenceSystem::isGeographic() const
1194 : : {
1195 : 0 : return d->mIsGeographic;
1196 : : }
1197 : :
1198 : 1 : QgsUnitTypes::DistanceUnit QgsCoordinateReferenceSystem::mapUnits() const
1199 : : {
1200 : 1 : if ( !d->mIsValid )
1201 : 1 : return QgsUnitTypes::DistanceUnknownUnit;
1202 : :
1203 : 0 : return d->mMapUnits;
1204 : 1 : }
1205 : :
1206 : 0 : QgsRectangle QgsCoordinateReferenceSystem::bounds() const
1207 : : {
1208 : 0 : if ( !d->mIsValid )
1209 : 0 : return QgsRectangle();
1210 : :
1211 : 0 : PJ *obj = d->threadLocalProjObject();
1212 : 0 : if ( !obj )
1213 : 0 : return QgsRectangle();
1214 : :
1215 : 0 : double westLon = 0;
1216 : 0 : double southLat = 0;
1217 : 0 : double eastLon = 0;
1218 : 0 : double northLat = 0;
1219 : :
1220 : 0 : if ( !proj_get_area_of_use( QgsProjContext::get(), obj,
1221 : : &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1222 : 0 : return QgsRectangle();
1223 : :
1224 : :
1225 : : // don't use the constructor which normalizes!
1226 : 0 : QgsRectangle rect;
1227 : 0 : rect.setXMinimum( westLon );
1228 : 0 : rect.setYMinimum( southLat );
1229 : 0 : rect.setXMaximum( eastLon );
1230 : 0 : rect.setYMaximum( northLat );
1231 : 0 : return rect;
1232 : 0 : }
1233 : :
1234 : 0 : void QgsCoordinateReferenceSystem::updateDefinition()
1235 : : {
1236 : 0 : if ( !d->mIsValid )
1237 : 0 : return;
1238 : :
1239 : 0 : if ( d->mSrsId >= USER_CRS_START_ID )
1240 : : {
1241 : : // user CRS, so update to new definition
1242 : 0 : createFromSrsId( d->mSrsId );
1243 : 0 : }
1244 : : else
1245 : : {
1246 : : // nothing to do -- only user CRS definitions can be changed
1247 : : }
1248 : 0 : }
1249 : :
1250 : 0 : void QgsCoordinateReferenceSystem::setProjString( const QString &proj4String )
1251 : : {
1252 : 0 : d.detach();
1253 : 0 : d->mProj4 = proj4String;
1254 : 0 : d->mWktPreferred.clear();
1255 : :
1256 : 0 : QgsLocaleNumC l;
1257 : 0 : QString trimmed = proj4String.trimmed();
1258 : :
1259 : 0 : trimmed += QLatin1String( " +type=crs" );
1260 : 0 : PJ_CONTEXT *ctx = QgsProjContext::get();
1261 : :
1262 : : {
1263 : 0 : d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create( ctx, trimmed.toLatin1().constData() ) ) );
1264 : : }
1265 : :
1266 : 0 : if ( !d->hasPj() )
1267 : : {
1268 : : #ifdef QGISDEBUG
1269 : : const int errNo = proj_context_errno( ctx );
1270 : : QgsDebugMsg( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
1271 : : #endif
1272 : 0 : d->mIsValid = false;
1273 : 0 : }
1274 : : else
1275 : : {
1276 : 0 : d->mEllipsoidAcronym.clear();
1277 : 0 : d->mIsValid = true;
1278 : : }
1279 : :
1280 : 0 : setMapUnits();
1281 : 0 : }
1282 : :
1283 : 1 : bool QgsCoordinateReferenceSystem::setWktString( const QString &wkt )
1284 : : {
1285 : 1 : bool res = false;
1286 : 1 : d->mIsValid = false;
1287 : 1 : d->mWktPreferred.clear();
1288 : :
1289 : 1 : PROJ_STRING_LIST warnings = nullptr;
1290 : 1 : PROJ_STRING_LIST grammerErrors = nullptr;
1291 : : {
1292 : 1 : d->setPj( QgsProjUtils::proj_pj_unique_ptr( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammerErrors ) ) );
1293 : : }
1294 : :
1295 : 1 : res = d->hasPj();
1296 : 1 : if ( !res )
1297 : : {
1298 : 0 : QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
1299 : 0 : QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
1300 : 0 : QgsDebugMsg( "INPUT: " + wkt );
1301 : 0 : for ( auto iter = warnings; iter && *iter; ++iter )
1302 : 0 : QgsDebugMsg( *iter );
1303 : 0 : for ( auto iter = grammerErrors; iter && *iter; ++iter )
1304 : 0 : QgsDebugMsg( *iter );
1305 : 0 : QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
1306 : 0 : }
1307 : 1 : proj_string_list_destroy( warnings );
1308 : 1 : proj_string_list_destroy( grammerErrors );
1309 : :
1310 : 1 : QgsReadWriteLocker locker( *sProj4CacheLock(), QgsReadWriteLocker::Unlocked );
1311 : 1 : if ( !res )
1312 : : {
1313 : 0 : locker.changeMode( QgsReadWriteLocker::Write );
1314 : 0 : if ( !sDisableWktCache )
1315 : 0 : sWktCache()->insert( wkt, *this );
1316 : 0 : return d->mIsValid;
1317 : : }
1318 : :
1319 : 1 : if ( d->hasPj() )
1320 : : {
1321 : : // try 1 - maybe we can directly grab the auth name and code from the crs already?
1322 : 1 : QString authName( proj_get_id_auth_name( d->threadLocalProjObject(), 0 ) );
1323 : 1 : QString authCode( proj_get_id_code( d->threadLocalProjObject(), 0 ) );
1324 : :
1325 : 1 : if ( authName.isEmpty() || authCode.isEmpty() )
1326 : : {
1327 : : // try 2, use proj's identify method and see if there's a nice candidate we can use
1328 : 0 : QgsProjUtils::identifyCrs( d->threadLocalProjObject(), authName, authCode );
1329 : 0 : }
1330 : :
1331 : 1 : if ( !authName.isEmpty() && !authCode.isEmpty() )
1332 : : {
1333 : 1 : if ( loadFromAuthCode( authName, authCode ) )
1334 : : {
1335 : 1 : locker.changeMode( QgsReadWriteLocker::Write );
1336 : 1 : if ( !sDisableWktCache )
1337 : 1 : sWktCache()->insert( wkt, *this );
1338 : 1 : return d->mIsValid;
1339 : : }
1340 : 0 : }
1341 : : else
1342 : : {
1343 : : // Still a valid CRS, just not a known one
1344 : 0 : d->mIsValid = true;
1345 : 0 : d->mDescription = QString( proj_get_name( d->threadLocalProjObject() ) );
1346 : : }
1347 : 0 : setMapUnits();
1348 : 1 : }
1349 : :
1350 : 0 : return d->mIsValid;
1351 : 1 : }
1352 : :
1353 : 10 : void QgsCoordinateReferenceSystem::setMapUnits()
1354 : : {
1355 : 10 : if ( !d->mIsValid )
1356 : : {
1357 : 0 : d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1358 : 0 : return;
1359 : : }
1360 : :
1361 : 10 : if ( !d->hasPj() )
1362 : : {
1363 : 0 : d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1364 : 0 : return;
1365 : : }
1366 : :
1367 : 10 : PJ_CONTEXT *context = QgsProjContext::get();
1368 : 10 : QgsProjUtils::proj_pj_unique_ptr crs( QgsProjUtils::crsToSingleCrs( d->threadLocalProjObject() ) );
1369 : 10 : QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, crs.get() ) );
1370 : 10 : if ( !coordinateSystem )
1371 : : {
1372 : 0 : d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1373 : 0 : return;
1374 : : }
1375 : :
1376 : 10 : const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1377 : 10 : if ( axisCount > 0 )
1378 : : {
1379 : 10 : const char *outUnitName = nullptr;
1380 : : // Read only first axis
1381 : 10 : proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
1382 : : nullptr,
1383 : : nullptr,
1384 : : nullptr,
1385 : : nullptr,
1386 : : &outUnitName,
1387 : : nullptr,
1388 : : nullptr );
1389 : :
1390 : 10 : const QString unitName( outUnitName );
1391 : :
1392 : : // proj unit names are freeform -- they differ from authority to authority :(
1393 : : // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1394 : 15 : if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
1395 : 5 : unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1396 : 5 : unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1397 : 5 : unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
1398 : 5 : unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1399 : 5 : unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1400 : 5 : unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
1401 : 5 : unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
1402 : 5 : unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1403 : 5 : unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
1404 : 5 : d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1405 : 5 : else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0
1406 : 5 : || unitName.compare( QLatin1String( "m" ), Qt::CaseInsensitive ) == 0
1407 : 0 : || unitName.compare( QLatin1String( "meter" ), Qt::CaseInsensitive ) == 0 )
1408 : 5 : d->mMapUnits = QgsUnitTypes::DistanceMeters;
1409 : : // we don't differentiate between these, suck it imperial users!
1410 : 0 : else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
1411 : 0 : unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
1412 : 0 : d->mMapUnits = QgsUnitTypes::DistanceFeet;
1413 : 0 : else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1414 : 0 : d->mMapUnits = QgsUnitTypes::DistanceKilometers;
1415 : 0 : else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1416 : 0 : d->mMapUnits = QgsUnitTypes::DistanceCentimeters;
1417 : 0 : else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1418 : 0 : d->mMapUnits = QgsUnitTypes::DistanceMillimeters;
1419 : 0 : else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
1420 : 0 : d->mMapUnits = QgsUnitTypes::DistanceMiles;
1421 : 0 : else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
1422 : 0 : d->mMapUnits = QgsUnitTypes::DistanceNauticalMiles;
1423 : 0 : else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
1424 : 0 : d->mMapUnits = QgsUnitTypes::DistanceYards;
1425 : : // TODO - maybe more values to handle here?
1426 : : else
1427 : 0 : d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1428 : : return;
1429 : 10 : }
1430 : : else
1431 : : {
1432 : 0 : d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1433 : 0 : return;
1434 : : }
1435 : 10 : }
1436 : :
1437 : :
1438 : 0 : long QgsCoordinateReferenceSystem::findMatchingProj()
1439 : : {
1440 : 0 : if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1441 : 0 : || !d->mIsValid )
1442 : : {
1443 : 0 : QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1444 : : "work if prj acr ellipsoid acr and proj4string are set"
1445 : : " and the current projection is valid!", 4 );
1446 : 0 : return 0;
1447 : : }
1448 : :
1449 : 0 : sqlite3_database_unique_ptr database;
1450 : 0 : sqlite3_statement_unique_ptr statement;
1451 : : int myResult;
1452 : :
1453 : : // Set up the query to retrieve the projection information
1454 : : // needed to populate the list
1455 : 0 : QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1456 : : "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1457 : 0 : .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1458 : 0 : QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1459 : : // Get the full path name to the sqlite3 spatial reference database.
1460 : 0 : QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1461 : :
1462 : : //check the db is available
1463 : 0 : myResult = openDatabase( myDatabaseFileName, database );
1464 : 0 : if ( myResult != SQLITE_OK )
1465 : : {
1466 : 0 : return 0;
1467 : : }
1468 : :
1469 : 0 : statement = database.prepare( mySql, myResult );
1470 : 0 : if ( myResult == SQLITE_OK )
1471 : : {
1472 : :
1473 : 0 : while ( statement.step() == SQLITE_ROW )
1474 : : {
1475 : 0 : QString mySrsId = statement.columnAsText( 0 );
1476 : 0 : QString myProj4String = statement.columnAsText( 1 );
1477 : 0 : if ( toProj() == myProj4String.trimmed() )
1478 : : {
1479 : 0 : return mySrsId.toLong();
1480 : : }
1481 : 0 : }
1482 : 0 : }
1483 : :
1484 : : //
1485 : : // Try the users db now
1486 : : //
1487 : :
1488 : 0 : myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1489 : : //check the db is available
1490 : 0 : myResult = openDatabase( myDatabaseFileName, database );
1491 : 0 : if ( myResult != SQLITE_OK )
1492 : : {
1493 : 0 : return 0;
1494 : : }
1495 : :
1496 : 0 : statement = database.prepare( mySql, myResult );
1497 : :
1498 : 0 : if ( myResult == SQLITE_OK )
1499 : : {
1500 : 0 : while ( statement.step() == SQLITE_ROW )
1501 : : {
1502 : 0 : QString mySrsId = statement.columnAsText( 0 );
1503 : 0 : QString myProj4String = statement.columnAsText( 1 );
1504 : 0 : if ( toProj() == myProj4String.trimmed() )
1505 : : {
1506 : 0 : return mySrsId.toLong();
1507 : : }
1508 : 0 : }
1509 : 0 : }
1510 : :
1511 : 0 : return 0;
1512 : 0 : }
1513 : :
1514 : 1537 : bool QgsCoordinateReferenceSystem::operator==( const QgsCoordinateReferenceSystem &srs ) const
1515 : : {
1516 : : // shortcut
1517 : 1537 : if ( d == srs.d )
1518 : 5 : return true;
1519 : :
1520 : 1532 : if ( !d->mIsValid && !srs.d->mIsValid )
1521 : 0 : return true;
1522 : :
1523 : 1532 : if ( !d->mIsValid || !srs.d->mIsValid )
1524 : 1518 : return false;
1525 : :
1526 : 14 : const bool isUser = d->mSrsId >= USER_CRS_START_ID;
1527 : 14 : const bool otherIsUser = srs.d->mSrsId >= USER_CRS_START_ID;
1528 : 14 : if ( isUser != otherIsUser )
1529 : 0 : return false;
1530 : :
1531 : : // we can't directly compare authid for user crses -- the actual definition of these may have changed
1532 : 14 : if ( !isUser && ( !d->mAuthId.isEmpty() || !srs.d->mAuthId.isEmpty() ) )
1533 : 14 : return d->mAuthId == srs.d->mAuthId;
1534 : :
1535 : 0 : return toWkt( WKT_PREFERRED ) == srs.toWkt( WKT_PREFERRED );
1536 : 1537 : }
1537 : :
1538 : 1535 : bool QgsCoordinateReferenceSystem::operator!=( const QgsCoordinateReferenceSystem &srs ) const
1539 : : {
1540 : 1535 : return !( *this == srs );
1541 : : }
1542 : :
1543 : 1 : QString QgsCoordinateReferenceSystem::toWkt( WktVariant variant, bool multiline, int indentationWidth ) const
1544 : : {
1545 : 1 : if ( PJ *obj = d->threadLocalProjObject() )
1546 : : {
1547 : 0 : const bool isDefaultPreferredFormat = variant == WKT_PREFERRED && !multiline;
1548 : 0 : if ( isDefaultPreferredFormat && !d->mWktPreferred.isEmpty() )
1549 : : {
1550 : : // can use cached value
1551 : 0 : return d->mWktPreferred;
1552 : : }
1553 : :
1554 : 0 : PJ_WKT_TYPE type = PJ_WKT1_GDAL;
1555 : 0 : switch ( variant )
1556 : : {
1557 : : case WKT1_GDAL:
1558 : 0 : type = PJ_WKT1_GDAL;
1559 : 0 : break;
1560 : : case WKT1_ESRI:
1561 : 0 : type = PJ_WKT1_ESRI;
1562 : 0 : break;
1563 : : case WKT2_2015:
1564 : 0 : type = PJ_WKT2_2015;
1565 : 0 : break;
1566 : : case WKT2_2015_SIMPLIFIED:
1567 : 0 : type = PJ_WKT2_2015_SIMPLIFIED;
1568 : 0 : break;
1569 : : case WKT2_2019:
1570 : 0 : type = PJ_WKT2_2019;
1571 : 0 : break;
1572 : : case WKT2_2019_SIMPLIFIED:
1573 : 0 : type = PJ_WKT2_2019_SIMPLIFIED;
1574 : 0 : break;
1575 : : }
1576 : :
1577 : 0 : const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
1578 : 0 : const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
1579 : 0 : const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
1580 : 0 : QString res = proj_as_wkt( QgsProjContext::get(), obj, type, options );
1581 : :
1582 : 0 : if ( isDefaultPreferredFormat )
1583 : : {
1584 : : // cache result for later use
1585 : 0 : d->mWktPreferred = res;
1586 : 0 : }
1587 : :
1588 : 0 : return res;
1589 : 0 : }
1590 : 1 : return QString();
1591 : 1 : }
1592 : :
1593 : 0 : bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
1594 : : {
1595 : 0 : d.detach();
1596 : 0 : bool result = true;
1597 : 0 : QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
1598 : :
1599 : 0 : if ( ! srsNode.isNull() )
1600 : : {
1601 : 0 : bool initialized = false;
1602 : :
1603 : 0 : bool ok = false;
1604 : 0 : long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
1605 : :
1606 : 0 : QDomNode node;
1607 : :
1608 : 0 : if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
1609 : : {
1610 : 0 : node = srsNode.namedItem( QStringLiteral( "authid" ) );
1611 : 0 : if ( !node.isNull() )
1612 : : {
1613 : 0 : createFromOgcWmsCrs( node.toElement().text() );
1614 : 0 : if ( isValid() )
1615 : : {
1616 : 0 : initialized = true;
1617 : 0 : }
1618 : 0 : }
1619 : :
1620 : 0 : if ( !initialized )
1621 : : {
1622 : 0 : node = srsNode.namedItem( QStringLiteral( "epsg" ) );
1623 : 0 : if ( !node.isNull() )
1624 : : {
1625 : 0 : operator=( QgsCoordinateReferenceSystem::fromEpsgId( node.toElement().text().toLong() ) );
1626 : 0 : if ( isValid() )
1627 : : {
1628 : 0 : initialized = true;
1629 : 0 : }
1630 : 0 : }
1631 : 0 : }
1632 : 0 : }
1633 : :
1634 : : // if wkt is present, prefer that since it's lossless (unlike proj4 strings)
1635 : 0 : if ( !initialized )
1636 : : {
1637 : : // before doing anything, we grab and set the stored CRS name (description).
1638 : : // this way if the stored CRS doesn't match anything available locally (i.e. from Proj's db
1639 : : // or the user's custom CRS list), then we will correctly show the CRS with its original
1640 : : // name (instead of just "custom crs")
1641 : 0 : const QString description = srsNode.namedItem( QStringLiteral( "description" ) ).toElement().text();
1642 : :
1643 : 0 : const QString wkt = srsNode.namedItem( QStringLiteral( "wkt" ) ).toElement().text();
1644 : 0 : initialized = createFromWktInternal( wkt, description );
1645 : 0 : }
1646 : :
1647 : 0 : if ( !initialized )
1648 : : {
1649 : 0 : node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1650 : 0 : const QString proj4 = node.toElement().text();
1651 : 0 : initialized = createFromProj( proj4 );
1652 : 0 : }
1653 : :
1654 : 0 : if ( !initialized )
1655 : : {
1656 : : // Setting from elements one by one
1657 : 0 : node = srsNode.namedItem( QStringLiteral( "proj4" ) );
1658 : 0 : const QString proj4 = node.toElement().text();
1659 : 0 : if ( !proj4.trimmed().isEmpty() )
1660 : 0 : setProjString( node.toElement().text() );
1661 : :
1662 : 0 : node = srsNode.namedItem( QStringLiteral( "srsid" ) );
1663 : 0 : d->mSrsId = node.toElement().text().toLong();
1664 : :
1665 : 0 : node = srsNode.namedItem( QStringLiteral( "srid" ) );
1666 : 0 : d->mSRID = node.toElement().text().toLong();
1667 : :
1668 : 0 : node = srsNode.namedItem( QStringLiteral( "authid" ) );
1669 : 0 : d->mAuthId = node.toElement().text();
1670 : :
1671 : 0 : node = srsNode.namedItem( QStringLiteral( "description" ) );
1672 : 0 : d->mDescription = node.toElement().text();
1673 : :
1674 : 0 : node = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
1675 : 0 : d->mProjectionAcronym = node.toElement().text();
1676 : :
1677 : 0 : node = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
1678 : 0 : d->mEllipsoidAcronym = node.toElement().text();
1679 : :
1680 : 0 : node = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
1681 : 0 : d->mIsGeographic = node.toElement().text().compare( QLatin1String( "true" ) );
1682 : :
1683 : 0 : d->mWktPreferred.clear();
1684 : :
1685 : : //make sure the map units have been set
1686 : 0 : setMapUnits();
1687 : 0 : }
1688 : 0 : }
1689 : : else
1690 : : {
1691 : : // Return empty CRS if none was found in the XML.
1692 : 0 : d = new QgsCoordinateReferenceSystemPrivate();
1693 : 0 : result = false;
1694 : : }
1695 : 0 : return result;
1696 : 0 : }
1697 : :
1698 : 0 : bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
1699 : : {
1700 : 0 : QDomElement layerNode = node.toElement();
1701 : 0 : QDomElement srsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
1702 : :
1703 : 0 : QDomElement wktElement = doc.createElement( QStringLiteral( "wkt" ) );
1704 : 0 : wktElement.appendChild( doc.createTextNode( toWkt( WKT_PREFERRED ) ) );
1705 : 0 : srsElement.appendChild( wktElement );
1706 : :
1707 : 0 : QDomElement proj4Element = doc.createElement( QStringLiteral( "proj4" ) );
1708 : 0 : proj4Element.appendChild( doc.createTextNode( toProj() ) );
1709 : 0 : srsElement.appendChild( proj4Element );
1710 : :
1711 : 0 : QDomElement srsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
1712 : 0 : srsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
1713 : 0 : srsElement.appendChild( srsIdElement );
1714 : :
1715 : 0 : QDomElement sridElement = doc.createElement( QStringLiteral( "srid" ) );
1716 : 0 : sridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
1717 : 0 : srsElement.appendChild( sridElement );
1718 : :
1719 : 0 : QDomElement authidElement = doc.createElement( QStringLiteral( "authid" ) );
1720 : 0 : authidElement.appendChild( doc.createTextNode( authid() ) );
1721 : 0 : srsElement.appendChild( authidElement );
1722 : :
1723 : 0 : QDomElement descriptionElement = doc.createElement( QStringLiteral( "description" ) );
1724 : 0 : descriptionElement.appendChild( doc.createTextNode( description() ) );
1725 : 0 : srsElement.appendChild( descriptionElement );
1726 : :
1727 : 0 : QDomElement projectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
1728 : 0 : projectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
1729 : 0 : srsElement.appendChild( projectionAcronymElement );
1730 : :
1731 : 0 : QDomElement ellipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
1732 : 0 : ellipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
1733 : 0 : srsElement.appendChild( ellipsoidAcronymElement );
1734 : :
1735 : 0 : QDomElement geographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
1736 : 0 : QString geoFlagText = QStringLiteral( "false" );
1737 : 0 : if ( isGeographic() )
1738 : : {
1739 : 0 : geoFlagText = QStringLiteral( "true" );
1740 : 0 : }
1741 : :
1742 : 0 : geographicFlagElement.appendChild( doc.createTextNode( geoFlagText ) );
1743 : 0 : srsElement.appendChild( geographicFlagElement );
1744 : :
1745 : 0 : layerNode.appendChild( srsElement );
1746 : :
1747 : : return true;
1748 : 0 : }
1749 : :
1750 : : //
1751 : : // Static helper methods below this point only please!
1752 : : //
1753 : :
1754 : :
1755 : : // Returns the whole proj4 string for the selected srsid
1756 : : //this is a static method! NOTE I've made it private for now to reduce API clutter TS
1757 : 0 : QString QgsCoordinateReferenceSystem::projFromSrsId( const int srsId )
1758 : : {
1759 : 0 : QString myDatabaseFileName;
1760 : 0 : QString myProjString;
1761 : 0 : QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
1762 : :
1763 : : //
1764 : : // Determine if this is a user projection or a system on
1765 : : // user projection defs all have srs_id >= 100000
1766 : : //
1767 : 0 : if ( srsId >= USER_CRS_START_ID )
1768 : : {
1769 : 0 : myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1770 : 0 : QFileInfo myFileInfo;
1771 : 0 : myFileInfo.setFile( myDatabaseFileName );
1772 : 0 : if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
1773 : : {
1774 : 0 : QgsDebugMsg( QStringLiteral( "users qgis.db not found" ) );
1775 : 0 : return QString();
1776 : : }
1777 : 0 : }
1778 : : else //must be a system projection then
1779 : : {
1780 : 0 : myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1781 : : }
1782 : :
1783 : 0 : sqlite3_database_unique_ptr database;
1784 : 0 : sqlite3_statement_unique_ptr statement;
1785 : :
1786 : : int rc;
1787 : 0 : rc = openDatabase( myDatabaseFileName, database );
1788 : 0 : if ( rc )
1789 : : {
1790 : 0 : return QString();
1791 : : }
1792 : :
1793 : 0 : statement = database.prepare( mySql, rc );
1794 : :
1795 : 0 : if ( rc == SQLITE_OK )
1796 : : {
1797 : 0 : if ( statement.step() == SQLITE_ROW )
1798 : : {
1799 : 0 : myProjString = statement.columnAsText( 0 );
1800 : 0 : }
1801 : 0 : }
1802 : :
1803 : 0 : return myProjString;
1804 : 0 : }
1805 : :
1806 : 2 : int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
1807 : : {
1808 : : int myResult;
1809 : 2 : if ( readonly )
1810 : 2 : myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
1811 : : else
1812 : 0 : myResult = database.open( path );
1813 : :
1814 : 2 : if ( myResult != SQLITE_OK )
1815 : : {
1816 : 0 : QgsDebugMsg( "Can't open database: " + database.errorMessage() );
1817 : : // XXX This will likely never happen since on open, sqlite creates the
1818 : : // database if it does not exist.
1819 : : // ... unfortunately it happens on Windows
1820 : 0 : QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
1821 : 0 : .arg( path )
1822 : 0 : .arg( myResult )
1823 : 0 : .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
1824 : 0 : }
1825 : 2 : return myResult;
1826 : 0 : }
1827 : :
1828 : 0 : void QgsCoordinateReferenceSystem::setCustomCrsValidation( CUSTOM_CRS_VALIDATION f )
1829 : : {
1830 : 0 : sCustomSrsValidation = f;
1831 : 0 : }
1832 : :
1833 : 0 : CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::customCrsValidation()
1834 : : {
1835 : 0 : return sCustomSrsValidation;
1836 : : }
1837 : :
1838 : 0 : void QgsCoordinateReferenceSystem::debugPrint()
1839 : : {
1840 : 0 : QgsDebugMsg( QStringLiteral( "***SpatialRefSystem***" ) );
1841 : 0 : QgsDebugMsg( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ) );
1842 : 0 : QgsDebugMsg( "* SrsId : " + QString::number( d->mSrsId ) );
1843 : 0 : QgsDebugMsg( "* Proj4 : " + toProj() );
1844 : 0 : QgsDebugMsg( "* WKT : " + toWkt( WKT_PREFERRED ) );
1845 : 0 : QgsDebugMsg( "* Desc. : " + d->mDescription );
1846 : 0 : if ( mapUnits() == QgsUnitTypes::DistanceMeters )
1847 : : {
1848 : 0 : QgsDebugMsg( QStringLiteral( "* Units : meters" ) );
1849 : 0 : }
1850 : 0 : else if ( mapUnits() == QgsUnitTypes::DistanceFeet )
1851 : : {
1852 : 0 : QgsDebugMsg( QStringLiteral( "* Units : feet" ) );
1853 : 0 : }
1854 : 0 : else if ( mapUnits() == QgsUnitTypes::DistanceDegrees )
1855 : : {
1856 : 0 : QgsDebugMsg( QStringLiteral( "* Units : degrees" ) );
1857 : 0 : }
1858 : 0 : }
1859 : :
1860 : 63 : void QgsCoordinateReferenceSystem::setValidationHint( const QString &html )
1861 : : {
1862 : 63 : mValidationHint = html;
1863 : 63 : }
1864 : :
1865 : 0 : QString QgsCoordinateReferenceSystem::validationHint()
1866 : : {
1867 : 0 : return mValidationHint;
1868 : : }
1869 : :
1870 : 0 : long QgsCoordinateReferenceSystem::saveAsUserCrs( const QString &name, Format nativeFormat )
1871 : : {
1872 : 0 : return QgsApplication::coordinateReferenceSystemRegistry()->addUserCrs( *this, name, nativeFormat );
1873 : : }
1874 : :
1875 : 0 : long QgsCoordinateReferenceSystem::getRecordCount()
1876 : : {
1877 : 0 : sqlite3_database_unique_ptr database;
1878 : 0 : sqlite3_statement_unique_ptr statement;
1879 : : int myResult;
1880 : 0 : long myRecordCount = 0;
1881 : : //check the db is available
1882 : 0 : myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
1883 : 0 : if ( myResult != SQLITE_OK )
1884 : : {
1885 : 0 : QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
1886 : 0 : return 0;
1887 : : }
1888 : : // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
1889 : 0 : QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
1890 : 0 : statement = database.prepare( mySql, myResult );
1891 : 0 : if ( myResult == SQLITE_OK )
1892 : : {
1893 : 0 : if ( statement.step() == SQLITE_ROW )
1894 : : {
1895 : 0 : QString myRecordCountString = statement.columnAsText( 0 );
1896 : 0 : myRecordCount = myRecordCountString.toLong();
1897 : 0 : }
1898 : 0 : }
1899 : 0 : return myRecordCount;
1900 : 0 : }
1901 : :
1902 : 10 : bool testIsGeographic( PJ *crs )
1903 : : {
1904 : 10 : PJ_CONTEXT *pjContext = QgsProjContext::get();
1905 : 10 : bool isGeographic = false;
1906 : 10 : QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, crs ) );
1907 : 10 : if ( coordinateSystem )
1908 : : {
1909 : 10 : const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
1910 : 10 : if ( axisCount > 0 )
1911 : : {
1912 : 10 : const char *outUnitAuthName = nullptr;
1913 : 10 : const char *outUnitAuthCode = nullptr;
1914 : : // Read only first axis
1915 : 10 : proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
1916 : : nullptr,
1917 : : nullptr,
1918 : : nullptr,
1919 : : nullptr,
1920 : : nullptr,
1921 : : &outUnitAuthName,
1922 : : &outUnitAuthCode );
1923 : :
1924 : 10 : if ( outUnitAuthName && outUnitAuthCode )
1925 : : {
1926 : 10 : const char *unitCategory = nullptr;
1927 : 10 : if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
1928 : : {
1929 : 10 : isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
1930 : 10 : }
1931 : 10 : }
1932 : 10 : }
1933 : 10 : }
1934 : 10 : return isGeographic;
1935 : 10 : }
1936 : :
1937 : 10 : void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
1938 : : {
1939 : 13 : thread_local const QRegularExpression projRegExp( QStringLiteral( "\\+proj=(\\S+)" ) );
1940 : 10 : const QRegularExpressionMatch projMatch = projRegExp.match( proj );
1941 : 10 : if ( !projMatch.hasMatch() )
1942 : : {
1943 : 0 : QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
1944 : 0 : return;
1945 : : }
1946 : 10 : operation = projMatch.captured( 1 );
1947 : :
1948 : 13 : thread_local const QRegularExpression ellipseRegExp( QStringLiteral( "\\+(?:ellps|datum)=(\\S+)" ) );
1949 : 10 : const QRegularExpressionMatch ellipseMatch = projRegExp.match( proj );
1950 : 10 : QString ellps;
1951 : 10 : if ( !ellipseMatch.hasMatch() )
1952 : : {
1953 : 0 : ellipsoid = ellipseMatch.captured( 1 );
1954 : 0 : }
1955 : : else
1956 : : {
1957 : : // satisfy not null constraint on ellipsoid_acronym field
1958 : : // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
1959 : : // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
1960 : : // set for these CRSes). Better just hack around and make the constraint happy for now,
1961 : : // and hope that the definitions get corrected in future.
1962 : 10 : ellipsoid.clear();
1963 : : }
1964 : 10 : }
1965 : :
1966 : :
1967 : 10 : bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
1968 : : {
1969 : 10 : d.detach();
1970 : 10 : d->mIsValid = false;
1971 : 10 : d->mWktPreferred.clear();
1972 : :
1973 : 10 : PJ_CONTEXT *pjContext = QgsProjContext::get();
1974 : 10 : QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
1975 : 10 : if ( !crs )
1976 : : {
1977 : 0 : return false;
1978 : : }
1979 : :
1980 : 10 : switch ( proj_get_type( crs.get() ) )
1981 : : {
1982 : : case PJ_TYPE_VERTICAL_CRS:
1983 : 0 : return false;
1984 : :
1985 : : default:
1986 : 10 : break;
1987 : : }
1988 : :
1989 : 10 : crs = QgsProjUtils::crsToSingleCrs( crs.get() );
1990 : :
1991 : 10 : QString proj4 = getFullProjString( crs.get() );
1992 : 10 : proj4.replace( QLatin1String( "+type=crs" ), QString() );
1993 : 10 : proj4 = proj4.trimmed();
1994 : :
1995 : 10 : d->mIsValid = true;
1996 : 10 : d->mProj4 = proj4;
1997 : 10 : d->mWktPreferred.clear();
1998 : 10 : d->mDescription = QString( proj_get_name( crs.get() ) );
1999 : 20 : d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
2000 : 10 : d->mIsGeographic = testIsGeographic( crs.get() );
2001 : 10 : d->mAxisInvertedDirty = true;
2002 : 10 : QString operation;
2003 : 10 : QString ellipsoid;
2004 : 10 : getOperationAndEllipsoidFromProjString( proj4, operation, ellipsoid );
2005 : 10 : d->mProjectionAcronym = operation;
2006 : 10 : d->mEllipsoidAcronym.clear();
2007 : 10 : d->setPj( std::move( crs ) );
2008 : :
2009 : 20 : const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
2010 : 10 : QString srsId;
2011 : 10 : QString srId;
2012 : 10 : if ( !dbVals.isEmpty() )
2013 : : {
2014 : 10 : const QStringList parts = dbVals.split( ',' );
2015 : 10 : d->mSrsId = parts.at( 0 ).toInt();
2016 : 10 : d->mSRID = parts.at( 1 ).toInt();
2017 : 10 : }
2018 : :
2019 : 10 : setMapUnits();
2020 : :
2021 : 10 : return true;
2022 : 10 : }
2023 : :
2024 : 0 : QList<long> QgsCoordinateReferenceSystem::userSrsIds()
2025 : : {
2026 : 0 : QList<long> results;
2027 : : // check user defined projection database
2028 : 0 : const QString db = QgsApplication::qgisUserDatabaseFilePath();
2029 : :
2030 : 0 : QFileInfo myInfo( db );
2031 : 0 : if ( !myInfo.exists() )
2032 : : {
2033 : 0 : QgsDebugMsg( "failed : " + db + " does not exist!" );
2034 : 0 : return results;
2035 : : }
2036 : :
2037 : 0 : sqlite3_database_unique_ptr database;
2038 : 0 : sqlite3_statement_unique_ptr statement;
2039 : :
2040 : : //check the db is available
2041 : 0 : int result = openDatabase( db, database );
2042 : 0 : if ( result != SQLITE_OK )
2043 : : {
2044 : 0 : QgsDebugMsg( "failed : " + db + " could not be opened!" );
2045 : 0 : return results;
2046 : : }
2047 : :
2048 : 0 : QString sql = QStringLiteral( "select srs_id from tbl_srs where srs_id >= %1" ).arg( USER_CRS_START_ID );
2049 : : int rc;
2050 : 0 : statement = database.prepare( sql, rc );
2051 : 0 : while ( true )
2052 : : {
2053 : 0 : int ret = statement.step();
2054 : :
2055 : 0 : if ( ret == SQLITE_DONE )
2056 : : {
2057 : : // there are no more rows to fetch - we can stop looping
2058 : 0 : break;
2059 : : }
2060 : :
2061 : 0 : if ( ret == SQLITE_ROW )
2062 : : {
2063 : 0 : results.append( statement.columnAsInt64( 0 ) );
2064 : 0 : }
2065 : : else
2066 : : {
2067 : 0 : QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
2068 : 0 : break;
2069 : : }
2070 : : }
2071 : :
2072 : 0 : return results;
2073 : 0 : }
2074 : :
2075 : 0 : long QgsCoordinateReferenceSystem::matchToUserCrs() const
2076 : : {
2077 : 0 : PJ *obj = d->threadLocalProjObject();
2078 : 0 : if ( !obj )
2079 : 0 : return 0;
2080 : :
2081 : 0 : const QList< long > ids = userSrsIds();
2082 : 0 : for ( long id : ids )
2083 : : {
2084 : 0 : QgsCoordinateReferenceSystem candidate = QgsCoordinateReferenceSystem::fromSrsId( id );
2085 : 0 : if ( candidate.projObject() && proj_is_equivalent_to( obj, candidate.projObject(), PJ_COMP_EQUIVALENT ) )
2086 : : {
2087 : 0 : return id;
2088 : : }
2089 : 0 : }
2090 : 0 : return 0;
2091 : 0 : }
2092 : :
2093 : 0 : static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2094 : : {
2095 : : #ifndef QGISDEBUG
2096 : : Q_UNUSED( message )
2097 : : #endif
2098 : 0 : if ( level == PJ_LOG_ERROR )
2099 : : {
2100 : 0 : QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
2101 : 0 : }
2102 : 0 : else if ( level == PJ_LOG_DEBUG )
2103 : : {
2104 : 0 : QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
2105 : 0 : }
2106 : 0 : }
2107 : :
2108 : 0 : int QgsCoordinateReferenceSystem::syncDatabase()
2109 : : {
2110 : 0 : setlocale( LC_ALL, "C" );
2111 : 0 : QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2112 : :
2113 : 0 : int inserted = 0, updated = 0, deleted = 0, errors = 0;
2114 : :
2115 : 0 : QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2116 : :
2117 : 0 : sqlite3_database_unique_ptr database;
2118 : 0 : if ( database.open( dbFilePath ) != SQLITE_OK )
2119 : : {
2120 : 0 : QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2121 : 0 : return -1;
2122 : : }
2123 : :
2124 : 0 : if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2125 : : {
2126 : 0 : QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2127 : 0 : return -1;
2128 : : }
2129 : :
2130 : 0 : sqlite3_statement_unique_ptr statement;
2131 : : int result;
2132 : 0 : char *errMsg = nullptr;
2133 : :
2134 : 0 : if ( sqlite3_exec( database.get(), "create table tbl_info (proj_major INT, proj_minor INT, proj_patch INT)", nullptr, nullptr, nullptr ) == SQLITE_OK )
2135 : : {
2136 : 0 : QString sql = QStringLiteral( "INSERT INTO tbl_info(proj_major, proj_minor, proj_patch) VALUES (%1, %2,%3)" )
2137 : 0 : .arg( QString::number( PROJ_VERSION_MAJOR ),
2138 : 0 : QString::number( PROJ_VERSION_MINOR ),
2139 : 0 : QString::number( PROJ_VERSION_PATCH ) );
2140 : 0 : if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2141 : : {
2142 : 0 : QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2143 : : sql,
2144 : : database.errorMessage(),
2145 : : errMsg ? errMsg : "(unknown error)" ) );
2146 : 0 : if ( errMsg )
2147 : 0 : sqlite3_free( errMsg );
2148 : 0 : return -1;
2149 : : }
2150 : 0 : }
2151 : : else
2152 : : {
2153 : : // retrieve last update details
2154 : 0 : QString sql = QStringLiteral( "SELECT proj_major, proj_minor, proj_patch FROM tbl_info" );
2155 : 0 : statement = database.prepare( sql, result );
2156 : 0 : if ( result != SQLITE_OK )
2157 : : {
2158 : 0 : QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2159 : 0 : return -1;
2160 : : }
2161 : 0 : if ( statement.step() == SQLITE_ROW )
2162 : : {
2163 : 0 : int major = statement.columnAsInt64( 0 );
2164 : 0 : int minor = statement.columnAsInt64( 1 );
2165 : 0 : int patch = statement.columnAsInt64( 2 );
2166 : 0 : if ( major == PROJ_VERSION_MAJOR && minor == PROJ_VERSION_MINOR && patch == PROJ_VERSION_PATCH )
2167 : : // yay, nothing to do!
2168 : 0 : return 0;
2169 : 0 : }
2170 : : else
2171 : : {
2172 : 0 : QgsDebugMsg( QStringLiteral( "Could not retrieve previous CRS sync PROJ version number" ) );
2173 : 0 : return -1;
2174 : : }
2175 : 0 : }
2176 : :
2177 : 0 : PJ_CONTEXT *pjContext = QgsProjContext::get();
2178 : : // silence proj warnings
2179 : 0 : proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2180 : :
2181 : 0 : PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2182 : :
2183 : 0 : int nextSrsId = 63321;
2184 : 0 : int nextSrId = 520003321;
2185 : 0 : for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2186 : : {
2187 : 0 : const QString authority( *authIter );
2188 : 0 : QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
2189 : 0 : PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2190 : :
2191 : 0 : QStringList allCodes;
2192 : :
2193 : 0 : for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2194 : : {
2195 : 0 : const QString code( *codesIter );
2196 : 0 : allCodes << QgsSqliteUtils::quotedString( code );
2197 : 0 : QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
2198 : 0 : QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2199 : 0 : if ( !crs )
2200 : : {
2201 : 0 : QgsDebugMsg( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
2202 : 0 : continue;
2203 : : }
2204 : :
2205 : 0 : switch ( proj_get_type( crs.get() ) )
2206 : : {
2207 : : case PJ_TYPE_VERTICAL_CRS: // don't need these in the CRS db
2208 : 0 : continue;
2209 : :
2210 : : default:
2211 : 0 : break;
2212 : : }
2213 : :
2214 : 0 : crs = QgsProjUtils::crsToSingleCrs( crs.get() );
2215 : :
2216 : 0 : QString proj4 = getFullProjString( crs.get() );
2217 : 0 : proj4.replace( QLatin1String( "+type=crs" ), QString() );
2218 : 0 : proj4 = proj4.trimmed();
2219 : :
2220 : 0 : if ( proj4.isEmpty() )
2221 : : {
2222 : 0 : QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
2223 : : // satisfy not null constraint
2224 : 0 : proj4 = "";
2225 : 0 : }
2226 : :
2227 : 0 : const bool deprecated = proj_is_deprecated( crs.get() );
2228 : 0 : const QString name( proj_get_name( crs.get() ) );
2229 : :
2230 : 0 : QString sql = QStringLiteral( "SELECT parameters,description,deprecated FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
2231 : 0 : statement = database.prepare( sql, result );
2232 : 0 : if ( result != SQLITE_OK )
2233 : : {
2234 : 0 : QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2235 : 0 : continue;
2236 : : }
2237 : :
2238 : 0 : QString srsProj4;
2239 : 0 : QString srsDesc;
2240 : 0 : bool srsDeprecated = deprecated;
2241 : 0 : if ( statement.step() == SQLITE_ROW )
2242 : : {
2243 : 0 : srsProj4 = statement.columnAsText( 0 );
2244 : 0 : srsDesc = statement.columnAsText( 1 );
2245 : 0 : srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2246 : 0 : }
2247 : :
2248 : 0 : if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
2249 : : {
2250 : 0 : if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
2251 : : {
2252 : 0 : errMsg = nullptr;
2253 : 0 : sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name=%4 AND auth_id=%5" )
2254 : 0 : .arg( QgsSqliteUtils::quotedString( proj4 ) )
2255 : 0 : .arg( QgsSqliteUtils::quotedString( name ) )
2256 : 0 : .arg( deprecated ? 1 : 0 )
2257 : 0 : .arg( QgsSqliteUtils::quotedString( authority ), QgsSqliteUtils::quotedString( code ) );
2258 : :
2259 : 0 : if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2260 : : {
2261 : 0 : QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2262 : : sql,
2263 : : database.errorMessage(),
2264 : : errMsg ? errMsg : "(unknown error)" ) );
2265 : 0 : if ( errMsg )
2266 : 0 : sqlite3_free( errMsg );
2267 : 0 : errors++;
2268 : 0 : }
2269 : : else
2270 : : {
2271 : 0 : updated++;
2272 : : }
2273 : 0 : }
2274 : 0 : }
2275 : : else
2276 : : {
2277 : : // there's a not-null contraint on these columns, so we must use empty strings instead
2278 : 0 : QString operation = "";
2279 : 0 : QString ellps = "";
2280 : 0 : getOperationAndEllipsoidFromProjString( proj4, operation, ellps );
2281 : 0 : const bool isGeographic = testIsGeographic( crs.get() );
2282 : :
2283 : : // work out srid and srsid
2284 : 0 : const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
2285 : 0 : QString srsId;
2286 : 0 : QString srId;
2287 : 0 : if ( !dbVals.isEmpty() )
2288 : : {
2289 : 0 : const QStringList parts = dbVals.split( ',' );
2290 : 0 : srsId = parts.at( 0 );
2291 : 0 : srId = parts.at( 1 );
2292 : 0 : }
2293 : 0 : if ( srId.isEmpty() )
2294 : : {
2295 : 0 : srId = QString::number( nextSrId );
2296 : 0 : nextSrId++;
2297 : 0 : }
2298 : 0 : if ( srsId.isEmpty() )
2299 : : {
2300 : 0 : srsId = QString::number( nextSrsId );
2301 : 0 : nextSrsId++;
2302 : 0 : }
2303 : :
2304 : 0 : if ( !srsId.isEmpty() )
2305 : : {
2306 : 0 : 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)" )
2307 : 0 : .arg( srsId )
2308 : 0 : .arg( QgsSqliteUtils::quotedString( name ),
2309 : 0 : QgsSqliteUtils::quotedString( operation ),
2310 : 0 : QgsSqliteUtils::quotedString( ellps ),
2311 : 0 : QgsSqliteUtils::quotedString( proj4 ) )
2312 : 0 : .arg( srId )
2313 : 0 : .arg( QgsSqliteUtils::quotedString( authority ) )
2314 : 0 : .arg( QgsSqliteUtils::quotedString( code ) )
2315 : 0 : .arg( isGeographic ? 1 : 0 )
2316 : 0 : .arg( deprecated ? 1 : 0 );
2317 : 0 : }
2318 : : else
2319 : : {
2320 : 0 : 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)" )
2321 : 0 : .arg( QgsSqliteUtils::quotedString( name ),
2322 : 0 : QgsSqliteUtils::quotedString( operation ),
2323 : 0 : QgsSqliteUtils::quotedString( ellps ),
2324 : 0 : QgsSqliteUtils::quotedString( proj4 ) )
2325 : 0 : .arg( srId )
2326 : 0 : .arg( QgsSqliteUtils::quotedString( authority ) )
2327 : 0 : .arg( QgsSqliteUtils::quotedString( code ) )
2328 : 0 : .arg( isGeographic ? 1 : 0 )
2329 : 0 : .arg( deprecated ? 1 : 0 );
2330 : : }
2331 : :
2332 : 0 : errMsg = nullptr;
2333 : 0 : if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2334 : : {
2335 : 0 : inserted++;
2336 : 0 : }
2337 : : else
2338 : : {
2339 : 0 : qCritical( "Could not execute: %s [%s/%s]\n",
2340 : 0 : sql.toLocal8Bit().constData(),
2341 : 0 : sqlite3_errmsg( database.get() ),
2342 : 0 : errMsg ? errMsg : "(unknown error)" );
2343 : 0 : errors++;
2344 : :
2345 : 0 : if ( errMsg )
2346 : 0 : sqlite3_free( errMsg );
2347 : : }
2348 : 0 : }
2349 : 0 : }
2350 : :
2351 : 0 : proj_string_list_destroy( codes );
2352 : :
2353 : 0 : const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
2354 : 0 : if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2355 : : {
2356 : 0 : deleted = sqlite3_changes( database.get() );
2357 : 0 : }
2358 : : else
2359 : : {
2360 : 0 : errors++;
2361 : 0 : qCritical( "Could not execute: %s [%s]\n",
2362 : 0 : sql.toLocal8Bit().constData(),
2363 : 0 : sqlite3_errmsg( database.get() ) );
2364 : : }
2365 : :
2366 : 0 : }
2367 : 0 : proj_string_list_destroy( authorities );
2368 : :
2369 : 0 : QString sql = QStringLiteral( "UPDATE tbl_info set proj_major=%1,proj_minor=%2,proj_patch=%3" )
2370 : 0 : .arg( QString::number( PROJ_VERSION_MAJOR ),
2371 : 0 : QString::number( PROJ_VERSION_MINOR ),
2372 : 0 : QString::number( PROJ_VERSION_PATCH ) );
2373 : 0 : if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2374 : : {
2375 : 0 : QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2376 : : sql,
2377 : : database.errorMessage(),
2378 : : errMsg ? errMsg : "(unknown error)" ) );
2379 : 0 : if ( errMsg )
2380 : 0 : sqlite3_free( errMsg );
2381 : 0 : return -1;
2382 : : }
2383 : :
2384 : 0 : if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2385 : : {
2386 : 0 : QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2387 : : QgsApplication::srsDatabaseFilePath(),
2388 : : sqlite3_errmsg( database.get() ) )
2389 : : );
2390 : 0 : return -1;
2391 : : }
2392 : :
2393 : : #ifdef QGISDEBUG
2394 : : QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( inserted ).arg( updated ).arg( deleted ).arg( errors ), 4 );
2395 : : #else
2396 : : Q_UNUSED( deleted )
2397 : : #endif
2398 : :
2399 : 0 : if ( errors > 0 )
2400 : 0 : return -errors;
2401 : : else
2402 : 0 : return updated + inserted;
2403 : 0 : }
2404 : :
2405 : 0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::stringCache()
2406 : : {
2407 : 0 : return *sStringCache();
2408 : : }
2409 : :
2410 : 0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::projCache()
2411 : : {
2412 : 0 : return *sProj4Cache();
2413 : : }
2414 : :
2415 : 0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::ogcCache()
2416 : : {
2417 : 0 : return *sOgcCache();
2418 : : }
2419 : :
2420 : 0 : const QHash<QString, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::wktCache()
2421 : : {
2422 : 0 : return *sWktCache();
2423 : : }
2424 : :
2425 : 0 : const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srIdCache()
2426 : : {
2427 : 0 : return *sSrIdCache();
2428 : : }
2429 : :
2430 : 0 : const QHash<long, QgsCoordinateReferenceSystem> &QgsCoordinateReferenceSystem::srsIdCache()
2431 : : {
2432 : 0 : return *sSrsIdCache();
2433 : : }
2434 : :
2435 : 0 : QString QgsCoordinateReferenceSystem::geographicCrsAuthId() const
2436 : : {
2437 : 0 : if ( isGeographic() )
2438 : : {
2439 : 0 : return d->mAuthId;
2440 : : }
2441 : 0 : else if ( PJ *obj = d->threadLocalProjObject() )
2442 : : {
2443 : 0 : QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), obj ) );
2444 : 0 : return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
2445 : 0 : }
2446 : : else
2447 : : {
2448 : 0 : return QString();
2449 : : }
2450 : 0 : }
2451 : :
2452 : 4 : PJ *QgsCoordinateReferenceSystem::projObject() const
2453 : : {
2454 : 4 : return d->threadLocalProjObject();
2455 : : }
2456 : :
2457 : 0 : QStringList QgsCoordinateReferenceSystem::recentProjections()
2458 : : {
2459 : 0 : QStringList projections;
2460 : 0 : const QList<QgsCoordinateReferenceSystem> res = recentCoordinateReferenceSystems();
2461 : 0 : projections.reserve( res.size() );
2462 : 0 : for ( const QgsCoordinateReferenceSystem &crs : res )
2463 : : {
2464 : 0 : projections << QString::number( crs.srsid() );
2465 : : }
2466 : 0 : return projections;
2467 : 0 : }
2468 : :
2469 : 0 : QList<QgsCoordinateReferenceSystem> QgsCoordinateReferenceSystem::recentCoordinateReferenceSystems()
2470 : : {
2471 : 0 : QList<QgsCoordinateReferenceSystem> res;
2472 : :
2473 : : // Read settings from persistent storage
2474 : 0 : QgsSettings settings;
2475 : 0 : QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
2476 : 0 : QStringList projectionsWkt = settings.value( QStringLiteral( "UI/recentProjectionsWkt" ) ).toStringList();
2477 : 0 : QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
2478 : 0 : int max = std::max( projectionsAuthId.size(), std::max( projectionsProj4.size(), projectionsWkt.size() ) );
2479 : 0 : res.reserve( max );
2480 : 0 : for ( int i = 0; i < max; ++i )
2481 : : {
2482 : 0 : const QString proj = projectionsProj4.value( i );
2483 : 0 : const QString wkt = projectionsWkt.value( i );
2484 : 0 : const QString authid = projectionsAuthId.value( i );
2485 : :
2486 : 0 : QgsCoordinateReferenceSystem crs;
2487 : 0 : if ( !authid.isEmpty() )
2488 : 0 : crs = QgsCoordinateReferenceSystem( authid );
2489 : 0 : if ( !crs.isValid() && !wkt.isEmpty() )
2490 : 0 : crs.createFromWkt( wkt );
2491 : 0 : if ( !crs.isValid() && !proj.isEmpty() )
2492 : 0 : crs.createFromProj( wkt );
2493 : :
2494 : 0 : if ( crs.isValid() )
2495 : 0 : res << crs;
2496 : 0 : }
2497 : 0 : return res;
2498 : 0 : }
2499 : :
2500 : 0 : void QgsCoordinateReferenceSystem::pushRecentCoordinateReferenceSystem( const QgsCoordinateReferenceSystem &crs )
2501 : : {
2502 : : // we only want saved and standard CRSes in the recent list
2503 : 0 : if ( crs.srsid() == 0 || !crs.isValid() )
2504 : 0 : return;
2505 : :
2506 : 0 : QList<QgsCoordinateReferenceSystem> recent = recentCoordinateReferenceSystems();
2507 : 0 : recent.removeAll( crs );
2508 : 0 : recent.insert( 0, crs );
2509 : :
2510 : : // trim to max 30 items
2511 : 0 : recent = recent.mid( 0, 30 );
2512 : 0 : QStringList authids;
2513 : 0 : authids.reserve( recent.size() );
2514 : 0 : QStringList proj;
2515 : 0 : proj.reserve( recent.size() );
2516 : 0 : QStringList wkt;
2517 : 0 : wkt.reserve( recent.size() );
2518 : 0 : for ( const QgsCoordinateReferenceSystem &c : std::as_const( recent ) )
2519 : : {
2520 : 0 : authids << c.authid();
2521 : 0 : proj << c.toProj();
2522 : 0 : wkt << c.toWkt( WKT_PREFERRED );
2523 : : }
2524 : :
2525 : 0 : QgsSettings settings;
2526 : 0 : settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), authids );
2527 : 0 : settings.setValue( QStringLiteral( "UI/recentProjectionsWkt" ), wkt );
2528 : 0 : settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), proj );
2529 : 0 : }
2530 : :
2531 : 8 : void QgsCoordinateReferenceSystem::invalidateCache( bool disableCache )
2532 : : {
2533 : 8 : sSrIdCacheLock()->lockForWrite();
2534 : 8 : if ( !sDisableSrIdCache )
2535 : : {
2536 : 5 : if ( disableCache )
2537 : 5 : sDisableSrIdCache = true;
2538 : 5 : sSrIdCache()->clear();
2539 : 5 : }
2540 : 8 : sSrIdCacheLock()->unlock();
2541 : :
2542 : 8 : sOgcLock()->lockForWrite();
2543 : 8 : if ( !sDisableOgcCache )
2544 : : {
2545 : 5 : if ( disableCache )
2546 : 5 : sDisableOgcCache = true;
2547 : 5 : sOgcCache()->clear();
2548 : 5 : }
2549 : 8 : sOgcLock()->unlock();
2550 : :
2551 : 8 : sProj4CacheLock()->lockForWrite();
2552 : 8 : if ( !sDisableProjCache )
2553 : : {
2554 : 5 : if ( disableCache )
2555 : 5 : sDisableProjCache = true;
2556 : 5 : sProj4Cache()->clear();
2557 : 5 : }
2558 : 8 : sProj4CacheLock()->unlock();
2559 : :
2560 : 8 : sCRSWktLock()->lockForWrite();
2561 : 8 : if ( !sDisableWktCache )
2562 : : {
2563 : 5 : if ( disableCache )
2564 : 5 : sDisableWktCache = true;
2565 : 5 : sWktCache()->clear();
2566 : 5 : }
2567 : 8 : sCRSWktLock()->unlock();
2568 : :
2569 : 8 : sCRSSrsIdLock()->lockForWrite();
2570 : 8 : if ( !sDisableSrsIdCache )
2571 : : {
2572 : 5 : if ( disableCache )
2573 : 5 : sDisableSrsIdCache = true;
2574 : 5 : sSrsIdCache()->clear();
2575 : 5 : }
2576 : 8 : sCRSSrsIdLock()->unlock();
2577 : :
2578 : 8 : sCrsStringLock()->lockForWrite();
2579 : 8 : if ( !sDisableStringCache )
2580 : : {
2581 : 5 : if ( disableCache )
2582 : 5 : sDisableStringCache = true;
2583 : 5 : sStringCache()->clear();
2584 : 5 : }
2585 : 8 : sCrsStringLock()->unlock();
2586 : 8 : }
2587 : :
2588 : : // invalid < regular < user
2589 : 0 : bool operator> ( const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2 )
2590 : : {
2591 : 0 : if ( c1.d == c2.d )
2592 : 0 : return false;
2593 : :
2594 : 0 : if ( !c1.d->mIsValid && !c2.d->mIsValid )
2595 : 0 : return false;
2596 : :
2597 : 0 : if ( !c1.d->mIsValid && c2.d->mIsValid )
2598 : 0 : return false;
2599 : :
2600 : 0 : if ( c1.d->mIsValid && !c2.d->mIsValid )
2601 : 0 : return true;
2602 : :
2603 : 0 : const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
2604 : 0 : const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
2605 : :
2606 : 0 : if ( c1IsUser && !c2IsUser )
2607 : 0 : return true;
2608 : :
2609 : 0 : if ( !c1IsUser && c2IsUser )
2610 : 0 : return false;
2611 : :
2612 : 0 : if ( !c1IsUser && !c2IsUser )
2613 : 0 : return c1.d->mAuthId > c2.d->mAuthId;
2614 : :
2615 : 0 : return c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) > c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
2616 : 0 : }
2617 : :
2618 : 0 : bool operator< ( const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2 )
2619 : : {
2620 : 0 : if ( c1.d == c2.d )
2621 : 0 : return false;
2622 : :
2623 : 0 : if ( !c1.d->mIsValid && !c2.d->mIsValid )
2624 : 0 : return false;
2625 : :
2626 : 0 : if ( c1.d->mIsValid && !c2.d->mIsValid )
2627 : 0 : return false;
2628 : :
2629 : 0 : if ( !c1.d->mIsValid && c2.d->mIsValid )
2630 : 0 : return true;
2631 : :
2632 : 0 : const bool c1IsUser = c1.d->mSrsId >= USER_CRS_START_ID;
2633 : 0 : const bool c2IsUser = c2.d->mSrsId >= USER_CRS_START_ID;
2634 : :
2635 : 0 : if ( !c1IsUser && c2IsUser )
2636 : 0 : return true;
2637 : :
2638 : 0 : if ( c1IsUser && !c2IsUser )
2639 : 0 : return false;
2640 : :
2641 : 0 : if ( !c1IsUser && !c2IsUser )
2642 : 0 : return c1.d->mAuthId < c2.d->mAuthId;
2643 : :
2644 : 0 : return c1.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) < c2.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
2645 : 0 : }
2646 : 0 : bool operator>= ( const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2 )
2647 : : {
2648 : 0 : return !( c1 < c2 );
2649 : : }
2650 : 0 : bool operator<= ( const QgsCoordinateReferenceSystem &c1, const QgsCoordinateReferenceSystem &c2 )
2651 : : {
2652 : 0 : return !( c1 > c2 );
2653 : : }
|