QGIS API Documentation  3.4.15-Madeira (e83d02e274)
qgscoordinatereferencesystem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinatereferencesystem.cpp
3 
4  -------------------
5  begin : 2007
6  copyright : (C) 2007 by Gary E. Sherman
7  email : [email protected]
8 ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
20 
21 #include "qgsreadwritelocker.h"
22 #include <cmath>
23 
24 #include <QDir>
25 #include <QDomNode>
26 #include <QDomElement>
27 #include <QFileInfo>
28 #include <QRegExp>
29 #include <QTextStream>
30 #include <QFile>
31 #include <QRegularExpression>
32 
33 #include "qgsapplication.h"
34 #include "qgslogger.h"
35 #include "qgsmessagelog.h"
36 #include "qgis.h" //const vals declared here
37 #include "qgslocalec.h"
38 #include "qgssettings.h"
39 
40 #include <sqlite3.h>
41 #ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
42 #define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
43 #endif
44 #include <proj_api.h>
45 
46 //gdal and ogr includes (needed for == operator)
47 #include <ogr_srs_api.h>
48 #include <cpl_error.h>
49 #include <cpl_conv.h>
50 #include <cpl_csv.h>
51 
52 
54 const int LAT_PREFIX_LEN = 7;
55 
56 CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::mCustomSrsValidation = nullptr;
57 
58 QReadWriteLock QgsCoordinateReferenceSystem::sSrIdCacheLock;
59 QHash< long, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sSrIdCache;
60 bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
61 
62 QReadWriteLock QgsCoordinateReferenceSystem::sOgcLock;
63 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sOgcCache;
64 bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
65 
66 QReadWriteLock QgsCoordinateReferenceSystem::sProj4CacheLock;
67 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sProj4Cache;
68 bool QgsCoordinateReferenceSystem::sDisableProj4Cache = false;
69 
70 QReadWriteLock QgsCoordinateReferenceSystem::sCRSWktLock;
71 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sWktCache;
72 bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
73 
74 QReadWriteLock QgsCoordinateReferenceSystem::sCRSSrsIdLock;
75 QHash< long, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sSrsIdCache;
76 bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
77 
78 QReadWriteLock QgsCoordinateReferenceSystem::sCrsStringLock;
79 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sStringCache;
80 bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
81 
82 //--------------------------
83 
85 {
86  d = new QgsCoordinateReferenceSystemPrivate();
87 }
88 
90 {
91  d = new QgsCoordinateReferenceSystemPrivate();
92  createFromString( definition );
93 }
94 
96 {
97  d = new QgsCoordinateReferenceSystemPrivate();
98  createFromId( id, type );
99 }
100 
102  : d( srs.d )
103 {
104 }
105 
107 {
108  d = srs.d;
109  return *this;
110 }
111 
113 {
114  QList<long> results;
115  // check both standard & user defined projection databases
116  QStringList dbs = QStringList() << QgsApplication::srsDatabaseFilePath() << QgsApplication::qgisUserDatabaseFilePath();
117 
118  Q_FOREACH ( const QString &db, dbs )
119  {
120  QFileInfo myInfo( db );
121  if ( !myInfo.exists() )
122  {
123  QgsDebugMsg( "failed : " + db + " does not exist!" );
124  continue;
125  }
126 
129 
130  //check the db is available
131  int result = openDatabase( db, database );
132  if ( result != SQLITE_OK )
133  {
134  QgsDebugMsg( "failed : " + db + " could not be opened!" );
135  continue;
136  }
137 
138  QString sql = QStringLiteral( "select srs_id from tbl_srs" );
139  int rc;
140  statement = database.prepare( sql, rc );
141  while ( true )
142  {
143  // this one is an infinitive loop, intended to fetch any row
144  int ret = statement.step();
145 
146  if ( ret == SQLITE_DONE )
147  {
148  // there are no more rows to fetch - we can stop looping
149  break;
150  }
151 
152  if ( ret == SQLITE_ROW )
153  {
154  results.append( statement.columnAsInt64( 0 ) );
155  }
156  else
157  {
158  QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
159  break;
160  }
161  }
162  }
163  std::sort( results.begin(), results.end() );
164  return results;
165 }
166 
168 {
170  crs.createFromOgcWmsCrs( ogcCrs );
171  return crs;
172 }
173 
175 {
176  return fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
177 }
178 
180 {
182  crs.createFromProj4( proj4 );
183  return crs;
184 }
185 
187 {
189  crs.createFromWkt( wkt );
190  return crs;
191 }
192 
194 {
196  crs.createFromSrsId( srsId );
197  return crs;
198 }
199 
201 {
202 }
203 
205 {
206  bool result = false;
207  switch ( type )
208  {
209  case InternalCrsId:
210  result = createFromSrsId( id );
211  break;
212  case PostgisCrsId:
213  result = createFromSrid( id );
214  break;
215  case EpsgCrsId:
216  result = createFromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( id ) );
217  break;
218  default:
219  //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
220  QgsDebugMsg( QStringLiteral( "Unexpected case reached!" ) );
221  };
222  return result;
223 }
224 
225 bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
226 {
227  QgsReadWriteLocker locker( sCrsStringLock, QgsReadWriteLocker::Read );
228  if ( !sDisableStringCache )
229  {
230  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache.constFind( definition );
231  if ( crsIt != sStringCache.constEnd() )
232  {
233  // found a match in the cache
234  *this = crsIt.value();
235  return true;
236  }
237  }
238  locker.unlock();
239 
240  bool result = false;
241  QRegularExpression reCrsId( "^(epsg|postgis|internal|user)\\:(\\d+)$", QRegularExpression::CaseInsensitiveOption );
242  QRegularExpressionMatch match = reCrsId.match( definition );
243  if ( match.capturedStart() == 0 )
244  {
245  QString authName = match.captured( 1 ).toLower();
246  CrsType type = InternalCrsId;
247  if ( authName == QLatin1String( "epsg" ) )
248  type = EpsgCrsId;
249  if ( authName == QLatin1String( "postgis" ) )
250  type = PostgisCrsId;
251  long id = match.captured( 2 ).toLong();
252  result = createFromId( id, type );
253  }
254  else
255  {
256  QRegularExpression reCrsStr( "^(?:(wkt|proj4)\\:)?(.+)$", QRegularExpression::CaseInsensitiveOption );
257  match = reCrsStr.match( definition );
258  if ( match.capturedStart() == 0 )
259  {
260  if ( match.captured( 1 ).compare( QLatin1String( "proj4" ), Qt::CaseInsensitive ) == 0 )
261  {
262  result = createFromProj4( match.captured( 2 ) );
263  //TODO: createFromProj4 used to save to the user database any new CRS
264  // this behavior was changed in order to separate creation and saving.
265  // Not sure if it necessary to save it here, should be checked by someone
266  // familiar with the code (should also give a more descriptive name to the generated CRS)
267  if ( srsid() == 0 )
268  {
269  QString myName = QStringLiteral( " * %1 (%2)" )
270  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
271  toProj4() );
272  saveAsUserCrs( myName );
273  }
274  }
275  else
276  {
277  result = createFromWkt( match.captured( 2 ) );
278  }
279  }
280  }
281 
283  if ( !sDisableStringCache )
284  sStringCache.insert( definition, *this );
285  return result;
286 }
287 
288 bool QgsCoordinateReferenceSystem::createFromUserInput( const QString &definition )
289 {
290  QString userWkt;
291  char *wkt = nullptr;
292  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
293 
294  // make sure towgs84 parameter is loaded if using an ESRI definition and gdal >= 1.9
295  if ( definition.startsWith( QLatin1String( "ESRI::" ) ) )
296  {
297  setupESRIWktFix();
298  }
299 
300  if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
301  {
302  if ( OSRExportToWkt( crs, &wkt ) == OGRERR_NONE )
303  {
304  userWkt = wkt;
305  CPLFree( wkt );
306  }
307  OSRDestroySpatialReference( crs );
308  }
309  //QgsDebugMsg( "definition: " + definition + " wkt = " + wkt );
310  return createFromWkt( userWkt );
311 }
312 
314 {
315  // make sure towgs84 parameter is loaded if gdal >= 1.9
316  // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
317  const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
318  const char *configNew = "GEOGCS";
319  // only set if it was not set, to let user change the value if needed
320  if ( strcmp( configOld, "" ) == 0 )
321  {
322  CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
323  if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
324  QgsLogger::warning( QStringLiteral( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2" )
325  .arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
326  QgsDebugMsgLevel( QStringLiteral( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ), 4 );
327  }
328  else
329  {
330  QgsDebugMsgLevel( QStringLiteral( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ), 4 );
331  }
332 }
333 
335 {
336  QgsReadWriteLocker locker( sOgcLock, QgsReadWriteLocker::Read );
337  if ( !sDisableOgcCache )
338  {
339  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache.constFind( crs );
340  if ( crsIt != sOgcCache.constEnd() )
341  {
342  // found a match in the cache
343  *this = crsIt.value();
344  return true;
345  }
346  }
347  locker.unlock();
348 
349  QString wmsCrs = crs;
350 
351  QRegExp re( "urn:ogc:def:crs:([^:]+).+([^:]+)", Qt::CaseInsensitive );
352  if ( re.exactMatch( wmsCrs ) )
353  {
354  wmsCrs = re.cap( 1 ) + ':' + re.cap( 2 );
355  }
356  else
357  {
358  re.setPattern( QStringLiteral( "(user|custom|qgis):(\\d+)" ) );
359  if ( re.exactMatch( wmsCrs ) && createFromSrsId( re.cap( 2 ).toInt() ) )
360  {
362  if ( !sDisableOgcCache )
363  sOgcCache.insert( crs, *this );
364  return true;
365  }
366  }
367 
368  if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), wmsCrs.toLower() ) )
369  {
371  if ( !sDisableOgcCache )
372  sOgcCache.insert( crs, *this );
373  return true;
374  }
375 
376  // NAD27
377  if ( wmsCrs.compare( QLatin1String( "CRS:27" ), Qt::CaseInsensitive ) == 0 ||
378  wmsCrs.compare( QLatin1String( "OGC:CRS27" ), Qt::CaseInsensitive ) == 0 )
379  {
380  // TODO: verify same axis orientation
381  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4267" ) );
382  }
383 
384  // NAD83
385  if ( wmsCrs.compare( QLatin1String( "CRS:83" ), Qt::CaseInsensitive ) == 0 ||
386  wmsCrs.compare( QLatin1String( "OGC:CRS83" ), Qt::CaseInsensitive ) == 0 )
387  {
388  // TODO: verify same axis orientation
389  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4269" ) );
390  }
391 
392  // WGS84
393  if ( wmsCrs.compare( QLatin1String( "CRS:84" ), Qt::CaseInsensitive ) == 0 ||
394  wmsCrs.compare( QLatin1String( "OGC:CRS84" ), Qt::CaseInsensitive ) == 0 )
395  {
396  createFromOgcWmsCrs( QStringLiteral( "EPSG:4326" ) );
397 
398  d.detach();
399  d->mAxisInverted = false;
400  d->mAxisInvertedDirty = false;
401 
403  if ( !sDisableOgcCache )
404  sOgcCache.insert( crs, *this );
405 
406  return d->mIsValid;
407  }
408 
410  if ( !sDisableOgcCache )
411  sOgcCache.insert( crs, QgsCoordinateReferenceSystem() );
412  return false;
413 }
414 
415 // Misc helper functions -----------------------
416 
417 
419 {
420  if ( d->mIsValid )
421  return;
422 
423  d.detach();
424 
425  // try to validate using custom validation routines
426  if ( mCustomSrsValidation )
427  mCustomSrsValidation( *this );
428 
429  if ( !d->mIsValid )
430  {
432  }
433 }
434 
436 {
437  QgsReadWriteLocker locker( sSrIdCacheLock, QgsReadWriteLocker::Read );
438  if ( !sDisableSrIdCache )
439  {
440  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache.constFind( id );
441  if ( crsIt != sSrIdCache.constEnd() )
442  {
443  // found a match in the cache
444  *this = crsIt.value();
445  return true;
446  }
447  }
448  locker.unlock();
449 
450  bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "srid" ), QString::number( id ) );
451 
453  if ( !sDisableSrIdCache )
454  sSrIdCache.insert( id, *this );
455 
456  return result;
457 }
458 
460 {
461  QgsReadWriteLocker locker( sCRSSrsIdLock, QgsReadWriteLocker::Read );
462  if ( !sDisableSrsIdCache )
463  {
464  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache.constFind( id );
465  if ( crsIt != sSrsIdCache.constEnd() )
466  {
467  // found a match in the cache
468  *this = crsIt.value();
469  return true;
470  }
471  }
472  locker.unlock();
473 
474  bool result = loadFromDatabase( id < USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() :
476  QStringLiteral( "srs_id" ), QString::number( id ) );
477 
479  if ( !sDisableSrsIdCache )
480  sSrsIdCache.insert( id, *this );
481  return result;
482 }
483 
484 bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
485 {
486  d.detach();
487 
488  QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
489  d->mIsValid = false;
490  d->mWkt.clear();
491 
492  QFileInfo myInfo( db );
493  if ( !myInfo.exists() )
494  {
495  QgsDebugMsg( "failed : " + db + " does not exist!" );
496  return d->mIsValid;
497  }
498 
501  int myResult;
502  //check the db is available
503  myResult = openDatabase( db, database );
504  if ( myResult != SQLITE_OK )
505  {
506  return d->mIsValid;
507  }
508 
509  /*
510  srs_id INTEGER PRIMARY KEY,
511  description text NOT NULL,
512  projection_acronym text NOT NULL,
513  ellipsoid_acronym NOT NULL,
514  parameters text NOT NULL,
515  srid integer NOT NULL,
516  auth_name varchar NOT NULL,
517  auth_id integer NOT NULL,
518  is_geo integer NOT NULL);
519  */
520 
521  QString mySql = "select srs_id,description,projection_acronym,"
522  "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo "
523  "from tbl_srs where " + expression + '=' + QgsSqliteUtils::quotedString( value ) + " order by deprecated";
524  statement = database.prepare( mySql, myResult );
525  // XXX Need to free memory from the error msg if one is set
526  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
527  {
528  d->mSrsId = statement.columnAsText( 0 ).toLong();
529  d->mDescription = statement.columnAsText( 1 );
530  d->mProjectionAcronym = statement.columnAsText( 2 );
531  d->mEllipsoidAcronym = statement.columnAsText( 3 );
532  d->mProj4 = statement.columnAsText( 4 );
533  d->mSRID = statement.columnAsText( 5 ).toLong();
534  d->mAuthId = statement.columnAsText( 6 );
535  d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
536  d->mAxisInvertedDirty = true;
537 
538  if ( d->mSrsId >= USER_CRS_START_ID && d->mAuthId.isEmpty() )
539  {
540  d->mAuthId = QStringLiteral( "USER:%1" ).arg( d->mSrsId );
541  }
542  else if ( d->mAuthId.startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
543  {
544  OSRDestroySpatialReference( d->mCRS );
545  d->mCRS = OSRNewSpatialReference( nullptr );
546  d->mIsValid = OSRSetFromUserInput( d->mCRS, d->mAuthId.toLower().toLatin1() ) == OGRERR_NONE;
547  setMapUnits();
548  }
549 
550  if ( !d->mIsValid )
551  {
552  setProj4String( d->mProj4 );
553  }
554  }
555  else
556  {
557  QgsDebugMsgLevel( "failed : " + mySql, 4 );
558  }
559  return d->mIsValid;
560 }
561 
563 {
564  if ( d->mAxisInvertedDirty )
565  {
566  OGRAxisOrientation orientation;
567  OSRGetAxis( d->mCRS, OSRIsGeographic( d->mCRS ) ? "GEOGCS" : "PROJCS", 0, &orientation );
568 
569  // If axis orientation is unknown, try again with OSRImportFromEPSGA for EPSG crs
570  if ( orientation == OAO_Other && d->mAuthId.startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
571  {
572  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
573 
574  if ( OSRImportFromEPSGA( crs, d->mAuthId.midRef( 5 ).toInt() ) == OGRERR_NONE )
575  {
576  OSRGetAxis( crs, OSRIsGeographic( crs ) ? "GEOGCS" : "PROJCS", 0, &orientation );
577  }
578 
579  OSRDestroySpatialReference( crs );
580  }
581 
582  d->mAxisInverted = orientation == OAO_North;
583  d->mAxisInvertedDirty = false;
584  }
585 
586  return d->mAxisInverted;
587 }
588 
590 {
591  d.detach();
592 
593  QgsReadWriteLocker locker( sCRSWktLock, QgsReadWriteLocker::Read );
594  if ( !sDisableWktCache )
595  {
596  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache.constFind( wkt );
597  if ( crsIt != sWktCache.constEnd() )
598  {
599  // found a match in the cache
600  *this = crsIt.value();
601  return true;
602  }
603  }
604  locker.unlock();
605 
606  d->mIsValid = false;
607  d->mWkt.clear();
608  d->mProj4.clear();
609 
610  if ( wkt.isEmpty() )
611  {
612  QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
613  return d->mIsValid;
614  }
615  QByteArray ba = wkt.toLatin1();
616  const char *pWkt = ba.data();
617 
618  OGRErr myInputResult = OSRImportFromWkt( d->mCRS, const_cast< char ** >( & pWkt ) );
619 
620  if ( myInputResult != OGRERR_NONE )
621  {
622  QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
623  QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
624  QgsDebugMsg( "INPUT: " + wkt );
625  QgsDebugMsg( QStringLiteral( "UNUSED WKT: %1" ).arg( pWkt ) );
626  QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
627 
629  if ( !sDisableWktCache )
630  sWktCache.insert( wkt, *this );
631  return d->mIsValid;
632  }
633 
634  if ( OSRAutoIdentifyEPSG( d->mCRS ) == OGRERR_NONE )
635  {
636  QString authid = QStringLiteral( "%1:%2" )
637  .arg( OSRGetAuthorityName( d->mCRS, nullptr ),
638  OSRGetAuthorityCode( d->mCRS, nullptr ) );
639  bool result = createFromOgcWmsCrs( authid );
641  if ( !sDisableWktCache )
642  sWktCache.insert( wkt, *this );
643  return result;
644  }
645 
646  // always morph from esri as it doesn't hurt anything
647  // FW: Hey, that's not right! It can screw stuff up! Disable
648  //myOgrSpatialRef.morphFromESRI();
649 
650  // create the proj4 structs needed for transforming
651  char *proj4src = nullptr;
652  OSRExportToProj4( d->mCRS, &proj4src );
653 
654  //now that we have the proj4string, delegate to createFromProj4 so
655  // that we can try to fill in the remaining class members...
656  //create from Proj will set the isValidFlag
657  if ( !createFromProj4( proj4src ) )
658  {
659  CPLFree( proj4src );
660 
661 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,5,0)
662  // try fixed up version
663  OSRFixup( d->mCRS );
664 #endif
665 
666  OSRExportToProj4( d->mCRS, &proj4src );
667 
668  createFromProj4( proj4src );
669  }
670  //TODO: createFromProj4 used to save to the user database any new CRS
671  // this behavior was changed in order to separate creation and saving.
672  // Not sure if it necessary to save it here, should be checked by someone
673  // familiar with the code (should also give a more descriptive name to the generated CRS)
674  if ( d->mSrsId == 0 )
675  {
676  QString myName = QStringLiteral( " * %1 (%2)" )
677  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
678  toProj4() );
679  saveAsUserCrs( myName );
680  }
681 
682  CPLFree( proj4src );
683 
685  if ( !sDisableWktCache )
686  sWktCache.insert( wkt, *this );
687 
688  return d->mIsValid;
689  //setMapunits will be called by createfromproj above
690 }
691 
693 {
694  return d->mIsValid;
695 }
696 
697 bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
698 {
699  d.detach();
700 
701  QgsReadWriteLocker locker( sProj4CacheLock, QgsReadWriteLocker::Read );
702  if ( !sDisableProj4Cache )
703  {
704  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache.constFind( proj4String );
705  if ( crsIt != sProj4Cache.constEnd() )
706  {
707  // found a match in the cache
708  *this = crsIt.value();
709  return true;
710  }
711  }
712  locker.unlock();
713 
714  //
715  // Examples:
716  // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
717  // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
718  //
719  // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
720  // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
721  //
722  QString myProj4String = proj4String.trimmed();
723  d->mIsValid = false;
724  d->mWkt.clear();
725 
726  QRegExp myProjRegExp( "\\+proj=(\\S+)" );
727  int myStart = myProjRegExp.indexIn( myProj4String );
728  if ( myStart == -1 )
729  {
731  if ( !sDisableProj4Cache )
732  sProj4Cache.insert( proj4String, *this );
733 
734  return d->mIsValid;
735  }
736 
737  d->mProjectionAcronym = myProjRegExp.cap( 1 );
738 
739  QRegExp myEllipseRegExp( "\\+ellps=(\\S+)" );
740  myStart = myEllipseRegExp.indexIn( myProj4String );
741  if ( myStart == -1 )
742  {
743  d->mEllipsoidAcronym.clear();
744  }
745  else
746  {
747  d->mEllipsoidAcronym = myEllipseRegExp.cap( 1 );
748  }
749 
750  QRegExp myAxisRegExp( "\\+a=(\\S+)" );
751  myStart = myAxisRegExp.indexIn( myProj4String );
752 
753  long mySrsId = 0;
754  QgsCoordinateReferenceSystem::RecordMap myRecord;
755 
756  /*
757  * We try to match the proj string to and srsid using the following logic:
758  * - perform a whole text search on proj4 string (if not null)
759  */
760  myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
761  if ( myRecord.empty() )
762  {
763  // Ticket #722 - aaronr
764  // Check if we can swap the lat_1 and lat_2 params (if they exist) to see if we match...
765  // First we check for lat_1 and lat_2
766  QRegExp myLat1RegExp( "\\+lat_1=\\S+" );
767  QRegExp myLat2RegExp( "\\+lat_2=\\S+" );
768  int myStart1 = 0;
769  int myLength1 = 0;
770  int myStart2 = 0;
771  int myLength2 = 0;
772  QString lat1Str;
773  QString lat2Str;
774  myStart1 = myLat1RegExp.indexIn( myProj4String, myStart1 );
775  myStart2 = myLat2RegExp.indexIn( myProj4String, myStart2 );
776  if ( myStart1 != -1 && myStart2 != -1 )
777  {
778  myLength1 = myLat1RegExp.matchedLength();
779  myLength2 = myLat2RegExp.matchedLength();
780  lat1Str = myProj4String.mid( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN );
781  lat2Str = myProj4String.mid( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN );
782  }
783  // If we found the lat_1 and lat_2 we need to swap and check to see if we can find it...
784  if ( !lat1Str.isEmpty() && !lat2Str.isEmpty() )
785  {
786  // Make our new string to check...
787  QString proj4StringModified = myProj4String;
788  // First just swap in the lat_2 value for lat_1 value
789  proj4StringModified.replace( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN, lat2Str );
790  // Now we have to find the lat_2 location again since it has potentially moved...
791  myStart2 = 0;
792  myStart2 = myLat2RegExp.indexIn( proj4String, myStart2 );
793  proj4StringModified.replace( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN, lat1Str );
794  QgsDebugMsgLevel( QStringLiteral( "trying proj4string match with swapped lat_1,lat_2" ), 4 );
795  myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( proj4StringModified.trimmed() ) + " order by deprecated" );
796  }
797  }
798 
799  if ( myRecord.empty() )
800  {
801  // match all parameters individually:
802  // - order of parameters doesn't matter
803  // - found definition may have more parameters (like +towgs84 in GDAL)
804  // - retry without datum, if no match is found (looks like +datum<>WGS84 was dropped in GDAL)
805 
806  QString sql = QStringLiteral( "SELECT * FROM tbl_srs WHERE " );
807  QString delim;
808  QString datum;
809 
810  // split on spaces followed by a plus sign (+) to deal
811  // also with parameters containing spaces (e.g. +nadgrids)
812  // make sure result is trimmed (#5598)
813  QStringList myParams;
814  Q_FOREACH ( const QString &param, myProj4String.split( QRegExp( "\\s+(?=\\+)" ), QString::SkipEmptyParts ) )
815  {
816  QString arg = QStringLiteral( "' '||parameters||' ' LIKE %1" ).arg( QgsSqliteUtils::quotedString( QStringLiteral( "% %1 %" ).arg( param.trimmed() ) ) );
817  if ( param.startsWith( QLatin1String( "+datum=" ) ) )
818  {
819  datum = arg;
820  }
821  else
822  {
823  sql += delim + arg;
824  delim = QStringLiteral( " AND " );
825  myParams << param.trimmed();
826  }
827  }
828 
829  if ( !datum.isEmpty() )
830  {
831  myRecord = getRecord( sql + delim + datum + " order by deprecated" );
832  }
833 
834  if ( myRecord.empty() )
835  {
836  // datum might have disappeared in definition - retry without it
837  myRecord = getRecord( sql + " order by deprecated" );
838  }
839 
840  if ( !myRecord.empty() )
841  {
842  // Bugfix 8487 : test param lists are equal, except for +datum
843  QStringList foundParams;
844  Q_FOREACH ( const QString &param, myRecord["parameters"].split( QRegExp( "\\s+(?=\\+)" ), QString::SkipEmptyParts ) )
845  {
846  if ( !param.startsWith( QLatin1String( "+datum=" ) ) )
847  foundParams << param.trimmed();
848  }
849 
850  myParams.sort();
851  foundParams.sort();
852 
853  if ( myParams != foundParams )
854  {
855  myRecord.clear();
856  }
857  }
858  }
859 
860  if ( !myRecord.empty() )
861  {
862  mySrsId = myRecord[QStringLiteral( "srs_id" )].toLong();
863  if ( mySrsId > 0 )
864  {
865  createFromSrsId( mySrsId );
866  }
867  }
868  else
869  {
870  // Last ditch attempt to piece together what we know of the projection to find a match...
871  setProj4String( myProj4String );
872  mySrsId = findMatchingProj();
873  if ( mySrsId > 0 )
874  {
875  createFromSrsId( mySrsId );
876  }
877  else
878  {
879  d->mIsValid = false;
880  }
881  }
882 
883  // if we failed to look up the projection in database, don't worry. we can still use it :)
884  if ( !d->mIsValid )
885  {
886  QgsDebugMsgLevel( QStringLiteral( "Projection is not found in databases." ), 4 );
887  //setProj4String will set mIsValidFlag to true if there is no issue
888  setProj4String( myProj4String );
889  }
890 
892  if ( !sDisableProj4Cache )
893  sProj4Cache.insert( proj4String, *this );
894 
895  return d->mIsValid;
896 }
897 
898 //private method meant for internal use by this class only
899 QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
900 {
901  QString myDatabaseFileName;
902  QgsCoordinateReferenceSystem::RecordMap myMap;
903  QString myFieldName;
904  QString myFieldValue;
907  int myResult;
908 
909  // Get the full path name to the sqlite3 spatial reference database.
910  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
911  QFileInfo myInfo( myDatabaseFileName );
912  if ( !myInfo.exists() )
913  {
914  QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
915  return myMap;
916  }
917 
918  //check the db is available
919  myResult = openDatabase( myDatabaseFileName, database );
920  if ( myResult != SQLITE_OK )
921  {
922  return myMap;
923  }
924 
925  statement = database.prepare( sql, myResult );
926  // XXX Need to free memory from the error msg if one is set
927  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
928  {
929  int myColumnCount = statement.columnCount();
930  //loop through each column in the record adding its expression name and value to the map
931  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
932  {
933  myFieldName = statement.columnName( myColNo );
934  myFieldValue = statement.columnAsText( myColNo );
935  myMap[myFieldName] = myFieldValue;
936  }
937  if ( statement.step() != SQLITE_DONE )
938  {
939  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
940  myMap.clear();
941  }
942  }
943  else
944  {
945  QgsDebugMsgLevel( "failed : " + sql, 4 );
946  }
947 
948  if ( myMap.empty() )
949  {
950  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
951  QFileInfo myFileInfo;
952  myFileInfo.setFile( myDatabaseFileName );
953  if ( !myFileInfo.exists() )
954  {
955  QgsDebugMsg( QStringLiteral( "user qgis.db not found" ) );
956  return myMap;
957  }
958 
959  //check the db is available
960  myResult = openDatabase( myDatabaseFileName, database );
961  if ( myResult != SQLITE_OK )
962  {
963  return myMap;
964  }
965 
966  statement = database.prepare( sql, myResult );
967  // XXX Need to free memory from the error msg if one is set
968  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
969  {
970  int myColumnCount = statement.columnCount();
971  //loop through each column in the record adding its field name and value to the map
972  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
973  {
974  myFieldName = statement.columnName( myColNo );
975  myFieldValue = statement.columnAsText( myColNo );
976  myMap[myFieldName] = myFieldValue;
977  }
978 
979  if ( statement.step() != SQLITE_DONE )
980  {
981  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
982  myMap.clear();
983  }
984  }
985  else
986  {
987  QgsDebugMsgLevel( "failed : " + sql, 4 );
988  }
989  }
990  return myMap;
991 }
992 
993 // Accessors -----------------------------------
994 
996 {
997  return d->mSrsId;
998 }
999 
1001 {
1002  return d->mSRID;
1003 }
1004 
1006 {
1007  return d->mAuthId;
1008 }
1009 
1011 {
1012  if ( d->mDescription.isNull() )
1013  {
1014  return QString();
1015  }
1016  else
1017  {
1018  return d->mDescription;
1019  }
1020 }
1021 
1023 {
1024  if ( d->mProjectionAcronym.isNull() )
1025  {
1026  return QString();
1027  }
1028  else
1029  {
1030  return d->mProjectionAcronym;
1031  }
1032 }
1033 
1035 {
1036  if ( d->mEllipsoidAcronym.isNull() )
1037  {
1038  return QString();
1039  }
1040  else
1041  {
1042  return d->mEllipsoidAcronym;
1043  }
1044 }
1045 
1047 {
1048  if ( !d->mIsValid )
1049  return QString();
1050 
1051  if ( d->mProj4.isEmpty() )
1052  {
1053  char *proj4src = nullptr;
1054  OSRExportToProj4( d->mCRS, &proj4src );
1055  d->mProj4 = proj4src;
1056  CPLFree( proj4src );
1057  }
1058  // Stray spaces at the end?
1059  return d->mProj4.trimmed();
1060 }
1061 
1063 {
1064  return d->mIsGeographic;
1065 }
1066 
1068 {
1069  if ( !d->mIsValid )
1071 
1072  return d->mMapUnits;
1073 }
1074 
1076 {
1077  if ( !d->mIsValid )
1078  return QgsRectangle();
1079 
1080  //check the db is available
1081  QString databaseFileName = QgsApplication::srsDatabaseFilePath();
1082 
1083  sqlite3_database_unique_ptr database;
1084  sqlite3_statement_unique_ptr statement;
1085 
1086  int result = openDatabase( databaseFileName, database );
1087  if ( result != SQLITE_OK )
1088  {
1089  return QgsRectangle();
1090  }
1091 
1092  QString sql = QStringLiteral( "select west_bound_lon, north_bound_lat, east_bound_lon, south_bound_lat from tbl_bounds "
1093  "where srid=%1" )
1094  .arg( d->mSRID );
1095  statement = database.prepare( sql, result );
1096 
1097  QgsRectangle rect;
1098  if ( result == SQLITE_OK )
1099  {
1100  if ( statement.step() == SQLITE_ROW )
1101  {
1102  double west = statement.columnAsDouble( 0 );
1103  double north = statement.columnAsDouble( 1 );
1104  double east = statement.columnAsDouble( 2 );
1105  double south = statement.columnAsDouble( 3 );
1106 
1107  rect.setXMinimum( west );
1108  rect.setYMinimum( south );
1109  rect.setXMaximum( east );
1110  rect.setYMaximum( north );
1111  }
1112  }
1113 
1114  return rect;
1115 }
1116 
1117 
1118 // Mutators -----------------------------------
1119 
1120 
1121 void QgsCoordinateReferenceSystem::setInternalId( long srsId )
1122 {
1123  d.detach();
1124  d->mSrsId = srsId;
1125 }
1126 void QgsCoordinateReferenceSystem::setAuthId( const QString &authId )
1127 {
1128  d.detach();
1129  d->mAuthId = authId;
1130 }
1131 void QgsCoordinateReferenceSystem::setSrid( long srid )
1132 {
1133  d.detach();
1134  d->mSRID = srid;
1135 }
1136 void QgsCoordinateReferenceSystem::setDescription( const QString &description )
1137 {
1138  d.detach();
1139  d->mDescription = description;
1140 }
1141 void QgsCoordinateReferenceSystem::setProj4String( const QString &proj4String )
1142 {
1143  d.detach();
1144  d->mProj4 = proj4String;
1145 
1146  QgsLocaleNumC l;
1147 
1148  OSRDestroySpatialReference( d->mCRS );
1149  d->mCRS = OSRNewSpatialReference( nullptr );
1150  d->mIsValid = OSRImportFromProj4( d->mCRS, proj4String.trimmed().toLatin1().constData() ) == OGRERR_NONE;
1151  // OSRImportFromProj4() may accept strings that are not valid proj.4 strings,
1152  // e.g if they lack a +ellps parameter, it will automatically add +ellps=WGS84, but as
1153  // we use the original mProj4 with QgsCoordinateTransform, it will fail to initialize
1154  // so better detect it now.
1155  projCtx pContext = pj_ctx_alloc();
1156  projPJ proj = pj_init_plus_ctx( pContext, proj4String.trimmed().toLatin1().constData() );
1157  if ( !proj )
1158  {
1159  QgsDebugMsgLevel( QStringLiteral( "proj.4 string rejected by pj_init_plus_ctx()" ), 4 );
1160  d->mIsValid = false;
1161  }
1162  else
1163  {
1164  pj_free( proj );
1165  }
1166  pj_ctx_free( pContext );
1167  d->mWkt.clear();
1168  setMapUnits();
1169 }
1170 
1171 void QgsCoordinateReferenceSystem::setGeographicFlag( bool geoFlag )
1172 {
1173  d.detach();
1174  d->mIsGeographic = geoFlag;
1175 }
1176 void QgsCoordinateReferenceSystem::setEpsg( long epsg )
1177 {
1178  d.detach();
1179  d->mAuthId = QStringLiteral( "EPSG:%1" ).arg( epsg );
1180 }
1181 void QgsCoordinateReferenceSystem::setProjectionAcronym( const QString &projectionAcronym )
1182 {
1183  d.detach();
1184  d->mProjectionAcronym = projectionAcronym;
1185 }
1186 void QgsCoordinateReferenceSystem::setEllipsoidAcronym( const QString &ellipsoidAcronym )
1187 {
1188  d.detach();
1189  d->mEllipsoidAcronym = ellipsoidAcronym;
1190 }
1191 
1192 void QgsCoordinateReferenceSystem::setMapUnits()
1193 {
1194  d.detach();
1195  if ( !d->mIsValid )
1196  {
1197  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1198  return;
1199  }
1200 
1201  char *unitName = nullptr;
1202 
1203 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,5,0)
1204  // Of interest to us is that this call adds in a unit parameter if
1205  // one doesn't already exist.
1206  OSRFixup( d->mCRS );
1207 #endif
1208 
1209  if ( OSRIsProjected( d->mCRS ) )
1210  {
1211  double toMeter = OSRGetLinearUnits( d->mCRS, &unitName );
1212  QString unit( unitName );
1213 
1214  // If the units parameter was created during the Fixup() call
1215  // above, the name of the units is likely to be 'unknown'. Try to
1216  // do better than that ... (but perhaps ogr should be enhanced to
1217  // do this instead?).
1218 
1219  static const double FEET_TO_METER = 0.3048;
1220  static const double SMALL_NUM = 1e-3;
1221 
1222  if ( std::fabs( toMeter - FEET_TO_METER ) < SMALL_NUM )
1223  unit = QStringLiteral( "Foot" );
1224 
1225  if ( qgsDoubleNear( toMeter, 1.0 ) ) //Unit name for meters would be "metre"
1226  d->mMapUnits = QgsUnitTypes::DistanceMeters;
1227  else if ( unit == QLatin1String( "Foot" ) )
1228  d->mMapUnits = QgsUnitTypes::DistanceFeet;
1229  else
1230  {
1231  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1232  }
1233  }
1234  else
1235  {
1236  OSRGetAngularUnits( d->mCRS, &unitName );
1237  QString unit( unitName );
1238  if ( unit == QLatin1String( "degree" ) )
1239  d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1240  else
1241  {
1242  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1243  }
1244  }
1245 }
1246 
1247 
1249 {
1250  if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1251  || !d->mIsValid )
1252  {
1253  QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1254  "work if prj acr ellipsoid acr and proj4string are set"
1255  " and the current projection is valid!", 4 );
1256  return 0;
1257  }
1258 
1259  sqlite3_database_unique_ptr database;
1260  sqlite3_statement_unique_ptr statement;
1261  int myResult;
1262 
1263  // Set up the query to retrieve the projection information
1264  // needed to populate the list
1265  QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1266  "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1267  .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1268  QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1269  // Get the full path name to the sqlite3 spatial reference database.
1270  QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1271 
1272  //check the db is available
1273  myResult = openDatabase( myDatabaseFileName, database );
1274  if ( myResult != SQLITE_OK )
1275  {
1276  return 0;
1277  }
1278 
1279  statement = database.prepare( mySql, myResult );
1280  if ( myResult == SQLITE_OK )
1281  {
1282 
1283  while ( statement.step() == SQLITE_ROW )
1284  {
1285  QString mySrsId = statement.columnAsText( 0 );
1286  QString myProj4String = statement.columnAsText( 1 );
1287  if ( toProj4() == myProj4String.trimmed() )
1288  {
1289  return mySrsId.toLong();
1290  }
1291  }
1292  }
1293 
1294  //
1295  // Try the users db now
1296  //
1297 
1298  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1299  //check the db is available
1300  myResult = openDatabase( myDatabaseFileName, database );
1301  if ( myResult != SQLITE_OK )
1302  {
1303  return 0;
1304  }
1305 
1306  statement = database.prepare( mySql, myResult );
1307 
1308  if ( myResult == SQLITE_OK )
1309  {
1310  while ( statement.step() == SQLITE_ROW )
1311  {
1312  QString mySrsId = statement.columnAsText( 0 );
1313  QString myProj4String = statement.columnAsText( 1 );
1314  if ( toProj4() == myProj4String.trimmed() )
1315  {
1316  return mySrsId.toLong();
1317  }
1318  }
1319  }
1320 
1321  return 0;
1322 }
1323 
1325 {
1326  return ( !d->mIsValid && !srs.d->mIsValid ) ||
1327  ( d->mIsValid && srs.d->mIsValid && srs.authid() == authid() );
1328 }
1329 
1331 {
1332  return !( *this == srs );
1333 }
1334 
1336 {
1337  if ( d->mWkt.isEmpty() )
1338  {
1339  char *wkt = nullptr;
1340  if ( OSRExportToWkt( d->mCRS, &wkt ) == OGRERR_NONE )
1341  {
1342  d->mWkt = wkt;
1343  CPLFree( wkt );
1344  }
1345  }
1346  return d->mWkt;
1347 }
1348 
1349 bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
1350 {
1351  d.detach();
1352  bool result = true;
1353  QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
1354 
1355  if ( ! srsNode.isNull() )
1356  {
1357  bool initialized = false;
1358 
1359  long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong();
1360 
1361  QDomNode myNode;
1362 
1363  if ( srsid < USER_CRS_START_ID )
1364  {
1365  myNode = srsNode.namedItem( QStringLiteral( "authid" ) );
1366  if ( !myNode.isNull() )
1367  {
1368  operator=( QgsCoordinateReferenceSystem::fromOgcWmsCrs( myNode.toElement().text() ) );
1369  if ( isValid() )
1370  {
1371  initialized = true;
1372  }
1373  }
1374 
1375  if ( !initialized )
1376  {
1377  myNode = srsNode.namedItem( QStringLiteral( "epsg" ) );
1378  if ( !myNode.isNull() )
1379  {
1380  operator=( QgsCoordinateReferenceSystem::fromEpsgId( myNode.toElement().text().toLong() ) );
1381  if ( isValid() )
1382  {
1383  initialized = true;
1384  }
1385  }
1386  }
1387  }
1388 
1389  if ( !initialized )
1390  {
1391  myNode = srsNode.namedItem( QStringLiteral( "proj4" ) );
1392 
1393  if ( !createFromProj4( myNode.toElement().text() ) )
1394  {
1395  // Setting from elements one by one
1396 
1397  myNode = srsNode.namedItem( QStringLiteral( "proj4" ) );
1398  setProj4String( myNode.toElement().text() );
1399 
1400  myNode = srsNode.namedItem( QStringLiteral( "srsid" ) );
1401  setInternalId( myNode.toElement().text().toLong() );
1402 
1403  myNode = srsNode.namedItem( QStringLiteral( "srid" ) );
1404  setSrid( myNode.toElement().text().toLong() );
1405 
1406  myNode = srsNode.namedItem( QStringLiteral( "authid" ) );
1407  setAuthId( myNode.toElement().text() );
1408 
1409  myNode = srsNode.namedItem( QStringLiteral( "description" ) );
1410  setDescription( myNode.toElement().text() );
1411 
1412  myNode = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
1413  setProjectionAcronym( myNode.toElement().text() );
1414 
1415  myNode = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
1416  setEllipsoidAcronym( myNode.toElement().text() );
1417 
1418  myNode = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
1419  if ( myNode.toElement().text().compare( QLatin1String( "true" ) ) )
1420  {
1421  setGeographicFlag( true );
1422  }
1423  else
1424  {
1425  setGeographicFlag( false );
1426  }
1427 
1428  //make sure the map units have been set
1429  setMapUnits();
1430  }
1431  //TODO: createFromProj4 used to save to the user database any new CRS
1432  // this behavior was changed in order to separate creation and saving.
1433  // Not sure if it necessary to save it here, should be checked by someone
1434  // familiar with the code (should also give a more descriptive name to the generated CRS)
1435  if ( d->mSrsId == 0 )
1436  {
1437  QString myName = QStringLiteral( " * %1 (%2)" )
1438  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
1439  toProj4() );
1440  saveAsUserCrs( myName );
1441  }
1442 
1443  }
1444  }
1445  else
1446  {
1447  // Return empty CRS if none was found in the XML.
1448  d = new QgsCoordinateReferenceSystemPrivate();
1449  result = false;
1450  }
1451  return result;
1452 }
1453 
1454 bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
1455 {
1456 
1457  QDomElement myLayerNode = node.toElement();
1458  QDomElement mySrsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
1459 
1460  QDomElement myProj4Element = doc.createElement( QStringLiteral( "proj4" ) );
1461  myProj4Element.appendChild( doc.createTextNode( toProj4() ) );
1462  mySrsElement.appendChild( myProj4Element );
1463 
1464  QDomElement mySrsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
1465  mySrsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
1466  mySrsElement.appendChild( mySrsIdElement );
1467 
1468  QDomElement mySridElement = doc.createElement( QStringLiteral( "srid" ) );
1469  mySridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
1470  mySrsElement.appendChild( mySridElement );
1471 
1472  QDomElement myEpsgElement = doc.createElement( QStringLiteral( "authid" ) );
1473  myEpsgElement.appendChild( doc.createTextNode( authid() ) );
1474  mySrsElement.appendChild( myEpsgElement );
1475 
1476  QDomElement myDescriptionElement = doc.createElement( QStringLiteral( "description" ) );
1477  myDescriptionElement.appendChild( doc.createTextNode( description() ) );
1478  mySrsElement.appendChild( myDescriptionElement );
1479 
1480  QDomElement myProjectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
1481  myProjectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
1482  mySrsElement.appendChild( myProjectionAcronymElement );
1483 
1484  QDomElement myEllipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
1485  myEllipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
1486  mySrsElement.appendChild( myEllipsoidAcronymElement );
1487 
1488  QDomElement myGeographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
1489  QString myGeoFlagText = QStringLiteral( "false" );
1490  if ( isGeographic() )
1491  {
1492  myGeoFlagText = QStringLiteral( "true" );
1493  }
1494 
1495  myGeographicFlagElement.appendChild( doc.createTextNode( myGeoFlagText ) );
1496  mySrsElement.appendChild( myGeographicFlagElement );
1497 
1498  myLayerNode.appendChild( mySrsElement );
1499 
1500  return true;
1501 }
1502 
1503 
1504 
1505 //
1506 // Static helper methods below this point only please!
1507 //
1508 
1509 
1510 // Returns the whole proj4 string for the selected srsid
1511 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
1512 QString QgsCoordinateReferenceSystem::proj4FromSrsId( const int srsId )
1513 {
1514  QString myDatabaseFileName;
1515  QString myProjString;
1516  QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
1517 
1518  //
1519  // Determine if this is a user projection or a system on
1520  // user projection defs all have srs_id >= 100000
1521  //
1522  if ( srsId >= USER_CRS_START_ID )
1523  {
1524  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1525  QFileInfo myFileInfo;
1526  myFileInfo.setFile( myDatabaseFileName );
1527  if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
1528  {
1529  QgsDebugMsg( QStringLiteral( "users qgis.db not found" ) );
1530  return QString();
1531  }
1532  }
1533  else //must be a system projection then
1534  {
1535  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1536  }
1537 
1538  sqlite3_database_unique_ptr database;
1539  sqlite3_statement_unique_ptr statement;
1540 
1541  int rc;
1542  rc = openDatabase( myDatabaseFileName, database );
1543  if ( rc )
1544  {
1545  return QString();
1546  }
1547 
1548  statement = database.prepare( mySql, rc );
1549 
1550  if ( rc == SQLITE_OK )
1551  {
1552  if ( statement.step() == SQLITE_ROW )
1553  {
1554  myProjString = statement.columnAsText( 0 );
1555  }
1556  }
1557 
1558  return myProjString;
1559 }
1560 
1561 int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
1562 {
1563  int myResult;
1564  if ( readonly )
1565  myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
1566  else
1567  myResult = database.open( path );
1568 
1569  if ( myResult != SQLITE_OK )
1570  {
1571  QgsDebugMsg( "Can't open database: " + database.errorMessage() );
1572  // XXX This will likely never happen since on open, sqlite creates the
1573  // database if it does not exist.
1574  // ... unfortunately it happens on Windows
1575  QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
1576  .arg( path )
1577  .arg( myResult )
1578  .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
1579  }
1580  return myResult;
1581 }
1582 
1584 {
1585  mCustomSrsValidation = f;
1586 }
1587 
1589 {
1590  return mCustomSrsValidation;
1591 }
1592 
1593 void QgsCoordinateReferenceSystem::debugPrint()
1594 {
1595  QgsDebugMsg( QStringLiteral( "***SpatialRefSystem***" ) );
1596  QgsDebugMsg( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ) );
1597  QgsDebugMsg( "* SrsId : " + QString::number( d->mSrsId ) );
1598  QgsDebugMsg( "* Proj4 : " + toProj4() );
1599  QgsDebugMsg( "* WKT : " + toWkt() );
1600  QgsDebugMsg( "* Desc. : " + d->mDescription );
1602  {
1603  QgsDebugMsg( QStringLiteral( "* Units : meters" ) );
1604  }
1605  else if ( mapUnits() == QgsUnitTypes::DistanceFeet )
1606  {
1607  QgsDebugMsg( QStringLiteral( "* Units : feet" ) );
1608  }
1609  else if ( mapUnits() == QgsUnitTypes::DistanceDegrees )
1610  {
1611  QgsDebugMsg( QStringLiteral( "* Units : degrees" ) );
1612  }
1613 }
1614 
1616 {
1617  d.detach();
1618  d->mValidationHint = html;
1619 }
1620 
1622 {
1623  return d->mValidationHint;
1624 }
1625 
1628 
1630 {
1631  if ( !d->mIsValid )
1632  {
1633  QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
1634  return -1;
1635  }
1636 
1637  QString mySql;
1638 
1639  QString proj4String = d->mProj4;
1640  if ( proj4String.isEmpty() )
1641  {
1642  proj4String = toProj4();
1643  }
1644 
1645  // ellipsoid acroynym column is incorrect marked as not null in many crs database instances,
1646  // hack around this by using an empty string instead
1647  const QString quotedEllipsoidString = ellipsoidAcronym().isNull() ? "''" : QgsSqliteUtils::quotedString( ellipsoidAcronym() );
1648 
1649  //if this is the first record we need to ensure that its srs_id is 10000. For
1650  //any rec after that sqlite3 will take care of the autonumbering
1651  //this was done to support sqlite 3.0 as it does not yet support
1652  //the autoinc related system tables.
1653  if ( getRecordCount() == 0 )
1654  {
1655  mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1656  + QString::number( USER_CRS_START_ID )
1657  + ',' + QgsSqliteUtils::quotedString( name )
1659  + ',' + quotedEllipsoidString
1661  + ",0)"; // <-- is_geo shamelessly hard coded for now
1662  }
1663  else
1664  {
1665  mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1668  + ',' + quotedEllipsoidString
1670  + ",0)"; // <-- is_geo shamelessly hard coded for now
1671  }
1672  sqlite3_database_unique_ptr database;
1673  sqlite3_statement_unique_ptr statement;
1674  int myResult;
1675  //check the db is available
1676  myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
1677  if ( myResult != SQLITE_OK )
1678  {
1679  QgsDebugMsg( QStringLiteral( "Can't open or create database %1: %2" )
1681  database.errorMessage() ) );
1682  return false;
1683  }
1684  statement = database.prepare( mySql, myResult );
1685 
1686  qint64 returnId;
1687  if ( myResult == SQLITE_OK && statement.step() == SQLITE_DONE )
1688  {
1689  QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( toProj4() ), QObject::tr( "CRS" ) );
1690 
1691  returnId = sqlite3_last_insert_rowid( database.get() );
1692  setInternalId( returnId );
1693  if ( authid().isEmpty() )
1694  setAuthId( QStringLiteral( "USER:%1" ).arg( returnId ) );
1695  setDescription( name );
1696 
1697  //We add the just created user CRS to the list of recently used CRS
1698  QgsSettings settings;
1699  //QStringList recentProjections = settings.value( "/UI/recentProjections" ).toStringList();
1700  QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
1701  QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
1702  //recentProjections.append();
1703  //settings.setValue( "/UI/recentProjections", recentProjections );
1704  projectionsProj4.append( toProj4() );
1705  projectionsAuthId.append( authid() );
1706  settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), projectionsProj4 );
1707  settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), projectionsAuthId );
1708 
1709  }
1710  else
1711  returnId = -1;
1712 
1713  invalidateCache();
1714  return returnId;
1715 }
1716 
1717 long QgsCoordinateReferenceSystem::getRecordCount()
1718 {
1719  sqlite3_database_unique_ptr database;
1720  sqlite3_statement_unique_ptr statement;
1721  int myResult;
1722  long myRecordCount = 0;
1723  //check the db is available
1724  myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
1725  if ( myResult != SQLITE_OK )
1726  {
1727  QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
1728  return 0;
1729  }
1730  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
1731  QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
1732  statement = database.prepare( mySql, myResult );
1733  if ( myResult == SQLITE_OK )
1734  {
1735  if ( statement.step() == SQLITE_ROW )
1736  {
1737  QString myRecordCountString = statement.columnAsText( 0 );
1738  myRecordCount = myRecordCountString.toLong();
1739  }
1740  }
1741  return myRecordCount;
1742 }
1743 
1744 // adapted from gdal/ogr/ogr_srs_dict.cpp
1745 bool QgsCoordinateReferenceSystem::loadWkts( QHash<int, QString> &wkts, const char *filename )
1746 {
1747  QgsDebugMsgLevel( QStringLiteral( "Loading %1" ).arg( filename ), 4 );
1748  const char *pszFilename = CPLFindFile( "gdal", filename );
1749  if ( !pszFilename )
1750  return false;
1751 
1752  QFile csv( pszFilename );
1753  if ( !csv.open( QIODevice::ReadOnly ) )
1754  return false;
1755 
1756  QTextStream lines( &csv );
1757 
1758  for ( ;; )
1759  {
1760  QString line = lines.readLine();
1761  if ( line.isNull() )
1762  break;
1763 
1764  if ( line.trimmed().isEmpty() || line.startsWith( '#' ) )
1765  {
1766  continue;
1767  }
1768  else if ( line.startsWith( QLatin1String( "include " ) ) )
1769  {
1770  if ( !loadWkts( wkts, line.mid( 8 ).toUtf8() ) )
1771  break;
1772  }
1773  else
1774  {
1775  int pos = line.indexOf( ',' );
1776  if ( pos < 0 )
1777  return false;
1778 
1779  bool ok;
1780  int epsg = line.leftRef( pos ).toInt( &ok );
1781  if ( !ok )
1782  return false;
1783 
1784  wkts.insert( epsg, line.mid( pos + 1 ) );
1785  }
1786  }
1787 
1788  csv.close();
1789 
1790  return true;
1791 }
1792 
1793 bool QgsCoordinateReferenceSystem::loadIds( QHash<int, QString> &wkts )
1794 {
1795  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
1796 
1797  Q_FOREACH ( const QString &csv, QStringList() << "gcs.csv" << "pcs.csv" << "vertcs.csv" << "compdcs.csv" << "geoccs.csv" )
1798  {
1799 
1800 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0)
1801  const QString filename = CPLFindFile( "gdal", csv.toUtf8() );
1802 #else
1803  const QString filename = QgsApplication::pkgDataPath() + QStringLiteral( "/resources/" ) + csv;
1804 #endif
1805 
1806  QFile f( filename );
1807  if ( !f.open( QIODevice::ReadOnly ) )
1808  continue;
1809 
1810  QTextStream lines( &f );
1811  int l = 0, n = 0;
1812 
1813  lines.readLine();
1814  for ( ;; )
1815  {
1816  l++;
1817  QString line = lines.readLine();
1818  if ( line.isNull() )
1819  break;
1820 
1821  if ( line.trimmed().isEmpty() )
1822  continue;
1823 
1824  int pos = line.indexOf( ',' );
1825  if ( pos < 0 )
1826  {
1827  qWarning( "No id found in: %s", qPrintable( line ) );
1828  continue;
1829  }
1830 
1831  bool ok;
1832  int epsg = line.leftRef( pos ).toInt( &ok );
1833  if ( !ok )
1834  {
1835  qWarning( "No valid id found in: %s", qPrintable( line ) );
1836  continue;
1837  }
1838 
1839  // some CRS are known to fail (see http://trac.osgeo.org/gdal/ticket/2900)
1840  if ( epsg == 2218 || epsg == 2221 || epsg == 2296 || epsg == 2297 || epsg == 2298 || epsg == 2299 || epsg == 2300 || epsg == 2301 || epsg == 2302 ||
1841  epsg == 2303 || epsg == 2304 || epsg == 2305 || epsg == 2306 || epsg == 2307 || epsg == 2963 || epsg == 2985 || epsg == 2986 || epsg == 3052 ||
1842  epsg == 3053 || epsg == 3139 || epsg == 3144 || epsg == 3145 || epsg == 3173 || epsg == 3295 || epsg == 3993 || epsg == 4087 || epsg == 4088 ||
1843  epsg == 5017 || epsg == 5221 || epsg == 5224 || epsg == 5225 || epsg == 5514 || epsg == 5515 || epsg == 5516 || epsg == 5819 || epsg == 5820 ||
1844  epsg == 5821 || epsg == 6200 || epsg == 6201 || epsg == 6202 || epsg == 6244 || epsg == 6245 || epsg == 6246 || epsg == 6247 || epsg == 6248 ||
1845  epsg == 6249 || epsg == 6250 || epsg == 6251 || epsg == 6252 || epsg == 6253 || epsg == 6254 || epsg == 6255 || epsg == 6256 || epsg == 6257 ||
1846  epsg == 6258 || epsg == 6259 || epsg == 6260 || epsg == 6261 || epsg == 6262 || epsg == 6263 || epsg == 6264 || epsg == 6265 || epsg == 6266 ||
1847  epsg == 6267 || epsg == 6268 || epsg == 6269 || epsg == 6270 || epsg == 6271 || epsg == 6272 || epsg == 6273 || epsg == 6274 || epsg == 6275 ||
1848  epsg == 6966 || epsg == 7082 || epsg == 32600 || epsg == 32663 || epsg == 32700 )
1849  continue;
1850 
1851  if ( OSRImportFromEPSG( crs, epsg ) != OGRERR_NONE )
1852  {
1853  qDebug( "EPSG %d: not imported", epsg );
1854  continue;
1855  }
1856 
1857  char *wkt = nullptr;
1858  if ( OSRExportToWkt( crs, &wkt ) != OGRERR_NONE )
1859  {
1860  qWarning( "EPSG %d: not exported to WKT", epsg );
1861  continue;
1862  }
1863 
1864  wkts.insert( epsg, wkt );
1865  n++;
1866 
1867  CPLFree( wkt );
1868  }
1869 
1870  f.close();
1871 
1872  QgsDebugMsgLevel( QStringLiteral( "Loaded %1/%2 from %3" ).arg( QString::number( n ), QString::number( l ), filename.toUtf8().constData() ), 4 );
1873  }
1874 
1875  OSRDestroySpatialReference( crs );
1876 
1877  return true;
1878 }
1879 
1880 #if defined(PJ_VERSION) && PJ_VERSION>=600
1881 static void qgis_stderr_logger( void *app_data, int level, const char *msg )
1882 {
1883  if ( strcmp( msg, "proj_create: crs not found" ) == 0 )
1884  return;
1885  pj_stderr_logger( app_data, level, msg );
1886 }
1887 #endif
1888 
1890 {
1891  setlocale( LC_ALL, "C" );
1892  QString dbFilePath = QgsApplication::srsDatabaseFilePath();
1893  syncDatumTransform( dbFilePath );
1894 
1895  int inserted = 0, updated = 0, deleted = 0, errors = 0;
1896 
1897  QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
1898 
1899  sqlite3_database_unique_ptr database;
1900  if ( database.open( dbFilePath ) != SQLITE_OK )
1901  {
1902  QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
1903  return -1;
1904  }
1905 
1906  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
1907  {
1908  QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
1909  return -1;
1910  }
1911 
1912  // fix up database, if not done already //
1913  if ( sqlite3_exec( database.get(), "alter table tbl_srs add noupdate boolean", nullptr, nullptr, nullptr ) == SQLITE_OK )
1914  ( void )sqlite3_exec( database.get(), "update tbl_srs set noupdate=(auth_name='EPSG' and auth_id in (5513,5514,5221,2065,102067,4156,4818))", nullptr, nullptr, nullptr );
1915 
1916  ( void )sqlite3_exec( database.get(), "UPDATE tbl_srs SET srid=141001 WHERE srid=41001 AND auth_name='OSGEO' AND auth_id='41001'", nullptr, nullptr, nullptr );
1917 
1918  OGRSpatialReferenceH crs = nullptr;
1919  sqlite3_statement_unique_ptr statement;
1920  int result;
1921  char *errMsg = nullptr;
1922 
1923  QString proj4;
1924  QString sql;
1925  QHash<int, QString> wkts;
1926  loadIds( wkts );
1927  loadWkts( wkts, "epsg.wkt" );
1928 
1929  QgsDebugMsgLevel( QStringLiteral( "%1 WKTs loaded" ).arg( wkts.count() ), 4 );
1930 
1931  for ( QHash<int, QString>::const_iterator it = wkts.constBegin(); it != wkts.constEnd(); ++it )
1932  {
1933  QByteArray ba( it.value().toUtf8() );
1934  char *psz = ba.data();
1935 
1936  if ( crs )
1937  OSRDestroySpatialReference( crs );
1938  crs = nullptr;
1939  crs = OSRNewSpatialReference( nullptr );
1940 
1941  OGRErr ogrErr = OSRImportFromWkt( crs, &psz );
1942  if ( ogrErr != OGRERR_NONE )
1943  continue;
1944 
1945  if ( OSRExportToProj4( crs, &psz ) != OGRERR_NONE )
1946  {
1947  CPLFree( psz );
1948  continue;
1949  }
1950 
1951  proj4 = psz;
1952  proj4 = proj4.trimmed();
1953 
1954  CPLFree( psz );
1955 
1956  if ( proj4.isEmpty() )
1957  continue;
1958 
1959  QString name( OSRIsGeographic( crs ) ? OSRGetAttrValue( crs, "GEOGCS", 0 ) :
1960  OSRIsGeocentric( crs ) ? OSRGetAttrValue( crs, "GEOCCS", 0 ) :
1961  OSRGetAttrValue( crs, "PROJCS", 0 ) );
1962  if ( name.isEmpty() )
1963  name = QObject::tr( "Imported from GDAL" );
1964 
1965  bool deprecated = name.contains( QLatin1Literal( "(deprecated)" ) );
1966 
1967  sql = QStringLiteral( "SELECT parameters,description,deprecated,noupdate FROM tbl_srs WHERE auth_name='EPSG' AND auth_id='%1'" ).arg( it.key() );
1968  statement = database.prepare( sql, result );
1969  if ( result != SQLITE_OK )
1970  {
1971  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
1972  continue;
1973  }
1974 
1975  QString srsProj4;
1976  QString srsDesc;
1977  bool srsDeprecated = deprecated;
1978  if ( statement.step() == SQLITE_ROW )
1979  {
1980  srsProj4 = statement.columnAsText( 0 );
1981  srsDesc = statement.columnAsText( 1 );
1982  srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
1983 
1984  if ( statement.columnAsText( 3 ).toInt() != 0 )
1985  {
1986  continue;
1987  }
1988  }
1989 
1990  if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
1991  {
1992  if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
1993  {
1994  errMsg = nullptr;
1995  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name='EPSG' AND auth_id=%4" )
1996  .arg( QgsSqliteUtils::quotedString( proj4 ) )
1997  .arg( QgsSqliteUtils::quotedString( name ) )
1998  .arg( deprecated ? 1 : 0 )
1999  .arg( it.key() );
2000 
2001  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2002  {
2003  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2004  sql,
2005  database.errorMessage(),
2006  errMsg ? errMsg : "(unknown error)" ) );
2007  if ( errMsg )
2008  sqlite3_free( errMsg );
2009  errors++;
2010  }
2011  else
2012  {
2013  updated++;
2014  }
2015  }
2016  }
2017  else
2018  {
2019  QRegExp projRegExp( "\\+proj=(\\S+)" );
2020  if ( projRegExp.indexIn( proj4 ) < 0 )
2021  {
2022  QgsDebugMsgLevel( QStringLiteral( "EPSG %1: no +proj argument found [%2]" ).arg( it.key() ).arg( proj4 ), 4 );
2023  continue;
2024  }
2025 
2026  QRegExp ellipseRegExp( "\\+ellps=(\\S+)" );
2027  QString ellps;
2028  if ( ellipseRegExp.indexIn( proj4 ) >= 0 )
2029  {
2030  ellps = ellipseRegExp.cap( 1 );
2031  }
2032  else
2033  {
2034  // satisfy not null constraint on ellipsoid_acronym field
2035  // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2036  // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2037  // set for these CRSes). Better just hack around and make the constraint happy for now,
2038  // and hope that the definitions get corrected in future.
2039  ellps = "";
2040  }
2041 
2042  sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1,%2,%3,%4,%5,'EPSG',%5,%6,%7)" )
2043  .arg( QgsSqliteUtils::quotedString( name ),
2044  QgsSqliteUtils::quotedString( projRegExp.cap( 1 ) ),
2046  QgsSqliteUtils::quotedString( proj4 ) )
2047  .arg( it.key() )
2048  .arg( OSRIsGeographic( crs ) )
2049  .arg( deprecated ? 1 : 0 );
2050 
2051  errMsg = nullptr;
2052  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2053  {
2054  inserted++;
2055  }
2056  else
2057  {
2058  qCritical( "Could not execute: %s [%s/%s]\n",
2059  sql.toLocal8Bit().constData(),
2060  sqlite3_errmsg( database.get() ),
2061  errMsg ? errMsg : "(unknown error)" );
2062  errors++;
2063 
2064  if ( errMsg )
2065  sqlite3_free( errMsg );
2066  }
2067  }
2068  }
2069 
2070  if ( crs )
2071  OSRDestroySpatialReference( crs );
2072  crs = nullptr;
2073 
2074  sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='EPSG' AND NOT auth_id IN (" );
2075  QString delim;
2076  QHash<int, QString>::const_iterator it = wkts.constBegin();
2077  for ( ; it != wkts.constEnd(); ++it )
2078  {
2079  sql += delim + QString::number( it.key() );
2080  delim = ',';
2081  }
2082  sql += QLatin1String( ") AND NOT noupdate" );
2083 
2084  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2085  {
2086  deleted = sqlite3_changes( database.get() );
2087  }
2088  else
2089  {
2090  errors++;
2091  qCritical( "Could not execute: %s [%s]\n",
2092  sql.toLocal8Bit().constData(),
2093  sqlite3_errmsg( database.get() ) );
2094  }
2095 
2096  projCtx pContext = pj_ctx_alloc();
2097 
2098 #if !defined(PJ_VERSION) || PJ_VERSION!=470
2099 #if defined(PJ_VERSION) && PJ_VERSION>=600
2100  pj_ctx_set_logger( pContext, qgis_stderr_logger );
2101 #endif
2102  sql = QStringLiteral( "select auth_name,auth_id,parameters from tbl_srs WHERE auth_name<>'EPSG' AND NOT deprecated AND NOT noupdate" );
2103  statement = database.prepare( sql, result );
2104  if ( result == SQLITE_OK )
2105  {
2106  while ( statement.step() == SQLITE_ROW )
2107  {
2108  QString auth_name = statement.columnAsText( 0 );
2109  QString auth_id = statement.columnAsText( 1 );
2110  QString params = statement.columnAsText( 2 );
2111 
2112  QString input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toLower(), auth_id );
2113  projPJ pj = pj_init_plus_ctx( pContext, input.toLatin1() );
2114  if ( !pj )
2115  {
2116  input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toUpper(), auth_id );
2117  pj = pj_init_plus_ctx( pContext, input.toLatin1() );
2118  }
2119 
2120  if ( pj )
2121  {
2122  char *def = pj_get_def( pj, 0 );
2123  if ( def )
2124  {
2125  proj4 = def;
2126  pj_dalloc( def );
2127 
2128  input.prepend( ' ' ).append( ' ' );
2129  if ( proj4.startsWith( input ) )
2130  {
2131  proj4 = proj4.mid( input.size() );
2132  proj4 = proj4.trimmed();
2133  }
2134 
2135  if ( proj4 != params )
2136  {
2137  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name=%2 AND auth_id=%3" )
2138  .arg( QgsSqliteUtils::quotedString( proj4 ),
2139  QgsSqliteUtils::quotedString( auth_name ),
2140  QgsSqliteUtils::quotedString( auth_id ) );
2141 
2142  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2143  {
2144  updated++;
2145  }
2146  else
2147  {
2148  qCritical( "Could not execute: %s [%s/%s]\n",
2149  sql.toLocal8Bit().constData(),
2150  sqlite3_errmsg( database.get() ),
2151  errMsg ? errMsg : "(unknown error)" );
2152  if ( errMsg )
2153  sqlite3_free( errMsg );
2154  errors++;
2155  }
2156  }
2157  }
2158  else
2159  {
2160  QgsDebugMsgLevel( QStringLiteral( "could not retrieve proj string for %1 from PROJ" ).arg( input ), 4 );
2161  }
2162  }
2163  else
2164  {
2165  QgsDebugMsgLevel( QStringLiteral( "could not retrieve crs for %1 from PROJ" ).arg( input ), 3 );
2166  }
2167 
2168  pj_free( pj );
2169  }
2170  }
2171  else
2172  {
2173  errors++;
2174  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2]\n" ).arg(
2175  sql,
2176  sqlite3_errmsg( database.get() ) ) );
2177  }
2178 #endif
2179 
2180  pj_ctx_free( pContext );
2181 
2182  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2183  {
2184  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2186  sqlite3_errmsg( database.get() ) )
2187  );
2188  return -1;
2189  }
2190 
2191  Q_UNUSED( deleted );
2192  QgsDebugMsgLevel( QStringLiteral( "CRS update (inserted:%1 updated:%2 deleted:%3 errors:%4)" ).arg( QString::number( inserted ), QString::number( updated ), QString::number( deleted ), QString::number( errors ) ), 4 );
2193 
2194  if ( errors > 0 )
2195  return -errors;
2196  else
2197  return updated + inserted;
2198 }
2199 
2200 bool QgsCoordinateReferenceSystem::syncDatumTransform( const QString &dbPath )
2201 {
2202 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(3,0,0)
2203  const char *filename = CSVFilename( "datum_shift.csv" );
2204  FILE *fp = VSIFOpen( filename, "rb" );
2205 #else
2206  const QString filename = QgsApplication::pkgDataPath() + QStringLiteral( "/resources/datum_shift.csv" );
2207  FILE *fp = VSIFOpen( filename.toUtf8().constData(), "rb" );
2208 #endif
2209  if ( !fp )
2210  {
2211  return false;
2212  }
2213 
2214  char **fieldnames = CSVReadParseLine( fp );
2215 
2216  // "SEQ_KEY","COORD_OP_CODE","SOURCE_CRS_CODE","TARGET_CRS_CODE","REMARKS","COORD_OP_SCOPE","AREA_OF_USE_CODE","AREA_SOUTH_BOUND_LAT","AREA_NORTH_BOUND_LAT","AREA_WEST_BOUND_LON","AREA_EAST_BOUND_LON","SHOW_OPERATION","DEPRECATED","COORD_OP_METHOD_CODE","DX","DY","DZ","RX","RY","RZ","DS","PREFERRED"
2217 
2218  struct
2219  {
2220  const char *src; //skip-init-check
2221  const char *dst; //skip-init-check
2222  int idx;
2223  } map[] =
2224  {
2225  // { "SEQ_KEY", "", -1 },
2226  { "SOURCE_CRS_CODE", "source_crs_code", -1 },
2227  { "TARGET_CRS_CODE", "target_crs_code", -1 },
2228  { "REMARKS", "remarks", -1 },
2229  { "COORD_OP_SCOPE", "scope", -1 },
2230  { "AREA_OF_USE_CODE", "area_of_use_code", -1 },
2231  // { "AREA_SOUTH_BOUND_LAT", "", -1 },
2232  // { "AREA_NORTH_BOUND_LAT", "", -1 },
2233  // { "AREA_WEST_BOUND_LON", "", -1 },
2234  // { "AREA_EAST_BOUND_LON", "", -1 },
2235  // { "SHOW_OPERATION", "", -1 },
2236  { "DEPRECATED", "deprecated", -1 },
2237  { "COORD_OP_METHOD_CODE", "coord_op_method_code", -1 },
2238  { "DX", "p1", -1 },
2239  { "DY", "p2", -1 },
2240  { "DZ", "p3", -1 },
2241  { "RX", "p4", -1 },
2242  { "RY", "p5", -1 },
2243  { "RZ", "p6", -1 },
2244  { "DS", "p7", -1 },
2245  { "PREFERRED", "preferred", -1 },
2246  { "COORD_OP_CODE", "coord_op_code", -1 },
2247  };
2248 
2249  QString update = QStringLiteral( "UPDATE tbl_datum_transform SET " );
2250  QString insert, values;
2251 
2252  int n = CSLCount( fieldnames );
2253 
2254  int idxid = -1, idxrx = -1, idxry = -1, idxrz = -1, idxmcode = -1;
2255  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2256  {
2257  bool last = i == sizeof( map ) / sizeof( *map ) - 1;
2258 
2259  map[i].idx = CSLFindString( fieldnames, map[i].src );
2260  if ( map[i].idx < 0 )
2261  {
2262  qWarning( "field %s not found", map[i].src );
2263  CSLDestroy( fieldnames );
2264  fclose( fp );
2265  return false;
2266  }
2267 
2268  if ( strcmp( map[i].src, "COORD_OP_CODE" ) == 0 )
2269  idxid = i;
2270  if ( strcmp( map[i].src, "RX" ) == 0 )
2271  idxrx = i;
2272  if ( strcmp( map[i].src, "RY" ) == 0 )
2273  idxry = i;
2274  if ( strcmp( map[i].src, "RZ" ) == 0 )
2275  idxrz = i;
2276  if ( strcmp( map[i].src, "COORD_OP_METHOD_CODE" ) == 0 )
2277  idxmcode = i;
2278 
2279  if ( i > 0 )
2280  {
2281  insert += ',';
2282  values += ',';
2283 
2284  if ( last )
2285  {
2286  update += QLatin1String( " WHERE " );
2287  }
2288  else
2289  {
2290  update += ',';
2291  }
2292  }
2293 
2294  update += QStringLiteral( "%1=%%2" ).arg( map[i].dst ).arg( i + 1 );
2295 
2296  insert += map[i].dst;
2297  values += QStringLiteral( "%%1" ).arg( i + 1 );
2298  }
2299 
2300  insert = "INSERT INTO tbl_datum_transform(" + insert + ") VALUES (" + values + ')';
2301 
2302  CSLDestroy( fieldnames );
2303 
2304  Q_ASSERT( idxid >= 0 );
2305  Q_ASSERT( idxrx >= 0 );
2306  Q_ASSERT( idxry >= 0 );
2307  Q_ASSERT( idxrz >= 0 );
2308 
2309  sqlite3_database_unique_ptr database;
2310  int openResult = database.open( dbPath );
2311  if ( openResult != SQLITE_OK )
2312  {
2313  fclose( fp );
2314  return false;
2315  }
2316 
2317  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2318  {
2319  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database.get() ) );
2320  fclose( fp );
2321  return false;
2322  }
2323 
2324  QStringList v;
2325  v.reserve( sizeof( map ) / sizeof( *map ) );
2326 
2327  for ( ;; )
2328  {
2329  char **values = CSVReadParseLine( fp );
2330  if ( !values )
2331  break;
2332 
2333  v.clear();
2334 
2335  if ( CSLCount( values ) == 0 )
2336  {
2337  CSLDestroy( values );
2338  break;
2339  }
2340 
2341  if ( CSLCount( values ) < n )
2342  {
2343  qWarning( "Only %d columns", CSLCount( values ) );
2344  CSLDestroy( values );
2345  continue;
2346  }
2347 
2348  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2349  {
2350  int idx = map[i].idx;
2351  Q_ASSERT( idx != -1 );
2352  Q_ASSERT( idx < n );
2353  v.insert( i, *values[ idx ] ? QgsSqliteUtils::quotedString( values[idx] ) : QStringLiteral( "NULL" ) );
2354  }
2355  CSLDestroy( values );
2356 
2357  //switch sign of rotation parameters. See http://trac.osgeo.org/proj/wiki/GenParms#towgs84-DatumtransformationtoWGS84
2358  if ( v.at( idxmcode ).compare( QLatin1String( "'9607'" ) ) == 0 )
2359  {
2360  v[ idxmcode ] = QStringLiteral( "'9606'" );
2361  v[ idxrx ] = '\'' + qgsDoubleToString( -( v[ idxrx ].remove( '\'' ).toDouble() ) ) + '\'';
2362  v[ idxry ] = '\'' + qgsDoubleToString( -( v[ idxry ].remove( '\'' ).toDouble() ) ) + '\'';
2363  v[ idxrz ] = '\'' + qgsDoubleToString( -( v[ idxrz ].remove( '\'' ).toDouble() ) ) + '\'';
2364  }
2365 
2366  //entry already in db?
2367  sqlite3_statement_unique_ptr statement;
2368  QString cOpCode;
2369  QString sql = QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( v[ idxid ] );
2370  int prepareRes;
2371  statement = database.prepare( sql, prepareRes );
2372  if ( prepareRes != SQLITE_OK )
2373  continue;
2374 
2375  if ( statement.step() == SQLITE_ROW )
2376  {
2377  cOpCode = statement.columnAsText( 0 );
2378  }
2379 
2380  sql = cOpCode.isEmpty() ? insert : update;
2381  for ( int i = 0; i < v.size(); i++ )
2382  {
2383  sql = sql.arg( v[i] );
2384  }
2385 
2386  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) != SQLITE_OK )
2387  {
2388  qCritical( "SQL: %s", sql.toUtf8().constData() );
2389  qCritical( "Error: %s", sqlite3_errmsg( database.get() ) );
2390  }
2391  }
2392 
2393  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2394  {
2395  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg( QgsApplication::srsDatabaseFilePath(), sqlite3_errmsg( database.get() ) ) );
2396  return false;
2397  }
2398 
2399  return true;
2400 }
2401 
2403 {
2404  if ( isGeographic() )
2405  {
2406  return d->mAuthId;
2407  }
2408  else if ( d->mCRS )
2409  {
2410  return OSRGetAuthorityName( d->mCRS, "GEOGCS" ) + QStringLiteral( ":" ) + OSRGetAuthorityCode( d->mCRS, "GEOGCS" );
2411  }
2412  else
2413  {
2414  return QString();
2415  }
2416 }
2417 
2419 {
2420  QStringList projections;
2421 
2422  // Read settings from persistent storage
2423  QgsSettings settings;
2424  projections = settings.value( QStringLiteral( "UI/recentProjections" ) ).toStringList();
2425  /*** The reading (above) of internal id from persistent storage should be removed sometime in the future */
2426  /*** This is kept now for backwards compatibility */
2427 
2428  QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
2429  QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
2430  if ( projectionsAuthId.size() >= projections.size() )
2431  {
2432  // We had saved state with AuthId and Proj4. Use that instead
2433  // to find out the crs id
2434  projections.clear();
2435  for ( int i = 0; i < projectionsAuthId.size(); i++ )
2436  {
2437  // Create a crs from the EPSG
2439  crs.createFromOgcWmsCrs( projectionsAuthId.at( i ) );
2440  if ( ! crs.isValid() )
2441  {
2442  // Couldn't create from EPSG, try the Proj4 string instead
2443  if ( i >= projectionsProj4.size() || !crs.createFromProj4( projectionsProj4.at( i ) ) )
2444  {
2445  // No? Skip this entry
2446  continue;
2447  }
2448  //If the CRS can be created but do not correspond to a CRS in the database, skip it (for example a deleted custom CRS)
2449  if ( crs.srsid() == 0 )
2450  {
2451  continue;
2452  }
2453  }
2454  projections << QString::number( crs.srsid() );
2455  }
2456  }
2457  return projections;
2458 }
2459 
2461 {
2462  sSrIdCacheLock.lockForWrite();
2463  if ( !sDisableSrIdCache )
2464  {
2465  if ( disableCache )
2466  sDisableSrIdCache = true;
2467  sSrIdCache.clear();
2468  }
2469  sSrIdCacheLock.unlock();
2470 
2471  sOgcLock.lockForWrite();
2472  if ( !sDisableOgcCache )
2473  {
2474  if ( disableCache )
2475  sDisableOgcCache = true;
2476  sOgcCache.clear();
2477  }
2478  sOgcLock.unlock();
2479 
2480  sProj4CacheLock.lockForWrite();
2481  if ( !sDisableProj4Cache )
2482  {
2483  if ( disableCache )
2484  sDisableProj4Cache = true;
2485  sProj4Cache.clear();
2486  }
2487  sProj4CacheLock.unlock();
2488 
2489  sCRSWktLock.lockForWrite();
2490  if ( !sDisableWktCache )
2491  {
2492  if ( disableCache )
2493  sDisableWktCache = true;
2494  sWktCache.clear();
2495  }
2496  sCRSWktLock.unlock();
2497 
2498  sCRSSrsIdLock.lockForWrite();
2499  if ( !sDisableSrsIdCache )
2500  {
2501  if ( disableCache )
2502  sDisableSrsIdCache = true;
2503  sSrsIdCache.clear();
2504  }
2505  sCRSSrsIdLock.unlock();
2506 
2507  sCrsStringLock.lockForWrite();
2508  if ( !sDisableStringCache )
2509  {
2510  if ( disableCache )
2511  sDisableStringCache = true;
2512  sStringCache.clear();
2513  }
2514  sCrsStringLock.unlock();
2515 }
QgsCoordinateReferenceSystem()
Constructs an invalid CRS object.
bool createFromId(long id, CrsType type=PostgisCrsId)
Sets this CRS by lookup of the given ID in the CRS database.
QString geographicCrsAuthId() const
Returns auth id of related geographic CRS.
QString columnName(int column) const
Returns the name of column.
A rectangle specified with double values.
Definition: qgsrectangle.h:40
static QgsCoordinateReferenceSystem fromProj4(const QString &proj4)
Creates a CRS from a proj4 style formatted string.
bool createFromString(const QString &definition)
Set up this CRS from a string definition.
static QList< long > validSrsIds()
Returns a list of all valid SRS IDs present in the CRS database.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateReferenceSystem objects.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:134
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
long srsid() const
Returns the internal CRS ID, if available.
void validate()
Perform some validation on this CRS.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:121
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
static void setCustomCrsValidation(CUSTOM_CRS_VALIDATION f)
Sets custom function to force valid CRS.
QString toWkt() const
Returns a WKT representation of this CRS.
long postgisSrid() const
Returns PostGIS SRID for the CRS.
const int LAT_PREFIX_LEN
The length of the string "+lat_1=".
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
bool operator!=(const QgsCoordinateReferenceSystem &srs) const
Overloaded != operator used to compare to CRS&#39;s.
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
const QgsCoordinateReferenceSystem & crs
Internal ID used by QGIS in the local SQLite database.
bool operator==(const QgsCoordinateReferenceSystem &srs) const
Overloaded == operator used to compare to CRS&#39;s.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
#define FEET_TO_METER
long saveAsUserCrs(const QString &name)
Save the proj4-string as a custom CRS.
bool createFromOgcWmsCrs(const QString &crs)
Sets this CRS to the given OGC WMS-format Coordinate Reference Systems.
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
static Q_INVOKABLE QgsCoordinateReferenceSystem fromEpsgId(long epsg)
Creates a CRS from a given EPSG ID.
void setValidationHint(const QString &html)
Set user hint for validation.
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
bool createFromSrid(long srid)
Sets this CRS by lookup of the given PostGIS SRID in the CRS database.
double columnAsDouble(int column) const
Gets column value from the current statement row as a double.
static QStringList recentProjections()
Returns a list of recently used projections.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
QString validationHint()
Gets user hint for validation.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:238
long findMatchingProj()
Walks the CRS databases (both system and user database) trying to match stored PROJ string to a datab...
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:139
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:61
QgsCoordinateReferenceSystem & operator=(const QgsCoordinateReferenceSystem &srs)
Assignment operator.
const QString GEO_EPSG_CRS_AUTHID
Geographic coord sys from EPSG authority.
Definition: qgis.cpp:69
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
int columnCount() const
Gets the number of columns that this statement returns.
int open(const QString &path)
Opens the database at the specified file path.
bool createFromWkt(const QString &wkt)
Sets this CRS using a WKT definition.
CrsType
Enumeration of types of IDs accepted in createFromId() method.
static QString pkgDataPath()
Returns the common root path of all application data directories.
static CUSTOM_CRS_VALIDATION customCrsValidation()
Gets custom function.
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:53
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
Unknown distance unit.
Definition: qgsunittypes.h:64
void unlock()
Unlocks the lock.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/...
Definition: qgis.h:549
bool createFromSrsId(long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool createFromUserInput(const QString &definition)
Set up this CRS from various text formats.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:144
This class represents a coordinate reference system (CRS).
bool createFromProj4(const QString &projString)
Sets this CRS by passing it a PROJ style formatted string.
QString authid() const
Returns the authority identifier for the CRS.
void changeMode(Mode mode)
Change the mode of the lock to mode.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
QgsUnitTypes::DistanceUnit mapUnits() const
Returns the units for the projection used by the CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
QString description() const
Returns the descriptive name of the CRS, e.g., "WGS 84" or "GDA 94 / Vicgrid94".
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
void(* CUSTOM_CRS_VALIDATION)(QgsCoordinateReferenceSystem &)
bool isGeographic() const
Returns whether the CRS is a geographic CRS (using lat/lon coordinates)
QString errorMessage() const
Returns the most recent error message encountered by the database.
static int syncDatabase()
Update proj.4 parameters in our database from proj.4.
static void setupESRIWktFix()
Make sure that ESRI WKT import is done properly.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:129
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
void * OGRSpatialReferenceH
static QString quotedString(const QString &value)
Returns a quoted string value, surround by &#39; characters and with special characters correctly escaped...
QString toProj4() const
Returns a Proj4 string representation of this CRS.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.