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