QGIS API Documentation  3.0.2-Girona (307d082)
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.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  int pos = line.indexOf( ',' );
1789  if ( pos < 0 )
1790  continue;
1791 
1792  bool ok;
1793  int epsg = line.leftRef( pos ).toInt( &ok );
1794  if ( !ok )
1795  continue;
1796 
1797  // some CRS are known to fail (see http://trac.osgeo.org/gdal/ticket/2900)
1798  if ( epsg == 2218 || epsg == 2221 || epsg == 2296 || epsg == 2297 || epsg == 2298 || epsg == 2299 || epsg == 2300 || epsg == 2301 || epsg == 2302 ||
1799  epsg == 2303 || epsg == 2304 || epsg == 2305 || epsg == 2306 || epsg == 2307 || epsg == 2963 || epsg == 2985 || epsg == 2986 || epsg == 3052 ||
1800  epsg == 3053 || epsg == 3139 || epsg == 3144 || epsg == 3145 || epsg == 3173 || epsg == 3295 || epsg == 3993 || epsg == 4087 || epsg == 4088 ||
1801  epsg == 5017 || epsg == 5221 || epsg == 5224 || epsg == 5225 || epsg == 5514 || epsg == 5515 || epsg == 5516 || epsg == 5819 || epsg == 5820 ||
1802  epsg == 5821 || epsg == 6200 || epsg == 6201 || epsg == 6202 || epsg == 6244 || epsg == 6245 || epsg == 6246 || epsg == 6247 || epsg == 6248 ||
1803  epsg == 6249 || epsg == 6250 || epsg == 6251 || epsg == 6252 || epsg == 6253 || epsg == 6254 || epsg == 6255 || epsg == 6256 || epsg == 6257 ||
1804  epsg == 6258 || epsg == 6259 || epsg == 6260 || epsg == 6261 || epsg == 6262 || epsg == 6263 || epsg == 6264 || epsg == 6265 || epsg == 6266 ||
1805  epsg == 6267 || epsg == 6268 || epsg == 6269 || epsg == 6270 || epsg == 6271 || epsg == 6272 || epsg == 6273 || epsg == 6274 || epsg == 6275 ||
1806  epsg == 6966 || epsg == 7082 || epsg == 32600 || epsg == 32663 || epsg == 32700 )
1807  continue;
1808 
1809  if ( OSRImportFromEPSG( crs, epsg ) != OGRERR_NONE )
1810  {
1811  qDebug( "EPSG %d: not imported", epsg );
1812  continue;
1813  }
1814 
1815  char *wkt = nullptr;
1816  if ( OSRExportToWkt( crs, &wkt ) != OGRERR_NONE )
1817  {
1818  qWarning( "EPSG %d: not exported to WKT", epsg );
1819  continue;
1820  }
1821 
1822  wkts.insert( epsg, wkt );
1823  n++;
1824 
1825  CPLFree( wkt );
1826  }
1827 
1828  f.close();
1829 
1830  QgsDebugMsgLevel( QStringLiteral( "Loaded %1/%2 from %3" ).arg( QString::number( n ), QString::number( l ), filename.toUtf8().constData() ), 4 );
1831  }
1832 
1833  OSRDestroySpatialReference( crs );
1834 
1835  return true;
1836 }
1837 
1839 {
1840  QString dbFilePath = QgsApplication::srsDatabaseFilePath();
1841  syncDatumTransform( dbFilePath );
1842 
1843  int inserted = 0, updated = 0, deleted = 0, errors = 0;
1844 
1845  QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
1846 
1847  sqlite3_database_unique_ptr database;
1848  if ( database.open( dbFilePath ) != SQLITE_OK )
1849  {
1850  QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
1851  return -1;
1852  }
1853 
1854  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
1855  {
1856  QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
1857  return -1;
1858  }
1859 
1860  // fix up database, if not done already //
1861  if ( sqlite3_exec( database.get(), "alter table tbl_srs add noupdate boolean", nullptr, nullptr, nullptr ) == SQLITE_OK )
1862  ( 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 );
1863 
1864  ( 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 );
1865 
1866  OGRSpatialReferenceH crs = nullptr;
1867  sqlite3_statement_unique_ptr statement;
1868  int result;
1869  char *errMsg = nullptr;
1870 
1871  QString proj4;
1872  QString sql;
1873  QHash<int, QString> wkts;
1874  loadIds( wkts );
1875  loadWkts( wkts, "epsg.wkt" );
1876 
1877  QgsDebugMsgLevel( QStringLiteral( "%1 WKTs loaded" ).arg( wkts.count() ), 4 );
1878 
1879  for ( QHash<int, QString>::const_iterator it = wkts.constBegin(); it != wkts.constEnd(); ++it )
1880  {
1881  QByteArray ba( it.value().toUtf8() );
1882  char *psz = ba.data();
1883 
1884  if ( crs )
1885  OSRDestroySpatialReference( crs );
1886  crs = nullptr;
1887  crs = OSRNewSpatialReference( nullptr );
1888 
1889  OGRErr ogrErr = OSRImportFromWkt( crs, &psz );
1890  if ( ogrErr != OGRERR_NONE )
1891  continue;
1892 
1893  if ( OSRExportToProj4( crs, &psz ) != OGRERR_NONE )
1894  {
1895  CPLFree( psz );
1896  continue;
1897  }
1898 
1899  proj4 = psz;
1900  proj4 = proj4.trimmed();
1901 
1902  CPLFree( psz );
1903 
1904  if ( proj4.isEmpty() )
1905  continue;
1906 
1907  sql = QStringLiteral( "SELECT parameters,noupdate FROM tbl_srs WHERE auth_name='EPSG' AND auth_id='%1'" ).arg( it.key() );
1908  statement = database.prepare( sql, result );
1909  if ( result != SQLITE_OK )
1910  {
1911  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
1912  continue;
1913  }
1914 
1915  QString srsProj4;
1916  if ( statement.step() == SQLITE_ROW )
1917  {
1918  srsProj4 = statement.columnAsText( 0 );
1919 
1920  if ( statement.columnAsText( 1 ).toInt() != 0 )
1921  {
1922  continue;
1923  }
1924  }
1925 
1926  if ( !srsProj4.isEmpty() )
1927  {
1928  if ( proj4 != srsProj4 )
1929  {
1930  errMsg = nullptr;
1931  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name='EPSG' AND auth_id=%2" ).arg( quotedValue( proj4 ) ).arg( it.key() );
1932 
1933  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
1934  {
1935  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
1936  sql,
1937  database.errorMessage(),
1938  errMsg ? errMsg : "(unknown error)" ) );
1939  if ( errMsg )
1940  sqlite3_free( errMsg );
1941  errors++;
1942  }
1943  else
1944  {
1945  updated++;
1946  }
1947  }
1948  }
1949  else
1950  {
1951  QRegExp projRegExp( "\\+proj=(\\S+)" );
1952  if ( projRegExp.indexIn( proj4 ) < 0 )
1953  {
1954  QgsDebugMsgLevel( QString( "EPSG %1: no +proj argument found [%2]" ).arg( it.key() ).arg( proj4 ), 4 );
1955  continue;
1956  }
1957 
1958  QRegExp ellipseRegExp( "\\+ellps=(\\S+)" );
1959  QString ellps;
1960  if ( ellipseRegExp.indexIn( proj4 ) >= 0 )
1961  {
1962  ellps = ellipseRegExp.cap( 1 );
1963  }
1964 
1965  QString name( OSRIsGeographic( crs ) ? OSRGetAttrValue( crs, "GEOGCS", 0 ) : OSRGetAttrValue( crs, "PROJCS", 0 ) );
1966  if ( name.isEmpty() )
1967  name = QObject::tr( "Imported from GDAL" );
1968 
1969  sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1,%2,%3,%4,%5,'EPSG',%5,%6,0)" )
1970  .arg( quotedValue( name ),
1971  quotedValue( projRegExp.cap( 1 ) ),
1972  quotedValue( ellps ),
1973  quotedValue( proj4 ) )
1974  .arg( it.key() )
1975  .arg( OSRIsGeographic( crs ) );
1976 
1977  errMsg = nullptr;
1978  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
1979  {
1980  inserted++;
1981  }
1982  else
1983  {
1984  qCritical( "Could not execute: %s [%s/%s]\n",
1985  sql.toLocal8Bit().constData(),
1986  sqlite3_errmsg( database.get() ),
1987  errMsg ? errMsg : "(unknown error)" );
1988  errors++;
1989 
1990  if ( errMsg )
1991  sqlite3_free( errMsg );
1992  }
1993  }
1994  }
1995 
1996  if ( crs )
1997  OSRDestroySpatialReference( crs );
1998  crs = nullptr;
1999 
2000  sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='EPSG' AND NOT auth_id IN (" );
2001  QString delim;
2002  QHash<int, QString>::const_iterator it = wkts.constBegin();
2003  for ( ; it != wkts.constEnd(); ++it )
2004  {
2005  sql += delim + QString::number( it.key() );
2006  delim = ',';
2007  }
2008  sql += QLatin1String( ") AND NOT noupdate" );
2009 
2010  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2011  {
2012  deleted = sqlite3_changes( database.get() );
2013  }
2014  else
2015  {
2016  errors++;
2017  qCritical( "Could not execute: %s [%s]\n",
2018  sql.toLocal8Bit().constData(),
2019  sqlite3_errmsg( database.get() ) );
2020  }
2021 
2022  projCtx pContext = pj_ctx_alloc();
2023 
2024 #if !defined(PJ_VERSION) || PJ_VERSION!=470
2025  sql = QStringLiteral( "select auth_name,auth_id,parameters from tbl_srs WHERE auth_name<>'EPSG' AND NOT deprecated AND NOT noupdate" );
2026  statement = database.prepare( sql, result );
2027  if ( result == SQLITE_OK )
2028  {
2029  while ( statement.step() == SQLITE_ROW )
2030  {
2031  QString auth_name = statement.columnAsText( 0 );
2032  QString auth_id = statement.columnAsText( 1 );
2033  QString params = statement.columnAsText( 2 );
2034 
2035  QString input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toLower(), auth_id );
2036  projPJ pj = pj_init_plus_ctx( pContext, input.toLatin1() );
2037  if ( !pj )
2038  {
2039  input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toUpper(), auth_id );
2040  pj = pj_init_plus_ctx( pContext, input.toLatin1() );
2041  }
2042 
2043  if ( pj )
2044  {
2045  char *def = pj_get_def( pj, 0 );
2046  if ( def )
2047  {
2048  proj4 = def;
2049  pj_dalloc( def );
2050 
2051  input.prepend( ' ' ).append( ' ' );
2052  if ( proj4.startsWith( input ) )
2053  {
2054  proj4 = proj4.mid( input.size() );
2055  proj4 = proj4.trimmed();
2056  }
2057 
2058  if ( proj4 != params )
2059  {
2060  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name=%2 AND auth_id=%3" )
2061  .arg( quotedValue( proj4 ),
2062  quotedValue( auth_name ),
2063  quotedValue( auth_id ) );
2064 
2065  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2066  {
2067  updated++;
2068  }
2069  else
2070  {
2071  qCritical( "Could not execute: %s [%s/%s]\n",
2072  sql.toLocal8Bit().constData(),
2073  sqlite3_errmsg( database.get() ),
2074  errMsg ? errMsg : "(unknown error)" );
2075  if ( errMsg )
2076  sqlite3_free( errMsg );
2077  errors++;
2078  }
2079  }
2080  }
2081  else
2082  {
2083  QgsDebugMsgLevel( QString( "could not retrieve proj string for %1 from PROJ" ).arg( input ), 4 );
2084  }
2085  }
2086  else
2087  {
2088  QgsDebugMsgLevel( QString( "could not retrieve crs for %1 from PROJ" ).arg( input ), 3 );
2089  }
2090 
2091  pj_free( pj );
2092  }
2093  }
2094  else
2095  {
2096  errors++;
2097  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2]\n" ).arg(
2098  sql,
2099  sqlite3_errmsg( database.get() ) ) );
2100  }
2101 #endif
2102 
2103  pj_ctx_free( pContext );
2104 
2105  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2106  {
2107  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2109  sqlite3_errmsg( database.get() ) )
2110  );
2111  return -1;
2112  }
2113 
2114  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 );
2115 
2116  if ( errors > 0 )
2117  return -errors;
2118  else
2119  return updated + inserted;
2120 }
2121 
2122 bool QgsCoordinateReferenceSystem::syncDatumTransform( const QString &dbPath )
2123 {
2124  const char *filename = CSVFilename( "datum_shift.csv" );
2125  FILE *fp = VSIFOpen( filename, "rb" );
2126  if ( !fp )
2127  {
2128  return false;
2129  }
2130 
2131  char **fieldnames = CSVReadParseLine( fp );
2132 
2133  // "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"
2134 
2135  struct
2136  {
2137  const char *src; //skip-init-check
2138  const char *dst; //skip-init-check
2139  int idx;
2140  } map[] =
2141  {
2142  // { "SEQ_KEY", "", -1 },
2143  { "SOURCE_CRS_CODE", "source_crs_code", -1 },
2144  { "TARGET_CRS_CODE", "target_crs_code", -1 },
2145  { "REMARKS", "remarks", -1 },
2146  { "COORD_OP_SCOPE", "scope", -1 },
2147  { "AREA_OF_USE_CODE", "area_of_use_code", -1 },
2148  // { "AREA_SOUTH_BOUND_LAT", "", -1 },
2149  // { "AREA_NORTH_BOUND_LAT", "", -1 },
2150  // { "AREA_WEST_BOUND_LON", "", -1 },
2151  // { "AREA_EAST_BOUND_LON", "", -1 },
2152  // { "SHOW_OPERATION", "", -1 },
2153  { "DEPRECATED", "deprecated", -1 },
2154  { "COORD_OP_METHOD_CODE", "coord_op_method_code", -1 },
2155  { "DX", "p1", -1 },
2156  { "DY", "p2", -1 },
2157  { "DZ", "p3", -1 },
2158  { "RX", "p4", -1 },
2159  { "RY", "p5", -1 },
2160  { "RZ", "p6", -1 },
2161  { "DS", "p7", -1 },
2162  { "PREFERRED", "preferred", -1 },
2163  { "COORD_OP_CODE", "coord_op_code", -1 },
2164  };
2165 
2166  QString update = QStringLiteral( "UPDATE tbl_datum_transform SET " );
2167  QString insert, values;
2168 
2169  int n = CSLCount( fieldnames );
2170 
2171  int idxid = -1, idxrx = -1, idxry = -1, idxrz = -1, idxmcode = -1;
2172  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2173  {
2174  bool last = i == sizeof( map ) / sizeof( *map ) - 1;
2175 
2176  map[i].idx = CSLFindString( fieldnames, map[i].src );
2177  if ( map[i].idx < 0 )
2178  {
2179  qWarning( "field %s not found", map[i].src );
2180  CSLDestroy( fieldnames );
2181  fclose( fp );
2182  return false;
2183  }
2184 
2185  if ( strcmp( map[i].src, "COORD_OP_CODE" ) == 0 )
2186  idxid = i;
2187  if ( strcmp( map[i].src, "RX" ) == 0 )
2188  idxrx = i;
2189  if ( strcmp( map[i].src, "RY" ) == 0 )
2190  idxry = i;
2191  if ( strcmp( map[i].src, "RZ" ) == 0 )
2192  idxrz = i;
2193  if ( strcmp( map[i].src, "COORD_OP_METHOD_CODE" ) == 0 )
2194  idxmcode = i;
2195 
2196  if ( i > 0 )
2197  {
2198  insert += ',';
2199  values += ',';
2200 
2201  if ( last )
2202  {
2203  update += QLatin1String( " WHERE " );
2204  }
2205  else
2206  {
2207  update += ',';
2208  }
2209  }
2210 
2211  update += QStringLiteral( "%1=%%2" ).arg( map[i].dst ).arg( i + 1 );
2212 
2213  insert += map[i].dst;
2214  values += QStringLiteral( "%%1" ).arg( i + 1 );
2215  }
2216 
2217  insert = "INSERT INTO tbl_datum_transform(" + insert + ") VALUES (" + values + ')';
2218 
2219  CSLDestroy( fieldnames );
2220 
2221  Q_ASSERT( idxid >= 0 );
2222  Q_ASSERT( idxrx >= 0 );
2223  Q_ASSERT( idxry >= 0 );
2224  Q_ASSERT( idxrz >= 0 );
2225 
2226  sqlite3_database_unique_ptr database;
2227  int openResult = database.open( dbPath );
2228  if ( openResult != SQLITE_OK )
2229  {
2230  fclose( fp );
2231  return false;
2232  }
2233 
2234  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2235  {
2236  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database.get() ) );
2237  fclose( fp );
2238  return false;
2239  }
2240 
2241  QStringList v;
2242  v.reserve( sizeof( map ) / sizeof( *map ) );
2243 
2244  for ( ;; )
2245  {
2246  char **values = CSVReadParseLine( fp );
2247  if ( !values )
2248  break;
2249 
2250  v.clear();
2251 
2252  if ( CSLCount( values ) == 0 )
2253  {
2254  CSLDestroy( values );
2255  break;
2256  }
2257 
2258  if ( CSLCount( values ) < n )
2259  {
2260  qWarning( "Only %d columns", CSLCount( values ) );
2261  CSLDestroy( values );
2262  continue;
2263  }
2264 
2265  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2266  {
2267  int idx = map[i].idx;
2268  Q_ASSERT( idx != -1 );
2269  Q_ASSERT( idx < n );
2270  v.insert( i, *values[ idx ] ? quotedValue( values[idx] ) : QStringLiteral( "NULL" ) );
2271  }
2272  CSLDestroy( values );
2273 
2274  //switch sign of rotation parameters. See http://trac.osgeo.org/proj/wiki/GenParms#towgs84-DatumtransformationtoWGS84
2275  if ( v.at( idxmcode ).compare( QLatin1String( "'9607'" ) ) == 0 )
2276  {
2277  v[ idxmcode ] = QStringLiteral( "'9606'" );
2278  v[ idxrx ] = '\'' + qgsDoubleToString( -( v[ idxrx ].remove( '\'' ).toDouble() ) ) + '\'';
2279  v[ idxry ] = '\'' + qgsDoubleToString( -( v[ idxry ].remove( '\'' ).toDouble() ) ) + '\'';
2280  v[ idxrz ] = '\'' + qgsDoubleToString( -( v[ idxrz ].remove( '\'' ).toDouble() ) ) + '\'';
2281  }
2282 
2283  //entry already in db?
2284  sqlite3_statement_unique_ptr statement;
2285  QString cOpCode;
2286  QString sql = QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( v[ idxid ] );
2287  int prepareRes;
2288  statement = database.prepare( sql, prepareRes );
2289  if ( prepareRes != SQLITE_OK )
2290  continue;
2291 
2292  if ( statement.step() == SQLITE_ROW )
2293  {
2294  cOpCode = statement.columnAsText( 0 );
2295  }
2296 
2297  sql = cOpCode.isEmpty() ? insert : update;
2298  for ( int i = 0; i < v.size(); i++ )
2299  {
2300  sql = sql.arg( v[i] );
2301  }
2302 
2303  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) != SQLITE_OK )
2304  {
2305  qCritical( "SQL: %s", sql.toUtf8().constData() );
2306  qCritical( "Error: %s", sqlite3_errmsg( database.get() ) );
2307  }
2308  }
2309 
2310  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2311  {
2312  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg( QgsApplication::srsDatabaseFilePath(), sqlite3_errmsg( database.get() ) ) );
2313  return false;
2314  }
2315 
2316  return true;
2317 }
2318 
2320 {
2321  if ( isGeographic() )
2322  {
2323  return d->mAuthId;
2324  }
2325  else if ( d->mCRS )
2326  {
2327  return OSRGetAuthorityName( d->mCRS, "GEOGCS" ) + QStringLiteral( ":" ) + OSRGetAuthorityCode( d->mCRS, "GEOGCS" );
2328  }
2329  else
2330  {
2331  return QString();
2332  }
2333 }
2334 
2336 {
2337  QStringList projections;
2338 
2339  // Read settings from persistent storage
2340  QgsSettings settings;
2341  projections = settings.value( QStringLiteral( "UI/recentProjections" ) ).toStringList();
2342  /*** The reading (above) of internal id from persistent storage should be removed sometime in the future */
2343  /*** This is kept now for backwards compatibility */
2344 
2345  QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
2346  QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
2347  if ( projectionsAuthId.size() >= projections.size() )
2348  {
2349  // We had saved state with AuthId and Proj4. Use that instead
2350  // to find out the crs id
2351  projections.clear();
2352  for ( int i = 0; i < projectionsAuthId.size(); i++ )
2353  {
2354  // Create a crs from the EPSG
2356  crs.createFromOgcWmsCrs( projectionsAuthId.at( i ) );
2357  if ( ! crs.isValid() )
2358  {
2359  // Couldn't create from EPSG, try the Proj4 string instead
2360  if ( i >= projectionsProj4.size() || !crs.createFromProj4( projectionsProj4.at( i ) ) )
2361  {
2362  // No? Skip this entry
2363  continue;
2364  }
2365  //If the CRS can be created but do not correspond to a CRS in the database, skip it (for example a deleted custom CRS)
2366  if ( crs.srsid() == 0 )
2367  {
2368  continue;
2369  }
2370  }
2371  projections << QString::number( crs.srsid() );
2372  }
2373  }
2374  return projections;
2375 }
2376 
2378 {
2379  sSrIdCacheLock.lockForWrite();
2380  sSrIdCache.clear();
2381  sSrIdCacheLock.unlock();
2382  sOgcLock.lockForWrite();
2383  sOgcCache.clear();
2384  sOgcLock.unlock();
2385  sProj4CacheLock.lockForWrite();
2386  sProj4Cache.clear();
2387  sProj4CacheLock.unlock();
2388  sCRSWktLock.lockForWrite();
2389  sWktCache.clear();
2390  sCRSWktLock.unlock();
2391  sCRSSrsIdLock.lockForWrite();
2392  sSrsIdCache.clear();
2393  sCRSSrsIdLock.unlock();
2394  sCrsStringLock.lockForWrite();
2395  sStringCache.clear();
2396  sCrsStringLock.unlock();
2397 }
QString geographicCrsAuthId() const
Returns auth id of related geographic CRS.
QgsCoordinateReferenceSystem()
Constructs an invalid CRS object.
bool operator!=(const QgsCoordinateReferenceSystem &srs) const
Overloaded != operator used to compare to CRS&#39;s.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
static QgsCoordinateReferenceSystem fromProj4(const QString &proj4)
Creates a CRS from a proj4 style formatted string.
bool createFromString(const QString &definition)
Set up this CRS from a string definition.
static QList< long > validSrsIds()
Returns a list of all valid SRS IDs present in the CRS database.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:94
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
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 ...
#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
static void setCustomCrsValidation(CUSTOM_CRS_VALIDATION f)
Sets custom function to force valid CRS.
QString toProj4() const
Returns a Proj4 string representation of this CRS.
const int LAT_PREFIX_LEN
The length of the string "+lat_1=".
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning)
add a message to the instance (and create it if necessary)
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 setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void setValidationHint(const QString &html)
Set user hint for validation.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
bool createFromSrsId(const long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
static QStringList recentProjections()
Returns a list of recently used projections.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
QString validationHint()
Get user hint for validation.
long postgisSrid() const
Returns PostGIS SRID for the CRS.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h: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:99
int columnCount() const
Get 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:451
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 setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:104
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), const Section section=NoSection) const
Returns the value for setting key.
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
This class represents a coordinate reference system (CRS).
QString toWkt() const
Returns a WKT representation of this CRS.
bool createFromProj4(const QString &projString)
Sets this CRS by passing it a PROJ style formatted string.
bool createFromId(const long id, CrsType type=PostgisCrsId)
Sets this CRS by lookup of the given ID in the CRS database.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
QgsUnitTypes::DistanceUnit mapUnits() const
Returns the units for the projection used by the CRS.
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
static void invalidateCache()
Clears the internal cache used to initialize QgsCoordinateReferenceSystem objects.
void(* CUSTOM_CRS_VALIDATION)(QgsCoordinateReferenceSystem &)
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
long srsid() const
Returns the internal CRS ID, if available.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
bool operator==(const QgsCoordinateReferenceSystem &srs) const
Overloaded == operator used to compare to CRS&#39;s.
QString columnName(int column) const
Returns the name of column.
double columnAsDouble(int column) const
Get column value from the current statement row as a double.
qlonglong columnAsInt64(int column) const
Get 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:89
bool createFromSrid(const long srid)
Sets this CRS by lookup of the given PostGIS SRID in the CRS database.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
void * OGRSpatialReferenceH
bool isValid() const
Returns whether this CRS is correctly initialized and usable.