QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
qgscoordinatereferencesystemregistry.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinatereferencesystemregistry.cpp
3  -------------------
4  begin : January 2021
5  copyright : (C) 2021 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
20 #include "qgscoordinatetransform.h"
21 #include "qgsapplication.h"
22 #include "qgslogger.h"
23 #include "qgsmessagelog.h"
24 #include "qgssqliteutils.h"
25 
26 #include <sqlite3.h>
27 
29  : QObject( parent )
30 {
31 
32 }
33 
34 QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> QgsCoordinateReferenceSystemRegistry::userCrsList() const
35 {
36  QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> res;
37 
38  //Setup connection to the existing custom CRS database:
40  //check the db is available
41  int result = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
42  if ( result != SQLITE_OK )
43  {
44  QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
45  return res;
46  }
47 
48  const QString sql = QStringLiteral( "select srs_id,description,parameters, wkt from tbl_srs" );
49  QgsDebugMsgLevel( QStringLiteral( "Query to populate existing list:%1" ).arg( sql ), 4 );
50  sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
51  if ( result == SQLITE_OK )
52  {
54  while ( preparedStatement.step() == SQLITE_ROW )
55  {
56  UserCrsDetails details;
57  details.id = preparedStatement.columnAsText( 0 ).toLong();
58  details.name = preparedStatement.columnAsText( 1 );
59  details.proj = preparedStatement.columnAsText( 2 );
60  details.wkt = preparedStatement.columnAsText( 3 );
61 
62  if ( !details.wkt.isEmpty() )
63  details.crs.createFromWkt( details.wkt );
64  else
65  details.crs.createFromProj( details.proj );
66 
67  res << details;
68  }
69  }
70  return res;
71 }
72 
74 {
75  if ( !crs.isValid() )
76  {
77  QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
78  return -1;
79  }
80 
81  QString mySql;
82 
83  QString proj4String = crs.d->mProj4;
84  if ( proj4String.isEmpty() )
85  {
86  proj4String = crs.toProj();
87  }
89 
90  // ellipsoid acroynym column is incorrectly marked as not null in many crs database instances,
91  // hack around this by using an empty string instead
92  const QString quotedEllipsoidString = crs.ellipsoidAcronym().isNull() ? QStringLiteral( "''" ) : QgsSqliteUtils::quotedString( crs.ellipsoidAcronym() );
93 
94  //if this is the first record we need to ensure that its srs_id is 10000. For
95  //any rec after that sqlite3 will take care of the autonumbering
96  //this was done to support sqlite 3.0 as it does not yet support
97  //the autoinc related system tables.
98  if ( QgsCoordinateReferenceSystem::getRecordCount() == 0 )
99  {
100  mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo,wkt) values ("
101  + QString::number( USER_CRS_START_ID )
102  + ',' + QgsSqliteUtils::quotedString( name )
103  + ',' + ( !crs.d->mProjectionAcronym.isEmpty() ? QgsSqliteUtils::quotedString( crs.d->mProjectionAcronym ) : QStringLiteral( "''" ) )
104  + ',' + quotedEllipsoidString
105  + ',' + ( !proj4String.isEmpty() ? QgsSqliteUtils::quotedString( proj4String ) : QStringLiteral( "''" ) )
106  + ",0," // <-- is_geo shamelessly hard coded for now
107  + ( nativeFormat == QgsCoordinateReferenceSystem::FormatWkt ? QgsSqliteUtils::quotedString( wktString ) : QStringLiteral( "''" ) )
108  + ')';
109  }
110  else
111  {
112  mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo,wkt) values ("
114  + ',' + ( !crs.d->mProjectionAcronym.isEmpty() ? QgsSqliteUtils::quotedString( crs.d->mProjectionAcronym ) : QStringLiteral( "''" ) )
115  + ',' + quotedEllipsoidString
116  + ',' + ( !proj4String.isEmpty() ? QgsSqliteUtils::quotedString( proj4String ) : QStringLiteral( "''" ) )
117  + ",0," // <-- is_geo shamelessly hard coded for now
118  + ( nativeFormat == QgsCoordinateReferenceSystem::FormatWkt ? QgsSqliteUtils::quotedString( wktString ) : QStringLiteral( "''" ) )
119  + ')';
120  }
123  //check the db is available
124  int myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
125  if ( myResult != SQLITE_OK )
126  {
127  QgsDebugMsg( QStringLiteral( "Can't open or create database %1: %2" )
129  database.errorMessage() ) );
130  return false;
131  }
132  statement = database.prepare( mySql, myResult );
133 
134  qint64 returnId = -1;
135  if ( myResult == SQLITE_OK && statement.step() == SQLITE_DONE )
136  {
137  QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
138 
139  returnId = sqlite3_last_insert_rowid( database.get() );
140  crs.d->mSrsId = returnId;
141  crs.d->mAuthId = QStringLiteral( "USER:%1" ).arg( returnId );
142  crs.d->mDescription = name;
143  }
144 
145  if ( returnId != -1 )
146  {
147  // If we have a projection acronym not in the user db previously, add it.
148  // This is a must, or else we can't select it from the vw_srs table.
149  // Actually, add it always and let the SQL PRIMARY KEY remove duplicates.
150  insertProjection( crs.projectionAcronym() );
151  }
152 
155 
156  if ( returnId != -1 )
157  {
158  emit userCrsAdded( crs.d->mAuthId );
159  emit crsDefinitionsChanged();
160  }
161 
162  return returnId;
163 }
164 
166 {
167  if ( !crs.isValid() )
168  {
169  QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
170  return false;
171  }
172 
173  const QString sql = "update tbl_srs set description="
175  + ",projection_acronym=" + ( !crs.projectionAcronym().isEmpty() ? QgsSqliteUtils::quotedString( crs.projectionAcronym() ) : QStringLiteral( "''" ) )
176  + ",ellipsoid_acronym=" + ( !crs.ellipsoidAcronym().isEmpty() ? QgsSqliteUtils::quotedString( crs.ellipsoidAcronym() ) : QStringLiteral( "''" ) )
177  + ",parameters=" + ( !crs.toProj().isEmpty() ? QgsSqliteUtils::quotedString( crs.toProj() ) : QStringLiteral( "''" ) )
178  + ",is_geo=0" // <--shamelessly hard coded for now
179  + ",wkt=" + ( nativeFormat == QgsCoordinateReferenceSystem::FormatWkt ? QgsSqliteUtils::quotedString( crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, false ) ) : QStringLiteral( "''" ) )
180  + " where srs_id=" + QgsSqliteUtils::quotedString( QString::number( id ) )
181  ;
182 
184  //check the db is available
185  int myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
186  if ( myResult != SQLITE_OK )
187  {
188  QgsDebugMsg( QStringLiteral( "Can't open or create database %1: %2" )
190  database.errorMessage() ) );
191  return false;
192  }
193 
194  bool res = true;
195  QString errorMessage;
196  if ( database.exec( sql, errorMessage ) != SQLITE_OK )
197  {
198  QgsMessageLog::logMessage( QObject::tr( "Error saving user CRS [%1]: %2" ).arg( crs.toProj(), errorMessage ), QObject::tr( "CRS" ) );
199  res = false;
200  }
201  else
202  {
203  const int changed = sqlite3_changes( database.get() );
204  if ( changed )
205  {
206  QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
207  }
208  else
209  {
210  QgsMessageLog::logMessage( QObject::tr( "Error saving user CRS [%1]: No matching ID found in database" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
211  res = false;
212  }
213  }
214 
215  if ( res )
216  {
217  // If we have a projection acronym not in the user db previously, add it.
218  // This is a must, or else we can't select it from the vw_srs table.
219  // Actually, add it always and let the SQL PRIMARY KEY remove duplicates.
220  insertProjection( crs.projectionAcronym() );
221  }
222 
225 
226  if ( res )
227  {
228  emit userCrsChanged( crs.d->mAuthId );
229  emit crsDefinitionsChanged();
230  }
231 
232  return res;
233 }
234 
236 {
238 
239  QString sql = "delete from tbl_srs where srs_id=" + QgsSqliteUtils::quotedString( QString::number( id ) );
240  QgsDebugMsgLevel( sql, 4 );
241  //check the db is available
242  int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
243  if ( result != SQLITE_OK )
244  {
245  QgsDebugMsg( QStringLiteral( "Can't open database: %1 \n please notify QGIS developers of this error \n %2 (file name) " ).arg( database.errorMessage(),
247  return false;
248  }
249 
250  bool res = true;
251  {
252  sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
253  if ( result != SQLITE_OK || preparedStatement.step() != SQLITE_DONE )
254  {
255  QgsDebugMsg( QStringLiteral( "failed to remove custom CRS from database: %1 [%2]" ).arg( sql, database.errorMessage() ) );
256  res = false;
257  }
258  else
259  {
260  const int changed = sqlite3_changes( database.get() );
261  if ( changed )
262  {
263  QgsMessageLog::logMessage( QObject::tr( "Removed user CRS [%1]" ).arg( id ), QObject::tr( "CRS" ) );
264  }
265  else
266  {
267  QgsMessageLog::logMessage( QObject::tr( "Error removing user CRS [%1]: No matching ID found in database" ).arg( id ), QObject::tr( "CRS" ) );
268  res = false;
269  }
270  }
271  }
272 
275 
276  if ( res )
277  {
278  emit userCrsRemoved( id );
279  emit crsDefinitionsChanged();
280  }
281 
282  return res;
283 }
284 
285 bool QgsCoordinateReferenceSystemRegistry::insertProjection( const QString &projectionAcronym )
286 {
288  sqlite3_database_unique_ptr srsDatabase;
289  QString sql;
290  //check the db is available
291  int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
292  if ( result != SQLITE_OK )
293  {
294  QgsDebugMsg( QStringLiteral( "Can't open database: %1 \n please notify QGIS developers of this error \n %2 (file name) " ).arg( database.errorMessage(),
296  return false;
297  }
298  int srsResult = srsDatabase.open( QgsApplication::srsDatabaseFilePath() );
299  if ( result != SQLITE_OK )
300  {
301  QgsDebugMsg( QStringLiteral( "Can't open database %1 [%2]" ).arg( QgsApplication::srsDatabaseFilePath(),
302  srsDatabase.errorMessage() ) );
303  return false;
304  }
305 
306  // Set up the query to retrieve the projection information needed to populate the PROJECTION list
307  QString srsSql = "select acronym,name,notes,parameters from tbl_projection where acronym=" + QgsSqliteUtils::quotedString( projectionAcronym );
308 
309  sqlite3_statement_unique_ptr srsPreparedStatement = srsDatabase.prepare( srsSql, srsResult );
310  if ( srsResult == SQLITE_OK )
311  {
312  if ( srsPreparedStatement.step() == SQLITE_ROW )
313  {
314  QgsDebugMsgLevel( QStringLiteral( "Trying to insert projection" ), 4 );
315  // We have the result from system srs.db. Now insert into user db.
316  sql = "insert into tbl_projection(acronym,name,notes,parameters) values ("
317  + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 0 ) )
318  + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 1 ) )
319  + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 2 ) )
320  + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 3 ) )
321  + ')';
322  sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
323  if ( result != SQLITE_OK || preparedStatement.step() != SQLITE_DONE )
324  {
325  QgsDebugMsg( QStringLiteral( "Could not insert projection into database: %1 [%2]" ).arg( sql, database.errorMessage() ) );
326  return false;
327  }
328  }
329  }
330  else
331  {
332  QgsDebugMsg( QStringLiteral( "prepare failed: %1 [%2]" ).arg( srsSql, srsDatabase.errorMessage() ) );
333  return false;
334  }
335 
336  return true;
337 }
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
QgsCoordinateReferenceSystem crs
QgsCoordinateReferenceSystem object representing the user-defined CRS.
void userCrsAdded(const QString &id)
Emitted whenever a new user CRS definition is added.
void userCrsChanged(const QString &id)
Emitted whenever an existing user CRS definition is changed.
void userCrsRemoved(long id)
Emitted when the user CRS with matching id is removed from the database.
void crsDefinitionsChanged()
Emitted whenever an operation has caused any of the known CRS definitions (including user-defined CRS...
bool updateUserCrs(long id, const QgsCoordinateReferenceSystem &crs, const QString &name, QgsCoordinateReferenceSystem::Format nativeFormat=QgsCoordinateReferenceSystem::FormatWkt)
Updates the definition of the existing user CRS with matching id.
QgsCoordinateReferenceSystemRegistry(QObject *parent=nullptr)
Constructor for QgsCoordinateReferenceSystemRegistry, with the specified parent object.
bool removeUserCrs(long id)
Removes the existing user CRS with matching id.
QList< QgsCoordinateReferenceSystemRegistry::UserCrsDetails > userCrsList() const
Returns a list containing the details of all registered custom (user-defined) CRSes.
long addUserCrs(const QgsCoordinateReferenceSystem &crs, const QString &name, QgsCoordinateReferenceSystem::Format nativeFormat=QgsCoordinateReferenceSystem::FormatWkt)
Adds a new crs definition as a custom ("USER") CRS.
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
bool createFromWkt(const QString &wkt)
Sets this CRS using a WKT definition.
QString toProj() const
Returns a Proj string representation of this CRS.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
bool createFromProj(const QString &projString, bool identify=true)
Sets this CRS by passing it a PROJ style formatted string.
Format
Projection definition formats.
@ FormatWkt
WKT format (always recommended over proj string format)
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateReferenceSystem objects.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateTransform objects.
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).
static QString quotedString(const QString &value)
Returns a quoted string value, surround by ' characters and with special characters correctly escaped...
Unique pointer for sqlite3 databases, which automatically closes the database when the pointer goes o...
sqlite3_statement_unique_ptr prepare(const QString &sql, int &resultCode) const
Prepares a sql statement, returning the result.
int open(const QString &path)
Opens the database at the specified file path.
QString errorMessage() const
Returns the most recent error message encountered by the database.
int open_v2(const QString &path, int flags, const char *zVfs)
Opens the database at the specified file path.
int exec(const QString &sql, QString &errorMessage) const
Executes the sql command in the database.
Unique pointer for sqlite3 prepared statements, which automatically finalizes the statement when the ...
QString columnAsText(int column) const
Returns the column value from the current statement row as a string.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:746
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs