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