Branch data Line data Source code
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 : :
18 : : #include "qgscoordinatereferencesystemregistry.h"
19 : : #include "qgscoordinatereferencesystem_p.h"
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 : :
28 : 5 : QgsCoordinateReferenceSystemRegistry::QgsCoordinateReferenceSystemRegistry( QObject *parent )
29 : 5 : : QObject( parent )
30 : 10 : {
31 : :
32 : 5 : }
33 : :
34 : 0 : QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> QgsCoordinateReferenceSystemRegistry::userCrsList() const
35 : : {
36 : 0 : QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> res;
37 : :
38 : : //Setup connection to the existing custom CRS database:
39 : 0 : sqlite3_database_unique_ptr database;
40 : : //check the db is available
41 : 0 : int result = database.open_v2( QgsApplication::qgisUserDatabaseFilePath(), SQLITE_OPEN_READONLY, nullptr );
42 : 0 : if ( result != SQLITE_OK )
43 : : {
44 : 0 : QgsDebugMsg( QStringLiteral( "Can't open database: %1" ).arg( database.errorMessage() ) );
45 : 0 : return res;
46 : : }
47 : :
48 : 0 : const QString sql = QStringLiteral( "select srs_id,description,parameters, wkt from tbl_srs" );
49 : 0 : QgsDebugMsgLevel( QStringLiteral( "Query to populate existing list:%1" ).arg( sql ), 4 );
50 : 0 : sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
51 : 0 : if ( result == SQLITE_OK )
52 : : {
53 : 0 : QgsCoordinateReferenceSystem crs;
54 : 0 : while ( preparedStatement.step() == SQLITE_ROW )
55 : : {
56 : 0 : UserCrsDetails details;
57 : 0 : details.id = preparedStatement.columnAsText( 0 ).toLong();
58 : 0 : details.name = preparedStatement.columnAsText( 1 );
59 : 0 : details.proj = preparedStatement.columnAsText( 2 );
60 : 0 : details.wkt = preparedStatement.columnAsText( 3 );
61 : :
62 : 0 : if ( !details.wkt.isEmpty() )
63 : 0 : details.crs.createFromWkt( details.wkt );
64 : : else
65 : 0 : details.crs.createFromProj( details.proj );
66 : :
67 : 0 : res << details;
68 : 0 : }
69 : 0 : }
70 : 0 : return res;
71 : 0 : }
72 : :
73 : 0 : long QgsCoordinateReferenceSystemRegistry::addUserCrs( const QgsCoordinateReferenceSystem &crs, const QString &name, QgsCoordinateReferenceSystem::Format nativeFormat )
74 : : {
75 : 0 : if ( !crs.isValid() )
76 : : {
77 : 0 : QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
78 : 0 : return -1;
79 : : }
80 : :
81 : 0 : QString mySql;
82 : :
83 : 0 : QString proj4String = crs.d->mProj4;
84 : 0 : if ( proj4String.isEmpty() )
85 : : {
86 : 0 : proj4String = crs.toProj();
87 : 0 : }
88 : 0 : QString wktString = crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED );
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 : 0 : 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 : 0 : if ( QgsCoordinateReferenceSystem::getRecordCount() == 0 )
99 : : {
100 : 0 : mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo,wkt) values ("
101 : 0 : + QString::number( USER_CRS_START_ID )
102 : 0 : + ',' + QgsSqliteUtils::quotedString( name )
103 : 0 : + ',' + ( !crs.d->mProjectionAcronym.isEmpty() ? QgsSqliteUtils::quotedString( crs.d->mProjectionAcronym ) : QStringLiteral( "''" ) )
104 : 0 : + ',' + quotedEllipsoidString
105 : 0 : + ',' + ( !proj4String.isEmpty() ? QgsSqliteUtils::quotedString( proj4String ) : QStringLiteral( "''" ) )
106 : 0 : + ",0," // <-- is_geo shamelessly hard coded for now
107 : 0 : + ( nativeFormat == QgsCoordinateReferenceSystem::FormatWkt ? QgsSqliteUtils::quotedString( wktString ) : QStringLiteral( "''" ) )
108 : 0 : + ')';
109 : 0 : }
110 : : else
111 : : {
112 : 0 : mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo,wkt) values ("
113 : 0 : + QgsSqliteUtils::quotedString( name )
114 : 0 : + ',' + ( !crs.d->mProjectionAcronym.isEmpty() ? QgsSqliteUtils::quotedString( crs.d->mProjectionAcronym ) : QStringLiteral( "''" ) )
115 : 0 : + ',' + quotedEllipsoidString
116 : 0 : + ',' + ( !proj4String.isEmpty() ? QgsSqliteUtils::quotedString( proj4String ) : QStringLiteral( "''" ) )
117 : 0 : + ",0," // <-- is_geo shamelessly hard coded for now
118 : 0 : + ( nativeFormat == QgsCoordinateReferenceSystem::FormatWkt ? QgsSqliteUtils::quotedString( wktString ) : QStringLiteral( "''" ) )
119 : 0 : + ')';
120 : : }
121 : 0 : sqlite3_database_unique_ptr database;
122 : 0 : sqlite3_statement_unique_ptr statement;
123 : : //check the db is available
124 : 0 : int myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
125 : 0 : if ( myResult != SQLITE_OK )
126 : : {
127 : 0 : QgsDebugMsg( QStringLiteral( "Can't open or create database %1: %2" )
128 : : .arg( QgsApplication::qgisUserDatabaseFilePath(),
129 : : database.errorMessage() ) );
130 : 0 : return false;
131 : : }
132 : 0 : statement = database.prepare( mySql, myResult );
133 : :
134 : 0 : qint64 returnId = -1;
135 : 0 : if ( myResult == SQLITE_OK && statement.step() == SQLITE_DONE )
136 : : {
137 : 0 : QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
138 : :
139 : 0 : returnId = sqlite3_last_insert_rowid( database.get() );
140 : 0 : crs.d->mSrsId = returnId;
141 : 0 : crs.d->mAuthId = QStringLiteral( "USER:%1" ).arg( returnId );
142 : 0 : crs.d->mDescription = name;
143 : 0 : }
144 : :
145 : 0 : 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 : 0 : insertProjection( crs.projectionAcronym() );
151 : 0 : }
152 : :
153 : 0 : QgsCoordinateReferenceSystem::invalidateCache();
154 : 0 : QgsCoordinateTransform::invalidateCache();
155 : :
156 : 0 : if ( returnId != -1 )
157 : : {
158 : 0 : emit userCrsAdded( crs.d->mAuthId );
159 : 0 : emit crsDefinitionsChanged();
160 : 0 : }
161 : :
162 : 0 : return returnId;
163 : 0 : }
164 : :
165 : 0 : bool QgsCoordinateReferenceSystemRegistry::updateUserCrs( long id, const QgsCoordinateReferenceSystem &crs, const QString &name, QgsCoordinateReferenceSystem::Format nativeFormat )
166 : : {
167 : 0 : if ( !crs.isValid() )
168 : : {
169 : 0 : QgsDebugMsgLevel( QStringLiteral( "Can't save an invalid CRS!" ), 4 );
170 : 0 : return false;
171 : : }
172 : :
173 : 0 : const QString sql = "update tbl_srs set description="
174 : 0 : + QgsSqliteUtils::quotedString( name )
175 : 0 : + ",projection_acronym=" + ( !crs.projectionAcronym().isEmpty() ? QgsSqliteUtils::quotedString( crs.projectionAcronym() ) : QStringLiteral( "''" ) )
176 : 0 : + ",ellipsoid_acronym=" + ( !crs.ellipsoidAcronym().isEmpty() ? QgsSqliteUtils::quotedString( crs.ellipsoidAcronym() ) : QStringLiteral( "''" ) )
177 : 0 : + ",parameters=" + ( !crs.toProj().isEmpty() ? QgsSqliteUtils::quotedString( crs.toProj() ) : QStringLiteral( "''" ) )
178 : 0 : + ",is_geo=0" // <--shamelessly hard coded for now
179 : 0 : + ",wkt=" + ( nativeFormat == QgsCoordinateReferenceSystem::FormatWkt ? QgsSqliteUtils::quotedString( crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, false ) ) : QStringLiteral( "''" ) )
180 : 0 : + " where srs_id=" + QgsSqliteUtils::quotedString( QString::number( id ) )
181 : : ;
182 : :
183 : 0 : sqlite3_database_unique_ptr database;
184 : : //check the db is available
185 : 0 : int myResult = database.open( QgsApplication::qgisUserDatabaseFilePath() );
186 : 0 : if ( myResult != SQLITE_OK )
187 : : {
188 : 0 : QgsDebugMsg( QStringLiteral( "Can't open or create database %1: %2" )
189 : : .arg( QgsApplication::qgisUserDatabaseFilePath(),
190 : : database.errorMessage() ) );
191 : 0 : return false;
192 : : }
193 : :
194 : 0 : bool res = true;
195 : 0 : QString errorMessage;
196 : 0 : if ( database.exec( sql, errorMessage ) != SQLITE_OK )
197 : : {
198 : 0 : QgsMessageLog::logMessage( QObject::tr( "Error saving user CRS [%1]: %2" ).arg( crs.toProj(), errorMessage ), QObject::tr( "CRS" ) );
199 : 0 : res = false;
200 : 0 : }
201 : : else
202 : : {
203 : 0 : const int changed = sqlite3_changes( database.get() );
204 : 0 : if ( changed )
205 : : {
206 : 0 : QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
207 : 0 : }
208 : : else
209 : : {
210 : 0 : QgsMessageLog::logMessage( QObject::tr( "Error saving user CRS [%1]: No matching ID found in database" ).arg( crs.toProj() ), QObject::tr( "CRS" ) );
211 : 0 : res = false;
212 : : }
213 : : }
214 : :
215 : 0 : 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 : 0 : insertProjection( crs.projectionAcronym() );
221 : 0 : }
222 : :
223 : 0 : QgsCoordinateReferenceSystem::invalidateCache();
224 : 0 : QgsCoordinateTransform::invalidateCache();
225 : :
226 : 0 : if ( res )
227 : : {
228 : 0 : emit userCrsChanged( crs.d->mAuthId );
229 : 0 : emit crsDefinitionsChanged();
230 : 0 : }
231 : :
232 : 0 : return res;
233 : 0 : }
234 : :
235 : 0 : bool QgsCoordinateReferenceSystemRegistry::removeUserCrs( long id )
236 : : {
237 : 0 : sqlite3_database_unique_ptr database;
238 : :
239 : 0 : QString sql = "delete from tbl_srs where srs_id=" + QgsSqliteUtils::quotedString( QString::number( id ) );
240 : 0 : QgsDebugMsgLevel( sql, 4 );
241 : : //check the db is available
242 : 0 : int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
243 : 0 : if ( result != SQLITE_OK )
244 : : {
245 : 0 : QgsDebugMsg( QStringLiteral( "Can't open database: %1 \n please notify QGIS developers of this error \n %2 (file name) " ).arg( database.errorMessage(),
246 : : QgsApplication::qgisUserDatabaseFilePath() ) );
247 : 0 : return false;
248 : : }
249 : :
250 : 0 : bool res = true;
251 : : {
252 : 0 : sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
253 : 0 : if ( result != SQLITE_OK || preparedStatement.step() != SQLITE_DONE )
254 : : {
255 : 0 : QgsDebugMsg( QStringLiteral( "failed to remove custom CRS from database: %1 [%2]" ).arg( sql, database.errorMessage() ) );
256 : 0 : res = false;
257 : 0 : }
258 : : else
259 : : {
260 : 0 : const int changed = sqlite3_changes( database.get() );
261 : 0 : if ( changed )
262 : : {
263 : 0 : QgsMessageLog::logMessage( QObject::tr( "Removed user CRS [%1]" ).arg( id ), QObject::tr( "CRS" ) );
264 : 0 : }
265 : : else
266 : : {
267 : 0 : QgsMessageLog::logMessage( QObject::tr( "Error removing user CRS [%1]: No matching ID found in database" ).arg( id ), QObject::tr( "CRS" ) );
268 : 0 : res = false;
269 : : }
270 : : }
271 : 0 : }
272 : :
273 : 0 : QgsCoordinateReferenceSystem::invalidateCache();
274 : 0 : QgsCoordinateTransform::invalidateCache();
275 : :
276 : 0 : if ( res )
277 : : {
278 : 0 : emit userCrsRemoved( id );
279 : 0 : emit crsDefinitionsChanged();
280 : 0 : }
281 : :
282 : 0 : return res;
283 : 0 : }
284 : :
285 : 0 : bool QgsCoordinateReferenceSystemRegistry::insertProjection( const QString &projectionAcronym )
286 : : {
287 : 0 : sqlite3_database_unique_ptr database;
288 : 0 : sqlite3_database_unique_ptr srsDatabase;
289 : 0 : QString sql;
290 : : //check the db is available
291 : 0 : int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
292 : 0 : if ( result != SQLITE_OK )
293 : : {
294 : 0 : QgsDebugMsg( QStringLiteral( "Can't open database: %1 \n please notify QGIS developers of this error \n %2 (file name) " ).arg( database.errorMessage(),
295 : : QgsApplication::qgisUserDatabaseFilePath() ) );
296 : 0 : return false;
297 : : }
298 : 0 : int srsResult = srsDatabase.open( QgsApplication::srsDatabaseFilePath() );
299 : 0 : if ( result != SQLITE_OK )
300 : : {
301 : 0 : QgsDebugMsg( QStringLiteral( "Can't open database %1 [%2]" ).arg( QgsApplication::srsDatabaseFilePath(),
302 : : srsDatabase.errorMessage() ) );
303 : 0 : return false;
304 : : }
305 : :
306 : : // Set up the query to retrieve the projection information needed to populate the PROJECTION list
307 : 0 : QString srsSql = "select acronym,name,notes,parameters from tbl_projection where acronym=" + QgsSqliteUtils::quotedString( projectionAcronym );
308 : :
309 : 0 : sqlite3_statement_unique_ptr srsPreparedStatement = srsDatabase.prepare( srsSql, srsResult );
310 : 0 : if ( srsResult == SQLITE_OK )
311 : : {
312 : 0 : if ( srsPreparedStatement.step() == SQLITE_ROW )
313 : : {
314 : 0 : QgsDebugMsgLevel( QStringLiteral( "Trying to insert projection" ), 4 );
315 : : // We have the result from system srs.db. Now insert into user db.
316 : 0 : sql = "insert into tbl_projection(acronym,name,notes,parameters) values ("
317 : 0 : + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 0 ) )
318 : 0 : + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 1 ) )
319 : 0 : + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 2 ) )
320 : 0 : + ',' + QgsSqliteUtils::quotedString( srsPreparedStatement.columnAsText( 3 ) )
321 : 0 : + ')';
322 : 0 : sqlite3_statement_unique_ptr preparedStatement = database.prepare( sql, result );
323 : 0 : if ( result != SQLITE_OK || preparedStatement.step() != SQLITE_DONE )
324 : : {
325 : 0 : QgsDebugMsg( QStringLiteral( "Could not insert projection into database: %1 [%2]" ).arg( sql, database.errorMessage() ) );
326 : 0 : return false;
327 : : }
328 : 0 : }
329 : 0 : }
330 : : else
331 : : {
332 : 0 : QgsDebugMsg( QStringLiteral( "prepare failed: %1 [%2]" ).arg( srsSql, srsDatabase.errorMessage() ) );
333 : 0 : return false;
334 : : }
335 : :
336 : 0 : return true;
337 : 0 : }
|