QGIS API Documentation 3.41.0-Master (fda2aa46e9a)
Loading...
Searching...
No Matches
qgsbookmarkmanager.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsbookmarkmanager.cpp
3 --------------------
4 Date : September 2019
5 Copyright : (C) 2019 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 "qgsbookmarkmanager.h"
17#include "moc_qgsbookmarkmanager.cpp"
18#include "qgsproject.h"
19#include "qgssettings.h"
20#include "qgssqliteutils.h"
21#include "qgsapplication.h"
22#include <QUuid>
23#include <QTextStream>
24#include <sqlite3.h>
25
26//
27// QgsBookMark
28//
29
30QString QgsBookmark::id() const
31{
32 return mId;
33}
34
35void QgsBookmark::setId( const QString &id )
36{
37 mId = id;
38}
39
40QgsBookmark QgsBookmark::fromXml( const QDomElement &element, const QDomDocument & )
41{
43 b.setId( element.attribute( QStringLiteral( "id" ) ) );
44 b.setName( element.attribute( QStringLiteral( "name" ) ) );
45 b.setGroup( element.attribute( QStringLiteral( "group" ) ) );
46 const QgsRectangle e = QgsRectangle::fromWkt( element.attribute( QStringLiteral( "extent" ) ) );
47 b.setRotation( element.attribute( QStringLiteral( "rotation" ) ).toDouble() );
49 crs.readXml( element );
51 return b;
52}
53
54QDomElement QgsBookmark::writeXml( QDomDocument &doc ) const
55{
56 QDomElement bookmarkElem = doc.createElement( QStringLiteral( "Bookmark" ) );
57 bookmarkElem.setAttribute( QStringLiteral( "id" ), mId );
58 bookmarkElem.setAttribute( QStringLiteral( "name" ), mName );
59 bookmarkElem.setAttribute( QStringLiteral( "group" ), mGroup );
60 bookmarkElem.setAttribute( QStringLiteral( "extent" ), mExtent.asWktPolygon() );
61 bookmarkElem.setAttribute( QStringLiteral( "rotation" ), mRotation );
62 mExtent.crs().writeXml( bookmarkElem, doc );
63 return bookmarkElem;
64}
65
66bool QgsBookmark::operator==( const QgsBookmark &other ) const
67{
68 return mId == other.mId
69 && mName == other.mName
70 && mExtent == other.mExtent
71 && mGroup == other.mGroup
72 && qgsDoubleNear( mRotation, other.mRotation );
73}
74
75bool QgsBookmark::operator!=( const QgsBookmark &other ) const
76{
77 return !( *this == other );
78}
79
80QString QgsBookmark::name() const
81{
82 return mName;
83}
84
85void QgsBookmark::setName( const QString &name )
86{
87 mName = name;
88}
89
90QString QgsBookmark::group() const
91{
92 return mGroup;
93}
94
95void QgsBookmark::setGroup( const QString &group )
96{
97 mGroup = group;
98}
99
101{
102 return mExtent;
103}
104
106{
107 mExtent = extent;
108}
109
111{
112 return mRotation;
113}
114
115void QgsBookmark::setRotation( double rotation )
116{
117 mRotation = rotation;
118}
119
120
121//
122// QgsBookmarkManager
123//
124
126{
127 QgsBookmarkManager *res = new QgsBookmarkManager( project );
128 res->mProject = project;
129 return res;
130}
131
133 : QObject( parent )
134{
135 // we defer actually loading bookmarks until initialize() is called..
136}
137
142
143QString QgsBookmarkManager::addBookmark( const QgsBookmark &b, bool *ok )
144{
145 if ( ok )
146 *ok = false;
147
148 QgsBookmark bookmark = b;
149 if ( bookmark.id().isEmpty() )
150 bookmark.setId( QUuid::createUuid().toString() );
151 else
152 {
153 // check for duplicate ID
154 for ( const QgsBookmark &b : std::as_const( mBookmarks ) )
155 {
156 if ( b.id() == bookmark.id() )
157 {
158 return QString();
159 }
160 }
161 }
162
163 if ( ok )
164 *ok = true;
165
166 emit bookmarkAboutToBeAdded( bookmark.id() );
167 mBookmarks << bookmark;
168 if ( !mGroups.contains( bookmark.group() ) )
169 mGroups << bookmark.group();
170 emit bookmarkAdded( bookmark.id() );
171 if ( mProject )
172 {
173 mProject->setDirty( true );
174 }
175
176 return bookmark.id();
177}
178
179bool QgsBookmarkManager::removeBookmark( const QString &id )
180{
181 if ( id.isEmpty() )
182 return false;
183
184 QString group;
185 int pos = -1;
186 int i = 0;
187 for ( const QgsBookmark &b : std::as_const( mBookmarks ) )
188 {
189 if ( b.id() == id )
190 {
191 group = b.group();
192 pos = i;
193 break;
194 }
195 i++;
196 }
197
198 if ( pos < 0 )
199 return false;
200
201 emit bookmarkAboutToBeRemoved( id );
202 mBookmarks.removeAt( pos );
203 if ( bookmarksByGroup( group ).isEmpty() )
204 mGroups.removeOne( group );
205 emit bookmarkRemoved( id );
206 if ( mProject )
207 {
208 mProject->setDirty( true );
209 }
210
211 return true;
212}
213
215{
216 // check for duplicate ID
217 int i = 0;
218 for ( const QgsBookmark &b : std::as_const( mBookmarks ) )
219 {
220 if ( b.id() == bookmark.id() )
221 {
222 if ( mBookmarks[i].group() != bookmark.group() )
223 {
224 if ( bookmarksByGroup( mBookmarks[i].group() ).count() == 1 )
225 mGroups.removeOne( mBookmarks[i].group() );
226 if ( !mGroups.contains( bookmark.group() ) )
227 mGroups << bookmark.group();
228 }
229 mBookmarks[i] = bookmark;
230 emit bookmarkChanged( bookmark.id() );
231 if ( mProject )
232 {
233 mProject->setDirty( true );
234 }
235
236 return true;
237 }
238 i++;
239 }
240 return false;
241}
242
244{
245 const QList< QgsBookmark > bookmarks = mBookmarks;
246 for ( const QgsBookmark &b : bookmarks )
247 {
248 removeBookmark( b.id() );
249 }
250}
251
252QStringList QgsBookmarkManager::groups() const
253{
254 return mGroups;
255}
256
257void QgsBookmarkManager::renameGroup( const QString &oldName, const QString &newName )
258{
259 for ( int i = 0; i < mBookmarks.count(); ++i )
260 {
261 if ( mBookmarks.at( i ).group() == oldName )
262 {
263 mBookmarks[ i ].setGroup( newName );
264 emit bookmarkChanged( mBookmarks.at( i ).id() );
265 }
266 }
267}
268
269QList<QgsBookmark> QgsBookmarkManager::bookmarks() const
270{
271 return mBookmarks;
272}
273
275{
276 for ( const QgsBookmark &b : mBookmarks )
277 {
278 if ( b.id() == id )
279 return b;
280 }
281 return QgsBookmark();
282}
283
284QList<QgsBookmark> QgsBookmarkManager::bookmarksByGroup( const QString &group )
285{
286 QList<QgsBookmark> bookmarks;
287 for ( const QgsBookmark &b : mBookmarks )
288 {
289 if ( b.group() == group )
290 bookmarks << b;
291 }
292 return bookmarks;
293}
294
295bool QgsBookmarkManager::readXml( const QDomElement &element, const QDomDocument &doc )
296{
297 clear();
298
299 QDomElement bookmarksElem = element;
300 if ( element.tagName() != QLatin1String( "Bookmarks" ) )
301 {
302 bookmarksElem = element.firstChildElement( QStringLiteral( "Bookmarks" ) );
303 }
304 bool result = true;
305 if ( mProject && bookmarksElem.isNull() )
306 {
307 // handle legacy projects
308 const int count = mProject->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/count" ) );
309 for ( int i = 0; i < count; ++i )
310 {
311 const double minX = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinX" ).arg( i ) );
312 const double minY = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MinY" ).arg( i ) );
313 const double maxX = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxX" ).arg( i ) );
314 const double maxY = mProject->readDoubleEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/MaxY" ).arg( i ) );
315 const long srid = mProject->readNumEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/SRID" ).arg( i ) );
316 QgsBookmark b;
317 b.setId( QStringLiteral( "bookmark_%1" ).arg( i ) );
318 b.setName( mProject->readEntry( QStringLiteral( "Bookmarks" ), QStringLiteral( "/Row-%1/Name" ).arg( i ) ) );
320
321 bool added = false;
322 addBookmark( b, &added );
323 result = added && result;
324 }
325 return result;
326 }
327
328 //restore each
329 QDomNodeList bookmarkNodes = element.elementsByTagName( QStringLiteral( "Bookmark" ) );
330 for ( int i = 0; i < bookmarkNodes.size(); ++i )
331 {
332 QgsBookmark b = QgsBookmark::fromXml( bookmarkNodes.at( i ).toElement(), doc );
333 bool added = false;
334 addBookmark( b, &added );
335 result = added && result;
336 }
337
338 return result;
339}
340
341QDomElement QgsBookmarkManager::writeXml( QDomDocument &doc ) const
342{
343 QDomElement bookmarksElem = doc.createElement( QStringLiteral( "Bookmarks" ) );
344
345 for ( const QgsBookmark &b : mBookmarks )
346 {
347 QDomElement bookmarkElem = b.writeXml( doc );
348 bookmarksElem.appendChild( bookmarkElem );
349 }
350 return bookmarksElem;
351}
352
353bool QgsBookmarkManager::moveBookmark( const QString &id, QgsBookmarkManager *destination )
354{
355 QgsBookmark b = bookmarkById( id );
356 if ( b.id().isEmpty() )
357 return false;
358
359 removeBookmark( id );
360 bool ok = false;
361 destination->addBookmark( b, &ok );
362 return ok;
363}
364
365bool QgsBookmarkManager::exportToFile( const QString &path, const QList<const QgsBookmarkManager *> &managers, const QString &group )
366{
367 // note - we don't use the other writeXml implementation, to maintain older format compatibility
368 QDomDocument doc( QStringLiteral( "qgis_bookmarks" ) );
369 QDomElement root = doc.createElement( QStringLiteral( "qgis_bookmarks" ) );
370 doc.appendChild( root );
371
372 QList<QString> headerList;
373 headerList
374 << QStringLiteral( "project" )
375 << QStringLiteral( "xmin" )
376 << QStringLiteral( "ymin" )
377 << QStringLiteral( "xmax" )
378 << QStringLiteral( "ymax" )
379 << QStringLiteral( "rotation" )
380 << QStringLiteral( "sr_id" );
381
382 for ( const QgsBookmarkManager *manager : managers )
383 {
384 const QList< QgsBookmark > bookmarks = manager->bookmarks();
385 for ( const QgsBookmark &b : bookmarks )
386 {
387 if ( !group.isEmpty() && b.group() != group )
388 continue;
389
390 QDomElement bookmark = doc.createElement( QStringLiteral( "bookmark" ) );
391 root.appendChild( bookmark );
392
393 QDomElement id = doc.createElement( QStringLiteral( "id" ) );
394 id.appendChild( doc.createTextNode( b.id() ) );
395 bookmark.appendChild( id );
396
397 QDomElement name = doc.createElement( QStringLiteral( "name" ) );
398 name.appendChild( doc.createTextNode( b.name() ) );
399 bookmark.appendChild( name );
400
401 QDomElement group = doc.createElement( QStringLiteral( "project" ) );
402 group.appendChild( doc.createTextNode( b.group() ) );
403 bookmark.appendChild( group );
404
405 QDomElement xMin = doc.createElement( QStringLiteral( "xmin" ) );
406 xMin.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().xMinimum() ) ) );
407 bookmark.appendChild( xMin );
408 QDomElement yMin = doc.createElement( QStringLiteral( "ymin" ) );
409 yMin.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().yMinimum() ) ) );
410 bookmark.appendChild( yMin );
411 QDomElement xMax = doc.createElement( QStringLiteral( "xmax" ) );
412 xMax.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().xMaximum() ) ) );
413 bookmark.appendChild( xMax );
414 QDomElement yMax = doc.createElement( QStringLiteral( "ymax" ) );
415 yMax.appendChild( doc.createTextNode( qgsDoubleToString( b.extent().yMaximum() ) ) );
416 bookmark.appendChild( yMax );
417
418 QDomElement rotation = doc.createElement( QStringLiteral( "rotation" ) );
419 rotation.appendChild( doc.createTextNode( qgsDoubleToString( b.rotation() ) ) );
420 bookmark.appendChild( rotation );
421
422 QDomElement crs = doc.createElement( QStringLiteral( "sr_id" ) );
423 crs.appendChild( doc.createTextNode( QString::number( b.extent().crs().srsid() ) ) );
424 bookmark.appendChild( crs );
425 }
426 }
427
428 QFile f( path );
429 if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
430 {
431 f.close();
432 return false;
433 }
434
435 QTextStream out( &f );
436#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
437 out.setCodec( "UTF-8" );
438#endif
439 doc.save( out, 2 );
440 f.close();
441
442 return true;
443}
444
445bool QgsBookmarkManager::importFromFile( const QString &path )
446{
447 if ( path.isEmpty() )
448 {
449 return false;
450 }
451
452 QFile f( path );
453 if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
454 {
455 return false;
456 }
457
458 QDomDocument doc;
459 if ( !doc.setContent( &f ) )
460 {
461 return false;
462 }
463 f.close();
464
465 QDomElement docElem = doc.documentElement();
466 QDomNodeList nodeList = docElem.elementsByTagName( QStringLiteral( "bookmark" ) );
467
468 bool res = true;
469 for ( int i = 0; i < nodeList.count(); i++ )
470 {
471 QDomNode bookmark = nodeList.at( i );
472 QDomElement name = bookmark.firstChildElement( QStringLiteral( "name" ) );
473 QDomElement prjname = bookmark.firstChildElement( QStringLiteral( "project" ) );
474 QDomElement xmin = bookmark.firstChildElement( QStringLiteral( "xmin" ) );
475 QDomElement ymin = bookmark.firstChildElement( QStringLiteral( "ymin" ) );
476 QDomElement xmax = bookmark.firstChildElement( QStringLiteral( "xmax" ) );
477 QDomElement ymax = bookmark.firstChildElement( QStringLiteral( "ymax" ) );
478 QDomElement rotation = bookmark.firstChildElement( QStringLiteral( "rotation" ) );
479 QDomElement srid = bookmark.firstChildElement( QStringLiteral( "sr_id" ) );
480
481 bool ok = false;
482 QgsBookmark b;
483 b.setName( name.text() );
484 b.setGroup( prjname.text() );
486 crs.createFromSrsId( srid.text().toLongLong() );
487 b.setExtent( QgsReferencedRectangle( QgsRectangle( xmin.text().toDouble(),
488 ymin.text().toDouble(),
489 xmax.text().toDouble(),
490 ymax.text().toDouble() ), crs ) );
491 b.setRotation( rotation.text().toDouble() );
492 addBookmark( b, &ok );
493 res = res && ok;
494 }
495
496 return res;
497}
498
499void QgsBookmarkManager::store()
500{
501 if ( !mFilePath.isEmpty() )
502 {
503 QFile f( mFilePath );
504 if ( !f.open( QFile::WriteOnly | QIODevice::Truncate ) )
505 {
506 f.close();
507 return;
508 }
509
510 QDomDocument doc;
511 QDomElement elem = writeXml( doc );
512 doc.appendChild( elem );
513
514 QTextStream out( &f );
515#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
516 out.setCodec( "UTF-8" );
517#endif
518 doc.save( out, 2 );
519 f.close();
520 }
521}
522
523void QgsBookmarkManager::initialize( const QString &filePath )
524{
525 if ( mInitialized )
526 return;
527
528 mFilePath = filePath;
529
530 mInitialized = true;
531
532 // restore state
533 if ( !QFileInfo::exists( mFilePath ) )
534 {
535 //convert old bookmarks from db
537 int result = database.open( QgsApplication::qgisUserDatabaseFilePath() );
538 if ( result != SQLITE_OK )
539 {
540 return;
541 }
542
543 sqlite3_statement_unique_ptr preparedStatement = database.prepare( QStringLiteral( "SELECT name,xmin,ymin,xmax,ymax,projection_srid FROM tbl_bookmarks" ), result );
544 if ( result == SQLITE_OK )
545 {
546 while ( preparedStatement.step() == SQLITE_ROW )
547 {
548 const QString name = preparedStatement.columnAsText( 0 );
549 const double xMin = preparedStatement.columnAsDouble( 1 );
550 const double yMin = preparedStatement.columnAsDouble( 2 );
551 const double xMax = preparedStatement.columnAsDouble( 3 );
552 const double yMax = preparedStatement.columnAsDouble( 4 );
553 const long long srid = preparedStatement.columnAsInt64( 5 );
554
555 QgsBookmark b;
556 b.setName( name );
557 const QgsRectangle extent( xMin, yMin, xMax, yMax );
559 addBookmark( b );
560 }
561 }
562 store();
563 }
564 else
565 {
566 QFile f( mFilePath );
567 if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
568 {
569 return;
570 }
571
572 QDomDocument doc;
573 if ( !doc.setContent( &f ) )
574 {
575 return;
576 }
577 f.close();
578
579 QDomElement elem = doc.documentElement();
580 readXml( elem, doc );
581 }
582}
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
Manages storage of a set of bookmarks.
void renameGroup(const QString &oldName, const QString &newName)
Renames an existing group from oldName to newName.
bool removeBookmark(const QString &id)
Removes the bookmark with matching id from the manager.
QList< QgsBookmark > bookmarksByGroup(const QString &group)
Returns a list of bookmark with a matching group, or an empty list if no matching bookmarks were foun...
void initialize(const QString &filePath)
Initializes the bookmark manager.
void bookmarkAboutToBeRemoved(const QString &id)
Emitted when a bookmark is about to be removed from the manager.
void bookmarkChanged(const QString &id)
Emitted when a bookmark is changed.
static QgsBookmarkManager * createProjectBasedManager(QgsProject *project)
Returns a newly created QgsBookmarkManager using a project-based bookmark store, linked to the specif...
bool readXml(const QDomElement &element, const QDomDocument &doc)
Reads the manager's state from a DOM element, restoring all bookmarks present in the XML document.
void clear()
Removes and deletes all bookmarks from the manager.
void bookmarkAdded(const QString &id)
Emitted when a bookmark has been added to the manager.
bool updateBookmark(const QgsBookmark &bookmark)
Updates the definition of a bookmark in the manager.
void bookmarkAboutToBeAdded(const QString &id)
Emitted when a bookmark is about to be added to the manager.
bool moveBookmark(const QString &id, QgsBookmarkManager *destination)
Moves the bookmark with matching id from this manager to a destination manager.
static bool exportToFile(const QString &path, const QList< const QgsBookmarkManager * > &managers, const QString &group=QString())
Exports all bookmarks from a list of managers to an xml file at the specified path.
QgsBookmark bookmarkById(const QString &id) const
Returns the bookmark with a matching id, or an empty bookmark if no matching bookmarks were found.
QStringList groups() const
Returns a list of all bookmark groups contained in the manager.
QString addBookmark(const QgsBookmark &bookmark, bool *ok=nullptr)
Adds a bookmark to the manager.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the state of the manager.
QgsBookmarkManager(QObject *parent=nullptr)
Constructor for QgsBookmarkManager, with the specified parent object.
QList< QgsBookmark > bookmarks() const
Returns a list of all bookmarks contained in the manager.
void bookmarkRemoved(const QString &id)
Emitted when a bookmark was removed from the manager.
bool importFromFile(const QString &path)
Imports the bookmarks from an xml file at the specified path.
Represents a spatial bookmark, with a name, CRS and extent.
static QgsBookmark fromXml(const QDomElement &element, const QDomDocument &doc)
Creates a bookmark using the properties from a DOM element.
void setGroup(const QString &group)
Sets the bookmark's group, which is a user-visible string identifying the bookmark's category.
bool operator!=(const QgsBookmark &other) const
void setRotation(double rotation)
Sets the bookmark's spatial map rotation.
QString id() const
Returns the bookmark's unique ID.
QgsReferencedRectangle extent() const
Returns the bookmark's spatial extent.
void setExtent(const QgsReferencedRectangle &extent)
Sets the bookmark's spatial extent.
double rotation() const
Returns the bookmark's map rotation.
void setId(const QString &id)
Sets the bookmark's unique id.
QDomElement writeXml(QDomDocument &doc) const
Returns a DOM element representing the bookmark's properties.
bool operator==(const QgsBookmark &other) const
QString group() const
Returns the bookmark's group, which is a user-visible string identifying the bookmark's category.
void setName(const QString &name)
Sets the bookmark's name, which is a user-visible string identifying the bookmark.
QString name() const
Returns the bookmark's name, which is a user-visible string identifying the bookmark.
This class represents a coordinate reference system (CRS).
bool readXml(const QDomNode &node)
Restores state from the given DOM node.
bool createFromSrsId(long srsId)
Sets this CRS by lookup of internal QGIS CRS ID in the CRS database.
bool writeXml(QDomNode &node, QDomDocument &doc) const
Stores state to the given Dom node in the given document.
static QgsCoordinateReferenceSystem fromSrsId(long srsId)
Creates a CRS from a specified QGIS SRS ID.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
int readNumEntry(const QString &scope, const QString &key, int def=0, bool *ok=nullptr) const
Reads an integer from the specified scope and key.
QString readEntry(const QString &scope, const QString &key, const QString &def=QString(), bool *ok=nullptr) const
Reads a string from the specified scope and key.
double readDoubleEntry(const QString &scope, const QString &key, double def=0, bool *ok=nullptr) const
Reads a double from the specified scope and key.
void setDirty(bool b=true)
Flag the project as dirty (modified).
A rectangle specified with double values.
static QgsRectangle fromWkt(const QString &wkt)
Creates a new rectangle from a wkt string.
QString asWktPolygon() const
Returns a string representation of the rectangle as a WKT Polygon.
QgsCoordinateReferenceSystem crs() const
Returns the associated coordinate reference system, or an invalid CRS if no reference system is set.
A QgsRectangle with associated coordinate reference system.
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.
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.
double columnAsDouble(int column) const
Gets column value from the current statement row as a double.
int step()
Steps to the next record in the statement, returning the sqlite3 result code.
qlonglong columnAsInt64(int column) const
Gets column value from the current statement row as a long long integer (64 bits).
QString qgsDoubleToString(double a, int precision=17)
Returns a string representation of a double.
Definition qgis.h:5834
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition qgis.h:5917
const QgsCoordinateReferenceSystem & crs