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