QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
22 QReadWriteLock QgsEllipsoidUtils::sEllipsoidCacheLock;
23 QHash< QString, QgsEllipsoidUtils::EllipsoidParameters > QgsEllipsoidUtils::sEllipsoidCache;
24 QReadWriteLock QgsEllipsoidUtils::sDefinitionCacheLock;
25 QList< QgsEllipsoidUtils::EllipsoidDefinition > QgsEllipsoidUtils::sDefinitionCache;
26 static bool sDisableCache = false;
27 
29 {
30  // check cache
31  {
32  sEllipsoidCacheLock.lockForRead();
33  if ( !sDisableCache )
34  {
35  QHash< QString, EllipsoidParameters >::const_iterator cacheIt = sEllipsoidCache.constFind( ellipsoid );
36  if ( cacheIt != sEllipsoidCache.constEnd() )
37  {
38  // found a match in the cache
39  QgsEllipsoidUtils::EllipsoidParameters params = cacheIt.value();
40  sEllipsoidCacheLock.unlock();
41  return params;
42  }
43  }
44  sEllipsoidCacheLock.unlock();
45  }
46 
47  EllipsoidParameters params;
48 
49  // Check if we have a custom projection, and set from text string.
50  // Format is "PARAMETER:<semi-major axis>:<semi minor axis>
51  // Numbers must be with (optional) decimal point and no other separators (C locale)
52  // Distances in meters. Flattening is calculated.
53  if ( ellipsoid.startsWith( QLatin1String( "PARAMETER" ) ) )
54  {
55  QStringList paramList = ellipsoid.split( ':' );
56  bool semiMajorOk, semiMinorOk;
57  double semiMajor = paramList[1].toDouble( & semiMajorOk );
58  double semiMinor = paramList[2].toDouble( & semiMinorOk );
59  if ( semiMajorOk && semiMinorOk )
60  {
61  params.semiMajor = semiMajor;
62  params.semiMinor = semiMinor;
63  params.inverseFlattening = semiMajor / ( semiMajor - semiMinor );
64  params.useCustomParameters = true;
65  }
66  else
67  {
68  params.valid = false;
69  }
70 
71  sEllipsoidCacheLock.lockForWrite();
72  if ( !sDisableCache )
73  {
74  sEllipsoidCache.insert( ellipsoid, params );
75  }
76  sEllipsoidCacheLock.unlock();
77  return params;
78  }
79 
80  // cache miss - get from database
81 
82  QString radius, parameter2;
83  //
84  // SQLITE3 stuff - get parameters for selected ellipsoid
85  //
88  // Continue with PROJ list of ellipsoids.
89 
90  //check the db is available
91  int result = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
92  if ( result )
93  {
94  QgsMessageLog::logMessage( QObject::tr( "Can not open srs database (%1): %2" ).arg( QgsApplication::srsDatabaseFilePath(), database.errorMessage() ) );
95  // XXX This will likely never happen since on open, sqlite creates the
96  // database if it does not exist.
97  return params;
98  }
99  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
100  QString sql = "select radius, parameter2 from tbl_ellipsoid where acronym='" + ellipsoid + '\'';
101  statement = database.prepare( sql, result );
102  // XXX Need to free memory from the error msg if one is set
103  if ( result == SQLITE_OK )
104  {
105  if ( statement.step() == SQLITE_ROW )
106  {
107  radius = statement.columnAsText( 0 );
108  parameter2 = statement.columnAsText( 1 );
109  }
110  }
111  // row for this ellipsoid wasn't found?
112  if ( radius.isEmpty() || parameter2.isEmpty() )
113  {
114  QgsDebugMsg( QStringLiteral( "setEllipsoid: no row in tbl_ellipsoid for acronym '%1'" ).arg( ellipsoid ) );
115  params.valid = false;
116  sEllipsoidCacheLock.lockForWrite();
117  if ( !sDisableCache )
118  {
119  sEllipsoidCache.insert( ellipsoid, params );
120  }
121  sEllipsoidCacheLock.unlock();
122  return params;
123  }
124 
125  // get major semiaxis
126  if ( radius.left( 2 ) == QLatin1String( "a=" ) )
127  params.semiMajor = radius.midRef( 2 ).toDouble();
128  else
129  {
130  QgsDebugMsg( QStringLiteral( "setEllipsoid: wrong format of radius field: '%1'" ).arg( radius ) );
131  params.valid = false;
132  sEllipsoidCacheLock.lockForWrite();
133  if ( !sDisableCache )
134  {
135  sEllipsoidCache.insert( ellipsoid, params );
136  }
137  sEllipsoidCacheLock.unlock();
138  return params;
139  }
140 
141  // get second parameter
142  // one of values 'b' or 'f' is in field parameter2
143  // second one must be computed using formula: invf = a/(a-b)
144  if ( parameter2.left( 2 ) == QLatin1String( "b=" ) )
145  {
146  params.semiMinor = parameter2.midRef( 2 ).toDouble();
147  params.inverseFlattening = params.semiMajor / ( params.semiMajor - params.semiMinor );
148  }
149  else if ( parameter2.left( 3 ) == QLatin1String( "rf=" ) )
150  {
151  params.inverseFlattening = parameter2.midRef( 3 ).toDouble();
152  params.semiMinor = params.semiMajor - ( params.semiMajor / params.inverseFlattening );
153  }
154  else
155  {
156  QgsDebugMsg( QStringLiteral( "setEllipsoid: wrong format of parameter2 field: '%1'" ).arg( parameter2 ) );
157  params.valid = false;
158  sEllipsoidCacheLock.lockForWrite();
159  if ( !sDisableCache )
160  {
161  sEllipsoidCache.insert( ellipsoid, params );
162  }
163  sEllipsoidCacheLock.unlock();
164  return params;
165  }
166 
167  QgsDebugMsgLevel( QStringLiteral( "setEllipsoid: a=%1, b=%2, 1/f=%3" ).arg( params.semiMajor ).arg( params.semiMinor ).arg( params.inverseFlattening ), 4 );
168 
169 
170  // get spatial ref system for ellipsoid
171  QString proj4 = "+proj=longlat +ellps=" + ellipsoid + " +no_defs";
173  //TODO: createFromProj4 used to save to the user database any new CRS
174  // this behavior was changed in order to separate creation and saving.
175  // Not sure if it necessary to save it here, should be checked by someone
176  // familiar with the code (should also give a more descriptive name to the generated CRS)
177  if ( destCRS.srsid() == 0 )
178  {
179  QString name = QStringLiteral( " * %1 (%2)" )
180  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
181  destCRS.toProj4() );
182  destCRS.saveAsUserCrs( name );
183  }
184  //
185 
186  // set transformation from project CRS to ellipsoid coordinates
187  params.crs = destCRS;
188 
189  sEllipsoidCacheLock.lockForWrite();
190  if ( !sDisableCache )
191  {
192  sEllipsoidCache.insert( ellipsoid, params );
193  }
194  sEllipsoidCacheLock.unlock();
195 
196  return params;
197 }
198 
199 QList<QgsEllipsoidUtils::EllipsoidDefinition> QgsEllipsoidUtils::definitions()
200 {
201  sDefinitionCacheLock.lockForRead();
202  if ( !sDefinitionCache.isEmpty() )
203  {
204  QList<QgsEllipsoidUtils::EllipsoidDefinition> defs = sDefinitionCache;
205  sDefinitionCacheLock.unlock();
206  return defs;
207  }
208  sDefinitionCacheLock.unlock();
209 
210  sDefinitionCacheLock.lockForWrite();
211 
214  int result;
215 
216  QList<QgsEllipsoidUtils::EllipsoidDefinition> defs;
217 
218  //check the db is available
219  result = database.open_v2( QgsApplication::srsDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
220  if ( result )
221  {
222  QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
223  // XXX This will likely never happen since on open, sqlite creates the
224  // database if it does not exist.
225  Q_ASSERT( result == 0 );
226  }
227 
228  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
229  QString sql = QStringLiteral( "select acronym, name from tbl_ellipsoid order by name" );
230  statement = database.prepare( sql, result );
231 
232  if ( result == SQLITE_OK )
233  {
234  while ( statement.step() == SQLITE_ROW )
235  {
237  def.acronym = statement.columnAsText( 0 );
238  def.description = statement.columnAsText( 1 );
239 
240  // use ellipsoidParameters so that result is cached
242 
243  defs << def;
244  }
245  }
246 
247  if ( !sDisableCache )
248  {
249  sDefinitionCache = defs;
250  }
251  sDefinitionCacheLock.unlock();
252 
253  return defs;
254 }
255 
257 {
258  QStringList result;
259  Q_FOREACH ( const QgsEllipsoidUtils::EllipsoidDefinition &def, definitions() )
260  {
261  result << def.acronym;
262  }
263  return result;
264 }
265 
266 void QgsEllipsoidUtils::invalidateCache( bool disableCache )
267 {
268  sEllipsoidCacheLock.lockForWrite();
269  sDefinitionCacheLock.lockForWrite();
270 
271  if ( !sDisableCache )
272  {
273  if ( disableCache )
274  sDisableCache = true;
275  sEllipsoidCache.clear();
276  sDefinitionCache.clear();
277  }
278 
279  sDefinitionCacheLock.unlock();
280  sEllipsoidCacheLock.unlock();
281 }
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
long srsid() const
Returns the internal CRS ID, if available.
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
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.
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 acronym
Acronym for ellipsoid.
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
This class represents a coordinate reference system (CRS).
double inverseFlattening
Inverse flattening.
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...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
static QStringList acronyms()
Returns a list of all known ellipsoid acronyms from the internal ellipsoid database.
QString errorMessage() const
Returns the most recent error message encountered by the database.
QString description
Description of ellipsoid.
QString toProj4() const
Returns a Proj4 string representation of this CRS.