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