QGIS API Documentation 3.35.0-Master (ffb31addd7f)
Loading...
Searching...
No Matches
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
21#include "qgsapplication.h"
22#include "qgslogger.h"
23#include "qgsmessagelog.h"
24#include "qgssqliteutils.h"
25#include "qgscelestialbody.h"
26#include "qgsprojutils.h"
27#include "qgsruntimeprofiler.h"
28#include "qgsexception.h"
29#include "qgsprojoperation.h"
30
31#include <QFileInfo>
32#include <sqlite3.h>
33#include <mutex>
34#include <proj.h>
35
37 : QObject( parent )
38{
39
40}
41
43
44QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> QgsCoordinateReferenceSystemRegistry::userCrsList() const
45{
46 QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> res;
47
48 //Setup connection to the existing custom CRS database:
50 //check the db is available
51 int result = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
52 if ( result != SQLITE_OK )
53 {
54 QgsDebugError( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
55 return res;
56 }
57
58 const QString sql = QStringLiteral( "select srs_id,description,parameters, wkt from tbl_srs" );
59 QgsDebugMsgLevel( QStringLiteral( "Query to populate existing list:%1" ).arg( sql ), 4 );
60 sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
61 if ( result == SQLITE_OK )
62 {
64 while ( preparedStatement.step() == SQLITE_ROW )
65 {
66 UserCrsDetails details;
67 details.id = preparedStatement.columnAsText( 0 ).toLong();
68 details.name = preparedStatement.columnAsText( 1 );
69 details.proj = preparedStatement.columnAsText( 2 );
70 details.wkt = preparedStatement.columnAsText( 3 );
71
72 if ( !details.wkt.isEmpty() )
73 details.crs.createFromWkt( details.wkt );
74 else
75 details.crs.createFromProj( details.proj );
76
77 res << details;
78 }
79 }
80 return res;
81}
82
84{
85 if ( !crs.isValid() )
86 {
87 QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
88 return -1;
89 }
90
91 QString mySql;
92
93 QString proj4String = crs.d->mProj4;
94 if ( proj4String.isEmpty() )
95 {
96 proj4String = crs.toProj();
97 }
98 const QString wktString = crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
99
100 // ellipsoid acroynym column is incorrectly marked as not null in many crs database instances,
101 // hack around this by using an empty string instead
102 const QString quotedEllipsoidString = crs.ellipsoidAcronym().isNull() ? QStringLiteral( "''" ) : QgsSqliteUtils::quotedString( crs.ellipsoidAcronym() );
103
104 //if this is the first record we need to ensure that its srs_id is 10000. For
105 //any rec after that sqlite3 will take care of the autonumbering
106 //this was done to support sqlite 3.0 as it does not yet support
107 //the autoinc related system tables.
108 if ( QgsCoordinateReferenceSystem::getRecordCount() == 0 )
109 {
110 mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo,wkt) values ("
111 + QString::number( USER_CRS_START_ID )
112 + ',' + QgsSqliteUtils::quotedString( name )
113 + ',' + ( !crs.d->mProjectionAcronym.isEmpty() ? QgsSqliteUtils::quotedString( crs.d->mProjectionAcronym ) : QStringLiteral( "''" ) )
114 + ',' + quotedEllipsoidString
115 + ',' + ( !proj4String.isEmpty() ? QgsSqliteUtils::quotedString( proj4String ) : QStringLiteral( "''" ) )
116 + ",0," // <-- is_geo shamelessly hard coded for now
117 + ( nativeFormat == Qgis::CrsDefinitionFormat::Wkt ? QgsSqliteUtils::quotedString( wktString ) : QStringLiteral( "''" ) )
118 + ')';
119 }
120 else
121 {
122 mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo,wkt) values ("
124 + ',' + ( !crs.d->mProjectionAcronym.isEmpty() ? QgsSqliteUtils::quotedString( crs.d->mProjectionAcronym ) : QStringLiteral( "''" ) )
125 + ',' + quotedEllipsoidString
126 + ',' + ( !proj4String.isEmpty() ? QgsSqliteUtils::quotedString( proj4String ) : QStringLiteral( "''" ) )
127 + ",0," // <-- is_geo shamelessly hard coded for now
128 + ( nativeFormat == Qgis::CrsDefinitionFormat::Wkt ? QgsSqliteUtils::quotedString( wktString ) : QStringLiteral( "''" ) )
129 + ')';
130 }
133 //check the db is available
134 int myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
135 if ( myResult != SQLITE_OK )
136 {
137 QgsDebugError( QStringLiteral( "Can't open or create database %1: %2" )
139 database.errorMessage() ) );
140 return false;
141 }
142 statement = database.prepare( mySql, myResult );
143
144 qint64 returnId = -1;
145 if ( myResult == SQLITE_OK && statement.step() == SQLITE_DONE )
146 {
147 QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
148
149 returnId = sqlite3_last_insert_rowid( database.get() );
150 crs.d->mSrsId = returnId;
151 crs.d->mAuthId = QStringLiteral( "USER:%1" ).arg( returnId );
152 crs.d->mDescription = name;
153 }
154
155 if ( returnId != -1 )
156 {
157 // If we have a projection acronym not in the user db previously, add it.
158 // This is a must, or else we can't select it from the vw_srs table.
159 // Actually, add it always and let the SQL PRIMARY KEY remove duplicates.
160 insertProjection( crs.projectionAcronym() );
161 }
162
165
166 if ( returnId != -1 )
167 {
168 emit userCrsAdded( crs.d->mAuthId );
170 }
171
172 return returnId;
173}
174
176{
177 if ( !crs.isValid() )
178 {
179 QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
180 return false;
181 }
182
183 const QString sql = "update tbl_srs set description="
185 + ",projection_acronym=" + ( !crs.projectionAcronym().isEmpty() ? QgsSqliteUtils::quotedString( crs.projectionAcronym() ) : QStringLiteral( "''" ) )
186 + ",ellipsoid_acronym=" + ( !crs.ellipsoidAcronym().isEmpty() ? QgsSqliteUtils::quotedString( crs.ellipsoidAcronym() ) : QStringLiteral( "''" ) )
187 + ",parameters=" + ( !crs.toProj().isEmpty() ? QgsSqliteUtils::quotedString( crs.toProj() ) : QStringLiteral( "''" ) )
188 + ",is_geo=0" // <--shamelessly hard coded for now
189 + ",wkt=" + ( nativeFormat == Qgis::CrsDefinitionFormat::Wkt ? QgsSqliteUtils::quotedString( crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, false ) ) : QStringLiteral( "''" ) )
190 + " where srs_id=" + QgsSqliteUtils::quotedString( QString::number( id ) )
191 ;
192
194 //check the db is available
195 const int myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
196 if ( myResult != SQLITE_OK )
197 {
198 QgsDebugError( QStringLiteral( "Can't open or create database %1: %2" )
200 database.errorMessage() ) );
201 return false;
202 }
203
204 bool res = true;
205 QString errorMessage;
206 if ( database.exec( sql, errorMessage ) != SQLITE_OK )
207 {
208 QgsMessageLog::logMessage( QObject::tr( "Error saving user CRS [%1]: %2" ).arg( crs.toProj(), errorMessage ), QObject::tr( "CRS" ) );
209 res = false;
210 }
211 else
212 {
213 const int changed = sqlite3_changes( database.get() );
214 if ( changed )
215 {
216 QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
217 }
218 else
219 {
220 QgsMessageLog::logMessage( QObject::tr( "Error saving user CRS [%1]: No matching ID found in database" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
221 res = false;
222 }
223 }
224
225 if ( res )
226 {
227 // If we have a projection acronym not in the user db previously, add it.
228 // This is a must, or else we can't select it from the vw_srs table.
229 // Actually, add it always and let the SQL PRIMARY KEY remove duplicates.
230 insertProjection( crs.projectionAcronym() );
231 }
232
235
236 if ( res )
237 {
238 emit userCrsChanged( QStringLiteral( "USER:%1" ).arg( id ) );
240 }
241
242 return res;
243}
244
246{
248
249 const QString sql = "delete from tbl_srs where srs_id=" + QgsSqliteUtils::quotedString( QString::number( id ) );
250 QgsDebugMsgLevel( sql, 4 );
251 //check the db is available
252 int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
253 if ( result != SQLITE_OK )
254 {
255 QgsDebugError( QStringLiteral( "Can't open database: %1 \n please notify QGIS developers of this error \n %2 (file name) " ).arg( database.errorMessage(),
257 return false;
258 }
259
260 bool res = true;
261 {
262 sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
263 if ( result != SQLITE_OK || preparedStatement.step() != SQLITE_DONE )
264 {
265 QgsDebugError( QStringLiteral( "failed to remove custom CRS from database: %1 [%2]" ).arg( sql, database.errorMessage() ) );
266 res = false;
267 }
268 else
269 {
270 const int changed = sqlite3_changes( database.get() );
271 if ( changed )
272 {
273 QgsMessageLog::logMessage( QObject::tr( "Removed user CRS [%1]" ).arg( id ), QObject::tr( "CRS" ) );
274 }
275 else
276 {
277 QgsMessageLog::logMessage( QObject::tr( "Error removing user CRS [%1]: No matching ID found in database" ).arg( id ), QObject::tr( "CRS" ) );
278 res = false;
279 }
280 }
281 }
282
285
286 if ( res )
287 {
288 emit userCrsRemoved( id );
290 }
291
292 return res;
293}
294
295
296bool QgsCoordinateReferenceSystemRegistry::insertProjection( const QString &projectionAcronym )
297{
299 sqlite3_database_unique_ptr srsDatabase;
300 QString sql;
301 //check the db is available
302 int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
303 if ( result != SQLITE_OK )
304 {
305 QgsDebugError( QStringLiteral( "Can't open database: %1 \n please notify QGIS developers of this error \n %2 (file name) " ).arg( database.errorMessage(),
307 return false;
308 }
309 int srsResult = srsDatabase.open( QgsApplication::srsDatabaseFilePath() );
310 if ( result != SQLITE_OK )
311 {
312 QgsDebugError( QStringLiteral( "Can't open database %1 [%2]" ).arg( QgsApplication::srsDatabaseFilePath(),
313 srsDatabase.errorMessage() ) );
314 return false;
315 }
316
317 // Set up the query to retrieve the projection information needed to populate the PROJECTION list
318 const QString srsSql = "select acronym,name,notes,parameters from tbl_projection where acronym=" + QgsSqliteUtils::quotedString( projectionAcronym );
319
320 sqlite3_statement_unique_ptr srsPreparedStatement = srsDatabase.prepare( srsSql, srsResult );
321 if ( srsResult == SQLITE_OK )
322 {
323 if ( srsPreparedStatement.step() == SQLITE_ROW )
324 {
325 QgsDebugMsgLevel( QStringLiteral( "Trying to insert projection" ), 4 );
326 // We have the result from system srs.db. Now insert into user db.
327 sql = "insert into tbl_projection(acronym,name,notes,parameters) values ("
328 + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 0 ) )
329 + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 1 ) )
330 + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 2 ) )
331 + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 3 ) )
332 + ')';
333 sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
334 if ( result != SQLITE_OK || preparedStatement.step() != SQLITE_DONE )
335 {
336 QgsDebugError( QStringLiteral( "Could not insert projection into database: %1 [%2]" ).arg( sql, database.errorMessage() ) );
337 return false;
338 }
339 }
340 }
341 else
342 {
343 QgsDebugError( QStringLiteral( "prepare failed: %1 [%2]" ).arg( srsSql, srsDatabase.errorMessage() ) );
344 return false;
345 }
346
347 return true;
348}
349
350QMap<QString, QgsProjOperation> QgsCoordinateReferenceSystemRegistry::projOperations() const
351{
352 static std::once_flag initialized;
353 std::call_once( initialized, [ = ]
354 {
355 const QgsScopedRuntimeProfile profile( QObject::tr( "Initialize PROJ operations" ) );
356
357 const PJ_OPERATIONS *operation = proj_list_operations();
358 while ( operation && operation->id )
359 {
360 QgsProjOperation value;
361 value.mValid = true;
362 value.mId = QString( operation->id );
363
364 const QString description( *operation->descr );
365 const QStringList descriptionParts = description.split( QStringLiteral( "\n\t" ) );
366 value.mDescription = descriptionParts.value( 0 );
367 value.mDetails = descriptionParts.mid( 1 ).join( '\n' );
368
369 mProjOperations.insert( value.id(), value );
370
371 operation++;
372 }
373 } );
374
375 return mProjOperations;
376}
377
379{
380#if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=1)
381
382 static std::once_flag initialized;
383 std::call_once( initialized, [ = ]
384 {
385 QgsScopedRuntimeProfile profile( QObject::tr( "Initialize celestial bodies" ) );
386
387 PJ_CONTEXT *context = QgsProjContext::get();
388
389 int resultCount = 0;
390 PROJ_CELESTIAL_BODY_INFO **list = proj_get_celestial_body_list_from_database( context, nullptr, &resultCount );
391 mCelestialBodies.reserve( resultCount );
392 for ( int i = 0; i < resultCount; i++ )
393 {
394 const PROJ_CELESTIAL_BODY_INFO *info = list[ i ];
395 if ( !info )
396 break;
397
398 QgsCelestialBody body;
399 body.mValid = true;
400 body.mAuthority = QString( info->auth_name );
401 body.mName = QString( info->name );
402
403 mCelestialBodies << body;
404 }
405 proj_celestial_body_list_destroy( list );
406 } );
407
408 return mCelestialBodies;
409#else
410 throw QgsNotSupportedException( QObject::tr( "Retrieving celestial bodies requires a QGIS build based on PROJ 8.1 or later" ) );
411#endif
412}
413
415{
416 static std::once_flag initialized;
417 std::call_once( initialized, [ = ]
418 {
419 QgsScopedRuntimeProfile profile( QObject::tr( "Initialize authorities" ) );
420
421 PJ_CONTEXT *pjContext = QgsProjContext::get();
422 PROJ_STRING_LIST authorities = proj_get_authorities_from_database( pjContext );
423
424 for ( auto authIter = authorities; authIter && *authIter; ++authIter )
425 {
426 const QString authority( *authIter );
427 mKnownAuthorities.insert( authority.toLower() );
428 }
429
430 proj_string_list_destroy( authorities );
431 } );
432
433 return mKnownAuthorities;
434}
435
437{
438 static std::once_flag initialized;
439 std::call_once( initialized, [ = ]
440 {
441 const QString srsDatabaseFileName = QgsApplication::srsDatabaseFilePath();
442 if ( QFileInfo::exists( srsDatabaseFileName ) )
443 {
444 // open the database containing the spatial reference data, and do a one-time read
446 int result = database.open_v2( srsDatabaseFileName, SQLITE_OPEN_READONLY, nullptr );
447 if ( result == SQLITE_OK )
448 {
449 const QString sql = QStringLiteral( "SELECT description, srs_id, auth_name, auth_id, projection_acronym, deprecated, srs_type FROM tbl_srs" );
450 sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
451 if ( result == SQLITE_OK )
452 {
453 while ( preparedStatement.step() == SQLITE_ROW )
454 {
455 QgsCrsDbRecord record;
456 record.description = preparedStatement.columnAsText( 0 );
457 record.srsId = preparedStatement.columnAsText( 1 );
458 record.authName = preparedStatement.columnAsText( 2 );
459 record.authId = preparedStatement.columnAsText( 3 );
460 record.projectionAcronym = preparedStatement.columnAsText( 4 );
461 record.deprecated = preparedStatement.columnAsText( 5 ).toInt();
462 record.type = qgsEnumKeyToValue( preparedStatement.columnAsText( 6 ), Qgis::CrsType::Unknown );
463 mCrsDbRecords.append( record );
464 }
465 }
466 }
467 }
468 } );
469
470 return mCrsDbRecords;
471}
@ Unknown
Unknown type.
CrsDefinitionFormat
CRS definition formats.
Definition qgis.h:2647
@ Wkt
WKT format (always recommended over proj string format)
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
Contains information about a celestial body.
QgsCoordinateReferenceSystem crs
QgsCoordinateReferenceSystem object representing the user-defined CRS.
QList< QgsCrsDbRecord > crsDbRecords() const
Returns the list of records from the QGIS srs db.
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.
QList< QgsCelestialBody > celestialBodies() const
Returns a list of all known celestial bodies.
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, Qgis::CrsDefinitionFormat nativeFormat=Qgis::CrsDefinitionFormat::Wkt)
Updates the definition of the existing user CRS with matching id.
QgsCoordinateReferenceSystemRegistry(QObject *parent=nullptr)
Constructor for QgsCoordinateReferenceSystemRegistry, with the specified parent object.
long addUserCrs(const QgsCoordinateReferenceSystem &crs, const QString &name, Qgis::CrsDefinitionFormat nativeFormat=Qgis::CrsDefinitionFormat::Wkt)
Adds a new crs definition as a custom ("USER") CRS.
QSet< QString > authorities() const
Returns a list of all known authorities.
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.
QMap< QString, QgsProjOperation > projOperations() const
Returns a map of all valid PROJ operations.
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.
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::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Custom exception class which is raised when an operation is not supported.
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
Contains information about a PROJ operation.
QString id() const
ID of operation.
Scoped object for logging of the runtime for a single operation or group of operations.
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.
T qgsEnumKeyToValue(const QString &key, const T &defaultValue, bool tryValueAsKey=true, bool *returnOk=nullptr)
Returns the value corresponding to the given key of an enum.
Definition qgis.h:4603
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition qgis.h:4912
struct projCtx_t PJ_CONTEXT
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39
#define QgsDebugError(str)
Definition qgslogger.h:38
const QgsCoordinateReferenceSystem & crs