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