QGIS API Documentation 4.3.0-Master (0cfde48c85b)
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#include <QMatrix3x3>
33#include <QString>
34
35using namespace Qt::StringLiterals;
36
37Q_GLOBAL_STATIC( QReadWriteLock, sEllipsoidCacheLock )
38typedef QHash< QString, QgsEllipsoidUtils::EllipsoidParameters > EllipsoidParamCache;
39Q_GLOBAL_STATIC( EllipsoidParamCache, sEllipsoidCache )
40
41Q_GLOBAL_STATIC( QReadWriteLock, sDefinitionCacheLock );
42typedef QList< QgsEllipsoidUtils::EllipsoidDefinition > EllipsoidDefinitionCache;
44
45static bool sDisableCache = false;
46
48{
49 // maps older QGIS ellipsoid acronyms to proj acronyms/names
50 static const QMap< QString, QString > sProj6EllipsoidAcronymMap {
51 { "clrk80", "clrk80ign" },
52 { "Adrastea2000", "ESRI:107909" },
53 { "Amalthea2000", "ESRI:107910" },
54 { "Ananke2000", "ESRI:107911" },
55 { "Ariel2000", "ESRI:107945" },
56 { "Atlas2000", "ESRI:107926" },
57 { "Belinda2000", "ESRI:107946" },
58 { "Bianca2000", "ESRI:107947" },
59 { "Callisto2000", "ESRI:107912" },
60 { "Calypso2000", "ESRI:107927" },
61 { "Carme2000", "ESRI:107913" },
62 { "Charon2000", "ESRI:107970" },
63 { "Cordelia2000", "ESRI:107948" },
64 { "Cressida2000", "ESRI:107949" },
65 { "Deimos2000", "ESRI:107906" },
66 { "Desdemona2000", "ESRI:107950" },
67 { "Despina2000", "ESRI:107961" },
68 { "Dione2000", "ESRI:107928" },
69 { "Elara2000", "ESRI:107914" },
70 { "Enceladus2000", "ESRI:107929" },
71 { "Epimetheus2000", "ESRI:107930" },
72 { "Europa2000", "ESRI:107915" },
73 { "Galatea2000", "ESRI:107962" },
74 { "Ganymede2000", "ESRI:107916" },
75 { "Helene2000", "ESRI:107931" },
76 { "Himalia2000", "ESRI:107917" },
77 { "Hyperion2000", "ESRI:107932" },
78 { "Iapetus2000", "ESRI:107933" },
79 { "Io2000", "ESRI:107918" },
80 { "Janus2000", "ESRI:107934" },
81 { "Juliet2000", "ESRI:107951" },
82 { "Jupiter2000", "ESRI:107908" },
83 { "Larissa2000", "ESRI:107963" },
84 { "Leda2000", "ESRI:107919" },
85 { "Lysithea2000", "ESRI:107920" },
86 { "Mars2000", "ESRI:107905" },
87 { "Mercury2000", "ESRI:107900" },
88 { "Metis2000", "ESRI:107921" },
89 { "Mimas2000", "ESRI:107935" },
90 { "Miranda2000", "ESRI:107952" },
91 { "Moon2000", "ESRI:107903" },
92 { "Naiad2000", "ESRI:107964" },
93 { "Neptune2000", "ESRI:107960" },
94 { "Nereid2000", "ESRI:107965" },
95 { "Oberon2000", "ESRI:107953" },
96 { "Ophelia2000", "ESRI:107954" },
97 { "Pan2000", "ESRI:107936" },
98 { "Pandora2000", "ESRI:107937" },
99 { "Pasiphae2000", "ESRI:107922" },
100 { "Phobos2000", "ESRI:107907" },
101 { "Phoebe2000", "ESRI:107938" },
102 { "Pluto2000", "ESRI:107969" },
103 { "Portia2000", "ESRI:107955" },
104 { "Prometheus2000", "ESRI:107939" },
105 { "Proteus2000", "ESRI:107966" },
106 { "Puck2000", "ESRI:107956" },
107 { "Rhea2000", "ESRI:107940" },
108 { "Rosalind2000", "ESRI:107957" },
109 { "Saturn2000", "ESRI:107925" },
110 { "Sinope2000", "ESRI:107923" },
111 { "Telesto2000", "ESRI:107941" },
112 { "Tethys2000", "ESRI:107942" },
113 { "Thalassa2000", "ESRI:107967" },
114 { "Thebe2000", "ESRI:107924" },
115 { "Titan2000", "ESRI:107943" },
116 { "Titania2000", "ESRI:107958" },
117 { "Triton2000", "ESRI:107968" },
118 { "Umbriel2000", "ESRI:107959" },
119 { "Uranus2000", "ESRI:107944" },
120 { "Venus2000", "ESRI:107902" },
121 { "IGNF:ELG053", "EPSG:7030" },
122 { "IGNF:ELG052", "EPSG:7043" },
123 { "IGNF:ELG102", "EPSG:7043" },
124 { "WGS66", "ESRI:107001" },
125 { "plessis", "EPSG:7027" },
126 { "IGNF:ELG017", "EPSG:7027" },
127 { "mod_airy", "EPSG:7002" },
128 { "IGNF:ELG037", "EPSG:7019" },
129 { "IGNF:ELG108", "EPSG:7036" },
130 { "cape", "EPSG:7034" },
131 { "IGNF:ELG010", "EPSG:7011" },
132 { "IGNF:ELG003", "EPSG:7012" },
133 { "IGNF:ELG004", "EPSG:7008" },
134 { "GSK2011", "EPSG:1025" },
135 { "airy", "EPSG:7001" },
136 { "aust_SA", "EPSG:7003" },
137 { "bessel", "EPSG:7004" },
138 { "clrk66", "EPSG:7008" },
139 { "clrk80ign", "EPSG:7011" },
140 { "evrst30", "EPSG:7015" },
141 { "evrstSS", "EPSG:7016" },
142 { "evrst48", "EPSG:7018" },
143 { "GRS80", "EPSG:7019" },
144 { "helmert", "EPSG:7020" },
145 { "intl", "EPSG:7022" },
146 { "krass", "EPSG:7024" },
147 { "NWL9D", "EPSG:7025" },
148 { "WGS84", "EPSG:7030" },
149 { "GRS67", "EPSG:7036" },
150 { "WGS72", "EPSG:7043" },
151 { "bess_nam", "EPSG:7046" },
152 { "IAU76", "EPSG:7049" },
153 { "sphere", "EPSG:7052" },
154 { "hough", "EPSG:7053" },
155 { "evrst69", "EPSG:7056" },
156 { "fschr60", "ESRI:107002" },
157 { "fschr68", "ESRI:107003" },
158 { "fschr60m", "ESRI:107004" },
159 { "walbeck", "ESRI:107007" },
160 { "IGNF:ELG001", "EPSG:7022" },
161 { "engelis", "EPSG:7054" },
162 { "evrst56", "EPSG:7044" },
163 { "SEasia", "ESRI:107004" },
164 { "SGS85", "EPSG:7054" },
165 { "andrae", "PROJ:ANDRAE" },
166 { "clrk80", "EPSG:7034" },
167 { "CPM", "PROJ:CPM" },
168 { "delmbr", "PROJ:DELMBR" },
169 { "Earth2000", "PROJ:EARTH2000" },
170 { "kaula", "PROJ:KAULA" },
171 { "lerch", "PROJ:LERCH" },
172 { "MERIT", "PROJ:MERIT" },
173 { "mprts", "PROJ:MPRTS" },
174 { "new_intl", "PROJ:NEW_INTL" },
175 { "WGS60", "PROJ:WGS60" }
176 };
177
178 QString ellipsoid = e;
179 // ensure ellipsoid database is populated when first called
180 static std::once_flag initialized;
181 std::call_once( initialized, [] {
182 const QgsScopedRuntimeProfile profile( QObject::tr( "Initialize ellipsoids" ) );
183 ( void ) definitions();
184 } );
185
186 ellipsoid = sProj6EllipsoidAcronymMap.value( ellipsoid, ellipsoid ); // silently upgrade older QGIS acronyms to proj acronyms
187
188 // check cache
189 {
190 const QgsReadWriteLocker locker( *sEllipsoidCacheLock(), QgsReadWriteLocker::Read );
191 if ( !sDisableCache )
192 {
193 const QHash< QString, EllipsoidParameters >::const_iterator cacheIt = sEllipsoidCache()->constFind( ellipsoid );
194 if ( cacheIt != sEllipsoidCache()->constEnd() )
195 {
196 // found a match in the cache
197 QgsEllipsoidUtils::EllipsoidParameters params = cacheIt.value();
198 return params;
199 }
200 }
201 }
202
203 EllipsoidParameters params;
204
205 // Check if we have a custom projection, and set from text string.
206 // Format is "PARAMETER:<semi-major axis>:<semi minor axis>
207 // Numbers must be with (optional) decimal point and no other separators (C locale)
208 // Distances in meters. Flattening is calculated.
209 if ( ellipsoid.startsWith( "PARAMETER"_L1 ) )
210 {
211 QStringList paramList = ellipsoid.split( ':' );
212 bool semiMajorOk, semiMinorOk;
213 const double semiMajor = paramList[1].toDouble( &semiMajorOk );
214 const double semiMinor = paramList[2].toDouble( &semiMinorOk );
215 if ( semiMajorOk && semiMinorOk )
216 {
217 params.semiMajor = semiMajor;
218 params.semiMinor = semiMinor;
219 params.inverseFlattening = semiMajor / ( semiMajor - semiMinor );
220 params.useCustomParameters = true;
221 params.crs.createFromProj( u"+proj=longlat +a=%1 +b=%2 +no_defs +type=crs"_s.arg( params.semiMajor, 0, 'g', 17 ).arg( params.semiMinor, 0, 'g', 17 ) );
222 }
223 else
224 {
225 params.valid = false;
226 }
227
228 const QgsReadWriteLocker locker( *sEllipsoidCacheLock(), QgsReadWriteLocker::Write );
229 if ( !sDisableCache )
230 {
231 sEllipsoidCache()->insert( ellipsoid, params );
232 }
233 return params;
234 }
235 params.valid = false;
236
237 const QgsReadWriteLocker l( *sEllipsoidCacheLock(), QgsReadWriteLocker::Write );
238 if ( !sDisableCache )
239 {
240 sEllipsoidCache()->insert( ellipsoid, params );
241 }
242
243 return params;
244}
245
246QList<QgsEllipsoidUtils::EllipsoidDefinition> QgsEllipsoidUtils::definitions()
247{
248 QgsReadWriteLocker defLocker( *sDefinitionCacheLock(), QgsReadWriteLocker::Read );
249 if ( !sDefinitionCache()->isEmpty() )
250 {
251 return *sDefinitionCache();
252 }
254
255 QList<QgsEllipsoidUtils::EllipsoidDefinition> defs;
256
257 QgsReadWriteLocker locker( *sEllipsoidCacheLock(), QgsReadWriteLocker::Write );
258
259 PJ_CONTEXT *context = QgsProjContext::get();
260 if ( PROJ_STRING_LIST authorities = proj_get_authorities_from_database( context ) )
261 {
262 PROJ_STRING_LIST authoritiesIt = authorities;
263 while ( char *authority = *authoritiesIt )
264 {
265 if ( PROJ_STRING_LIST codes = proj_get_codes_from_database( context, authority, PJ_TYPE_ELLIPSOID, 0 ) )
266 {
267 PROJ_STRING_LIST codesIt = codes;
268 while ( char *code = *codesIt )
269 {
270 const QgsProjUtils::proj_pj_unique_ptr ellipsoid( proj_create_from_database( context, authority, code, PJ_CATEGORY_ELLIPSOID, 0, nullptr ) );
271 if ( ellipsoid )
272 {
274 QString name = QString( proj_get_name( ellipsoid.get() ) );
275 def.acronym = u"%1:%2"_s.arg( authority, code );
276 name.replace( '_', ' ' );
277 def.description = u"%1 (%2:%3)"_s.arg( name, authority, code );
278
279 def.celestialBodyName = proj_get_celestial_body_name( context, ellipsoid.get() );
280
281 double semiMajor, semiMinor, invFlattening;
282 int semiMinorComputed = 0;
283 if ( proj_ellipsoid_get_parameters( context, ellipsoid.get(), &semiMajor, &semiMinor, &semiMinorComputed, &invFlattening ) )
284 {
285 def.parameters.semiMajor = semiMajor;
286 def.parameters.semiMinor = semiMinor;
287 def.parameters.inverseFlattening = invFlattening;
288 if ( !semiMinorComputed )
289 def.parameters.crs.createFromProj( u"+proj=longlat +a=%1 +b=%2 +no_defs +type=crs"_s.arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.semiMinor, 0, 'g', 17 ), false );
290 else if ( !qgsDoubleNear( def.parameters.inverseFlattening, 0.0 ) )
291 def.parameters.crs.createFromProj( u"+proj=longlat +a=%1 +rf=%2 +no_defs +type=crs"_s.arg( def.parameters.semiMajor, 0, 'g', 17 ).arg( def.parameters.inverseFlattening, 0, 'g', 17 ), false );
292 else
293 def.parameters.crs.createFromProj( u"+proj=longlat +a=%1 +no_defs +type=crs"_s.arg( def.parameters.semiMajor, 0, 'g', 17 ), false );
294 }
295 else
296 {
297 def.parameters.valid = false;
298 }
299
300 defs << def;
301 if ( !sDisableCache )
302 {
303 sEllipsoidCache()->insert( def.acronym, def.parameters );
304 }
305 }
306
307 codesIt++;
308 }
309 proj_string_list_destroy( codes );
310 }
311
312 authoritiesIt++;
313 }
314 proj_string_list_destroy( authorities );
315 }
316 locker.unlock();
317
318 QCollator collator;
319 collator.setCaseSensitivity( Qt::CaseInsensitive );
320 std::sort( defs.begin(), defs.end(), [&collator]( const EllipsoidDefinition &a, const EllipsoidDefinition &b ) { return collator.compare( a.description, b.description ) < 0; } );
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
345
346void 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}
359
360QQuaternion QgsEllipsoidUtils::quaternionFromNormalUpRight( const QVector3D &normalUp, const QVector3D &normalRight )
361{
362 const QVector3D right = normalRight.normalized();
363 const QVector3D up = normalUp.normalized();
364 // In a right-handed coordinate system with X=right, Z=up:
365 // Y = cross(up, right) = forward (pointing North for ENU)
366 const QVector3D forward = QVector3D::crossProduct( up, right ).normalized();
367
368 // Build rotation matrix with columns [right, forward, up]
369 // QGenericMatrix constructor takes row-major input
370 const float matData[9] = { right.x(), forward.x(), up.x(), right.y(), forward.y(), up.y(), right.z(), forward.z(), up.z() };
371 const QMatrix3x3 rotMatrix( matData );
372 return QQuaternion::fromRotationMatrix( rotMatrix );
373}
374
375QQuaternion QgsEllipsoidUtils::ellipsoidEastNorthUpRotation( const QgsVector3D &position, double semiMajorAxis, double semiMinorAxis )
376{
377 QgsVector3D up( position.x() / ( semiMajorAxis * semiMajorAxis ), position.y() / ( semiMajorAxis * semiMajorAxis ), position.z() / ( semiMinorAxis * semiMinorAxis ) );
378 up.normalize();
379
380 QgsVector3D east( -position.y(), position.x(), 0.0 );
381 if ( east.length() < 1e-6 )
382 {
383 east = QgsVector3D( 1.0, 0.0, 0.0 );
384 }
385 else
386 {
387 east.normalize();
388 }
389
391}
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 QQuaternion ellipsoidEastNorthUpRotation(const QgsVector3D &position, double semiMajorAxis, double semiMinorAxis)
Returns a rotation quaternion for the east-north-up (ENU) reference frame at position on the ellipsoi...
static QList< QgsCelestialBody > celestialBodies()
Returns a list of all known celestial bodies.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used.
static QQuaternion quaternionFromNormalUpRight(const QVector3D &normalUp, const QVector3D &normalRight)
Builds a rotation quaternion from an "up" direction and a "right" direction, with the remaining basis...
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.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:33
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:60
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:62
QVector3D toVector3D() const
Converts the current object to QVector3D.
double x() const
Returns X coordinate.
Definition qgsvector3d.h:58
void normalize()
Normalizes the current vector in place.
double length() const
Returns the length of the vector.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:7725
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).