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