QGIS API Documentation  3.10.0-A Coruña (6c816b4204)
qgsellipsoidutils.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsellipsoidutils.cpp
3  ----------------------
4  Date : April 2017
5  Copyright : (C) 2017 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 
16 #include "qgsellipsoidutils.h"
17 #include "qgsapplication.h"
18 #include "qgslogger.h"
19 #include "qgsmessagelog.h"
20 #include <sqlite3.h>
21 #include <QCollator>
22 #include "qgsprojutils.h"
23 #include "qgsreadwritelocker.h"
24 
25 #if PROJ_VERSION_MAJOR>=6
26 #include <proj.h>
27 #include <mutex>
28 #endif
29 
30 QReadWriteLock QgsEllipsoidUtils::sEllipsoidCacheLock;
31 QHash< QString, QgsEllipsoidUtils::EllipsoidParameters > QgsEllipsoidUtils::sEllipsoidCache;
32 QReadWriteLock QgsEllipsoidUtils::sDefinitionCacheLock;
33 QList< QgsEllipsoidUtils::EllipsoidDefinition > QgsEllipsoidUtils::sDefinitionCache;
34 static bool sDisableCache = false;
35 
36 // maps older QGIS ellipsoid acronyms to proj acronyms/names
37 const QMap< QString, QString > sProj6EllipsoidAcronymMap
38 {
39  { "clrk80", "clrk80ign" },
40  {"Adrastea2000", "ESRI:107909"},
41  {"Amalthea2000", "ESRI:107910"},
42  {"Ananke2000", "ESRI:107911"},
43  {"Ariel2000", "ESRI:107945"},
44  {"Atlas2000", "ESRI:107926"},
45  {"Belinda2000", "ESRI:107946"},
46  {"Bianca2000", "ESRI:107947"},
47  {"Callisto2000", "ESRI:107912"},
48  {"Calypso2000", "ESRI:107927"},
49  {"Carme2000", "ESRI:107913"},
50  {"Charon2000", "ESRI:107970"},
51  {"Cordelia2000", "ESRI:107948"},
52  {"Cressida2000", "ESRI:107949"},
53  {"Deimos2000", "ESRI:107906"},
54  {"Desdemona2000", "ESRI:107950"},
55  {"Despina2000", "ESRI:107961"},
56  {"Dione2000", "ESRI:107928"},
57  {"Elara2000", "ESRI:107914"},
58  {"Enceladus2000", "ESRI:107929"},
59  {"Epimetheus2000", "ESRI:107930"},
60  {"Europa2000", "ESRI:107915"},
61  {"Galatea2000", "ESRI:107962"},
62  {"Ganymede2000", "ESRI:107916"},
63  {"Helene2000", "ESRI:107931"},
64  {"Himalia2000", "ESRI:107917"},
65  {"Hyperion2000", "ESRI:107932"},
66  {"Iapetus2000", "ESRI:107933"},
67  {"Io2000", "ESRI:107918"},
68  {"Janus2000", "ESRI:107934"},
69  {"Juliet2000", "ESRI:107951"},
70  {"Jupiter2000", "ESRI:107908"},
71  {"Larissa2000", "ESRI:107963"},
72  {"Leda2000", "ESRI:107919"},
73  {"Lysithea2000", "ESRI:107920"},
74  {"Mars2000", "ESRI:107905"},
75  {"Mercury2000", "ESRI:107900"},
76  {"Metis2000", "ESRI:107921"},
77  {"Mimas2000", "ESRI:107935"},
78  {"Miranda2000", "ESRI:107952"},
79  {"Moon2000", "ESRI:107903"},
80  {"Naiad2000", "ESRI:107964"},
81  {"Neptune2000", "ESRI:107960"},
82  {"Nereid2000", "ESRI:107965"},
83  {"Oberon2000", "ESRI:107953"},
84  {"Ophelia2000", "ESRI:107954"},
85  {"Pan2000", "ESRI:107936"},
86  {"Pandora2000", "ESRI:107937"},
87  {"Pasiphae2000", "ESRI:107922"},
88  {"Phobos2000", "ESRI:107907"},
89  {"Phoebe2000", "ESRI:107938"},
90  {"Pluto2000", "ESRI:107969"},
91  {"Portia2000", "ESRI:107955"},
92  {"Prometheus2000", "ESRI:107939"},
93  {"Proteus2000", "ESRI:107966"},
94  {"Puck2000", "ESRI:107956"},
95  {"Rhea2000", "ESRI:107940"},
96  {"Rosalind2000", "ESRI:107957"},
97  {"Saturn2000", "ESRI:107925"},
98  {"Sinope2000", "ESRI:107923"},
99  {"Telesto2000", "ESRI:107941"},
100  {"Tethys2000", "ESRI:107942"},
101  {"Thalassa2000", "ESRI:107967"},
102  {"Thebe2000", "ESRI:107924"},
103  {"Titan2000", "ESRI:107943"},
104  {"Titania2000", "ESRI:107958"},
105  {"Triton2000", "ESRI:107968"},
106  {"Umbriel2000", "ESRI:107959"},
107  {"Uranus2000", "ESRI:107944"},
108  {"Venus2000", "ESRI:107902"},
109  {"IGNF:ELG053", "EPSG:7030"},
110  {"IGNF:ELG052", "EPSG:7043"},
111  {"IGNF:ELG102", "EPSG:7043"},
112  {"WGS66", "ESRI:107001"},
113  {"plessis", "EPSG:7027"},
114  {"IGNF:ELG017", "EPSG:7027"},
115  {"mod_airy", "EPSG:7002"},
116  {"IGNF:ELG037", "EPSG:7019"},
117  {"IGNF:ELG108", "EPSG:7036"},
118  {"cape", "EPSG:7034"},
119  {"IGNF:ELG010", "EPSG:7011"},
120  {"IGNF:ELG003", "EPSG:7012"},
121  {"IGNF:ELG004", "EPSG:7008"},
122  {"GSK2011", "EPSG:1025"},
123  {"airy", "EPSG:7001"},
124  {"aust_SA", "EPSG:7003"},
125  {"bessel", "EPSG:7004"},
126  {"clrk66", "EPSG:7008"},
127  {"clrk80ign", "EPSG:7011"},
128  {"evrst30", "EPSG:7015"},
129  {"evrstSS", "EPSG:7016"},
130  {"evrst48", "EPSG:7018"},
131  {"GRS80", "EPSG:7019"},
132  {"helmert", "EPSG:7020"},
133  {"intl", "EPSG:7022"},
134  {"krass", "EPSG:7024"},
135  {"NWL9D", "EPSG:7025"},
136  {"WGS84", "EPSG:7030"},
137  {"GRS67", "EPSG:7036"},
138  {"WGS72", "EPSG:7043"},
139  {"bess_nam", "EPSG:7046"},
140  {"IAU76", "EPSG:7049"},
141  {"sphere", "EPSG:7052"},
142  {"hough", "EPSG:7053"},
143  {"evrst69", "EPSG:7056"},
144  {"fschr60", "ESRI:107002"},
145  {"fschr68", "ESRI:107003"},
146  {"fschr60m", "ESRI:107004"},
147  {"walbeck", "ESRI:107007"},
148  {"IGNF:ELG001", "EPSG:7022"},
149  {"engelis", "EPSG:7054"},
150  {"evrst56", "EPSG:7044"},
151  {"SEasia", "ESRI:107004"},
152  {"SGS85", "EPSG:7054"},
153  {"andrae", "PROJ:ANDRAE"},
154  {"clrk80", "EPSG:7034"},
155  {"CPM", "PROJ:CPM"},
156  {"delmbr", "PROJ:DELMBR"},
157  {"Earth2000", "PROJ:EARTH2000"},
158  {"kaula", "PROJ:KAULA"},
159  {"lerch", "PROJ:LERCH"},
160  {"MERIT", "PROJ:MERIT"},
161  {"mprts", "PROJ:MPRTS"},
162  {"new_intl", "PROJ:NEW_INTL"},
163  {"WGS60", "PROJ:WGS60"}
164 };
165 
167 {
168  QString ellipsoid = e;
169 #if PROJ_VERSION_MAJOR >= 6
170  // ensure ellipsoid database is populated when first called
171  static std::once_flag initialized;
172  std::call_once( initialized, [ = ]
173  {
174  ( void )definitions();
175  } );
176 
177  ellipsoid = sProj6EllipsoidAcronymMap.value( ellipsoid, ellipsoid ); // silently upgrade older QGIS acronyms to proj acronyms
178 #endif
179 
180  // check cache
181  {
182  QgsReadWriteLocker locker( sEllipsoidCacheLock, QgsReadWriteLocker::Read );
183  if ( !sDisableCache )
184  {
185  QHash< QString, EllipsoidParameters >::const_iterator cacheIt = sEllipsoidCache.constFind( ellipsoid );
186  if ( cacheIt != sEllipsoidCache.constEnd() )
187  {
188  // found a match in the cache
189  QgsEllipsoidUtils::EllipsoidParameters params = cacheIt.value();
190  return params;
191  }
192  }
193  }
194 
195  EllipsoidParameters params;
196 
197  // Check if we have a custom projection, and set from text string.
198  // Format is "PARAMETER:<semi-major axis>:<semi minor axis>
199  // Numbers must be with (optional) decimal point and no other separators (C locale)
200  // Distances in meters. Flattening is calculated.
201  if ( ellipsoid.startsWith( QLatin1String( "PARAMETER" ) ) )
202  {
203  QStringList paramList = ellipsoid.split( ':' );
204  bool semiMajorOk, semiMinorOk;
205  double semiMajor = paramList[1].toDouble( & semiMajorOk );
206  double semiMinor = paramList[2].toDouble( & semiMinorOk );
207  if ( semiMajorOk && semiMinorOk )
208  {
209  params.semiMajor = semiMajor;
210  params.semiMinor = semiMinor;
211  params.inverseFlattening = semiMajor / ( semiMajor - semiMinor );
212  params.useCustomParameters = true;
213  }
214  else
215  {
216  params.valid = false;
217  }
218 
219  QgsReadWriteLocker locker( sEllipsoidCacheLock, QgsReadWriteLocker::Write );
220  if ( !sDisableCache )
221  {
222  sEllipsoidCache.insert( ellipsoid, params );
223  }
224  return params;
225  }
226 
227 #if PROJ_VERSION_MAJOR< 6
228  // cache miss - get from database
229  // NOT REQUIRED FOR PROJ >= 6 -- we populate known types once by calling definitions() above
230 
231  QString radius, parameter2;
232  //
233  // SQLITE3 stuff - get parameters for selected ellipsoid
234  //
237  // Continue with PROJ list of ellipsoids.
238 
239  //check the db is available
240  int result = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
241  if ( result )
242  {
243  QgsMessageLog::logMessage( QObject::tr( "Can not open srs database (%1): %2" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
244  // XXX This will likely never happen since on open, sqlite creates the
245  // database if it does not exist.
246  return params;
247  }
248  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
249  QString sql = "select radius, parameter2 from tbl_ellipsoid where acronym='" + ellipsoid + '\'';
250  statement = database.prepare( sql, result );
251  // XXX Need to free memory from the error msg if one is set
252  if ( result == SQLITE_OK )
253  {
254  if ( statement.step() == SQLITE_ROW )
255  {
256  radius = statement.columnAsText( 0 );
257  parameter2 = statement.columnAsText( 1 );
258  }
259  }
260  // row for this ellipsoid wasn't found?
261  if ( radius.isEmpty() || parameter2.isEmpty() )
262  {
263  QgsDebugMsg( QStringLiteral( "setEllipsoid: no row in tbl_ellipsoid for acronym '%1'" ).arg( ellipsoid ) );
264  params.valid = false;
265  sEllipsoidCacheLock.lockForWrite();
266  if ( !sDisableCache )
267  {
268  sEllipsoidCache.insert( ellipsoid, params );
269  }
270  sEllipsoidCacheLock.unlock();
271  return params;
272  }
273 
274  // get major semiaxis
275  if ( radius.left( 2 ) == QLatin1String( "a=" ) )
276  params.semiMajor = radius.midRef( 2 ).toDouble();
277  else
278  {
279  QgsDebugMsg( QStringLiteral( "setEllipsoid: wrong format of radius field: '%1'" ).arg( radius ) );
280  params.valid = false;
281  sEllipsoidCacheLock.lockForWrite();
282  if ( !sDisableCache )
283  {
284  sEllipsoidCache.insert( ellipsoid, params );
285  }
286  sEllipsoidCacheLock.unlock();
287  return params;
288  }
289 
290  // get second parameter
291  // one of values 'b' or 'f' is in field parameter2
292  // second one must be computed using formula: invf = a/(a-b)
293  if ( parameter2.left( 2 ) == QLatin1String( "b=" ) )
294  {
295  params.semiMinor = parameter2.midRef( 2 ).toDouble();
296  params.inverseFlattening = params.semiMajor / ( params.semiMajor - params.semiMinor );
297  }
298  else if ( parameter2.left( 3 ) == QLatin1String( "rf=" ) )
299  {
300  params.inverseFlattening = parameter2.midRef( 3 ).toDouble();
301  params.semiMinor = params.semiMajor - ( params.semiMajor / params.inverseFlattening );
302  }
303  else
304  {
305  QgsDebugMsg( QStringLiteral( "setEllipsoid: wrong format of parameter2 field: '%1'" ).arg( parameter2 ) );
306  params.valid = false;
307  sEllipsoidCacheLock.lockForWrite();
308  if ( !sDisableCache )
309  {
310  sEllipsoidCache.insert( ellipsoid, params );
311  }
312  sEllipsoidCacheLock.unlock();
313  return params;
314  }
315 
316  QgsDebugMsgLevel( QStringLiteral( "setEllipsoid: a=%1, b=%2, 1/f=%3" ).arg( params.semiMajor ).arg( params.semiMinor ).arg( params.inverseFlattening ), 4 );
317 
318 
319  // get spatial ref system for ellipsoid
320  QString proj4 = "+proj=longlat +ellps=" + ellipsoid + " +no_defs";
322  //TODO: createFromProj4 used to save to the user database any new CRS
323  // this behavior was changed in order to separate creation and saving.
324  // Not sure if it necessary to save it here, should be checked by someone
325  // familiar with the code (should also give a more descriptive name to the generated CRS)
326  if ( destCRS.srsid() == 0 )
327  {
328  QString name = QStringLiteral( " * %1 (%2)" )
329  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
330  destCRS.toProj4() );
331  destCRS.saveAsUserCrs( name );
332  }
333  //
334 
335  // set transformation from project CRS to ellipsoid coordinates
336  params.crs = destCRS;
337 
338  sEllipsoidCacheLock.lockForWrite();
339  if ( !sDisableCache )
340  {
341  sEllipsoidCache.insert( ellipsoid, params );
342  }
343  sEllipsoidCacheLock.unlock();
344  return params;
345 #else
346  params.valid = false;
347 
348  QgsReadWriteLocker l( sEllipsoidCacheLock, QgsReadWriteLocker::Write );
349  if ( !sDisableCache )
350  {
351  sEllipsoidCache.insert( ellipsoid, params );
352  }
353 
354  return params;
355 #endif
356 }
357 
358 QList<QgsEllipsoidUtils::EllipsoidDefinition> QgsEllipsoidUtils::definitions()
359 {
360  QgsReadWriteLocker defLocker( sDefinitionCacheLock, QgsReadWriteLocker::Read );
361  if ( !sDefinitionCache.isEmpty() )
362  {
363  QList<QgsEllipsoidUtils::EllipsoidDefinition> defs = sDefinitionCache;
364  return defs;
365  }
367 
368  QList<QgsEllipsoidUtils::EllipsoidDefinition> defs;
369 
370 #if PROJ_VERSION_MAJOR>=6
371  QgsReadWriteLocker locker( sEllipsoidCacheLock, QgsReadWriteLocker::Write );
372 
373  PJ_CONTEXT *context = QgsProjContext::get();
374  if ( PROJ_STRING_LIST authorities = proj_get_authorities_from_database( context ) )
375  {
376  PROJ_STRING_LIST authoritiesIt = authorities;
377  while ( char *authority = *authoritiesIt )
378  {
379  if ( PROJ_STRING_LIST codes = proj_get_codes_from_database( context, authority, PJ_TYPE_ELLIPSOID, 0 ) )
380  {
381  PROJ_STRING_LIST codesIt = codes;
382  while ( char *code = *codesIt )
383  {
384  QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_create_from_database( context, authority, code, PJ_CATEGORY_ELLIPSOID, 0, nullptr ) );
385  if ( ellipsoid.get() )
386  {
388  QString name = QString( proj_get_name( ellipsoid.get() ) );
389  def.acronym = QStringLiteral( "%1:%2" ).arg( authority, code );
390  name.replace( '_', ' ' );
391  def.description = QStringLiteral( "%1 (%2:%3)" ).arg( name, authority, code );
392 
393  double semiMajor, semiMinor, invFlattening;
394  int semiMinorComputed = 0;
395  if ( proj_ellipsoid_get_parameters( context, ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
396  {
397  def.parameters.semiMajor = semiMajor;
398  def.parameters.semiMinor = semiMinor;
399  def.parameters.inverseFlattening = invFlattening;
400  if ( !semiMinorComputed )
401  def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +b=%2 +no_defs +type=crs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.semiMinor, 0, 'g', 17 ) );
402  else if ( !qgsDoubleNear( def.parameters.inverseFlattening, 0.0 ) )
403  def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +rf=%2 +no_defs +type=crs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.inverseFlattening, 0, 'g', 17 ) );
404  else
405  def.parameters.crs = QgsCoordinateReferenceSystem::fromProj4( QStringLiteral( "+proj=longlat +a=%1 +no_defs +type=crs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ) );
406  }
407  else
408  {
409  def.parameters.valid = false;
410  }
411 
412  defs << def;
413  if ( !sDisableCache )
414  {
415  sEllipsoidCache.insert( def.acronym, def.parameters );
416  }
417  }
418 
419  codesIt++;
420  }
421  proj_string_list_destroy( codes );
422  }
423 
424  authoritiesIt++;
425  }
426  proj_string_list_destroy( authorities );
427  }
428  locker.unlock();
429 
430 #else
433  int result;
434 
435  //check the db is available
436  result = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
437  if ( result )
438  {
439  QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
440  // XXX This will likely never happen since on open, sqlite creates the
441  // database if it does not exist.
442  Q_ASSERT( result == 0 );
443  }
444 
445  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
446  QString sql = QStringLiteral( "select acronym, name from tbl_ellipsoid order by name" );
447  statement = database.prepare( sql, result );
448 
449  if ( result == SQLITE_OK )
450  {
451  while ( statement.step() == SQLITE_ROW )
452  {
454  def.acronym = statement.columnAsText( 0 );
455  def.description = statement.columnAsText( 1 );
456 
457  // use ellipsoidParameters so that result is cached
459 
460  defs << def;
461  }
462  }
463 
464 #endif
465 
466  QCollator collator;
467  collator.setCaseSensitivity( Qt::CaseInsensitive );
468  std::sort( defs.begin(), defs.end(), [&collator]( const EllipsoidDefinition & a, const EllipsoidDefinition & b )
469  {
470  return collator.compare( a.description, b.description ) < 0;
471  } );
472  if ( !sDisableCache )
473  {
474  sDefinitionCache = defs;
475  }
476 
477  return defs;
478 }
479 
481 {
482  QStringList result;
483  const auto constDefinitions = definitions();
484  for ( const QgsEllipsoidUtils::EllipsoidDefinition &def : constDefinitions )
485  {
486  result << def.acronym;
487  }
488  return result;
489 }
490 
491 void QgsEllipsoidUtils::invalidateCache( bool disableCache )
492 {
493  QgsReadWriteLocker locker1( sEllipsoidCacheLock, QgsReadWriteLocker::Write );
494  QgsReadWriteLocker locker2( sDefinitionCacheLock, QgsReadWriteLocker::Write );
495 
496  if ( !sDisableCache )
497  {
498  if ( disableCache )
499  sDisableCache = true;
500  sEllipsoidCache.clear();
501  sDefinitionCache.clear();
502  }
503 }
static QgsCoordinateReferenceSystem fromProj4(const QString &proj4)
Creates a CRS from a proj4 style formatted string.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used.
bool useCustomParameters
Whether custom parameters alone should be used (semiMajor/semiMinor only)
Contains definition of an ellipsoid.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
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
QString toProj4() const
Returns a Proj4 string representation of this CRS.
Contains parameters for an ellipsoid.
static EllipsoidParameters ellipsoidParameters(const QString &ellipsoid)
Returns the parameters for the specified ellipsoid.
long saveAsUserCrs(const QString &name)
Save the proj4-string as a custom CRS.
QgsCoordinateReferenceSystem crs
Associated coordinate reference system.
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...
bool valid
Whether ellipsoid parameters are valid.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
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).
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
const QMap< QString, QString > sProj6EllipsoidAcronymMap
QString acronym
authority:code for QGIS builds with proj version 6 or greater, or custom acronym for ellipsoid for ea...
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
void unlock()
Unlocks the lock.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
void PJ_CONTEXT
Definition: qgsprojutils.h:135
This class represents a coordinate reference system (CRS).
double inverseFlattening
Inverse flattening.
void changeMode(Mode mode)
Change the mode of the lock to mode.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
QgsEllipsoidUtils::EllipsoidParameters parameters
Ellipsoid parameters.
static QList< QgsEllipsoidUtils::EllipsoidDefinition > definitions()
Returns a list of the definitions for all known ellipsoids from the internal ellipsoid database...
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
static QStringList acronyms()
Returns a list of all known ellipsoid acronyms from the internal ellipsoid database.
long srsid() const
Returns the internal CRS ID, if available.
QString description
Description of ellipsoid.