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