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