QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
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 
22 #include "qgsreadwritelocker.h"
23 
24 #include <cmath>
25 
26 #include <QDir>
27 #include <QDomNode>
28 #include <QDomElement>
29 #include <QFileInfo>
30 #include <QRegExp>
31 #include <QTextStream>
32 #include <QFile>
33 #include <QRegularExpression>
34 
35 #include "qgsapplication.h"
36 #include "qgslogger.h"
37 #include "qgsmessagelog.h"
38 #include "qgis.h" //const vals declared here
39 #include "qgslocalec.h"
40 #include "qgssettings.h"
41 
42 #include <sqlite3.h>
43 #if PROJ_VERSION_MAJOR>=6
44 #include "qgsprojutils.h"
45 #include <proj.h>
46 #include <proj_experimental.h>
47 #else
48 #include <proj_api.h>
49 #endif
50 
51 //gdal and ogr includes (needed for == operator)
52 #include <ogr_srs_api.h>
53 #include <cpl_error.h>
54 #include <cpl_conv.h>
55 #include <cpl_csv.h>
56 
57 
58 
60 const int LAT_PREFIX_LEN = 7;
61 
62 CUSTOM_CRS_VALIDATION QgsCoordinateReferenceSystem::sCustomSrsValidation = nullptr;
63 
64 QReadWriteLock QgsCoordinateReferenceSystem::sSrIdCacheLock;
65 QHash< long, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sSrIdCache;
66 bool QgsCoordinateReferenceSystem::sDisableSrIdCache = false;
67 
68 QReadWriteLock QgsCoordinateReferenceSystem::sOgcLock;
69 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sOgcCache;
70 bool QgsCoordinateReferenceSystem::sDisableOgcCache = false;
71 
72 QReadWriteLock QgsCoordinateReferenceSystem::sProj4CacheLock;
73 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sProj4Cache;
74 bool QgsCoordinateReferenceSystem::sDisableProj4Cache = false;
75 
76 QReadWriteLock QgsCoordinateReferenceSystem::sCRSWktLock;
77 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sWktCache;
78 bool QgsCoordinateReferenceSystem::sDisableWktCache = false;
79 
80 QReadWriteLock QgsCoordinateReferenceSystem::sCRSSrsIdLock;
81 QHash< long, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sSrsIdCache;
82 bool QgsCoordinateReferenceSystem::sDisableSrsIdCache = false;
83 
84 QReadWriteLock QgsCoordinateReferenceSystem::sCrsStringLock;
85 QHash< QString, QgsCoordinateReferenceSystem > QgsCoordinateReferenceSystem::sStringCache;
86 bool QgsCoordinateReferenceSystem::sDisableStringCache = false;
87 
88 #if PROJ_VERSION_MAJOR>=6
89 QString getFullProjString( PJ *obj )
90 {
91  // see https://lists.osgeo.org/pipermail/proj/2019-May/008565.html, it's not sufficient to just
92  // use proj_as_proj_string
93  QgsProjUtils::proj_pj_unique_ptr boundCrs( proj_crs_create_bound_crs_to_WGS84( QgsProjContext::get(), obj, nullptr ) );
94  if ( boundCrs )
95  {
96  if ( const char *proj4src = proj_as_proj_string( QgsProjContext::get(), boundCrs.get(), PJ_PROJ_4, nullptr ) )
97  {
98  return QString( proj4src );
99  }
100  }
101 
102  return QString( proj_as_proj_string( QgsProjContext::get(), obj, PJ_PROJ_4, nullptr ) );
103 }
104 #endif
105 //--------------------------
106 
108 {
109  d = new QgsCoordinateReferenceSystemPrivate();
110 }
111 
113 {
114  d = new QgsCoordinateReferenceSystemPrivate();
115  createFromString( definition );
116 }
117 
119 {
120  d = new QgsCoordinateReferenceSystemPrivate();
121  createFromId( id, type );
122 }
123 
125  : d( srs.d )
126 {
127 }
128 
130 {
131  d = srs.d;
132  return *this;
133 }
134 
136 {
137  QList<long> results;
138  // check both standard & user defined projection databases
139  QStringList dbs = QStringList() << QgsApplication::srsDatabaseFilePath() << QgsApplication::qgisUserDatabaseFilePath();
140 
141  const auto constDbs = dbs;
142  for ( const QString &db : constDbs )
143  {
144  QFileInfo myInfo( db );
145  if ( !myInfo.exists() )
146  {
147  QgsDebugMsg( "failed : " + db + " does not exist!" );
148  continue;
149  }
150 
153 
154  //check the db is available
155  int result = openDatabase( db, database );
156  if ( result != SQLITE_OK )
157  {
158  QgsDebugMsg( "failed : " + db + " could not be opened!" );
159  continue;
160  }
161 
162  QString sql = QStringLiteral( "select srs_id from tbl_srs" );
163  int rc;
164  statement = database.prepare( sql, rc );
165  while ( true )
166  {
167  // this one is an infinitive loop, intended to fetch any row
168  int ret = statement.step();
169 
170  if ( ret == SQLITE_DONE )
171  {
172  // there are no more rows to fetch - we can stop looping
173  break;
174  }
175 
176  if ( ret == SQLITE_ROW )
177  {
178  results.append( statement.columnAsInt64( 0 ) );
179  }
180  else
181  {
182  QgsMessageLog::logMessage( QObject::tr( "SQLite error: %2\nSQL: %1" ).arg( sql, sqlite3_errmsg( database.get() ) ), QObject::tr( "SpatiaLite" ) );
183  break;
184  }
185  }
186  }
187  std::sort( results.begin(), results.end() );
188  return results;
189 }
190 
192 {
194  crs.createFromOgcWmsCrs( ogcCrs );
195  return crs;
196 }
197 
199 {
200  QgsCoordinateReferenceSystem res = fromOgcWmsCrs( "EPSG:" + QString::number( epsg ) );
201  if ( res.isValid() )
202  return res;
203 
204  // pre proj6 builds allowed use of ESRI: codes here (e.g. 54030), so we need to keep compatibility
205  res = fromOgcWmsCrs( "ESRI:" + QString::number( epsg ) );
206  if ( res.isValid() )
207  return res;
208 
210 }
211 
213 {
215  crs.createFromProj4( proj4 );
216  return crs;
217 }
218 
220 {
222  crs.createFromWkt( wkt );
223  return crs;
224 }
225 
227 {
229  crs.createFromSrsId( srsId );
230  return crs;
231 }
232 
234 {
235 }
236 
238 {
239  bool result = false;
240  switch ( type )
241  {
242  case InternalCrsId:
243  result = createFromSrsId( id );
244  break;
245  case PostgisCrsId:
246  result = createFromSrid( id );
247  break;
248  case EpsgCrsId:
249  result = createFromOgcWmsCrs( QStringLiteral( "EPSG:%1" ).arg( id ) );
250  break;
251  default:
252  //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
253  QgsDebugMsg( QStringLiteral( "Unexpected case reached!" ) );
254  };
255  return result;
256 }
257 
258 bool QgsCoordinateReferenceSystem::createFromString( const QString &definition )
259 {
260  QgsReadWriteLocker locker( sCrsStringLock, QgsReadWriteLocker::Read );
261  if ( !sDisableStringCache )
262  {
263  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sStringCache.constFind( definition );
264  if ( crsIt != sStringCache.constEnd() )
265  {
266  // found a match in the cache
267  *this = crsIt.value();
268  return true;
269  }
270  }
271  locker.unlock();
272 
273  bool result = false;
274  QRegularExpression reCrsId( QStringLiteral( "^(epsg|esri|osgeo|ignf|zangi|iau2000|postgis|internal|user)\\:(\\w+)$" ), QRegularExpression::CaseInsensitiveOption );
275  QRegularExpressionMatch match = reCrsId.match( definition );
276  if ( match.capturedStart() == 0 )
277  {
278  QString authName = match.captured( 1 ).toLower();
279  if ( authName == QLatin1String( "epsg" ) )
280  {
281  result = createFromOgcWmsCrs( definition );
282  }
283  else if ( authName == QLatin1String( "postgis" ) )
284  {
285  const long id = match.captured( 2 ).toLong();
286  result = createFromId( id, PostgisCrsId );
287  }
288  else if ( authName == QLatin1String( "esri" ) || authName == QLatin1String( "osgeo" ) || authName == QLatin1String( "ignf" ) || authName == QLatin1String( "zangi" ) || authName == QLatin1String( "iau2000" ) )
289  {
290  result = createFromOgcWmsCrs( definition );
291  }
292  else
293  {
294  const long id = match.captured( 2 ).toLong();
295  result = createFromId( id, InternalCrsId );
296  }
297  }
298  else
299  {
300  QRegularExpression reCrsStr( "^(?:(wkt|proj4)\\:)?(.+)$", QRegularExpression::CaseInsensitiveOption );
301  match = reCrsStr.match( definition );
302  if ( match.capturedStart() == 0 )
303  {
304  if ( match.captured( 1 ).compare( QLatin1String( "proj4" ), Qt::CaseInsensitive ) == 0 )
305  {
306  result = createFromProj4( match.captured( 2 ) );
307  //TODO: createFromProj4 used to save to the user database any new CRS
308  // this behavior was changed in order to separate creation and saving.
309  // Not sure if it necessary to save it here, should be checked by someone
310  // familiar with the code (should also give a more descriptive name to the generated CRS)
311  if ( srsid() == 0 )
312  {
313  QString myName = QStringLiteral( " * %1 (%2)" )
314  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
315  toProj4() );
316  saveAsUserCrs( myName );
317  }
318  }
319  else
320  {
321  result = createFromWkt( match.captured( 2 ) );
322  }
323  }
324  }
325 
327  if ( !sDisableStringCache )
328  sStringCache.insert( definition, *this );
329  return result;
330 }
331 
332 bool QgsCoordinateReferenceSystem::createFromUserInput( const QString &definition )
333 {
334  QString userWkt;
335  char *wkt = nullptr;
336  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
337 
338  // make sure towgs84 parameter is loaded if using an ESRI definition and gdal >= 1.9
339  if ( definition.startsWith( QLatin1String( "ESRI::" ) ) )
340  {
341  setupESRIWktFix();
342  }
343 
344  if ( OSRSetFromUserInput( crs, definition.toLocal8Bit().constData() ) == OGRERR_NONE )
345  {
346  if ( OSRExportToWkt( crs, &wkt ) == OGRERR_NONE )
347  {
348  userWkt = wkt;
349  CPLFree( wkt );
350  }
351  OSRDestroySpatialReference( crs );
352  }
353  //QgsDebugMsg( "definition: " + definition + " wkt = " + wkt );
354  return createFromWkt( userWkt );
355 }
356 
358 {
359  // make sure towgs84 parameter is loaded if gdal >= 1.9
360  // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
361  const char *configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
362  const char *configNew = "GEOGCS";
363  // only set if it was not set, to let user change the value if needed
364  if ( strcmp( configOld, "" ) == 0 )
365  {
366  CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
367  if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
368  QgsLogger::warning( QStringLiteral( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2" )
369  .arg( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) );
370  QgsDebugMsgLevel( QStringLiteral( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ), 4 );
371  }
372  else
373  {
374  QgsDebugMsgLevel( QStringLiteral( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ), 4 );
375  }
376 }
377 
379 {
380  QgsReadWriteLocker locker( sOgcLock, QgsReadWriteLocker::Read );
381  if ( !sDisableOgcCache )
382  {
383  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sOgcCache.constFind( crs );
384  if ( crsIt != sOgcCache.constEnd() )
385  {
386  // found a match in the cache
387  *this = crsIt.value();
388  return true;
389  }
390  }
391  locker.unlock();
392 
393  QString wmsCrs = crs;
394 
395  QRegExp re_uri( "http://www\\.opengis\\.net/def/crs/([^/]+).+/(\\d+)", Qt::CaseInsensitive );
396  QRegExp re_urn( "urn:ogc:def:crs:([^:]+).+([^:]+)", Qt::CaseInsensitive );
397  if ( re_uri.exactMatch( wmsCrs ) )
398  {
399  wmsCrs = re_uri.cap( 1 ) + ':' + re_uri.cap( 2 );
400  }
401  else if ( re_urn.exactMatch( wmsCrs ) )
402  {
403  wmsCrs = re_urn.cap( 1 ) + ':' + re_urn.cap( 2 );
404  }
405  else
406  {
407  re_urn.setPattern( QStringLiteral( "(user|custom|qgis):(\\d+)" ) );
408  if ( re_urn.exactMatch( wmsCrs ) && createFromSrsId( re_urn.cap( 2 ).toInt() ) )
409  {
411  if ( !sDisableOgcCache )
412  sOgcCache.insert( crs, *this );
413  return true;
414  }
415  }
416 
417 #if PROJ_VERSION_MAJOR>=6
418  // first chance for proj 6 - scan through legacy systems and try to use authid directly
419  const QString legacyKey = wmsCrs.toLower();
420  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
421  {
422  if ( it.key().compare( legacyKey, Qt::CaseInsensitive ) == 0 )
423  {
424  const QStringList parts = it.key().split( ':' );
425  const QString auth = parts.at( 0 );
426  const QString code = parts.at( 1 );
427  if ( loadFromAuthCode( auth, code ) )
428  {
430  if ( !sDisableOgcCache )
431  sOgcCache.insert( crs, *this );
432  return true;
433  }
434  }
435  }
436 #endif
437 
438  if ( loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "lower(auth_name||':'||auth_id)" ), wmsCrs.toLower() ) )
439  {
441  if ( !sDisableOgcCache )
442  sOgcCache.insert( crs, *this );
443  return true;
444  }
445 
446  // NAD27
447  if ( wmsCrs.compare( QLatin1String( "CRS:27" ), Qt::CaseInsensitive ) == 0 ||
448  wmsCrs.compare( QLatin1String( "OGC:CRS27" ), Qt::CaseInsensitive ) == 0 )
449  {
450  // TODO: verify same axis orientation
451  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4267" ) );
452  }
453 
454  // NAD83
455  if ( wmsCrs.compare( QLatin1String( "CRS:83" ), Qt::CaseInsensitive ) == 0 ||
456  wmsCrs.compare( QLatin1String( "OGC:CRS83" ), Qt::CaseInsensitive ) == 0 )
457  {
458  // TODO: verify same axis orientation
459  return createFromOgcWmsCrs( QStringLiteral( "EPSG:4269" ) );
460  }
461 
462  // WGS84
463  if ( wmsCrs.compare( QLatin1String( "CRS:84" ), Qt::CaseInsensitive ) == 0 ||
464  wmsCrs.compare( QLatin1String( "OGC:CRS84" ), Qt::CaseInsensitive ) == 0 )
465  {
466  createFromOgcWmsCrs( QStringLiteral( "EPSG:4326" ) );
467 
468  d.detach();
469  d->mAxisInverted = false;
470  d->mAxisInvertedDirty = false;
471 
473  if ( !sDisableOgcCache )
474  sOgcCache.insert( crs, *this );
475 
476  return d->mIsValid;
477  }
478 
480  if ( !sDisableOgcCache )
481  sOgcCache.insert( crs, QgsCoordinateReferenceSystem() );
482  return false;
483 }
484 
485 // Misc helper functions -----------------------
486 
487 
489 {
490  if ( d->mIsValid || !sCustomSrsValidation )
491  return;
492 
493  d.detach();
494 
495  // try to validate using custom validation routines
496  if ( sCustomSrsValidation )
497  sCustomSrsValidation( *this );
498 }
499 
501 {
502  QgsReadWriteLocker locker( sSrIdCacheLock, QgsReadWriteLocker::Read );
503  if ( !sDisableSrIdCache )
504  {
505  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrIdCache.constFind( id );
506  if ( crsIt != sSrIdCache.constEnd() )
507  {
508  // found a match in the cache
509  *this = crsIt.value();
510  return true;
511  }
512  }
513  locker.unlock();
514 
515 #if PROJ_VERSION_MAJOR>=6
516  // first chance for proj 6 - scan through legacy systems and try to use authid directly
517  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
518  {
519  if ( it.value().endsWith( QStringLiteral( ",%1" ).arg( id ) ) )
520  {
521  const QStringList parts = it.key().split( ':' );
522  const QString auth = parts.at( 0 );
523  const QString code = parts.at( 1 );
524  if ( loadFromAuthCode( auth, code ) )
525  {
527  if ( !sDisableSrIdCache )
528  sSrIdCache.insert( id, *this );
529 
530  return true;
531  }
532  }
533  }
534 #endif
535 
536  bool result = loadFromDatabase( QgsApplication::srsDatabaseFilePath(), QStringLiteral( "srid" ), QString::number( id ) );
537 
539  if ( !sDisableSrIdCache )
540  sSrIdCache.insert( id, *this );
541 
542  return result;
543 }
544 
546 {
547  QgsReadWriteLocker locker( sCRSSrsIdLock, QgsReadWriteLocker::Read );
548  if ( !sDisableSrsIdCache )
549  {
550  QHash< long, QgsCoordinateReferenceSystem >::const_iterator crsIt = sSrsIdCache.constFind( id );
551  if ( crsIt != sSrsIdCache.constEnd() )
552  {
553  // found a match in the cache
554  *this = crsIt.value();
555  return true;
556  }
557  }
558  locker.unlock();
559 
560 #if PROJ_VERSION_MAJOR>=6
561  // first chance for proj 6 - scan through legacy systems and try to use authid directly
562  for ( auto it = sAuthIdToQgisSrsIdMap.constBegin(); it != sAuthIdToQgisSrsIdMap.constEnd(); ++it )
563  {
564  if ( it.value().startsWith( QString::number( id ) + ',' ) )
565  {
566  const QStringList parts = it.key().split( ':' );
567  const QString auth = parts.at( 0 );
568  const QString code = parts.at( 1 );
569  if ( loadFromAuthCode( auth, code ) )
570  {
572  if ( !sDisableSrsIdCache )
573  sSrsIdCache.insert( id, *this );
574  return true;
575  }
576  }
577  }
578 #endif
579 
580  bool result = loadFromDatabase( id < USER_CRS_START_ID ? QgsApplication::srsDatabaseFilePath() :
582  QStringLiteral( "srs_id" ), QString::number( id ) );
583 
585  if ( !sDisableSrsIdCache )
586  sSrsIdCache.insert( id, *this );
587  return result;
588 }
589 
590 bool QgsCoordinateReferenceSystem::loadFromDatabase( const QString &db, const QString &expression, const QString &value )
591 {
592  d.detach();
593 
594  QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
595  d->mIsValid = false;
596  d->mWkt.clear();
597 
598  QFileInfo myInfo( db );
599  if ( !myInfo.exists() )
600  {
601  QgsDebugMsg( "failed : " + db + " does not exist!" );
602  return d->mIsValid;
603  }
604 
607  int myResult;
608  //check the db is available
609  myResult = openDatabase( db, database );
610  if ( myResult != SQLITE_OK )
611  {
612  return d->mIsValid;
613  }
614 
615  /*
616  srs_id INTEGER PRIMARY KEY,
617  description text NOT NULL,
618  projection_acronym text NOT NULL,
619  ellipsoid_acronym NOT NULL,
620  parameters text NOT NULL,
621  srid integer NOT NULL,
622  auth_name varchar NOT NULL,
623  auth_id integer NOT NULL,
624  is_geo integer NOT NULL);
625  */
626 
627  QString mySql = "select srs_id,description,projection_acronym,"
628  "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo "
629  "from tbl_srs where " + expression + '=' + QgsSqliteUtils::quotedString( value ) + " order by deprecated";
630  statement = database.prepare( mySql, myResult );
631  // XXX Need to free memory from the error msg if one is set
632  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
633  {
634  d->mSrsId = statement.columnAsText( 0 ).toLong();
635  d->mDescription = statement.columnAsText( 1 );
636  d->mProjectionAcronym = statement.columnAsText( 2 );
637  d->mEllipsoidAcronym = statement.columnAsText( 3 );
638  d->mProj4 = statement.columnAsText( 4 );
639  d->mSRID = statement.columnAsText( 5 ).toLong();
640  d->mAuthId = statement.columnAsText( 6 );
641  d->mIsGeographic = statement.columnAsText( 7 ).toInt() != 0;
642  d->mAxisInvertedDirty = true;
643 
644  if ( d->mSrsId >= USER_CRS_START_ID && d->mAuthId.isEmpty() )
645  {
646  d->mAuthId = QStringLiteral( "USER:%1" ).arg( d->mSrsId );
647  }
648  else if ( !d->mAuthId.startsWith( QLatin1String( "USER:" ), Qt::CaseInsensitive ) )
649  {
650 #if PROJ_VERSION_MAJOR>=6
651  QStringList parts = d->mAuthId.split( ':' );
652  QString auth = parts.at( 0 );
653  QString code = parts.at( 1 );
654 
655  {
656  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( QgsProjContext::get(), auth.toLatin1(), code.toLatin1(), PJ_CATEGORY_CRS, false, nullptr ) );
657  d->mPj = QgsProjUtils::crsToSingleCrs( crs.get() );
658  }
659 
660  d->mIsValid = static_cast< bool >( d->mPj );
661 #else
662  OSRDestroySpatialReference( d->mCRS );
663  d->mCRS = OSRNewSpatialReference( nullptr );
664  d->mIsValid = OSRSetFromUserInput( d->mCRS, d->mAuthId.toLower().toLatin1() ) == OGRERR_NONE;
665 #endif
666  setMapUnits();
667  }
668 
669  if ( !d->mIsValid )
670  {
671  setProj4String( d->mProj4 );
672  }
673  }
674  else
675  {
676  QgsDebugMsgLevel( "failed : " + mySql, 4 );
677  }
678  return d->mIsValid;
679 }
680 
682 {
683  if ( d->mAxisInvertedDirty )
684  {
685 #if PROJ_VERSION_MAJOR>=6
686  d->mAxisInverted = QgsProjUtils::axisOrderIsSwapped( d->mPj.get() );
687 #else
688  OGRAxisOrientation orientation;
689  OSRGetAxis( d->mCRS, OSRIsGeographic( d->mCRS ) ? "GEOGCS" : "PROJCS", 0, &orientation );
690 
691  // If axis orientation is unknown, try again with OSRImportFromEPSGA for EPSG crs
692  if ( orientation == OAO_Other && d->mAuthId.startsWith( QLatin1String( "EPSG:" ), Qt::CaseInsensitive ) )
693  {
694  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
695 
696  if ( OSRImportFromEPSGA( crs, d->mAuthId.midRef( 5 ).toInt() ) == OGRERR_NONE )
697  {
698  OSRGetAxis( crs, OSRIsGeographic( crs ) ? "GEOGCS" : "PROJCS", 0, &orientation );
699  }
700 
701  OSRDestroySpatialReference( crs );
702  }
703 
704  d->mAxisInverted = orientation == OAO_North;
705 #endif
706  d->mAxisInvertedDirty = false;
707  }
708 
709  return d->mAxisInverted;
710 }
711 
713 {
714  d.detach();
715 
716  QgsReadWriteLocker locker( sCRSWktLock, QgsReadWriteLocker::Read );
717  if ( !sDisableWktCache )
718  {
719  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sWktCache.constFind( wkt );
720  if ( crsIt != sWktCache.constEnd() )
721  {
722  // found a match in the cache
723  *this = crsIt.value();
724  return true;
725  }
726  }
727  locker.unlock();
728 
729  d->mIsValid = false;
730  d->mWkt.clear();
731  d->mProj4.clear();
732  if ( wkt.isEmpty() )
733  {
734  QgsDebugMsgLevel( QStringLiteral( "theWkt is uninitialized, operation failed" ), 4 );
735  return d->mIsValid;
736  }
737 
738  bool res = false;
739 #if PROJ_VERSION_MAJOR>=6
740  PROJ_STRING_LIST warnings = nullptr;
741  PROJ_STRING_LIST grammerErrors = nullptr;
742 
743  {
744  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_wkt( QgsProjContext::get(), wkt.toLatin1().constData(), nullptr, &warnings, &grammerErrors ) );
745  d->mPj = QgsProjUtils::crsToSingleCrs( crs.get() );
746  }
747 
748  res = static_cast< bool >( d->mPj );
749  if ( !res )
750  {
751  QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
752  QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
753  QgsDebugMsg( "INPUT: " + wkt );
754  for ( auto iter = warnings; iter && *iter; ++iter )
755  QgsDebugMsg( *iter );
756  for ( auto iter = grammerErrors; iter && *iter; ++iter )
757  QgsDebugMsg( *iter );
758  QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
759  }
760  proj_string_list_destroy( warnings );
761  proj_string_list_destroy( grammerErrors );
762 #else
763  QByteArray ba = wkt.toLatin1();
764  const char *pWkt = ba.data();
765 
766  OGRErr myInputResult = OSRImportFromWkt( d->mCRS, const_cast< char ** >( & pWkt ) );
767  res = myInputResult == OGRERR_NONE;
768  if ( !res )
769  {
770  QgsDebugMsg( QStringLiteral( "\n---------------------------------------------------------------" ) );
771  QgsDebugMsg( QStringLiteral( "This CRS could *** NOT *** be set from the supplied Wkt " ) );
772  QgsDebugMsg( "INPUT: " + wkt );
773  QgsDebugMsg( QStringLiteral( "UNUSED WKT: %1" ).arg( pWkt ) );
774  QgsDebugMsg( QStringLiteral( "---------------------------------------------------------------\n" ) );
775  }
776 #endif
777  if ( !res )
778  {
780  if ( !sDisableWktCache )
781  sWktCache.insert( wkt, *this );
782  return d->mIsValid;
783  }
784 
785 #if PROJ_VERSION_MAJOR>=6
786  if ( d->mPj )
787  {
788  const QString authName( proj_get_id_auth_name( d->mPj.get(), 0 ) );
789  const QString authCode( proj_get_id_code( d->mPj.get(), 0 ) );
790  if ( !authName.isEmpty() && !authCode.isEmpty() )
791  {
792  if ( loadFromAuthCode( authName, authCode ) )
793  {
795  if ( !sDisableWktCache )
796  sWktCache.insert( wkt, *this );
797  return true;
798  }
799  }
800  }
801 #else
802  if ( OSRAutoIdentifyEPSG( d->mCRS ) == OGRERR_NONE )
803  {
804  QString authid = QStringLiteral( "%1:%2" )
805  .arg( OSRGetAuthorityName( d->mCRS, nullptr ),
806  OSRGetAuthorityCode( d->mCRS, nullptr ) );
807  bool result = createFromOgcWmsCrs( authid );
809  if ( !sDisableWktCache )
810  sWktCache.insert( wkt, *this );
811  return result;
812  }
813 #endif
814 
815  // always morph from esri as it doesn't hurt anything
816  // FW: Hey, that's not right! It can screw stuff up! Disable
817  //myOgrSpatialRef.morphFromESRI();
818 
819 
820  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
821  // WARNING - wkt to proj conversion is lossy -- we should reevaluate all this logic!!
822  // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
823 
824 #if PROJ_VERSION_MAJOR>=6
825  // create the proj4 structs needed for transforming
826  if ( d->mPj )
827  {
828  const QString proj4String = getFullProjString( d->mPj.get() );
829  if ( !proj4String.isEmpty() )
830  {
831  //now that we have the proj4string, delegate to createFromProj4 so
832  // that we can try to fill in the remaining class members...
833  //create from Proj will set the isValidFlag
834  createFromProj4( proj4String );
835  }
836  }
837 #else
838  // create the proj4 structs needed for transforming
839  char *proj4src = nullptr;
840  OSRExportToProj4( d->mCRS, &proj4src );
841 
842  //now that we have the proj4string, delegate to createFromProj4 so
843  // that we can try to fill in the remaining class members...
844  //create from Proj will set the isValidFlag
845  if ( !createFromProj4( proj4src ) )
846  {
847  CPLFree( proj4src );
848 
849 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,5,0)
850  // try fixed up version
851  OSRFixup( d->mCRS );
852 #endif
853 
854  OSRExportToProj4( d->mCRS, &proj4src );
855 
856  createFromProj4( proj4src );
857  }
858 #endif
859 
860  //TODO: createFromProj4 used to save to the user database any new CRS
861  // this behavior was changed in order to separate creation and saving.
862  // Not sure if it necessary to save it here, should be checked by someone
863  // familiar with the code (should also give a more descriptive name to the generated CRS)
864  if ( d->mSrsId == 0 )
865  {
866  QString myName = QStringLiteral( " * %1 (%2)" )
867  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
868  toProj4() );
869  saveAsUserCrs( myName );
870  }
871 
872 #if PROJ_VERSION_MAJOR<6
873  CPLFree( proj4src );
874 #endif
875 
877  if ( !sDisableWktCache )
878  sWktCache.insert( wkt, *this );
879 
880  return d->mIsValid;
881  //setMapunits will be called by createfromproj above
882 }
883 
885 {
886  return d->mIsValid;
887 }
888 
889 bool QgsCoordinateReferenceSystem::createFromProj4( const QString &proj4String )
890 {
891  d.detach();
892 
893  if ( proj4String.trimmed().isEmpty() )
894  {
895  d->mIsValid = false;
896  d->mWkt.clear();
897  d->mProj4.clear();
898  return false;
899  }
900 
901  QgsReadWriteLocker locker( sProj4CacheLock, QgsReadWriteLocker::Read );
902  if ( !sDisableProj4Cache )
903  {
904  QHash< QString, QgsCoordinateReferenceSystem >::const_iterator crsIt = sProj4Cache.constFind( proj4String );
905  if ( crsIt != sProj4Cache.constEnd() )
906  {
907  // found a match in the cache
908  *this = crsIt.value();
909  return true;
910  }
911  }
912  locker.unlock();
913 
914  //
915  // Examples:
916  // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
917  // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
918  //
919  // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
920  // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
921  //
922  QString myProj4String = proj4String.trimmed();
923  myProj4String.remove( QStringLiteral( "+type=crs" ) );
924  myProj4String = myProj4String.trimmed();
925 
926  // hack!
927 #if PROJ_VERSION_MAJOR>=6
928  myProj4String.remove( QStringLiteral( "+towgs84=0,0,0,0,0,0,0" ) );
929  myProj4String = myProj4String.trimmed();
930 #endif
931 
932  d->mIsValid = false;
933  d->mWkt.clear();
934 
935  // broken on proj <= 6.1.0
936 #if PROJ_VERSION_MAJOR>=6
937  // first, try to use proj to do this for us...
938  const QString projCrsString = myProj4String + ( myProj4String.contains( QStringLiteral( "+type=crs" ) ) ? QString() : QStringLiteral( " +type=crs" ) );
939  QgsProjUtils::proj_pj_unique_ptr crs( proj_create( QgsProjContext::get(), projCrsString.toLatin1().constData() ) );
940  if ( crs )
941  {
942  //crs = QgsProjUtils::crsToSingleCrs( crs.get() ) ;
943  int *confidence = nullptr;
944  if ( PJ_OBJ_LIST *crsList = proj_identify( QgsProjContext::get(), crs.get(), nullptr, nullptr, &confidence ) )
945  {
946  const int count = proj_list_get_count( crsList );
947  int bestConfidence = 0;
948  QgsProjUtils::proj_pj_unique_ptr matchedCrs;
949  for ( int i = 0; i < count; ++i )
950  {
951  if ( confidence[i] >= bestConfidence )
952  {
953  // prefer EPSG codes for compatibility with earlier qgis conversions
954  QgsProjUtils::proj_pj_unique_ptr candidateCrs( proj_list_get( QgsProjContext::get(), crsList, i ) );
955  candidateCrs = QgsProjUtils::crsToSingleCrs( candidateCrs.get() );
956  const QString authName( proj_get_id_auth_name( candidateCrs.get(), 0 ) );
957  if ( confidence[i] > bestConfidence || authName == QLatin1String( "EPSG" ) )
958  {
959  bestConfidence = confidence[i];
960  matchedCrs = std::move( candidateCrs );
961  }
962  }
963  }
964  proj_list_destroy( crsList );
965  proj_int_list_destroy( confidence );
966  if ( matchedCrs && bestConfidence >= 70 )
967  {
968  const QString authName( proj_get_id_auth_name( matchedCrs.get(), 0 ) );
969  const QString authCode( proj_get_id_code( matchedCrs.get(), 0 ) );
970  if ( !authName.isEmpty() && !authCode.isEmpty() )
971  {
972  const QString authid = QStringLiteral( "%1:%2" ).arg( authName, authCode );
973  if ( createFromOgcWmsCrs( authid ) )
974  {
976  if ( !sDisableProj4Cache )
977  sProj4Cache.insert( proj4String, *this );
978  return true;
979  }
980  }
981  }
982  }
983  }
984 #endif
985 
986  // IDEALLY!!
987  // don't do any of this for proj 6 -- responsibility for all this rests in the proj library.
988  // Woohoo! we can be free of this legacy cruft FOREVER!
989  // (and if any of this has value, take it up with the proj project. That's where it belongs)
990  // I ***think*** this is safe to disable for proj 6.1.1 and above
991 #if 1
992  QRegExp myProjRegExp( "\\+proj=(\\S+)" );
993  int myStart = myProjRegExp.indexIn( myProj4String );
994  if ( myStart == -1 )
995  {
997  if ( !sDisableProj4Cache )
998  sProj4Cache.insert( proj4String, *this );
999 
1000  return d->mIsValid;
1001  }
1002 
1003  d->mProjectionAcronym = myProjRegExp.cap( 1 );
1004 
1005  QRegExp myEllipseRegExp( "\\+ellps=(\\S+)" );
1006  myStart = myEllipseRegExp.indexIn( myProj4String );
1007  if ( myStart == -1 )
1008  {
1009  d->mEllipsoidAcronym.clear();
1010  }
1011  else
1012  {
1013  d->mEllipsoidAcronym = myEllipseRegExp.cap( 1 );
1014  }
1015 
1016  QRegExp myAxisRegExp( "\\+a=(\\S+)" );
1017  myStart = myAxisRegExp.indexIn( myProj4String );
1018 
1019  long mySrsId = 0;
1020  QgsCoordinateReferenceSystem::RecordMap myRecord;
1021 
1022  /*
1023  * We try to match the proj string to and srsid using the following logic:
1024  * - perform a whole text search on proj4 string (if not null)
1025  */
1026  myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( myProj4String ) + " order by deprecated" );
1027  if ( myRecord.empty() )
1028  {
1029  // Ticket #722 - aaronr
1030  // Check if we can swap the lat_1 and lat_2 params (if they exist) to see if we match...
1031  // First we check for lat_1 and lat_2
1032  QRegExp myLat1RegExp( "\\+lat_1=\\S+" );
1033  QRegExp myLat2RegExp( "\\+lat_2=\\S+" );
1034  int myStart1 = 0;
1035  int myLength1 = 0;
1036  int myStart2 = 0;
1037  int myLength2 = 0;
1038  QString lat1Str;
1039  QString lat2Str;
1040  myStart1 = myLat1RegExp.indexIn( myProj4String, myStart1 );
1041  myStart2 = myLat2RegExp.indexIn( myProj4String, myStart2 );
1042  if ( myStart1 != -1 && myStart2 != -1 )
1043  {
1044  myLength1 = myLat1RegExp.matchedLength();
1045  myLength2 = myLat2RegExp.matchedLength();
1046  lat1Str = myProj4String.mid( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN );
1047  lat2Str = myProj4String.mid( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN );
1048  }
1049  // If we found the lat_1 and lat_2 we need to swap and check to see if we can find it...
1050  if ( !lat1Str.isEmpty() && !lat2Str.isEmpty() )
1051  {
1052  // Make our new string to check...
1053  QString proj4StringModified = myProj4String;
1054  // First just swap in the lat_2 value for lat_1 value
1055  proj4StringModified.replace( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN, lat2Str );
1056  // Now we have to find the lat_2 location again since it has potentially moved...
1057  myStart2 = 0;
1058  myStart2 = myLat2RegExp.indexIn( proj4String, myStart2 );
1059  proj4StringModified.replace( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN, lat1Str );
1060  QgsDebugMsgLevel( QStringLiteral( "trying proj4string match with swapped lat_1,lat_2" ), 4 );
1061  myRecord = getRecord( "select * from tbl_srs where parameters=" + QgsSqliteUtils::quotedString( proj4StringModified.trimmed() ) + " order by deprecated" );
1062  }
1063  }
1064 
1065  if ( myRecord.empty() )
1066  {
1067  // match all parameters individually:
1068  // - order of parameters doesn't matter
1069  // - found definition may have more parameters (like +towgs84 in GDAL)
1070  // - retry without datum, if no match is found (looks like +datum<>WGS84 was dropped in GDAL)
1071 
1072  QString sql = QStringLiteral( "SELECT * FROM tbl_srs WHERE " );
1073  QString delim;
1074  QString datum;
1075 
1076  // split on spaces followed by a plus sign (+) to deal
1077  // also with parameters containing spaces (e.g. +nadgrids)
1078  // make sure result is trimmed (#5598)
1079  QStringList myParams;
1080  const auto constSplit = myProj4String.split( QRegExp( "\\s+(?=\\+)" ), QString::SkipEmptyParts );
1081  for ( const QString &param : constSplit )
1082  {
1083  QString arg = QStringLiteral( "' '||parameters||' ' LIKE %1" ).arg( QgsSqliteUtils::quotedString( QStringLiteral( "% %1 %" ).arg( param.trimmed() ) ) );
1084  if ( param.startsWith( QLatin1String( "+datum=" ) ) )
1085  {
1086  datum = arg;
1087  }
1088  else
1089  {
1090  sql += delim + arg;
1091  delim = QStringLiteral( " AND " );
1092  myParams << param.trimmed();
1093  }
1094  }
1095 
1096  if ( !datum.isEmpty() )
1097  {
1098  myRecord = getRecord( sql + delim + datum + " order by deprecated" );
1099  }
1100 
1101  if ( myRecord.empty() )
1102  {
1103  // datum might have disappeared in definition - retry without it
1104  myRecord = getRecord( sql + " order by deprecated" );
1105  }
1106 
1107  if ( !myRecord.empty() )
1108  {
1109  // Bugfix 8487 : test param lists are equal, except for +datum
1110  QStringList foundParams;
1111  const auto constSplit = myRecord["parameters"].split( QRegExp( "\\s+(?=\\+)" ), QString::SkipEmptyParts );
1112  for ( const QString &param : constSplit )
1113  {
1114  if ( !param.startsWith( QLatin1String( "+datum=" ) ) )
1115  foundParams << param.trimmed();
1116  }
1117 
1118  myParams.sort();
1119  foundParams.sort();
1120 
1121  if ( myParams != foundParams )
1122  {
1123  myRecord.clear();
1124  }
1125  }
1126  }
1127 
1128  if ( !myRecord.empty() )
1129  {
1130  mySrsId = myRecord[QStringLiteral( "srs_id" )].toLong();
1131  if ( mySrsId > 0 )
1132  {
1133  createFromSrsId( mySrsId );
1134  }
1135  }
1136  else
1137  {
1138  // Last ditch attempt to piece together what we know of the projection to find a match...
1139  setProj4String( myProj4String );
1140  mySrsId = findMatchingProj();
1141  if ( mySrsId > 0 )
1142  {
1143  createFromSrsId( mySrsId );
1144  }
1145  else
1146  {
1147  d->mIsValid = false;
1148  }
1149  }
1150 #endif
1151 
1152  // if we failed to look up the projection in database, don't worry. we can still use it :)
1153  if ( !d->mIsValid )
1154  {
1155  QgsDebugMsgLevel( QStringLiteral( "Projection is not found in databases." ), 4 );
1156  //setProj4String will set mIsValidFlag to true if there is no issue
1157  setProj4String( myProj4String );
1158  }
1159 
1161  if ( !sDisableProj4Cache )
1162  sProj4Cache.insert( proj4String, *this );
1163 
1164  return d->mIsValid;
1165 }
1166 
1167 //private method meant for internal use by this class only
1168 QgsCoordinateReferenceSystem::RecordMap QgsCoordinateReferenceSystem::getRecord( const QString &sql )
1169 {
1170  QString myDatabaseFileName;
1171  QgsCoordinateReferenceSystem::RecordMap myMap;
1172  QString myFieldName;
1173  QString myFieldValue;
1174  sqlite3_database_unique_ptr database;
1175  sqlite3_statement_unique_ptr statement;
1176  int myResult;
1177 
1178  // Get the full path name to the sqlite3 spatial reference database.
1179  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1180  QFileInfo myInfo( myDatabaseFileName );
1181  if ( !myInfo.exists() )
1182  {
1183  QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
1184  return myMap;
1185  }
1186 
1187  //check the db is available
1188  myResult = openDatabase( myDatabaseFileName, database );
1189  if ( myResult != SQLITE_OK )
1190  {
1191  return myMap;
1192  }
1193 
1194  statement = database.prepare( sql, myResult );
1195  // XXX Need to free memory from the error msg if one is set
1196  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1197  {
1198  int myColumnCount = statement.columnCount();
1199  //loop through each column in the record adding its expression name and value to the map
1200  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1201  {
1202  myFieldName = statement.columnName( myColNo );
1203  myFieldValue = statement.columnAsText( myColNo );
1204  myMap[myFieldName] = myFieldValue;
1205  }
1206  if ( statement.step() != SQLITE_DONE )
1207  {
1208  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1209  //be less fussy on proj 6 -- the db has MANY more entries!
1210 #if PROJ_VERSION_MAJOR<6
1211  myMap.clear();
1212 #endif
1213  }
1214  }
1215  else
1216  {
1217  QgsDebugMsgLevel( "failed : " + sql, 4 );
1218  }
1219 
1220  if ( myMap.empty() )
1221  {
1222  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1223  QFileInfo myFileInfo;
1224  myFileInfo.setFile( myDatabaseFileName );
1225  if ( !myFileInfo.exists() )
1226  {
1227  QgsDebugMsg( QStringLiteral( "user qgis.db not found" ) );
1228  return myMap;
1229  }
1230 
1231  //check the db is available
1232  myResult = openDatabase( myDatabaseFileName, database );
1233  if ( myResult != SQLITE_OK )
1234  {
1235  return myMap;
1236  }
1237 
1238  statement = database.prepare( sql, myResult );
1239  // XXX Need to free memory from the error msg if one is set
1240  if ( myResult == SQLITE_OK && statement.step() == SQLITE_ROW )
1241  {
1242  int myColumnCount = statement.columnCount();
1243  //loop through each column in the record adding its field name and value to the map
1244  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
1245  {
1246  myFieldName = statement.columnName( myColNo );
1247  myFieldValue = statement.columnAsText( myColNo );
1248  myMap[myFieldName] = myFieldValue;
1249  }
1250 
1251  if ( statement.step() != SQLITE_DONE )
1252  {
1253  QgsDebugMsgLevel( QStringLiteral( "Multiple records found in srs.db" ), 4 );
1254  myMap.clear();
1255  }
1256  }
1257  else
1258  {
1259  QgsDebugMsgLevel( "failed : " + sql, 4 );
1260  }
1261  }
1262  return myMap;
1263 }
1264 
1265 // Accessors -----------------------------------
1266 
1268 {
1269  return d->mSrsId;
1270 }
1271 
1273 {
1274  return d->mSRID;
1275 }
1276 
1278 {
1279  return d->mAuthId;
1280 }
1281 
1283 {
1284  if ( d->mDescription.isNull() )
1285  {
1286  return QString();
1287  }
1288  else
1289  {
1290  return d->mDescription;
1291  }
1292 }
1293 
1295 {
1296  if ( d->mProjectionAcronym.isNull() )
1297  {
1298  return QString();
1299  }
1300  else
1301  {
1302  return d->mProjectionAcronym;
1303  }
1304 }
1305 
1307 {
1308  if ( d->mEllipsoidAcronym.isNull() )
1309  {
1310  return QString();
1311  }
1312  else
1313  {
1314  return d->mEllipsoidAcronym;
1315  }
1316 }
1317 
1319 {
1320  if ( !d->mIsValid )
1321  return QString();
1322 
1323  if ( d->mProj4.isEmpty() )
1324  {
1325 #if PROJ_VERSION_MAJOR>=6
1326  if ( d->mPj )
1327  {
1328  d->mProj4 = getFullProjString( d->mPj.get() );
1329  }
1330 #else
1331  char *proj4src = nullptr;
1332  OSRExportToProj4( d->mCRS, &proj4src );
1333  d->mProj4 = proj4src;
1334  CPLFree( proj4src );
1335 #endif
1336  }
1337  // Stray spaces at the end?
1338  return d->mProj4.trimmed();
1339 }
1340 
1342 {
1343  return d->mIsGeographic;
1344 }
1345 
1347 {
1348  if ( !d->mIsValid )
1350 
1351  return d->mMapUnits;
1352 }
1353 
1355 {
1356  if ( !d->mIsValid )
1357  return QgsRectangle();
1358 
1359 #if PROJ_VERSION_MAJOR>=6
1360  if ( !d->mPj )
1361  return QgsRectangle();
1362 
1363  double westLon = 0;
1364  double southLat = 0;
1365  double eastLon = 0;
1366  double northLat = 0;
1367 
1368  if ( !proj_get_area_of_use( QgsProjContext::get(), d->mPj.get(),
1369  &westLon, &southLat, &eastLon, &northLat, nullptr ) )
1370  return QgsRectangle();
1371 
1372 
1373  // don't use the constructor which normalizes!
1374  QgsRectangle rect;
1375  rect.setXMinimum( westLon );
1376  rect.setYMinimum( southLat );
1377  rect.setXMaximum( eastLon );
1378  rect.setYMaximum( northLat );
1379  return rect;
1380 
1381 #else
1382  //check the db is available
1383  QString databaseFileName = QgsApplication::srsDatabaseFilePath();
1384 
1385  sqlite3_database_unique_ptr database;
1386  sqlite3_statement_unique_ptr statement;
1387 
1388  int result = openDatabase( databaseFileName, database );
1389  if ( result != SQLITE_OK )
1390  {
1391  return QgsRectangle();
1392  }
1393 
1394  QString sql = QStringLiteral( "select west_bound_lon, north_bound_lat, east_bound_lon, south_bound_lat from tbl_bounds "
1395  "where srid=%1" )
1396  .arg( d->mSRID );
1397  statement = database.prepare( sql, result );
1398 
1399  QgsRectangle rect;
1400  if ( result == SQLITE_OK )
1401  {
1402  if ( statement.step() == SQLITE_ROW )
1403  {
1404  double west = statement.columnAsDouble( 0 );
1405  double north = statement.columnAsDouble( 1 );
1406  double east = statement.columnAsDouble( 2 );
1407  double south = statement.columnAsDouble( 3 );
1408 
1409  rect.setXMinimum( west );
1410  rect.setYMinimum( south );
1411  rect.setXMaximum( east );
1412  rect.setYMaximum( north );
1413  }
1414  }
1415  return rect;
1416 #endif
1417 }
1418 
1419 
1420 // Mutators -----------------------------------
1421 
1422 
1423 void QgsCoordinateReferenceSystem::setInternalId( long srsId )
1424 {
1425  d.detach();
1426  d->mSrsId = srsId;
1427 }
1428 void QgsCoordinateReferenceSystem::setAuthId( const QString &authId )
1429 {
1430  d.detach();
1431  d->mAuthId = authId;
1432 }
1433 void QgsCoordinateReferenceSystem::setSrid( long srid )
1434 {
1435  d.detach();
1436  d->mSRID = srid;
1437 }
1438 void QgsCoordinateReferenceSystem::setDescription( const QString &description )
1439 {
1440  d.detach();
1441  d->mDescription = description;
1442 }
1443 void QgsCoordinateReferenceSystem::setProj4String( const QString &proj4String )
1444 {
1445  d.detach();
1446  d->mProj4 = proj4String;
1447 
1448  QgsLocaleNumC l;
1449  QString trimmed = proj4String.trimmed();
1450 
1451 #if PROJ_VERSION_MAJOR>=6
1452  trimmed += QStringLiteral( " +type=crs" );
1454 
1455  {
1456  QgsProjUtils::proj_pj_unique_ptr crs( proj_create( ctx, trimmed.toLatin1().constData() ) );
1457  d->mPj = QgsProjUtils::crsToSingleCrs( crs.get() );
1458  }
1459 
1460  if ( !d->mPj )
1461  {
1462 #ifdef QGISDEBUG
1463  const int errNo = proj_context_errno( ctx );
1464  QgsDebugMsg( QStringLiteral( "proj string rejected: %1" ).arg( proj_errno_string( errNo ) ) );
1465 #endif
1466  d->mIsValid = false;
1467  }
1468  else
1469  {
1470  QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_get_ellipsoid( ctx, d->mPj.get() ) );
1471  if ( ellipsoid )
1472  {
1473  const QString ellipsoidAuthName( proj_get_id_auth_name( ellipsoid.get(), 0 ) );
1474  const QString ellipsoidAuthCode( proj_get_id_code( ellipsoid.get(), 0 ) );
1475  d->mEllipsoidAcronym = QStringLiteral( "%1:%2" ).arg( ellipsoidAuthName, ellipsoidAuthCode );
1476  }
1477 
1478  d->mIsValid = true;
1479  }
1480 #else
1481  OSRDestroySpatialReference( d->mCRS );
1482  d->mCRS = OSRNewSpatialReference( nullptr );
1483  d->mIsValid = OSRImportFromProj4( d->mCRS, trimmed.toLatin1().constData() ) == OGRERR_NONE;
1484 
1485  // OSRImportFromProj4() may accept strings that are not valid proj.4 strings,
1486  // e.g if they lack a +ellps parameter, it will automatically add +ellps=WGS84, but as
1487  // we use the original mProj4 with QgsCoordinateTransform, it will fail to initialize
1488  // so better detect it now.
1489  projCtx pContext = pj_ctx_alloc();
1490  projPJ proj = pj_init_plus_ctx( pContext, proj4String.trimmed().toLatin1().constData() );
1491  if ( !proj )
1492  {
1493  QgsDebugMsgLevel( QStringLiteral( "proj.4 string rejected by pj_init_plus_ctx()" ), 4 );
1494  d->mIsValid = false;
1495  }
1496  else
1497  {
1498  pj_free( proj );
1499  }
1500  pj_ctx_free( pContext );
1501 #endif
1502 
1503  d->mWkt.clear();
1504  setMapUnits();
1505 }
1506 
1507 void QgsCoordinateReferenceSystem::setGeographicFlag( bool geoFlag )
1508 {
1509  d.detach();
1510  d->mIsGeographic = geoFlag;
1511 }
1512 void QgsCoordinateReferenceSystem::setEpsg( long epsg )
1513 {
1514  d.detach();
1515  d->mAuthId = QStringLiteral( "EPSG:%1" ).arg( epsg );
1516 }
1517 void QgsCoordinateReferenceSystem::setProjectionAcronym( const QString &projectionAcronym )
1518 {
1519  d.detach();
1520  d->mProjectionAcronym = projectionAcronym;
1521 }
1522 void QgsCoordinateReferenceSystem::setEllipsoidAcronym( const QString &ellipsoidAcronym )
1523 {
1524  d.detach();
1525  d->mEllipsoidAcronym = ellipsoidAcronym;
1526 }
1527 
1528 void QgsCoordinateReferenceSystem::setMapUnits()
1529 {
1530  d.detach();
1531  if ( !d->mIsValid )
1532  {
1533  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1534  return;
1535  }
1536 
1537 #if PROJ_VERSION_MAJOR<6
1538 #if GDAL_VERSION_NUM < GDAL_COMPUTE_VERSION(2,5,0)
1539  // Of interest to us is that this call adds in a unit parameter if
1540  // one doesn't already exist.
1541  OSRFixup( d->mCRS );
1542 #endif
1543 #endif
1544 
1545 #if PROJ_VERSION_MAJOR>=6
1546  if ( !d->mPj )
1547  {
1548  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1549  return;
1550  }
1551 
1552  PJ_CONTEXT *context = QgsProjContext::get();
1553  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( context, d->mPj.get() ) );
1554  if ( !coordinateSystem )
1555  {
1556  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1557  return;
1558  }
1559 
1560  const int axisCount = proj_cs_get_axis_count( context, coordinateSystem.get() );
1561  if ( axisCount > 0 )
1562  {
1563  const char *outUnitName = nullptr;
1564  // Read only first axis
1565  proj_cs_get_axis_info( context, coordinateSystem.get(), 0,
1566  nullptr,
1567  nullptr,
1568  nullptr,
1569  nullptr,
1570  &outUnitName,
1571  nullptr,
1572  nullptr );
1573 
1574  const QString unitName( outUnitName );
1575 
1576  // proj unit names are freeform -- they differ from authority to authority :(
1577  // see https://lists.osgeo.org/pipermail/proj/2019-April/008444.html
1578  if ( unitName.compare( QLatin1String( "degree" ), Qt::CaseInsensitive ) == 0 ||
1579  unitName.compare( QLatin1String( "degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1580  unitName.compare( QLatin1String( "degree minute second hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1581  unitName.compare( QLatin1String( "degree minute" ), Qt::CaseInsensitive ) == 0 ||
1582  unitName.compare( QLatin1String( "degree hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1583  unitName.compare( QLatin1String( "degree minute hemisphere" ), Qt::CaseInsensitive ) == 0 ||
1584  unitName.compare( QLatin1String( "hemisphere degree" ), Qt::CaseInsensitive ) == 0 ||
1585  unitName.compare( QLatin1String( "hemisphere degree minute" ), Qt::CaseInsensitive ) == 0 ||
1586  unitName.compare( QLatin1String( "hemisphere degree minute second" ), Qt::CaseInsensitive ) == 0 ||
1587  unitName.compare( QLatin1String( "degree (supplier to define representation)" ), Qt::CaseInsensitive ) == 0 )
1588  d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1589  else if ( unitName.compare( QLatin1String( "metre" ), Qt::CaseInsensitive ) == 0 )
1590  d->mMapUnits = QgsUnitTypes::DistanceMeters;
1591  // we don't differentiate between these, suck it imperial users!
1592  else if ( unitName.compare( QLatin1String( "US survey foot" ), Qt::CaseInsensitive ) == 0 ||
1593  unitName.compare( QLatin1String( "foot" ), Qt::CaseInsensitive ) == 0 )
1594  d->mMapUnits = QgsUnitTypes::DistanceFeet;
1595  else if ( unitName.compare( QLatin1String( "kilometre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1596  d->mMapUnits = QgsUnitTypes::DistanceKilometers;
1597  else if ( unitName.compare( QLatin1String( "centimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1598  d->mMapUnits = QgsUnitTypes::DistanceCentimeters;
1599  else if ( unitName.compare( QLatin1String( "millimetre" ), Qt::CaseInsensitive ) == 0 ) //#spellok
1600  d->mMapUnits = QgsUnitTypes::DistanceMillimeters;
1601  else if ( unitName.compare( QLatin1String( "Statute mile" ), Qt::CaseInsensitive ) == 0 )
1602  d->mMapUnits = QgsUnitTypes::DistanceMiles;
1603  else if ( unitName.compare( QLatin1String( "nautical mile" ), Qt::CaseInsensitive ) == 0 )
1604  d->mMapUnits = QgsUnitTypes::DistanceNauticalMiles;
1605  else if ( unitName.compare( QLatin1String( "yard" ), Qt::CaseInsensitive ) == 0 )
1606  d->mMapUnits = QgsUnitTypes::DistanceYards;
1607  // TODO - maybe more values to handle here?
1608  else
1609  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1610  return;
1611  }
1612  else
1613  {
1614  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1615  return;
1616  }
1617 
1618 #else
1619  char *unitName = nullptr;
1620 
1621  if ( OSRIsProjected( d->mCRS ) )
1622  {
1623  double toMeter = OSRGetLinearUnits( d->mCRS, &unitName );
1624  QString unit( unitName );
1625 
1626  // If the units parameter was created during the Fixup() call
1627  // above, the name of the units is likely to be 'unknown'. Try to
1628  // do better than that ... (but perhaps ogr should be enhanced to
1629  // do this instead?).
1630 
1631  static const double FEET_TO_METER = 0.3048;
1632  static const double SMALL_NUM = 1e-3;
1633 
1634  if ( std::fabs( toMeter - FEET_TO_METER ) < SMALL_NUM )
1635  unit = QStringLiteral( "Foot" );
1636 
1637  if ( qgsDoubleNear( toMeter, 1.0 ) ) //Unit name for meters would be "metre"
1638  d->mMapUnits = QgsUnitTypes::DistanceMeters;
1639  else if ( unit == QLatin1String( "Foot" ) )
1640  d->mMapUnits = QgsUnitTypes::DistanceFeet;
1641  else
1642  {
1643  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1644  }
1645  }
1646  else
1647  {
1648  OSRGetAngularUnits( d->mCRS, &unitName );
1649  QString unit( unitName );
1650  if ( unit == QLatin1String( "degree" ) )
1651  d->mMapUnits = QgsUnitTypes::DistanceDegrees;
1652  else
1653  {
1654  d->mMapUnits = QgsUnitTypes::DistanceUnknownUnit;
1655  }
1656  }
1657 #endif
1658 }
1659 
1660 
1662 {
1663  if ( d->mEllipsoidAcronym.isNull() || d->mProjectionAcronym.isNull()
1664  || !d->mIsValid )
1665  {
1666  QgsDebugMsgLevel( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1667  "work if prj acr ellipsoid acr and proj4string are set"
1668  " and the current projection is valid!", 4 );
1669  return 0;
1670  }
1671 
1672  sqlite3_database_unique_ptr database;
1673  sqlite3_statement_unique_ptr statement;
1674  int myResult;
1675 
1676  // Set up the query to retrieve the projection information
1677  // needed to populate the list
1678  QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1679  "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1680  .arg( QgsSqliteUtils::quotedString( d->mProjectionAcronym ),
1681  QgsSqliteUtils::quotedString( d->mEllipsoidAcronym ) );
1682  // Get the full path name to the sqlite3 spatial reference database.
1683  QString myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1684 
1685  //check the db is available
1686  myResult = openDatabase( myDatabaseFileName, database );
1687  if ( myResult != SQLITE_OK )
1688  {
1689  return 0;
1690  }
1691 
1692  statement = database.prepare( mySql, myResult );
1693  if ( myResult == SQLITE_OK )
1694  {
1695 
1696  while ( statement.step() == SQLITE_ROW )
1697  {
1698  QString mySrsId = statement.columnAsText( 0 );
1699  QString myProj4String = statement.columnAsText( 1 );
1700  if ( toProj4() == myProj4String.trimmed() )
1701  {
1702  return mySrsId.toLong();
1703  }
1704  }
1705  }
1706 
1707  //
1708  // Try the users db now
1709  //
1710 
1711  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1712  //check the db is available
1713  myResult = openDatabase( myDatabaseFileName, database );
1714  if ( myResult != SQLITE_OK )
1715  {
1716  return 0;
1717  }
1718 
1719  statement = database.prepare( mySql, myResult );
1720 
1721  if ( myResult == SQLITE_OK )
1722  {
1723  while ( statement.step() == SQLITE_ROW )
1724  {
1725  QString mySrsId = statement.columnAsText( 0 );
1726  QString myProj4String = statement.columnAsText( 1 );
1727  if ( toProj4() == myProj4String.trimmed() )
1728  {
1729  return mySrsId.toLong();
1730  }
1731  }
1732  }
1733 
1734  return 0;
1735 }
1736 
1738 {
1739  return ( !d->mIsValid && !srs.d->mIsValid ) ||
1740  ( d->mIsValid && srs.d->mIsValid && srs.authid() == authid() );
1741 }
1742 
1744 {
1745  return !( *this == srs );
1746 }
1747 
1748 QString QgsCoordinateReferenceSystem::toWkt( WktVariant variant, bool multiline, int indentationWidth ) const
1749 {
1750  if ( d->mWkt.isEmpty() )
1751  {
1752 #if PROJ_VERSION_MAJOR>=6
1753  if ( d->mPj )
1754  {
1755  PJ_WKT_TYPE type;
1756  switch ( variant )
1757  {
1758  case WKT1_GDAL:
1759  type = PJ_WKT1_GDAL;
1760  break;
1761  case WKT1_ESRI:
1762  type = PJ_WKT1_ESRI;
1763  break;
1764  case WKT2_2015:
1765  type = PJ_WKT2_2015;
1766  break;
1767  case WKT2_2015_SIMPLIFIED:
1768  type = PJ_WKT2_2015_SIMPLIFIED;
1769  break;
1770  case WKT2_2018:
1771  type = PJ_WKT2_2018;
1772  break;
1773  case WKT2_2018_SIMPLIFIED:
1774  type = PJ_WKT2_2018_SIMPLIFIED;
1775  break;
1776  }
1777 
1778  const QByteArray multiLineOption = QStringLiteral( "MULTILINE=%1" ).arg( multiline ? QStringLiteral( "YES" ) : QStringLiteral( "NO" ) ).toLocal8Bit();
1779  const QByteArray indentatationWidthOption = QStringLiteral( "INDENTATION_WIDTH=%1" ).arg( multiline ? QString::number( indentationWidth ) : QStringLiteral( "0" ) ).toLocal8Bit();
1780  const char *const options[] = {multiLineOption.constData(), indentatationWidthOption.constData(), nullptr};
1781  d->mWkt = QString( proj_as_wkt( QgsProjContext::get(), d->mPj.get(), type, options ) );
1782  }
1783 #else
1784  Q_UNUSED( variant )
1785  Q_UNUSED( multiline )
1786  Q_UNUSED( indentationWidth )
1787  char *wkt = nullptr;
1788  if ( OSRExportToWkt( d->mCRS, &wkt ) == OGRERR_NONE )
1789  {
1790  d->mWkt = wkt;
1791  CPLFree( wkt );
1792  }
1793 #endif
1794  }
1795  return d->mWkt;
1796 }
1797 
1798 bool QgsCoordinateReferenceSystem::readXml( const QDomNode &node )
1799 {
1800  d.detach();
1801  bool result = true;
1802  QDomNode srsNode = node.namedItem( QStringLiteral( "spatialrefsys" ) );
1803 
1804  if ( ! srsNode.isNull() )
1805  {
1806  bool initialized = false;
1807 
1808  bool ok = false;
1809  long srsid = srsNode.namedItem( QStringLiteral( "srsid" ) ).toElement().text().toLong( &ok );
1810 
1811  QDomNode myNode;
1812 
1813  if ( ok && srsid > 0 && srsid < USER_CRS_START_ID )
1814  {
1815  myNode = srsNode.namedItem( QStringLiteral( "authid" ) );
1816  if ( !myNode.isNull() )
1817  {
1818  operator=( QgsCoordinateReferenceSystem::fromOgcWmsCrs( myNode.toElement().text() ) );
1819  if ( isValid() )
1820  {
1821  initialized = true;
1822  }
1823  }
1824 
1825  if ( !initialized )
1826  {
1827  myNode = srsNode.namedItem( QStringLiteral( "epsg" ) );
1828  if ( !myNode.isNull() )
1829  {
1830  operator=( QgsCoordinateReferenceSystem::fromEpsgId( myNode.toElement().text().toLong() ) );
1831  if ( isValid() )
1832  {
1833  initialized = true;
1834  }
1835  }
1836  }
1837  }
1838 
1839  if ( !initialized )
1840  {
1841  myNode = srsNode.namedItem( QStringLiteral( "proj4" ) );
1842  const QString proj4 = myNode.toElement().text();
1843 
1844  if ( !createFromProj4( proj4 ) )
1845  {
1846  // Setting from elements one by one
1847  if ( !proj4.trimmed().isEmpty() )
1848  setProj4String( myNode.toElement().text() );
1849 
1850  myNode = srsNode.namedItem( QStringLiteral( "srsid" ) );
1851  setInternalId( myNode.toElement().text().toLong() );
1852 
1853  myNode = srsNode.namedItem( QStringLiteral( "srid" ) );
1854  setSrid( myNode.toElement().text().toLong() );
1855 
1856  myNode = srsNode.namedItem( QStringLiteral( "authid" ) );
1857  setAuthId( myNode.toElement().text() );
1858 
1859  myNode = srsNode.namedItem( QStringLiteral( "description" ) );
1860  setDescription( myNode.toElement().text() );
1861 
1862  myNode = srsNode.namedItem( QStringLiteral( "projectionacronym" ) );
1863  setProjectionAcronym( myNode.toElement().text() );
1864 
1865  myNode = srsNode.namedItem( QStringLiteral( "ellipsoidacronym" ) );
1866  setEllipsoidAcronym( myNode.toElement().text() );
1867 
1868  myNode = srsNode.namedItem( QStringLiteral( "geographicflag" ) );
1869  if ( myNode.toElement().text().compare( QLatin1String( "true" ) ) )
1870  {
1871  setGeographicFlag( true );
1872  }
1873  else
1874  {
1875  setGeographicFlag( false );
1876  }
1877 
1878  //make sure the map units have been set
1879  setMapUnits();
1880  }
1881  //TODO: createFromProj4 used to save to the user database any new CRS
1882  // this behavior was changed in order to separate creation and saving.
1883  // Not sure if it necessary to save it here, should be checked by someone
1884  // familiar with the code (should also give a more descriptive name to the generated CRS)
1885  if ( isValid() && d->mSrsId == 0 )
1886  {
1887  QString myName = QStringLiteral( " * %1 (%2)" )
1888  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
1889  toProj4() );
1890  saveAsUserCrs( myName );
1891  }
1892 
1893  }
1894  }
1895  else
1896  {
1897  // Return empty CRS if none was found in the XML.
1898  d = new QgsCoordinateReferenceSystemPrivate();
1899  result = false;
1900  }
1901  return result;
1902 }
1903 
1904 bool QgsCoordinateReferenceSystem::writeXml( QDomNode &node, QDomDocument &doc ) const
1905 {
1906 
1907  QDomElement myLayerNode = node.toElement();
1908  QDomElement mySrsElement = doc.createElement( QStringLiteral( "spatialrefsys" ) );
1909 
1910  QDomElement myProj4Element = doc.createElement( QStringLiteral( "proj4" ) );
1911  myProj4Element.appendChild( doc.createTextNode( toProj4() ) );
1912  mySrsElement.appendChild( myProj4Element );
1913 
1914  QDomElement mySrsIdElement = doc.createElement( QStringLiteral( "srsid" ) );
1915  mySrsIdElement.appendChild( doc.createTextNode( QString::number( srsid() ) ) );
1916  mySrsElement.appendChild( mySrsIdElement );
1917 
1918  QDomElement mySridElement = doc.createElement( QStringLiteral( "srid" ) );
1919  mySridElement.appendChild( doc.createTextNode( QString::number( postgisSrid() ) ) );
1920  mySrsElement.appendChild( mySridElement );
1921 
1922  QDomElement myEpsgElement = doc.createElement( QStringLiteral( "authid" ) );
1923  myEpsgElement.appendChild( doc.createTextNode( authid() ) );
1924  mySrsElement.appendChild( myEpsgElement );
1925 
1926  QDomElement myDescriptionElement = doc.createElement( QStringLiteral( "description" ) );
1927  myDescriptionElement.appendChild( doc.createTextNode( description() ) );
1928  mySrsElement.appendChild( myDescriptionElement );
1929 
1930  QDomElement myProjectionAcronymElement = doc.createElement( QStringLiteral( "projectionacronym" ) );
1931  myProjectionAcronymElement.appendChild( doc.createTextNode( projectionAcronym() ) );
1932  mySrsElement.appendChild( myProjectionAcronymElement );
1933 
1934  QDomElement myEllipsoidAcronymElement = doc.createElement( QStringLiteral( "ellipsoidacronym" ) );
1935  myEllipsoidAcronymElement.appendChild( doc.createTextNode( ellipsoidAcronym() ) );
1936  mySrsElement.appendChild( myEllipsoidAcronymElement );
1937 
1938  QDomElement myGeographicFlagElement = doc.createElement( QStringLiteral( "geographicflag" ) );
1939  QString myGeoFlagText = QStringLiteral( "false" );
1940  if ( isGeographic() )
1941  {
1942  myGeoFlagText = QStringLiteral( "true" );
1943  }
1944 
1945  myGeographicFlagElement.appendChild( doc.createTextNode( myGeoFlagText ) );
1946  mySrsElement.appendChild( myGeographicFlagElement );
1947 
1948  myLayerNode.appendChild( mySrsElement );
1949 
1950  return true;
1951 }
1952 
1953 
1954 
1955 //
1956 // Static helper methods below this point only please!
1957 //
1958 
1959 
1960 // Returns the whole proj4 string for the selected srsid
1961 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
1962 QString QgsCoordinateReferenceSystem::proj4FromSrsId( const int srsId )
1963 {
1964  QString myDatabaseFileName;
1965  QString myProjString;
1966  QString mySql = QStringLiteral( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( srsId );
1967 
1968  //
1969  // Determine if this is a user projection or a system on
1970  // user projection defs all have srs_id >= 100000
1971  //
1972  if ( srsId >= USER_CRS_START_ID )
1973  {
1974  myDatabaseFileName = QgsApplication::qgisUserDatabaseFilePath();
1975  QFileInfo myFileInfo;
1976  myFileInfo.setFile( myDatabaseFileName );
1977  if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
1978  {
1979  QgsDebugMsg( QStringLiteral( "users qgis.db not found" ) );
1980  return QString();
1981  }
1982  }
1983  else //must be a system projection then
1984  {
1985  myDatabaseFileName = QgsApplication::srsDatabaseFilePath();
1986  }
1987 
1988  sqlite3_database_unique_ptr database;
1989  sqlite3_statement_unique_ptr statement;
1990 
1991  int rc;
1992  rc = openDatabase( myDatabaseFileName, database );
1993  if ( rc )
1994  {
1995  return QString();
1996  }
1997 
1998  statement = database.prepare( mySql, rc );
1999 
2000  if ( rc == SQLITE_OK )
2001  {
2002  if ( statement.step() == SQLITE_ROW )
2003  {
2004  myProjString = statement.columnAsText( 0 );
2005  }
2006  }
2007 
2008  return myProjString;
2009 }
2010 
2011 int QgsCoordinateReferenceSystem::openDatabase( const QString &path, sqlite3_database_unique_ptr &database, bool readonly )
2012 {
2013  int myResult;
2014  if ( readonly )
2015  myResult = database.open_v2( path, SQLITE_OPEN_READONLY, nullptr );
2016  else
2017  myResult = database.open( path );
2018 
2019  if ( myResult != SQLITE_OK )
2020  {
2021  QgsDebugMsg( "Can't open database: " + database.errorMessage() );
2022  // XXX This will likely never happen since on open, sqlite creates the
2023  // database if it does not exist.
2024  // ... unfortunately it happens on Windows
2025  QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
2026  .arg( path )
2027  .arg( myResult )
2028  .arg( database.errorMessage() ), QObject::tr( "CRS" ) );
2029  }
2030  return myResult;
2031 }
2032 
2034 {
2035  sCustomSrsValidation = f;
2036 }
2037 
2039 {
2040  return sCustomSrsValidation;
2041 }
2042 
2043 void QgsCoordinateReferenceSystem::debugPrint()
2044 {
2045  QgsDebugMsg( QStringLiteral( "***SpatialRefSystem***" ) );
2046  QgsDebugMsg( "* Valid : " + ( d->mIsValid ? QString( "true" ) : QString( "false" ) ) );
2047  QgsDebugMsg( "* SrsId : " + QString::number( d->mSrsId ) );
2048  QgsDebugMsg( "* Proj4 : " + toProj4() );
2049  QgsDebugMsg( "* WKT : " + toWkt() );
2050  QgsDebugMsg( "* Desc. : " + d->mDescription );
2052  {
2053  QgsDebugMsg( QStringLiteral( "* Units : meters" ) );
2054  }
2055  else if ( mapUnits() == QgsUnitTypes::DistanceFeet )
2056  {
2057  QgsDebugMsg( QStringLiteral( "* Units : feet" ) );
2058  }
2059  else if ( mapUnits() == QgsUnitTypes::DistanceDegrees )
2060  {
2061  QgsDebugMsg( QStringLiteral( "* Units : degrees" ) );
2062  }
2063 }
2064 
2066 {
2067  d.detach();
2068  d->mValidationHint = html;
2069 }
2070 
2072 {
2073  return d->mValidationHint;
2074 }
2075 
2078 
2080 {
2081  if ( !d->mIsValid )
2082  {
2083  QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
2084  return -1;
2085  }
2086 
2087  QString mySql;
2088 
2089  QString proj4String = d->mProj4;
2090  if ( proj4String.isEmpty() )
2091  {
2092  proj4String = toProj4();
2093  }
2094 
2095  // ellipsoid acroynym column is incorrect marked as not null in many crs database instances,
2096  // hack around this by using an empty string instead
2097  const QString quotedEllipsoidString = ellipsoidAcronym().isNull() ? "''" : QgsSqliteUtils::quotedString( ellipsoidAcronym() );
2098 
2099  //if this is the first record we need to ensure that its srs_id is 10000. For
2100  //any rec after that sqlite3 will take care of the autonumbering
2101  //this was done to support sqlite 3.0 as it does not yet support
2102  //the autoinc related system tables.
2103  if ( getRecordCount() == 0 )
2104  {
2105  mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
2106  + QString::number( USER_CRS_START_ID )
2107  + ',' + QgsSqliteUtils::quotedString( name )
2109  + ',' + quotedEllipsoidString
2111  + ",0)"; // <-- is_geo shamelessly hard coded for now
2112  }
2113  else
2114  {
2115  mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
2118  + ',' + quotedEllipsoidString
2120  + ",0)"; // <-- is_geo shamelessly hard coded for now
2121  }
2122  sqlite3_database_unique_ptr database;
2123  sqlite3_statement_unique_ptr statement;
2124  int myResult;
2125  //check the db is available
2126  myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
2127  if ( myResult != SQLITE_OK )
2128  {
2129  QgsDebugMsg( QStringLiteral( "Can't open or create database %1: %2" )
2131  database.errorMessage() ) );
2132  return false;
2133  }
2134  statement = database.prepare( mySql, myResult );
2135 
2136  qint64 returnId;
2137  if ( myResult == SQLITE_OK && statement.step() == SQLITE_DONE )
2138  {
2139  QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( toProj4() ), QObject::tr( "CRS" ) );
2140 
2141  returnId = sqlite3_last_insert_rowid( database.get() );
2142  setInternalId( returnId );
2143  if ( authid().isEmpty() )
2144  setAuthId( QStringLiteral( "USER:%1" ).arg( returnId ) );
2145  setDescription( name );
2146 
2147  //We add the just created user CRS to the list of recently used CRS
2148  QgsSettings settings;
2149  //QStringList recentProjections = settings.value( "/UI/recentProjections" ).toStringList();
2150  QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
2151  QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
2152  //recentProjections.append();
2153  //settings.setValue( "/UI/recentProjections", recentProjections );
2154  projectionsProj4.append( toProj4() );
2155  projectionsAuthId.append( authid() );
2156  settings.setValue( QStringLiteral( "UI/recentProjectionsProj4" ), projectionsProj4 );
2157  settings.setValue( QStringLiteral( "UI/recentProjectionsAuthId" ), projectionsAuthId );
2158 
2159  }
2160  else
2161  returnId = -1;
2162 
2163  invalidateCache();
2164  return returnId;
2165 }
2166 
2167 long QgsCoordinateReferenceSystem::getRecordCount()
2168 {
2169  sqlite3_database_unique_ptr database;
2170  sqlite3_statement_unique_ptr statement;
2171  int myResult;
2172  long myRecordCount = 0;
2173  //check the db is available
2174  myResult = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
2175  if ( myResult != SQLITE_OK )
2176  {
2177  QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
2178  return 0;
2179  }
2180  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
2181  QString mySql = QStringLiteral( "select count(*) from tbl_srs" );
2182  statement = database.prepare( mySql, myResult );
2183  if ( myResult == SQLITE_OK )
2184  {
2185  if ( statement.step() == SQLITE_ROW )
2186  {
2187  QString myRecordCountString = statement.columnAsText( 0 );
2188  myRecordCount = myRecordCountString.toLong();
2189  }
2190  }
2191  return myRecordCount;
2192 }
2193 
2194 #if PROJ_VERSION_MAJOR>=6
2195 bool testIsGeographic( PJ *crs )
2196 {
2197  PJ_CONTEXT *pjContext = QgsProjContext::get();
2198  bool isGeographic = false;
2199  QgsProjUtils::proj_pj_unique_ptr coordinateSystem( proj_crs_get_coordinate_system( pjContext, crs ) );
2200  if ( coordinateSystem )
2201  {
2202  const int axisCount = proj_cs_get_axis_count( pjContext, coordinateSystem.get() );
2203  if ( axisCount > 0 )
2204  {
2205  const char *outUnitAuthName = nullptr;
2206  const char *outUnitAuthCode = nullptr;
2207  // Read only first axis
2208  proj_cs_get_axis_info( pjContext, coordinateSystem.get(), 0,
2209  nullptr,
2210  nullptr,
2211  nullptr,
2212  nullptr,
2213  nullptr,
2214  &outUnitAuthName,
2215  &outUnitAuthCode );
2216 
2217  if ( outUnitAuthName && outUnitAuthCode )
2218  {
2219  const char *unitCategory = nullptr;
2220  if ( proj_uom_get_info_from_database( pjContext, outUnitAuthName, outUnitAuthCode, nullptr, nullptr, &unitCategory ) )
2221  {
2222  isGeographic = QString( unitCategory ).compare( QLatin1String( "angular" ), Qt::CaseInsensitive ) == 0;
2223  }
2224  }
2225  }
2226  }
2227  return isGeographic;
2228 }
2229 
2230 void getOperationAndEllipsoidFromProjString( const QString &proj, QString &operation, QString &ellipsoid )
2231 {
2232  QRegExp projRegExp( "\\+proj=(\\S+)" );
2233  if ( projRegExp.indexIn( proj ) < 0 )
2234  {
2235  QgsDebugMsgLevel( QStringLiteral( "no +proj argument found [%2]" ).arg( proj ), 2 );
2236  return;
2237  }
2238  operation = projRegExp.cap( 1 );
2239 
2240  QRegExp ellipseRegExp( "\\+(?:ellps|datum)=(\\S+)" );
2241  QString ellps;
2242  if ( ellipseRegExp.indexIn( proj ) >= 0 )
2243  {
2244  ellipsoid = ellipseRegExp.cap( 1 );
2245  }
2246  else
2247  {
2248  // satisfy not null constraint on ellipsoid_acronym field
2249  // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2250  // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2251  // set for these CRSes). Better just hack around and make the constraint happy for now,
2252  // and hope that the definitions get corrected in future.
2253  ellipsoid = "";
2254  }
2255 }
2256 
2257 
2258 bool QgsCoordinateReferenceSystem::loadFromAuthCode( const QString &auth, const QString &code )
2259 {
2260  d.detach();
2261  d->mIsValid = false;
2262  d->mWkt.clear();
2263 
2264  PJ_CONTEXT *pjContext = QgsProjContext::get();
2265  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, auth.toUtf8().constData(), code.toUtf8().constData(), PJ_CATEGORY_CRS, false, nullptr ) );
2266  if ( !crs )
2267  {
2268  return false;
2269  }
2270 
2271  switch ( proj_get_type( crs.get() ) )
2272  {
2273  case PJ_TYPE_VERTICAL_CRS:
2274  return false;
2275 
2276  default:
2277  break;
2278  }
2279 
2280  crs = QgsProjUtils::crsToSingleCrs( crs.get() );
2281 
2282  QString proj4 = getFullProjString( crs.get() );
2283  proj4.replace( QStringLiteral( "+type=crs" ), QString() );
2284  proj4 = proj4.trimmed();
2285 
2286  d->mIsValid = true;
2287  d->mProj4 = proj4;
2288  d->mDescription = QString( proj_get_name( crs.get() ) );
2289  d->mAuthId = QStringLiteral( "%1:%2" ).arg( auth, code );
2290  d->mIsGeographic = testIsGeographic( crs.get() );
2291  d->mAxisInvertedDirty = true;
2292  QString operation;
2293  QString ellipsoid;
2294  getOperationAndEllipsoidFromProjString( proj4, operation, ellipsoid );
2295  d->mProjectionAcronym = operation;
2296  d->mEllipsoidAcronym = ellipsoid;
2297  d->mPj = std::move( crs );
2298 
2299  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( auth, code ).toUpper() );
2300  QString srsId;
2301  QString srId;
2302  if ( !dbVals.isEmpty() )
2303  {
2304  const QStringList parts = dbVals.split( ',' );
2305  d->mSrsId = parts.at( 0 ).toInt();
2306  d->mSRID = parts.at( 1 ).toInt();
2307  }
2308 
2309  setMapUnits();
2310 
2311  return true;
2312 }
2313 #endif
2314 
2315 #if PROJ_VERSION_MAJOR<6
2316 // adapted from gdal/ogr/ogr_srs_dict.cpp
2317 bool QgsCoordinateReferenceSystem::loadWkts( QHash<int, QString> &wkts, const char *filename )
2318 {
2319  QgsDebugMsgLevel( QStringLiteral( "Loading %1" ).arg( filename ), 4 );
2320  const char *pszFilename = CPLFindFile( "gdal", filename );
2321  if ( !pszFilename )
2322  return false;
2323 
2324  QFile csv( pszFilename );
2325  if ( !csv.open( QIODevice::ReadOnly ) )
2326  return false;
2327 
2328  QTextStream lines( &csv );
2329 
2330  for ( ;; )
2331  {
2332  QString line = lines.readLine();
2333  if ( line.isNull() )
2334  break;
2335 
2336  if ( line.trimmed().isEmpty() || line.startsWith( '#' ) )
2337  {
2338  continue;
2339  }
2340  else if ( line.startsWith( QLatin1String( "include " ) ) )
2341  {
2342  if ( !loadWkts( wkts, line.mid( 8 ).toUtf8() ) )
2343  break;
2344  }
2345  else
2346  {
2347  int pos = line.indexOf( ',' );
2348  if ( pos < 0 )
2349  return false;
2350 
2351  bool ok;
2352  int epsg = line.leftRef( pos ).toInt( &ok );
2353  if ( !ok )
2354  return false;
2355 
2356  wkts.insert( epsg, line.mid( pos + 1 ) );
2357  }
2358  }
2359 
2360  csv.close();
2361 
2362  return true;
2363 }
2364 
2365 bool QgsCoordinateReferenceSystem::loadIds( QHash<int, QString> &wkts )
2366 {
2367  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
2368 
2369  static const QStringList csvs { QStringList() << QStringLiteral( "gcs.csv" ) << QStringLiteral( "pcs.csv" ) << QStringLiteral( "vertcs.csv" ) << QStringLiteral( "compdcs.csv" ) << QStringLiteral( "geoccs.csv" ) };
2370  for ( const QString &csv : csvs )
2371  {
2372  QString filename = CPLFindFile( "gdal", csv.toUtf8() );
2373 
2374  QFile f( filename );
2375  if ( !f.open( QIODevice::ReadOnly ) )
2376  continue;
2377 
2378  QTextStream lines( &f );
2379  int l = 0, n = 0;
2380 
2381  lines.readLine();
2382  for ( ;; )
2383  {
2384  l++;
2385  QString line = lines.readLine();
2386  if ( line.isNull() )
2387  break;
2388 
2389  if ( line.trimmed().isEmpty() )
2390  continue;
2391 
2392  int pos = line.indexOf( ',' );
2393  if ( pos < 0 )
2394  {
2395  qWarning( "No id found in: %s", qPrintable( line ) );
2396  continue;
2397  }
2398 
2399  bool ok;
2400  int epsg = line.leftRef( pos ).toInt( &ok );
2401  if ( !ok )
2402  {
2403  qWarning( "No valid id found in: %s", qPrintable( line ) );
2404  continue;
2405  }
2406 
2407  // some CRS are known to fail (see http://trac.osgeo.org/gdal/ticket/2900)
2408  if ( epsg == 2218 || epsg == 2221 || epsg == 2296 || epsg == 2297 || epsg == 2298 || epsg == 2299 || epsg == 2300 || epsg == 2301 || epsg == 2302 ||
2409  epsg == 2303 || epsg == 2304 || epsg == 2305 || epsg == 2306 || epsg == 2307 || epsg == 2963 || epsg == 2985 || epsg == 2986 || epsg == 3052 ||
2410  epsg == 3053 || epsg == 3139 || epsg == 3144 || epsg == 3145 || epsg == 3173 || epsg == 3295 || epsg == 3993 || epsg == 4087 || epsg == 4088 ||
2411  epsg == 5017 || epsg == 5221 || epsg == 5224 || epsg == 5225 || epsg == 5514 || epsg == 5515 || epsg == 5516 || epsg == 5819 || epsg == 5820 ||
2412  epsg == 5821 || epsg == 6200 || epsg == 6201 || epsg == 6202 || epsg == 6244 || epsg == 6245 || epsg == 6246 || epsg == 6247 || epsg == 6248 ||
2413  epsg == 6249 || epsg == 6250 || epsg == 6251 || epsg == 6252 || epsg == 6253 || epsg == 6254 || epsg == 6255 || epsg == 6256 || epsg == 6257 ||
2414  epsg == 6258 || epsg == 6259 || epsg == 6260 || epsg == 6261 || epsg == 6262 || epsg == 6263 || epsg == 6264 || epsg == 6265 || epsg == 6266 ||
2415  epsg == 6267 || epsg == 6268 || epsg == 6269 || epsg == 6270 || epsg == 6271 || epsg == 6272 || epsg == 6273 || epsg == 6274 || epsg == 6275 ||
2416  epsg == 6966 || epsg == 7082 || epsg == 32600 || epsg == 32663 || epsg == 32700 )
2417  continue;
2418 
2419  if ( OSRImportFromEPSG( crs, epsg ) != OGRERR_NONE )
2420  {
2421  qDebug( "EPSG %d: not imported", epsg );
2422  continue;
2423  }
2424 
2425  char *wkt = nullptr;
2426  if ( OSRExportToWkt( crs, &wkt ) != OGRERR_NONE )
2427  {
2428  qWarning( "EPSG %d: not exported to WKT", epsg );
2429  continue;
2430  }
2431 
2432  wkts.insert( epsg, wkt );
2433  n++;
2434 
2435  CPLFree( wkt );
2436  }
2437 
2438  f.close();
2439 
2440  QgsDebugMsgLevel( QStringLiteral( "Loaded %1/%2 from %3" ).arg( QString::number( n ), QString::number( l ), filename.toUtf8().constData() ), 4 );
2441  }
2442 
2443  OSRDestroySpatialReference( crs );
2444 
2445  return true;
2446 }
2447 #endif
2448 
2449 #if PROJ_VERSION_MAJOR>=6
2450 static void sync_db_proj_logger( void * /* user_data */, int level, const char *message )
2451 {
2452 #ifndef QGISDEBUG
2453  Q_UNUSED( message )
2454 #endif
2455  if ( level == PJ_LOG_ERROR )
2456  {
2457  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 2 );
2458  }
2459  else if ( level == PJ_LOG_DEBUG )
2460  {
2461  QgsDebugMsgLevel( QStringLiteral( "PROJ: %1" ).arg( message ), 3 );
2462  }
2463 }
2464 #endif
2465 
2467 {
2468  setlocale( LC_ALL, "C" );
2469  QString dbFilePath = QgsApplication::srsDatabaseFilePath();
2470 
2471 #if PROJ_VERSION_MAJOR<6
2472  syncDatumTransform( dbFilePath );
2473 #endif
2474 
2475  int inserted = 0, updated = 0, deleted = 0, errors = 0;
2476 
2477  QgsDebugMsgLevel( QStringLiteral( "Load srs db from: %1" ).arg( QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData() ), 4 );
2478 
2479  sqlite3_database_unique_ptr database;
2480  if ( database.open( dbFilePath ) != SQLITE_OK )
2481  {
2482  QgsDebugMsg( QStringLiteral( "Could not open database: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2483  return -1;
2484  }
2485 
2486  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2487  {
2488  QgsDebugMsg( QStringLiteral( "Could not begin transaction: %1 (%2)\n" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
2489  return -1;
2490  }
2491 
2492 #if PROJ_VERSION_MAJOR<6
2493 // fix up database, if not done already //
2494  if ( sqlite3_exec( database.get(), "alter table tbl_srs add noupdate boolean", nullptr, nullptr, nullptr ) == SQLITE_OK )
2495  ( 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 );
2496 
2497  ( 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 );
2498 #endif
2499 
2500  sqlite3_statement_unique_ptr statement;
2501  int result;
2502  char *errMsg = nullptr;
2503 
2504 #if PROJ_VERSION_MAJOR>=6
2505  PJ_CONTEXT *pjContext = QgsProjContext::get();
2506  // silence proj warnings
2507  proj_log_func( pjContext, nullptr, sync_db_proj_logger );
2508 
2509  PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
2510 
2511  int nextSrsId = 60000;
2512  int nextSrId = 520000000;
2513  for ( auto authIter = authorities; authIter && *authIter; ++authIter )
2514  {
2515  const QString authority( *authIter );
2516  QgsDebugMsgLevel( QStringLiteral( "Loading authority '%1'" ).arg( authority ), 2 );
2517  PROJ_STRING_LIST codes = proj_get_codes_from_database( pjContext, *authIter, PJ_TYPE_CRS, true );
2518 
2519  QStringList allCodes;
2520 
2521  for ( auto codesIter = codes; codesIter && *codesIter; ++codesIter )
2522  {
2523  const QString code( *codesIter );
2524  allCodes << QgsSqliteUtils::quotedString( code );
2525  QgsDebugMsgLevel( QStringLiteral( "Loading code '%1'" ).arg( code ), 4 );
2526  QgsProjUtils::proj_pj_unique_ptr crs( proj_create_from_database( pjContext, *authIter, *codesIter, PJ_CATEGORY_CRS, false, nullptr ) );
2527  if ( !crs )
2528  {
2529  QgsDebugMsg( QStringLiteral( "Could not load '%1:%2'" ).arg( authority, code ) );
2530  continue;
2531  }
2532 
2533  switch ( proj_get_type( crs.get() ) )
2534  {
2535  case PJ_TYPE_VERTICAL_CRS: // don't need these in the CRS db
2536  continue;
2537 
2538  default:
2539  break;
2540  }
2541 
2542  crs = QgsProjUtils::crsToSingleCrs( crs.get() );
2543 
2544  QString proj4 = getFullProjString( crs.get() );
2545  proj4.replace( QStringLiteral( "+type=crs" ), QString() );
2546  proj4 = proj4.trimmed();
2547 
2548  if ( proj4.isEmpty() )
2549  {
2550  QgsDebugMsgLevel( QStringLiteral( "No proj4 for '%1:%2'" ).arg( authority, code ), 2 );
2551  // satisfy not null constraint
2552  proj4 = "";
2553  }
2554 
2555  const bool deprecated = proj_is_deprecated( crs.get() );
2556  const QString name( proj_get_name( crs.get() ) );
2557 
2558  QString sql = QStringLiteral( "SELECT parameters,description,deprecated FROM tbl_srs WHERE auth_name='%1' AND auth_id='%2'" ).arg( authority, code );
2559  statement = database.prepare( sql, result );
2560  if ( result != SQLITE_OK )
2561  {
2562  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2563  continue;
2564  }
2565 
2566  QString srsProj4;
2567  QString srsDesc;
2568  bool srsDeprecated = deprecated;
2569  if ( statement.step() == SQLITE_ROW )
2570  {
2571  srsProj4 = statement.columnAsText( 0 );
2572  srsDesc = statement.columnAsText( 1 );
2573  srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2574  }
2575 
2576  if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
2577  {
2578  if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
2579  {
2580  errMsg = nullptr;
2581  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name=%4 AND auth_id=%5" )
2582  .arg( QgsSqliteUtils::quotedString( proj4 ) )
2583  .arg( QgsSqliteUtils::quotedString( name ) )
2584  .arg( deprecated ? 1 : 0 )
2585  .arg( QgsSqliteUtils::quotedString( authority ), QgsSqliteUtils::quotedString( code ) );
2586 
2587  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2588  {
2589  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2590  sql,
2591  database.errorMessage(),
2592  errMsg ? errMsg : "(unknown error)" ) );
2593  if ( errMsg )
2594  sqlite3_free( errMsg );
2595  errors++;
2596  }
2597  else
2598  {
2599  updated++;
2600  }
2601  }
2602  }
2603  else
2604  {
2605  // there's a not-null contraint on these columns, so we must use empty strings instead
2606  QString operation = "";
2607  QString ellps = "";
2608  getOperationAndEllipsoidFromProjString( proj4, operation, ellps );
2609  const bool isGeographic = testIsGeographic( crs.get() );
2610 
2611  // work out srid and srsid
2612  const QString dbVals = sAuthIdToQgisSrsIdMap.value( QStringLiteral( "%1:%2" ).arg( authority, code ) );
2613  QString srsId;
2614  QString srId;
2615  if ( !dbVals.isEmpty() )
2616  {
2617  const QStringList parts = dbVals.split( ',' );
2618  srsId = parts.at( 0 );
2619  srId = parts.at( 1 );
2620  }
2621  if ( srId.isEmpty() )
2622  {
2623  srId = QString::number( nextSrId );
2624  nextSrId++;
2625  }
2626  if ( srsId.isEmpty() )
2627  {
2628  srsId = QString::number( nextSrsId );
2629  nextSrsId++;
2630  }
2631 
2632  if ( !srsId.isEmpty() )
2633  {
2634  sql = QStringLiteral( "INSERT INTO tbl_srs(srs_id, description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1, %2,%3,%4,%5,%6,%7,%8,%9,%10)" )
2635  .arg( srsId )
2636  .arg( QgsSqliteUtils::quotedString( name ),
2637  QgsSqliteUtils::quotedString( operation ),
2638  QgsSqliteUtils::quotedString( ellps ),
2639  QgsSqliteUtils::quotedString( proj4 ) )
2640  .arg( srId )
2641  .arg( QgsSqliteUtils::quotedString( authority ) )
2642  .arg( QgsSqliteUtils::quotedString( code ) )
2643  .arg( isGeographic ? 1 : 0 )
2644  .arg( deprecated ? 1 : 0 );
2645  }
2646  else
2647  {
2648  sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%2,%3,%4,%5,%6,%7,%8,%9,%10)" )
2649  .arg( QgsSqliteUtils::quotedString( name ),
2650  QgsSqliteUtils::quotedString( operation ),
2651  QgsSqliteUtils::quotedString( ellps ),
2652  QgsSqliteUtils::quotedString( proj4 ) )
2653  .arg( srId )
2654  .arg( QgsSqliteUtils::quotedString( authority ) )
2655  .arg( QgsSqliteUtils::quotedString( code ) )
2656  .arg( isGeographic ? 1 : 0 )
2657  .arg( deprecated ? 1 : 0 );
2658  }
2659 
2660  errMsg = nullptr;
2661  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2662  {
2663  inserted++;
2664  }
2665  else
2666  {
2667  qCritical( "Could not execute: %s [%s/%s]\n",
2668  sql.toLocal8Bit().constData(),
2669  sqlite3_errmsg( database.get() ),
2670  errMsg ? errMsg : "(unknown error)" );
2671  errors++;
2672 
2673  if ( errMsg )
2674  sqlite3_free( errMsg );
2675  }
2676  }
2677  }
2678 
2679  proj_string_list_destroy( codes );
2680 
2681  const QString sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='%1' AND NOT auth_id IN (%2)" ).arg( authority, allCodes.join( ',' ) );
2682  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2683  {
2684  deleted = sqlite3_changes( database.get() );
2685  }
2686  else
2687  {
2688  errors++;
2689  qCritical( "Could not execute: %s [%s]\n",
2690  sql.toLocal8Bit().constData(),
2691  sqlite3_errmsg( database.get() ) );
2692  }
2693 
2694  }
2695  proj_string_list_destroy( authorities );
2696 
2697 #else
2698 
2699  OGRSpatialReferenceH crs = nullptr;
2700 
2701  QString proj4;
2702  QString sql;
2703  QHash<int, QString> wkts;
2704  loadIds( wkts );
2705  loadWkts( wkts, "epsg.wkt" );
2706 
2707  QgsDebugMsgLevel( QStringLiteral( "%1 WKTs loaded" ).arg( wkts.count() ), 4 );
2708 
2709  for ( QHash<int, QString>::const_iterator it = wkts.constBegin(); it != wkts.constEnd(); ++it )
2710  {
2711  QByteArray ba( it.value().toUtf8() );
2712  char *psz = ba.data();
2713 
2714  if ( crs )
2715  OSRDestroySpatialReference( crs );
2716  crs = nullptr;
2717  crs = OSRNewSpatialReference( nullptr );
2718 
2719  OGRErr ogrErr = OSRImportFromWkt( crs, &psz );
2720  if ( ogrErr != OGRERR_NONE )
2721  continue;
2722 
2723  if ( OSRExportToProj4( crs, &psz ) != OGRERR_NONE )
2724  {
2725  CPLFree( psz );
2726  continue;
2727  }
2728 
2729  proj4 = psz;
2730  proj4 = proj4.trimmed();
2731 
2732  CPLFree( psz );
2733 
2734  if ( proj4.isEmpty() )
2735  continue;
2736 
2737  QString name( OSRIsGeographic( crs ) ? OSRGetAttrValue( crs, "GEOGCS", 0 ) :
2738  OSRIsGeocentric( crs ) ? OSRGetAttrValue( crs, "GEOCCS", 0 ) :
2739  OSRGetAttrValue( crs, "PROJCS", 0 ) );
2740  if ( name.isEmpty() )
2741  name = QObject::tr( "Imported from GDAL" );
2742 
2743  bool deprecated = name.contains( QLatin1Literal( "(deprecated)" ) );
2744 
2745  sql = QStringLiteral( "SELECT parameters,description,deprecated,noupdate FROM tbl_srs WHERE auth_name='EPSG' AND auth_id='%1'" ).arg( it.key() );
2746  statement = database.prepare( sql, result );
2747  if ( result != SQLITE_OK )
2748  {
2749  QgsDebugMsg( QStringLiteral( "Could not prepare: %1 [%2]\n" ).arg( sql, database.errorMessage() ) );
2750  continue;
2751  }
2752 
2753  QString srsProj4;
2754  QString srsDesc;
2755  bool srsDeprecated = deprecated;
2756  if ( statement.step() == SQLITE_ROW )
2757  {
2758  srsProj4 = statement.columnAsText( 0 );
2759  srsDesc = statement.columnAsText( 1 );
2760  srsDeprecated = statement.columnAsText( 2 ).toInt() != 0;
2761 
2762  if ( statement.columnAsText( 3 ).toInt() != 0 )
2763  {
2764  continue;
2765  }
2766  }
2767 
2768  if ( !srsProj4.isEmpty() || !srsDesc.isEmpty() )
2769  {
2770  if ( proj4 != srsProj4 || name != srsDesc || deprecated != srsDeprecated )
2771  {
2772  errMsg = nullptr;
2773  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1,description=%2,deprecated=%3 WHERE auth_name='EPSG' AND auth_id=%4" )
2774  .arg( QgsSqliteUtils::quotedString( proj4 ) )
2775  .arg( QgsSqliteUtils::quotedString( name ) )
2776  .arg( deprecated ? 1 : 0 )
2777  .arg( it.key() );
2778 
2779  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
2780  {
2781  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2/%3]\n" ).arg(
2782  sql,
2783  database.errorMessage(),
2784  errMsg ? errMsg : "(unknown error)" ) );
2785  if ( errMsg )
2786  sqlite3_free( errMsg );
2787  errors++;
2788  }
2789  else
2790  {
2791  updated++;
2792  }
2793  }
2794  }
2795  else
2796  {
2797  QRegExp projRegExp( "\\+proj=(\\S+)" );
2798  if ( projRegExp.indexIn( proj4 ) < 0 )
2799  {
2800  QgsDebugMsgLevel( QStringLiteral( "EPSG %1: no +proj argument found [%2]" ).arg( it.key() ).arg( proj4 ), 4 );
2801  continue;
2802  }
2803 
2804  QRegExp ellipseRegExp( "\\+ellps=(\\S+)" );
2805  QString ellps;
2806  if ( ellipseRegExp.indexIn( proj4 ) >= 0 )
2807  {
2808  ellps = ellipseRegExp.cap( 1 );
2809  }
2810  else
2811  {
2812  // satisfy not null constraint on ellipsoid_acronym field
2813  // possibly we should drop the constraint, yet the crses with missing ellipsoid_acronym are malformed
2814  // and will result in oddities within other areas of QGIS (e.g. project ellipsoid won't be correctly
2815  // set for these CRSes). Better just hack around and make the constraint happy for now,
2816  // and hope that the definitions get corrected in future.
2817  ellps = "";
2818  }
2819 
2820  sql = QStringLiteral( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1,%2,%3,%4,%5,'EPSG',%5,%6,%7)" )
2821  .arg( QgsSqliteUtils::quotedString( name ),
2822  QgsSqliteUtils::quotedString( projRegExp.cap( 1 ) ),
2824  QgsSqliteUtils::quotedString( proj4 ) )
2825  .arg( it.key() )
2826  .arg( OSRIsGeographic( crs ) )
2827  .arg( deprecated ? 1 : 0 );
2828 
2829  errMsg = nullptr;
2830  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2831  {
2832  inserted++;
2833  }
2834  else
2835  {
2836  qCritical( "Could not execute: %s [%s/%s]\n",
2837  sql.toLocal8Bit().constData(),
2838  sqlite3_errmsg( database.get() ),
2839  errMsg ? errMsg : "(unknown error)" );
2840  errors++;
2841 
2842  if ( errMsg )
2843  sqlite3_free( errMsg );
2844  }
2845  }
2846  }
2847 
2848  if ( crs )
2849  OSRDestroySpatialReference( crs );
2850  crs = nullptr;
2851 
2852  sql = QStringLiteral( "DELETE FROM tbl_srs WHERE auth_name='EPSG' AND NOT auth_id IN (" );
2853  QString delim;
2854  QHash<int, QString>::const_iterator it = wkts.constBegin();
2855  for ( ; it != wkts.constEnd(); ++it )
2856  {
2857  sql += delim + QString::number( it.key() );
2858  delim = ',';
2859  }
2860  sql += QLatin1String( ") AND NOT noupdate" );
2861 
2862  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
2863  {
2864  deleted = sqlite3_changes( database.get() );
2865  }
2866  else
2867  {
2868  errors++;
2869  qCritical( "Could not execute: %s [%s]\n",
2870  sql.toLocal8Bit().constData(),
2871  sqlite3_errmsg( database.get() ) );
2872  }
2873 
2874  projCtx pContext = pj_ctx_alloc();
2875 
2876 #if !defined(PJ_VERSION) || PJ_VERSION!=470
2877  sql = QStringLiteral( "select auth_name,auth_id,parameters from tbl_srs WHERE auth_name<>'EPSG' AND NOT deprecated AND NOT noupdate" );
2878  statement = database.prepare( sql, result );
2879  if ( result == SQLITE_OK )
2880  {
2881  while ( statement.step() == SQLITE_ROW )
2882  {
2883  QString auth_name = statement.columnAsText( 0 );
2884  QString auth_id = statement.columnAsText( 1 );
2885  QString params = statement.columnAsText( 2 );
2886 
2887  QString input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toLower(), auth_id );
2888  projPJ pj = pj_init_plus_ctx( pContext, input.toLatin1() );
2889  if ( !pj )
2890  {
2891  input = QStringLiteral( "+init=%1:%2" ).arg( auth_name.toUpper(), auth_id );
2892  pj = pj_init_plus_ctx( pContext, input.toLatin1() );
2893  }
2894 
2895  if ( pj )
2896  {
2897  char *def = pj_get_def( pj, 0 );
2898  if ( def )
2899  {
2900  proj4 = def;
2901  pj_dalloc( def );
2902 
2903  input.prepend( ' ' ).append( ' ' );
2904  if ( proj4.startsWith( input ) )
2905  {
2906  proj4 = proj4.mid( input.size() );
2907  proj4 = proj4.trimmed();
2908  }
2909 
2910  if ( proj4 != params )
2911  {
2912  sql = QStringLiteral( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name=%2 AND auth_id=%3" )
2913  .arg( QgsSqliteUtils::quotedString( proj4 ),
2914  QgsSqliteUtils::quotedString( auth_name ),
2915  QgsSqliteUtils::quotedString( auth_id ) );
2916 
2917  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
2918  {
2919  updated++;
2920  }
2921  else
2922  {
2923  qCritical( "Could not execute: %s [%s/%s]\n",
2924  sql.toLocal8Bit().constData(),
2925  sqlite3_errmsg( database.get() ),
2926  errMsg ? errMsg : "(unknown error)" );
2927  if ( errMsg )
2928  sqlite3_free( errMsg );
2929  errors++;
2930  }
2931  }
2932  }
2933  else
2934  {
2935  QgsDebugMsgLevel( QStringLiteral( "could not retrieve proj string for %1 from PROJ" ).arg( input ), 4 );
2936  }
2937  }
2938  else
2939  {
2940  QgsDebugMsgLevel( QStringLiteral( "could not retrieve crs for %1 from PROJ" ).arg( input ), 3 );
2941  }
2942 
2943  pj_free( pj );
2944  }
2945  }
2946  else
2947  {
2948  errors++;
2949  QgsDebugMsg( QStringLiteral( "Could not execute: %1 [%2]\n" ).arg(
2950  sql,
2951  sqlite3_errmsg( database.get() ) ) );
2952  }
2953 #endif
2954 
2955  pj_ctx_free( pContext );
2956 
2957 #endif
2958 
2959  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2960  {
2961  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg(
2963  sqlite3_errmsg( database.get() ) )
2964  );
2965  return -1;
2966  }
2967 
2968  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 );
2969 
2970  if ( errors > 0 )
2971  return -errors;
2972  else
2973  return updated + inserted;
2974 }
2975 
2976 #if PROJ_VERSION_MAJOR<6
2977 bool QgsCoordinateReferenceSystem::syncDatumTransform( const QString &dbPath )
2978 {
2979  const char *filename = CSVFilename( "datum_shift.csv" );
2980  FILE *fp = VSIFOpen( filename, "rb" );
2981  if ( !fp )
2982  {
2983  return false;
2984  }
2985 
2986  char **fieldnames = CSVReadParseLine( fp );
2987 
2988  // "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"
2989 
2990  struct
2991  {
2992  const char *src; //skip-init-check
2993  const char *dst; //skip-init-check
2994  int idx;
2995  } map[] =
2996  {
2997  // { "SEQ_KEY", "", -1 },
2998  { "SOURCE_CRS_CODE", "source_crs_code", -1 },
2999  { "TARGET_CRS_CODE", "target_crs_code", -1 },
3000  { "REMARKS", "remarks", -1 },
3001  { "COORD_OP_SCOPE", "scope", -1 },
3002  { "AREA_OF_USE_CODE", "area_of_use_code", -1 },
3003  // { "AREA_SOUTH_BOUND_LAT", "", -1 },
3004  // { "AREA_NORTH_BOUND_LAT", "", -1 },
3005  // { "AREA_WEST_BOUND_LON", "", -1 },
3006  // { "AREA_EAST_BOUND_LON", "", -1 },
3007  // { "SHOW_OPERATION", "", -1 },
3008  { "DEPRECATED", "deprecated", -1 },
3009  { "COORD_OP_METHOD_CODE", "coord_op_method_code", -1 },
3010  { "DX", "p1", -1 },
3011  { "DY", "p2", -1 },
3012  { "DZ", "p3", -1 },
3013  { "RX", "p4", -1 },
3014  { "RY", "p5", -1 },
3015  { "RZ", "p6", -1 },
3016  { "DS", "p7", -1 },
3017  { "PREFERRED", "preferred", -1 },
3018  { "COORD_OP_CODE", "coord_op_code", -1 },
3019  };
3020 
3021  QString update = QStringLiteral( "UPDATE tbl_datum_transform SET " );
3022  QString insert, values;
3023 
3024  int n = CSLCount( fieldnames );
3025 
3026  int idxid = -1, idxrx = -1, idxry = -1, idxrz = -1, idxmcode = -1;
3027  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
3028  {
3029  bool last = i == sizeof( map ) / sizeof( *map ) - 1;
3030 
3031  map[i].idx = CSLFindString( fieldnames, map[i].src );
3032  if ( map[i].idx < 0 )
3033  {
3034  qWarning( "field %s not found", map[i].src );
3035  CSLDestroy( fieldnames );
3036  fclose( fp );
3037  return false;
3038  }
3039 
3040  if ( strcmp( map[i].src, "COORD_OP_CODE" ) == 0 )
3041  idxid = i;
3042  if ( strcmp( map[i].src, "RX" ) == 0 )
3043  idxrx = i;
3044  if ( strcmp( map[i].src, "RY" ) == 0 )
3045  idxry = i;
3046  if ( strcmp( map[i].src, "RZ" ) == 0 )
3047  idxrz = i;
3048  if ( strcmp( map[i].src, "COORD_OP_METHOD_CODE" ) == 0 )
3049  idxmcode = i;
3050 
3051  if ( i > 0 )
3052  {
3053  insert += ',';
3054  values += ',';
3055 
3056  if ( last )
3057  {
3058  update += QLatin1String( " WHERE " );
3059  }
3060  else
3061  {
3062  update += ',';
3063  }
3064  }
3065 
3066  update += QStringLiteral( "%1=%%2" ).arg( map[i].dst ).arg( i + 1 );
3067 
3068  insert += map[i].dst;
3069  values += QStringLiteral( "%%1" ).arg( i + 1 );
3070  }
3071 
3072  insert = "INSERT INTO tbl_datum_transform(" + insert + ") VALUES (" + values + ')';
3073 
3074  CSLDestroy( fieldnames );
3075 
3076  Q_ASSERT( idxid >= 0 );
3077  Q_ASSERT( idxrx >= 0 );
3078  Q_ASSERT( idxry >= 0 );
3079  Q_ASSERT( idxrz >= 0 );
3080 
3081  sqlite3_database_unique_ptr database;
3082  int openResult = database.open( dbPath );
3083  if ( openResult != SQLITE_OK )
3084  {
3085  fclose( fp );
3086  return false;
3087  }
3088 
3089  if ( sqlite3_exec( database.get(), "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
3090  {
3091  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDatabaseFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database.get() ) );
3092  fclose( fp );
3093  return false;
3094  }
3095 
3096  QStringList v;
3097  v.reserve( sizeof( map ) / sizeof( *map ) );
3098 
3099  for ( ;; )
3100  {
3101  char **values = CSVReadParseLine( fp );
3102  if ( !values )
3103  break;
3104 
3105  v.clear();
3106 
3107  if ( CSLCount( values ) == 0 )
3108  {
3109  CSLDestroy( values );
3110  break;
3111  }
3112 
3113  if ( CSLCount( values ) < n )
3114  {
3115  qWarning( "Only %d columns", CSLCount( values ) );
3116  CSLDestroy( values );
3117  continue;
3118  }
3119 
3120  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
3121  {
3122  int idx = map[i].idx;
3123  Q_ASSERT( idx != -1 );
3124  Q_ASSERT( idx < n );
3125  v.insert( i, *values[ idx ] ? QgsSqliteUtils::quotedString( values[idx] ) : QStringLiteral( "NULL" ) );
3126  }
3127  CSLDestroy( values );
3128 
3129  //switch sign of rotation parameters. See http://trac.osgeo.org/proj/wiki/GenParms#towgs84-DatumtransformationtoWGS84
3130  if ( v.at( idxmcode ).compare( QLatin1String( "'9607'" ) ) == 0 )
3131  {
3132  v[ idxmcode ] = QStringLiteral( "'9606'" );
3133  v[ idxrx ] = '\'' + qgsDoubleToString( -( v[ idxrx ].remove( '\'' ).toDouble() ) ) + '\'';
3134  v[ idxry ] = '\'' + qgsDoubleToString( -( v[ idxry ].remove( '\'' ).toDouble() ) ) + '\'';
3135  v[ idxrz ] = '\'' + qgsDoubleToString( -( v[ idxrz ].remove( '\'' ).toDouble() ) ) + '\'';
3136  }
3137 
3138  //entry already in db?
3139  sqlite3_statement_unique_ptr statement;
3140  QString cOpCode;
3141  QString sql = QStringLiteral( "SELECT coord_op_code FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( v[ idxid ] );
3142  int prepareRes;
3143  statement = database.prepare( sql, prepareRes );
3144  if ( prepareRes != SQLITE_OK )
3145  continue;
3146 
3147  if ( statement.step() == SQLITE_ROW )
3148  {
3149  cOpCode = statement.columnAsText( 0 );
3150  }
3151 
3152  sql = cOpCode.isEmpty() ? insert : update;
3153  for ( int i = 0; i < v.size(); i++ )
3154  {
3155  sql = sql.arg( v[i] );
3156  }
3157 
3158  if ( sqlite3_exec( database.get(), sql.toUtf8(), nullptr, nullptr, nullptr ) != SQLITE_OK )
3159  {
3160  qCritical( "SQL: %s", sql.toUtf8().constData() );
3161  qCritical( "Error: %s", sqlite3_errmsg( database.get() ) );
3162  }
3163  }
3164 
3165  if ( sqlite3_exec( database.get(), "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
3166  {
3167  QgsDebugMsg( QStringLiteral( "Could not commit transaction: %1 [%2]\n" ).arg( QgsApplication::srsDatabaseFilePath(), sqlite3_errmsg( database.get() ) ) );
3168  return false;
3169  }
3170 
3171  return true;
3172 }
3173 #endif
3174 
3176 {
3177  if ( isGeographic() )
3178  {
3179  return d->mAuthId;
3180  }
3181 #if PROJ_VERSION_MAJOR>=6
3182  else if ( d->mPj )
3183  {
3184  QgsProjUtils::proj_pj_unique_ptr geoCrs( proj_crs_get_geodetic_crs( QgsProjContext::get(), d->mPj.get() ) );
3185  return geoCrs ? QStringLiteral( "%1:%2" ).arg( proj_get_id_auth_name( geoCrs.get(), 0 ), proj_get_id_code( geoCrs.get(), 0 ) ) : QString();
3186  }
3187 #else
3188  else if ( d->mCRS )
3189  {
3190  return OSRGetAuthorityName( d->mCRS, "GEOGCS" ) + QStringLiteral( ":" ) + OSRGetAuthorityCode( d->mCRS, "GEOGCS" );
3191  }
3192 #endif
3193  else
3194  {
3195  return QString();
3196  }
3197 }
3198 
3199 #if PROJ_VERSION_MAJOR>=6
3200 PJ *QgsCoordinateReferenceSystem::projObject() const
3201 {
3202  return d->mPj.get();
3203 }
3204 #endif
3205 
3207 {
3208  QStringList projections;
3209 
3210  // Read settings from persistent storage
3211  QgsSettings settings;
3212  projections = settings.value( QStringLiteral( "UI/recentProjections" ) ).toStringList();
3213  /*** The reading (above) of internal id from persistent storage should be removed sometime in the future */
3214  /*** This is kept now for backwards compatibility */
3215 
3216  QStringList projectionsProj4 = settings.value( QStringLiteral( "UI/recentProjectionsProj4" ) ).toStringList();
3217  QStringList projectionsAuthId = settings.value( QStringLiteral( "UI/recentProjectionsAuthId" ) ).toStringList();
3218  if ( projectionsAuthId.size() >= projections.size() )
3219  {
3220  // We had saved state with AuthId and Proj4. Use that instead
3221  // to find out the crs id
3222  projections.clear();
3223  for ( int i = 0; i < projectionsAuthId.size(); i++ )
3224  {
3225  // Create a crs from the EPSG
3227  crs.createFromOgcWmsCrs( projectionsAuthId.at( i ) );
3228  if ( ! crs.isValid() )
3229  {
3230  // Couldn't create from EPSG, try the Proj4 string instead
3231  if ( i >= projectionsProj4.size() || !crs.createFromProj4( projectionsProj4.at( i ) ) )
3232  {
3233  // No? Skip this entry
3234  continue;
3235  }
3236  //If the CRS can be created but do not correspond to a CRS in the database, skip it (for example a deleted custom CRS)
3237  if ( crs.srsid() == 0 )
3238  {
3239  continue;
3240  }
3241  }
3242  projections << QString::number( crs.srsid() );
3243  }
3244  }
3245  return projections;
3246 }
3247 
3249 {
3250  sSrIdCacheLock.lockForWrite();
3251  if ( !sDisableSrIdCache )
3252  {
3253  if ( disableCache )
3254  sDisableSrIdCache = true;
3255  sSrIdCache.clear();
3256  }
3257  sSrIdCacheLock.unlock();
3258 
3259  sOgcLock.lockForWrite();
3260  if ( !sDisableOgcCache )
3261  {
3262  if ( disableCache )
3263  sDisableOgcCache = true;
3264  sOgcCache.clear();
3265  }
3266  sOgcLock.unlock();
3267 
3268  sProj4CacheLock.lockForWrite();
3269  if ( !sDisableProj4Cache )
3270  {
3271  if ( disableCache )
3272  sDisableProj4Cache = true;
3273  sProj4Cache.clear();
3274  }
3275  sProj4CacheLock.unlock();
3276 
3277  sCRSWktLock.lockForWrite();
3278  if ( !sDisableWktCache )
3279  {
3280  if ( disableCache )
3281  sDisableWktCache = true;
3282  sWktCache.clear();
3283  }
3284  sCRSWktLock.unlock();
3285 
3286  sCRSSrsIdLock.lockForWrite();
3287  if ( !sDisableSrsIdCache )
3288  {
3289  if ( disableCache )
3290  sDisableSrsIdCache = true;
3291  sSrsIdCache.clear();
3292  }
3293  sCRSSrsIdLock.unlock();
3294 
3295  sCrsStringLock.lockForWrite();
3296  if ( !sDisableStringCache )
3297  {
3298  if ( disableCache )
3299  sDisableStringCache = true;
3300  sStringCache.clear();
3301  }
3302  sCrsStringLock.unlock();
3303 }
QString geographicCrsAuthId() const
Returns auth id of related geographic CRS.
QgsCoordinateReferenceSystem()
Constructs an invalid CRS object.
bool createFromId(long id, CrsType type=PostgisCrsId)
Sets this CRS by lookup of the given ID in the CRS database.
bool operator!=(const QgsCoordinateReferenceSystem &srs) const
Overloaded != operator used to compare to CRS&#39;s.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
static QgsCoordinateReferenceSystem fromProj4(const QString &proj4)
Creates a CRS from a proj4 style formatted string.
bool createFromString(const QString &definition)
Set up this CRS from a string definition.
static QList< long > validSrsIds()
Returns a list of all valid SRS IDs present in the CRS database.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateReferenceSystem objects.
void setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:135
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void validate()
Perform some validation on this CRS.
static void warning(const QString &msg)
Goes to qWarning.
Definition: qgslogger.cpp:121
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:280
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=".
Full WKT2 string, conforming to ISO 19162:2015(E) / OGC 12-063r5 with all possible nodes and new keyw...
const QgsCoordinateReferenceSystem & crs
WKT2_2018 with the simplification rule of WKT2_SIMPLIFIED.
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.
Same as WKT2_2015 with the following exceptions: UNIT keyword used. ID node only on top element...
QString errorMessage() const
Returns the most recent error message encountered by the database.
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
static Q_INVOKABLE QgsCoordinateReferenceSystem fromEpsgId(long epsg)
Creates a CRS from a given EPSG ID.
void setValidationHint(const QString &html)
Set user hint for validation.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
bool createFromSrid(long srid)
Sets this CRS by lookup of the given PostGIS SRID in the CRS database.
WKT1 as traditionally output by GDAL, deriving from OGC 01-009. A notable departure from WKT1_GDAL wi...
static QStringList recentProjections()
Returns a list of recently used projections.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
QString validationHint()
Gets user hint for validation.
long postgisSrid() const
Returns PostGIS SRID for the CRS.
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition: qgis.h:240
long findMatchingProj()
Walks the CRS databases (both system and user database) trying to match stored PROJ string to a datab...
void setYMinimum(double y)
Set the minimum y value.
Definition: qgsrectangle.h:140
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
int columnCount() const
Gets the number of columns that this statement returns.
QString description() const
Returns the descriptive name of the CRS, e.g., "WGS 84" or "GDA 94 / Vicgrid94".
Degrees, for planar geographic CRS distance measurements.
Definition: qgsunittypes.h:74
QgsCoordinateReferenceSystem & operator=(const QgsCoordinateReferenceSystem &srs)
Assignment operator.
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
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.
Full WKT2 string, conforming to ISO 19162:2018 / OGC 18-010, with all possible nodes and new keyword ...
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
DistanceUnit
Units of distance.
Definition: qgsunittypes.h:66
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
Unknown distance unit.
Definition: qgsunittypes.h:77
void unlock()
Unlocks the lock.
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:584
bool createFromSrsId(long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool createFromUserInput(const QString &definition)
Set up this CRS from various text formats.
void PJ_CONTEXT
Definition: qgsprojutils.h:135
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
void setYMaximum(double y)
Set the maximum y value.
Definition: qgsrectangle.h:145
bool hasAxisInverted() const
Returns whether axis is inverted (e.g., for WMS 1.3) for the CRS.
This class represents a coordinate reference system (CRS).
bool createFromProj4(const QString &projString)
Sets this CRS by passing it a PROJ style formatted string.
void changeMode(Mode mode)
Change the mode of the lock to mode.
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.
WktVariant
WKT formatting variants, only used for builds based on Proj >= 6.
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
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.
Terrestrial miles.
Definition: qgsunittypes.h:73
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
bool operator==(const QgsCoordinateReferenceSystem &srs) const
Overloaded == operator used to compare to CRS&#39;s.
QString columnName(int column) const
Returns the name of column.
double columnAsDouble(int column) const
Gets column value from the current statement row as a double.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
static int syncDatabase()
Update proj.4 parameters in our database from proj.4.
QString authid() const
Returns the authority identifier for the CRS.
static void setupESRIWktFix()
Make sure that ESRI WKT import is done properly.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:130
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
void * OGRSpatialReferenceH
WKT1 as traditionally output by ESRI software, deriving from OGC 99-049.
static QString quotedString(const QString &value)
Returns a quoted string value, surround by &#39; characters and with special characters correctly escaped...
bool isValid() const
Returns whether this CRS is correctly initialized and usable.