QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 #include "qgsruntimeprofiler.h"
26 #include "qgscelestialbody.h"
27 
28 #include <proj.h>
29 #include <mutex>
30 
31 Q_GLOBAL_STATIC( QReadWriteLock, sEllipsoidCacheLock )
32 typedef QHash< QString, QgsEllipsoidUtils::EllipsoidParameters > EllipsoidParamCache;
33 Q_GLOBAL_STATIC( EllipsoidParamCache, sEllipsoidCache )
34 
35 Q_GLOBAL_STATIC( QReadWriteLock, sDefinitionCacheLock );
36 typedef QList< QgsEllipsoidUtils::EllipsoidDefinition > EllipsoidDefinitionCache;
37 Q_GLOBAL_STATIC( EllipsoidDefinitionCache, sDefinitionCache )
38 
39 static bool sDisableCache = false;
40 
41 QgsEllipsoidUtils::EllipsoidParameters QgsEllipsoidUtils::ellipsoidParameters( const QString &e )
42 {
43 // maps older QGIS ellipsoid acronyms to proj acronyms/names
44  static const QMap< QString, QString > sProj6EllipsoidAcronymMap
45  {
46  { "clrk80", "clrk80ign" },
47  {"Adrastea2000", "ESRI:107909"},
48  {"Amalthea2000", "ESRI:107910"},
49  {"Ananke2000", "ESRI:107911"},
50  {"Ariel2000", "ESRI:107945"},
51  {"Atlas2000", "ESRI:107926"},
52  {"Belinda2000", "ESRI:107946"},
53  {"Bianca2000", "ESRI:107947"},
54  {"Callisto2000", "ESRI:107912"},
55  {"Calypso2000", "ESRI:107927"},
56  {"Carme2000", "ESRI:107913"},
57  {"Charon2000", "ESRI:107970"},
58  {"Cordelia2000", "ESRI:107948"},
59  {"Cressida2000", "ESRI:107949"},
60  {"Deimos2000", "ESRI:107906"},
61  {"Desdemona2000", "ESRI:107950"},
62  {"Despina2000", "ESRI:107961"},
63  {"Dione2000", "ESRI:107928"},
64  {"Elara2000", "ESRI:107914"},
65  {"Enceladus2000", "ESRI:107929"},
66  {"Epimetheus2000", "ESRI:107930"},
67  {"Europa2000", "ESRI:107915"},
68  {"Galatea2000", "ESRI:107962"},
69  {"Ganymede2000", "ESRI:107916"},
70  {"Helene2000", "ESRI:107931"},
71  {"Himalia2000", "ESRI:107917"},
72  {"Hyperion2000", "ESRI:107932"},
73  {"Iapetus2000", "ESRI:107933"},
74  {"Io2000", "ESRI:107918"},
75  {"Janus2000", "ESRI:107934"},
76  {"Juliet2000", "ESRI:107951"},
77  {"Jupiter2000", "ESRI:107908"},
78  {"Larissa2000", "ESRI:107963"},
79  {"Leda2000", "ESRI:107919"},
80  {"Lysithea2000", "ESRI:107920"},
81  {"Mars2000", "ESRI:107905"},
82  {"Mercury2000", "ESRI:107900"},
83  {"Metis2000", "ESRI:107921"},
84  {"Mimas2000", "ESRI:107935"},
85  {"Miranda2000", "ESRI:107952"},
86  {"Moon2000", "ESRI:107903"},
87  {"Naiad2000", "ESRI:107964"},
88  {"Neptune2000", "ESRI:107960"},
89  {"Nereid2000", "ESRI:107965"},
90  {"Oberon2000", "ESRI:107953"},
91  {"Ophelia2000", "ESRI:107954"},
92  {"Pan2000", "ESRI:107936"},
93  {"Pandora2000", "ESRI:107937"},
94  {"Pasiphae2000", "ESRI:107922"},
95  {"Phobos2000", "ESRI:107907"},
96  {"Phoebe2000", "ESRI:107938"},
97  {"Pluto2000", "ESRI:107969"},
98  {"Portia2000", "ESRI:107955"},
99  {"Prometheus2000", "ESRI:107939"},
100  {"Proteus2000", "ESRI:107966"},
101  {"Puck2000", "ESRI:107956"},
102  {"Rhea2000", "ESRI:107940"},
103  {"Rosalind2000", "ESRI:107957"},
104  {"Saturn2000", "ESRI:107925"},
105  {"Sinope2000", "ESRI:107923"},
106  {"Telesto2000", "ESRI:107941"},
107  {"Tethys2000", "ESRI:107942"},
108  {"Thalassa2000", "ESRI:107967"},
109  {"Thebe2000", "ESRI:107924"},
110  {"Titan2000", "ESRI:107943"},
111  {"Titania2000", "ESRI:107958"},
112  {"Triton2000", "ESRI:107968"},
113  {"Umbriel2000", "ESRI:107959"},
114  {"Uranus2000", "ESRI:107944"},
115  {"Venus2000", "ESRI:107902"},
116  {"IGNF:ELG053", "EPSG:7030"},
117  {"IGNF:ELG052", "EPSG:7043"},
118  {"IGNF:ELG102", "EPSG:7043"},
119  {"WGS66", "ESRI:107001"},
120  {"plessis", "EPSG:7027"},
121  {"IGNF:ELG017", "EPSG:7027"},
122  {"mod_airy", "EPSG:7002"},
123  {"IGNF:ELG037", "EPSG:7019"},
124  {"IGNF:ELG108", "EPSG:7036"},
125  {"cape", "EPSG:7034"},
126  {"IGNF:ELG010", "EPSG:7011"},
127  {"IGNF:ELG003", "EPSG:7012"},
128  {"IGNF:ELG004", "EPSG:7008"},
129  {"GSK2011", "EPSG:1025"},
130  {"airy", "EPSG:7001"},
131  {"aust_SA", "EPSG:7003"},
132  {"bessel", "EPSG:7004"},
133  {"clrk66", "EPSG:7008"},
134  {"clrk80ign", "EPSG:7011"},
135  {"evrst30", "EPSG:7015"},
136  {"evrstSS", "EPSG:7016"},
137  {"evrst48", "EPSG:7018"},
138  {"GRS80", "EPSG:7019"},
139  {"helmert", "EPSG:7020"},
140  {"intl", "EPSG:7022"},
141  {"krass", "EPSG:7024"},
142  {"NWL9D", "EPSG:7025"},
143  {"WGS84", "EPSG:7030"},
144  {"GRS67", "EPSG:7036"},
145  {"WGS72", "EPSG:7043"},
146  {"bess_nam", "EPSG:7046"},
147  {"IAU76", "EPSG:7049"},
148  {"sphere", "EPSG:7052"},
149  {"hough", "EPSG:7053"},
150  {"evrst69", "EPSG:7056"},
151  {"fschr60", "ESRI:107002"},
152  {"fschr68", "ESRI:107003"},
153  {"fschr60m", "ESRI:107004"},
154  {"walbeck", "ESRI:107007"},
155  {"IGNF:ELG001", "EPSG:7022"},
156  {"engelis", "EPSG:7054"},
157  {"evrst56", "EPSG:7044"},
158  {"SEasia", "ESRI:107004"},
159  {"SGS85", "EPSG:7054"},
160  {"andrae", "PROJ:ANDRAE"},
161  {"clrk80", "EPSG:7034"},
162  {"CPM", "PROJ:CPM"},
163  {"delmbr", "PROJ:DELMBR"},
164  {"Earth2000", "PROJ:EARTH2000"},
165  {"kaula", "PROJ:KAULA"},
166  {"lerch", "PROJ:LERCH"},
167  {"MERIT", "PROJ:MERIT"},
168  {"mprts", "PROJ:MPRTS"},
169  {"new_intl", "PROJ:NEW_INTL"},
170  {"WGS60", "PROJ:WGS60"}
171  };
172 
173  QString ellipsoid = e;
174  // ensure ellipsoid database is populated when first called
175  static std::once_flag initialized;
176  std::call_once( initialized, [ = ]
177  {
178  const QgsScopedRuntimeProfile profile( QObject::tr( "Initialize ellipsoids" ) );
179  ( void )definitions();
180  } );
181 
182  ellipsoid = sProj6EllipsoidAcronymMap.value( ellipsoid, ellipsoid ); // silently upgrade older QGIS acronyms to proj acronyms
183 
184  // check cache
185  {
186  const QgsReadWriteLocker locker( *sEllipsoidCacheLock(), QgsReadWriteLocker::Read );
187  if ( !sDisableCache )
188  {
189  const QHash< QString, EllipsoidParameters >::const_iterator cacheIt = sEllipsoidCache()->constFind( ellipsoid );
190  if ( cacheIt != sEllipsoidCache()->constEnd() )
191  {
192  // found a match in the cache
193  QgsEllipsoidUtils::EllipsoidParameters params = cacheIt.value();
194  return params;
195  }
196  }
197  }
198 
199  EllipsoidParameters params;
200 
201  // Check if we have a custom projection, and set from text string.
202  // Format is "PARAMETER:<semi-major axis>:<semi minor axis>
203  // Numbers must be with (optional) decimal point and no other separators (C locale)
204  // Distances in meters. Flattening is calculated.
205  if ( ellipsoid.startsWith( QLatin1String( "PARAMETER" ) ) )
206  {
207  QStringList paramList = ellipsoid.split( ':' );
208  bool semiMajorOk, semiMinorOk;
209  const double semiMajor = paramList[1].toDouble( & semiMajorOk );
210  const double semiMinor = paramList[2].toDouble( & semiMinorOk );
211  if ( semiMajorOk && semiMinorOk )
212  {
213  params.semiMajor = semiMajor;
214  params.semiMinor = semiMinor;
215  params.inverseFlattening = semiMajor / ( semiMajor - semiMinor );
216  params.useCustomParameters = true;
217  }
218  else
219  {
220  params.valid = false;
221  }
222 
223  const QgsReadWriteLocker locker( *sEllipsoidCacheLock(), QgsReadWriteLocker::Write );
224  if ( !sDisableCache )
225  {
226  sEllipsoidCache()->insert( ellipsoid, params );
227  }
228  return params;
229  }
230  params.valid = false;
231 
232  const QgsReadWriteLocker l( *sEllipsoidCacheLock(), QgsReadWriteLocker::Write );
233  if ( !sDisableCache )
234  {
235  sEllipsoidCache()->insert( ellipsoid, params );
236  }
237 
238  return params;
239 }
240 
241 QList<QgsEllipsoidUtils::EllipsoidDefinition> QgsEllipsoidUtils::definitions()
242 {
243  QgsReadWriteLocker defLocker( *sDefinitionCacheLock(), QgsReadWriteLocker::Read );
244  if ( !sDefinitionCache()->isEmpty() )
245  {
246  return *sDefinitionCache();
247  }
249 
250  QList<QgsEllipsoidUtils::EllipsoidDefinition> defs;
251 
252  QgsReadWriteLocker locker( *sEllipsoidCacheLock(), QgsReadWriteLocker::Write );
253 
254  PJ_CONTEXT *context = QgsProjContext::get();
255  if ( PROJ_STRING_LIST authorities = proj_get_authorities_from_database( context ) )
256  {
257  PROJ_STRING_LIST authoritiesIt = authorities;
258  while ( char *authority = *authoritiesIt )
259  {
260  if ( PROJ_STRING_LIST codes = proj_get_codes_from_database( context, authority, PJ_TYPE_ELLIPSOID, 0 ) )
261  {
262  PROJ_STRING_LIST codesIt = codes;
263  while ( char *code = *codesIt )
264  {
265  const QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_create_from_database( context, authority, code, PJ_CATEGORY_ELLIPSOID, 0, nullptr ) );
266  if ( ellipsoid.get() )
267  {
269  QString name = QString( proj_get_name( ellipsoid.get() ) );
270  def.acronym = QStringLiteral( "%1:%2" ).arg( authority, code );
271  name.replace( '_', ' ' );
272  def.description = QStringLiteral( "%1 (%2:%3)" ).arg( name, authority, code );
273 
274 #if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=1)
275  def.celestialBodyName = proj_get_celestial_body_name( context, ellipsoid.get() );
276 #endif
277 
278  double semiMajor, semiMinor, invFlattening;
279  int semiMinorComputed = 0;
280  if ( proj_ellipsoid_get_parameters( context, ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
281  {
282  def.parameters.semiMajor = semiMajor;
283  def.parameters.semiMinor = semiMinor;
284  def.parameters.inverseFlattening = invFlattening;
285  if ( !semiMinorComputed )
286  def.parameters.crs.createFromProj( 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 ), false );
287  else if ( !qgsDoubleNear( def.parameters.inverseFlattening, 0.0 ) )
288  def.parameters.crs.createFromProj( 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 ), false );
289  else
290  def.parameters.crs.createFromProj( QStringLiteral( "+proj=longlat +a=%1 +no_defs +type=crs" ).arg( def.parameters.semiMajor, 0, 'g', 17 ), false );
291  }
292  else
293  {
294  def.parameters.valid = false;
295  }
296 
297  defs << def;
298  if ( !sDisableCache )
299  {
300  sEllipsoidCache()->insert( def.acronym, def.parameters );
301  }
302  }
303 
304  codesIt++;
305  }
306  proj_string_list_destroy( codes );
307  }
308 
309  authoritiesIt++;
310  }
311  proj_string_list_destroy( authorities );
312  }
313  locker.unlock();
314 
315  QCollator collator;
316  collator.setCaseSensitivity( Qt::CaseInsensitive );
317  std::sort( defs.begin(), defs.end(), [&collator]( const EllipsoidDefinition & a, const EllipsoidDefinition & b )
318  {
319  return collator.compare( a.description, b.description ) < 0;
320  } );
321  if ( !sDisableCache )
322  {
323  *sDefinitionCache() = defs;
324  }
325 
326  return defs;
327 }
328 
330 {
331  QStringList result;
332  const QList<QgsEllipsoidUtils::EllipsoidDefinition> defs = definitions();
333  result.reserve( defs.size() );
334  for ( const QgsEllipsoidUtils::EllipsoidDefinition &def : defs )
335  {
336  result << def.acronym;
337  }
338  return result;
339 }
340 
341 QList<QgsCelestialBody> QgsEllipsoidUtils::celestialBodies()
342 {
344 }
345 
346 void QgsEllipsoidUtils::invalidateCache( bool disableCache )
347 {
348  const QgsReadWriteLocker locker1( *sEllipsoidCacheLock(), QgsReadWriteLocker::Write );
349  const QgsReadWriteLocker locker2( *sDefinitionCacheLock(), QgsReadWriteLocker::Write );
350 
351  if ( !sDisableCache )
352  {
353  if ( disableCache )
354  sDisableCache = true;
355  sEllipsoidCache()->clear();
356  sDefinitionCache()->clear();
357  }
358 }
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
QList< QgsCelestialBody > celestialBodies() const
Returns a list of all known celestial bodies.
bool createFromProj(const QString &projString, bool identify=true)
Sets this CRS by passing it a PROJ style formatted string.
Contains utility functions for working with ellipsoids and querying the ellipsoid database.
static QList< QgsEllipsoidUtils::EllipsoidDefinition > definitions()
Returns a list of the definitions for all known ellipsoids from the internal ellipsoid database.
static QList< QgsCelestialBody > celestialBodies()
Returns a list of all known celestial bodies.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used.
static QStringList acronyms()
Returns a list of all known ellipsoid acronyms 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.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
Definition: qgsprojutils.h:141
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
@ Write
Lock for write.
@ Read
Lock for read.
void unlock()
Unlocks the lock.
void changeMode(Mode mode)
Change the mode of the lock to mode.
Scoped object for logging of the runtime for a single operation or group of operations.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:1234
struct projCtx_t PJ_CONTEXT
QList< QgsEllipsoidUtils::EllipsoidDefinition > EllipsoidDefinitionCache
QHash< QString, QgsEllipsoidUtils::EllipsoidParameters > EllipsoidParamCache
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
Contains definition of an ellipsoid.
QString acronym
authority:code for QGIS builds with proj version 6 or greater, or custom acronym for ellipsoid for ea...
QString celestialBodyName
Name of the associated celestial body (e.g.
QString description
Description of ellipsoid.
QgsEllipsoidUtils::EllipsoidParameters parameters
Ellipsoid parameters.
Contains parameters for an ellipsoid.
bool valid
Whether ellipsoid parameters are valid.
QgsCoordinateReferenceSystem crs
Associated coordinate reference system.
double inverseFlattening
Inverse flattening.
bool useCustomParameters
Whether custom parameters alone should be used (semiMajor/semiMinor only)