QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsprojectionselectiontreewidget.cpp
Go to the documentation of this file.
1/***************************************************************************
2 * qgsprojectionselector.cpp *
3 * Copyright (C) 2005 by Tim Sutton *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU General Public License as published by *
8 * the Free Software Foundation; either version 2 of the License, or *
9 * (at your option) any later version. *
10 ***************************************************************************/
12
13//standard includes
14#include <sqlite3.h>
15
16//qgis includes
17#include "qgis.h" //magic numbers here
18#include "qgsapplication.h"
19#include "qgslogger.h"
21#include "qgsmessagelog.h"
22#include "qgssettings.h"
23#include "qgsrectangle.h"
25#include "qgsdatums.h"
26#include "qgsprojoperation.h"
27#include "qgsstringutils.h"
28
29//qt includes
30#include <QFileInfo>
31#include <QHeaderView>
32#include <QResizeEvent>
33#include <QMessageBox>
34#include <QRegularExpression>
35
37 : QWidget( parent )
38{
39 setupUi( this );
40
41 QFont f = teProjection->font();
42 f.setPointSize( f.pointSize() - 2 );
43 teProjection->setFont( f );
44
45 leSearch->setShowSearchIcon( true );
46
47 connect( lstCoordinateSystems, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked );
48 connect( lstRecent, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked );
49 connect( lstCoordinateSystems, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged );
50 connect( lstRecent, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged );
51 connect( cbxHideDeprecated, &QCheckBox::stateChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
52 connect( leSearch, &QgsFilterLineEdit::textChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
53
54 mAreaCanvas->setVisible( mShowMap );
55
56 // Get the full path name to the sqlite3 spatial reference database.
57 mSrsDatabaseFileName = QgsApplication::srsDatabaseFilePath();
58
59 lstCoordinateSystems->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
60 lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
61 lstCoordinateSystems->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
62
63 // Hide (internal) ID column
64 lstCoordinateSystems->setColumnHidden( QgisCrsIdColumn, true );
65
66 lstRecent->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
67 lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
68 lstRecent->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
69
70 // Hide (internal) ID column
71 lstRecent->setColumnHidden( QgisCrsIdColumn, true );
72
74
75 mCheckBoxNoProjection->setHidden( true );
76 mCheckBoxNoProjection->setEnabled( false );
77 connect( mCheckBoxNoProjection, &QCheckBox::toggled, this, [ = ]
78 {
79 if ( !mBlockSignals )
80 {
81 emit crsSelected();
83 }
84 } );
85 connect( mCheckBoxNoProjection, &QCheckBox::toggled, this, [ = ]( bool checked )
86 {
87 if ( mCheckBoxNoProjection->isEnabled() )
88 {
89 mFrameProjections->setDisabled( checked );
90 }
91 } );
92
93 QgsSettings settings;
94 mSplitter->restoreState( settings.value( QStringLiteral( "Windows/ProjectionSelector/splitterState" ) ).toByteArray() );
95}
96
98{
99 QgsSettings settings;
100 settings.setValue( QStringLiteral( "Windows/ProjectionSelector/splitterState" ), mSplitter->saveState() );
101
102 // Push current projection to front, only if set
103 const QgsCoordinateReferenceSystem selectedCrs = crs();
104 if ( selectedCrs.isValid() )
106}
107
109{
110 lstCoordinateSystems->header()->resizeSection( NameColumn, event->size().width() - 240 );
111 lstCoordinateSystems->header()->resizeSection( AuthidColumn, 240 );
112 lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
113
114 lstRecent->header()->resizeSection( NameColumn, event->size().width() - 240 );
115 lstRecent->header()->resizeSection( AuthidColumn, 240 );
116 lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
117}
118
120{
121 if ( mInitialized )
122 return;
123
124 // ensure the projection list view is actually populated
125 // before we show this widget
126 loadCrsList( &mCrsFilter );
127 loadUserCrsList( &mCrsFilter );
128
129 if ( !mRecentProjListDone )
130 {
131 for ( const QgsCoordinateReferenceSystem &crs : std::as_const( mRecentProjections ) )
132 insertRecent( crs );
133 mRecentProjListDone = true;
134 }
135
136 // apply deferred selection
137 mBlockSignals = true; // we've already emitted the signal, when the deferred crs was first set
138 applySelection();
139 mBlockSignals = false;
140
141 emit initialized();
142
143 // Pass up the inheritance hierarchy
144 QWidget::showEvent( event );
145 mInitialized = true;
146}
147
148QString QgsProjectionSelectionTreeWidget::ogcWmsCrsFilterAsSqlExpression( QSet<QString> *crsFilter )
149{
150 QString sqlExpression = QStringLiteral( "1" ); // it's "SQL" for "true"
151 QMap<QString, QStringList> authParts;
152
153 if ( !crsFilter )
154 return sqlExpression;
155
156 /*
157 Ref: WMS 1.3.0, section 6.7.3 "Layer CRS":
158
159 Every Layer CRS has an identifier that is a character string. Two types of
160 Layer CRS identifiers are permitted: "label" and "URL" identifiers:
161
162 Label: The identifier includes a namespace prefix, a colon, a numeric or
163 string code, and in some instances a comma followed by additional
164 parameters. This International Standard defines three namespaces:
165 CRS, EpsgCrsId and AUTO2 [...]
166
167 URL: The identifier is a fully-qualified Uniform Resource Locator that
168 references a publicly-accessible file containing a definition of the CRS
169 that is compliant with ISO 19111.
170 */
171
172 // iterate through all incoming CRSs
173
174 const auto authIds { *crsFilter };
175 for ( const QString &auth_id : authIds )
176 {
177 QStringList parts = auth_id.split( ':' );
178
179 if ( parts.size() < 2 )
180 continue;
181
182 authParts[ parts.at( 0 ).toUpper()].append( parts.at( 1 ).toUpper() );
183 }
184
185 if ( authParts.isEmpty() )
186 return sqlExpression;
187
188 if ( !authParts.isEmpty() )
189 {
190 QString prefix = QStringLiteral( " AND (" );
191 for ( auto it = authParts.constBegin(); it != authParts.constEnd(); ++it )
192 {
193 sqlExpression += QStringLiteral( "%1(upper(auth_name)='%2' AND upper(auth_id) IN ('%3'))" )
194 .arg( prefix,
195 it.key(),
196 it.value().join( QLatin1String( "','" ) ) );
197 prefix = QStringLiteral( " OR " );
198 }
199 sqlExpression += ')';
200 }
201
202 QgsDebugMsgLevel( "exiting with '" + sqlExpression + "'.", 4 );
203
204 return sqlExpression;
205}
206
207void QgsProjectionSelectionTreeWidget::applySelection( int column, QString value )
208{
209 if ( !mProjListDone || !mUserProjListDone )
210 {
211 // defer selection until loaded
212 mSearchColumn = column;
213 mSearchValue = value;
214 return;
215 }
216
217 if ( column == QgsProjectionSelectionTreeWidget::None )
218 {
219 // invoked deferred selection
220 column = mSearchColumn;
221 value = mSearchValue;
222
223 mSearchColumn = QgsProjectionSelectionTreeWidget::None;
224 mSearchValue.clear();
225 }
226
227 if ( column == QgsProjectionSelectionTreeWidget::None )
228 return;
229
230 QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( value, Qt::MatchExactly | Qt::MatchRecursive, column );
231 if ( !nodes.isEmpty() )
232 {
233 QgsDebugMsgLevel( QStringLiteral( "found %1,%2" ).arg( column ).arg( value ), 4 );
234 lstCoordinateSystems->setCurrentItem( nodes.first() );
235 }
236 else
237 {
238 QgsDebugMsgLevel( QStringLiteral( "nothing found for %1,%2" ).arg( column ).arg( value ), 4 );
239 // deselect the selected item to avoid confusing the user
240 lstCoordinateSystems->clearSelection();
241 lstRecent->clearSelection();
242 teProjection->clear();
243 }
244}
245
246void QgsProjectionSelectionTreeWidget::insertRecent( const QgsCoordinateReferenceSystem &crs )
247{
248 if ( !mProjListDone || !mUserProjListDone )
249 return;
250
251 QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( QString::number( crs.srsid() ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
252 if ( nodes.isEmpty() )
253 return;
254
255 lstRecent->insertTopLevelItem( 0, new QTreeWidgetItem( lstRecent, QStringList()
256 << nodes.first()->text( NameColumn )
257 << nodes.first()->text( AuthidColumn )
258 << nodes.first()->text( QgisCrsIdColumn ) ) );
259}
260
261//note this line just returns the projection name!
262QString QgsProjectionSelectionTreeWidget::selectedName()
263{
264 // return the selected wkt name from the list view
265 QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
266 return lvi ? lvi->text( NameColumn ) : QString();
267}
268
270{
271 if ( !crs.isValid() )
272 {
273 mCheckBoxNoProjection->setChecked( true );
274 }
275 else
276 {
277 bool changed = false;
278 if ( !mInitialized )
279 {
280 changed = mDeferredLoadCrs != crs;
281 mDeferredLoadCrs = crs;
282 }
283 mBlockSignals = true;
284 mCheckBoxNoProjection->setChecked( false );
285 mBlockSignals = false;
286
287 if ( !crs.authid().isEmpty() )
288 applySelection( AuthidColumn, crs.authid() );
289 else
290 loadUnknownCrs( crs );
291 if ( changed )
292 {
293 emit crsSelected();
295 }
296 }
297}
298
300{
301 mAreaCanvas->setCanvasRect( rect );
302}
303
305{
306 return mAreaCanvas->canvasRect();
307}
308
309QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const
310{
311 // Only return the attribute if there is a node in the tree
312 // selected that has an srs_id. This prevents error if the user
313 // selects a top-level node rather than an actual coordinate
314 // system
315 //
316 // Get the selected node and make sure it is a srs andx
317 // not a top-level projection node
318 QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
319 if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
320 return QString();
321
322 //
323 // Determine if this is a user projection or a system on
324 // user projection defs all have srs_id >= 100000
325 //
326 QString databaseFileName;
327 if ( lvi->text( QgisCrsIdColumn ).toLong() >= USER_CRS_START_ID )
328 {
329 databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
330 if ( !QFileInfo::exists( databaseFileName ) )
331 {
332 return QString();
333 }
334 }
335 else
336 {
337 databaseFileName = mSrsDatabaseFileName;
338 }
339
340 //
341 // set up the database
342 // XXX We could probably hold the database open for the life of this object,
343 // assuming that it will never be used anywhere else. Given the low overhead,
344 // opening it each time seems to be a reasonable approach at this time.
345 sqlite3 *database = nullptr;
346 int rc = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
347 if ( rc )
348 {
349 QgsMessageLog::logMessage( tr( "Resource Location Error" ), tr( "Error reading database file from: \n %1\n"
350 "Because of this the projection selector will not work…" ).arg( databaseFileName ),
351 Qgis::MessageLevel::Critical );
352 return QString();
353 }
354
355 // prepare the sql statement
356 const char *tail = nullptr;
357 sqlite3_stmt *stmt = nullptr;
358 QString sql = QStringLiteral( "select %1 from tbl_srs where srs_id=%2" )
359 .arg( expression,
360 lvi->text( QgisCrsIdColumn ) );
361
362 QgsDebugMsgLevel( QStringLiteral( "Finding selected attribute using : %1" ).arg( sql ), 4 );
363 rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
364 // XXX Need to free memory from the error msg if one is set
365 QString attributeValue;
366 if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
367 {
368 // get the first row of the result set
369 attributeValue = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
370 }
371
372 // close the statement
373 sqlite3_finalize( stmt );
374 // close the database
375 sqlite3_close( database );
376
377 // return the srs
378 return attributeValue;
379}
380
382{
383 if ( mCheckBoxNoProjection->isEnabled() && mCheckBoxNoProjection->isChecked() )
385
386 if ( !mInitialized && mDeferredLoadCrs.isValid() )
387 return mDeferredLoadCrs;
388
389 const QString srsIdString = getSelectedExpression( QStringLiteral( "srs_id" ) );
390 if ( !srsIdString.isEmpty() )
391 {
392 int srid = srsIdString.toLong();
393 if ( srid >= USER_CRS_START_ID )
394 return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) );
395 else
396 return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) );
397 }
398 else
399 {
400 // custom CRS
401 QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
402 if ( lvi && lvi->data( 0, RoleWkt ).isValid() )
403 return QgsCoordinateReferenceSystem::fromWkt( lvi->data( 0, RoleWkt ).toString() );
404 else if ( lvi && lvi->data( 0, RoleProj ).isValid() )
405 return QgsCoordinateReferenceSystem::fromProj( lvi->data( 0, RoleProj ).toString() );
406 else
408 }
409}
410
412{
413 mCheckBoxNoProjection->setVisible( show );
414 mCheckBoxNoProjection->setEnabled( show );
415 if ( show )
416 {
417 mFrameProjections->setDisabled( mCheckBoxNoProjection->isChecked() );
418 }
419}
420
422{
423 mShowMap = show;
424 mAreaCanvas->setVisible( show );
425}
426
428{
429 return !mCheckBoxNoProjection->isHidden();
430}
431
433{
434 mCheckBoxNoProjection->setText( text );
435}
436
438{
439 return mShowMap;
440}
441
443{
444 QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
445 if ( mCheckBoxNoProjection->isChecked() )
446 return true;
447 else if ( !mInitialized && mDeferredLoadCrs.isValid() )
448 return true;
449 else
450 return item && ( !item->text( QgisCrsIdColumn ).isEmpty() || item->data( 0, RoleWkt ).isValid() );
451}
452
453long QgsProjectionSelectionTreeWidget::selectedCrsId()
454{
455 QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
456
457 if ( item && !item->text( QgisCrsIdColumn ).isEmpty() )
458 return lstCoordinateSystems->currentItem()->text( QgisCrsIdColumn ).toLong();
459 else
460 return 0;
461}
462
463
464void QgsProjectionSelectionTreeWidget::setOgcWmsCrsFilter( const QSet<QString> &crsFilter )
465{
466 mCrsFilter = crsFilter;
467 mProjListDone = false;
468 mUserProjListDone = false;
469 lstCoordinateSystems->clear();
470}
471
472void QgsProjectionSelectionTreeWidget::loadUserCrsList( QSet<QString> *crsFilter )
473{
474 if ( mUserProjListDone )
475 return;
476
477 QgsDebugMsgLevel( QStringLiteral( "Fetching user projection list..." ), 4 );
478
479 // User defined coordinate system node
480 // Make in an italic font to distinguish them from real projections
481 mUserProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "User Defined Coordinate Systems" ) ) );
482 mUserProjList->setFlags( mUserProjList->flags() & ~Qt::ItemIsSelectable );
483
484 QFont fontTemp = mUserProjList->font( 0 );
485 fontTemp.setItalic( true );
486 fontTemp.setBold( true );
487 mUserProjList->setFont( 0, fontTemp );
488 mUserProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
489
490 const QList<QgsCoordinateReferenceSystemRegistry::UserCrsDetails> userCrsList = QgsApplication::coordinateReferenceSystemRegistry()->userCrsList();
491 for ( const QgsCoordinateReferenceSystemRegistry::UserCrsDetails &details : userCrsList )
492 {
493 const QString authid = QStringLiteral( "USER:%1" ).arg( details.id );
494 if ( crsFilter && !crsFilter->isEmpty() && !crsFilter->contains( authid ) && !crsFilter->contains( authid.toLower() ) )
495 continue;
496
497 QTreeWidgetItem *newItem = new QTreeWidgetItem( mUserProjList, QStringList() << details.name );
498 newItem->setText( QgisCrsIdColumn, QString::number( details.id ) );
499 newItem->setText( AuthidColumn, authid );
500 }
501
502 mUserProjListDone = true;
503}
504
505void QgsProjectionSelectionTreeWidget::loadCrsList( QSet<QString> *crsFilter )
506{
507 if ( mProjListDone )
508 return;
509
510 // convert our Coordinate Reference System filter into the SQL expression
511 QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
512
513 // Create the top-level nodes for the list view of projections
514 // Make in an italic font to distinguish them from real projections
515 //
516 // Geographic coordinate system node
517 mGeoList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Geographic Coordinate Systems" ) ) );
518 mGeoList->setFlags( mGeoList->flags() & ~Qt::ItemIsSelectable );
519
520 QFont fontTemp = mGeoList->font( 0 );
521 fontTemp.setItalic( true );
522 fontTemp.setBold( true );
523 mGeoList->setFont( 0, fontTemp );
524 mGeoList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) ) );
525
526 // Projected coordinate system node
527 mProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Projected Coordinate Systems" ) ) );
528 mProjList->setFlags( mProjList->flags() & ~Qt::ItemIsSelectable );
529
530 fontTemp = mProjList->font( 0 );
531 fontTemp.setItalic( true );
532 fontTemp.setBold( true );
533 mProjList->setFont( 0, fontTemp );
534 mProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) );
535
536 //bail out in case the projections db does not exist
537 //this is necessary in case the pc is running linux with a
538 //read only filesystem because otherwise sqlite will try
539 //to create the db file on the fly
540
541 if ( !QFileInfo::exists( mSrsDatabaseFileName ) )
542 {
543 mProjListDone = true;
544 return;
545 }
546
547 // open the database containing the spatial reference data
548 sqlite3 *database = nullptr;
549 int rc = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
550 if ( rc )
551 {
552 // XXX This will likely never happen since on open, sqlite creates the
553 // database if it does not exist.
554 showDBMissingWarning( mSrsDatabaseFileName );
555 return;
556 }
557
558 const char *tail = nullptr;
559 sqlite3_stmt *stmt = nullptr;
560 // Set up the query to retrieve the projection information needed to populate the list
561 //note I am giving the full field names for clarity here and in case someone
562 //changes the underlying view TS
563 QString sql = QStringLiteral( "select description, srs_id, upper(auth_name||':'||auth_id), is_geo, name, parameters, deprecated from vw_srs where %1 order by name,description" )
564 .arg( sqlFilter );
565
566 rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
567 // XXX Need to free memory from the error msg if one is set
568 if ( rc == SQLITE_OK )
569 {
570 QTreeWidgetItem *newItem = nullptr;
571 // Cache some stuff to speed up creating of the list of projected
572 // spatial reference systems
573 QString previousSrsType;
574 QTreeWidgetItem *previousSrsTypeNode = nullptr;
575
576 while ( sqlite3_step( stmt ) == SQLITE_ROW )
577 {
578 // check to see if the srs is geographic
579 int isGeo = sqlite3_column_int( stmt, 3 );
580 if ( isGeo )
581 {
582 // this is a geographic coordinate system
583 // Add it to the tree (field 0)
584 newItem = new QTreeWidgetItem( mGeoList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
585
586 // display the authority name (field 2) in the second column of the list view
587 newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
588
589 // display the qgis srs_id (field 1) in the third column of the list view
590 newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
591 }
592 else
593 {
594 // This is a projected srs
595 QTreeWidgetItem *node = nullptr;
596 QString srsType = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 4 ) );
597 if ( srsType.isEmpty() )
598 srsType = tr( "Other" );
599
600 // Find the node for this type and add the projection to it
601 // If the node doesn't exist, create it
602 if ( srsType == previousSrsType )
603 {
604 node = previousSrsTypeNode;
605 }
606 else
607 {
608 // Different from last one, need to search
609 QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( srsType, Qt::MatchExactly | Qt::MatchRecursive, NameColumn );
610 if ( nodes.isEmpty() )
611 {
612 // the node doesn't exist -- create it
613 // Make in an italic font to distinguish them from real projections
614 node = new QTreeWidgetItem( mProjList, QStringList( srsType ) );
615 node->setFlags( node->flags() & ~Qt::ItemIsSelectable );
616
617 QFont fontTemp = node->font( 0 );
618 fontTemp.setItalic( true );
619 node->setFont( 0, fontTemp );
620 }
621 else
622 {
623 node = nodes.first();
624 }
625 // Update the cache.
626 previousSrsType = srsType;
627 previousSrsTypeNode = node;
628 }
629 // add the item, setting the projection name in the first column of the list view
630 newItem = new QTreeWidgetItem( node, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
631 // display the authority id (field 2) in the second column of the list view
632 newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
633 // display the qgis srs_id (field 1) in the third column of the list view
634 newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
635 // expand also parent node
636 newItem->parent()->setExpanded( true );
637 }
638
639 // display the qgis deprecated in the user data of the item
640 newItem->setData( 0, RoleDeprecated, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 6 ) ) );
641 newItem->setHidden( cbxHideDeprecated->isChecked() );
642 }
643 mProjList->setExpanded( true );
644 }
645
646 // close the sqlite3 statement
647 sqlite3_finalize( stmt );
648 // close the database
649 sqlite3_close( database );
650
651 mProjListDone = true;
652}
653
654void QgsProjectionSelectionTreeWidget::loadUnknownCrs( const QgsCoordinateReferenceSystem &crs )
655{
656 if ( !mUnknownList )
657 {
658 mUnknownList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Custom Coordinate Systems" ) ) );
659 mUnknownList->setFlags( mUnknownList->flags() & ~Qt::ItemIsSelectable );
660
661 QFont fontTemp = mUnknownList->font( 0 );
662 fontTemp.setItalic( true );
663 fontTemp.setBold( true );
664 mUnknownList->setFont( 0, fontTemp );
665 mUnknownList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
666 }
667
668 QTreeWidgetItem *newItem = new QTreeWidgetItem( mUnknownList, QStringList( crs.description().isEmpty() ? QObject::tr( "Custom CRS" ) : crs.description() ) );
669 newItem->setData( 0, RoleWkt, crs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) );
670 newItem->setData( 0, RoleProj, crs.toProj() );
671
672 lstCoordinateSystems->setCurrentItem( newItem );
673}
674
675// New coordinate system selected from the list
676void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
677{
678 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
679
680 if ( !current )
681 {
682 QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
683 return;
684 }
685
686 lstCoordinateSystems->scrollToItem( current );
687
688 // If the item has children, it's not an end node in the tree, and
689 // hence is just a grouping thingy, not an actual CRS.
690 if ( current->childCount() == 0 )
691 {
692 // Found a real CRS
693 if ( !mBlockSignals )
694 {
695 emit crsSelected();
696 emit hasValidSelectionChanged( true );
697 }
698
699 updateBoundsPreview();
700
701 const QString crsId = current->text( QgisCrsIdColumn );
702 if ( !crsId.isEmpty() )
703 {
704 QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn );
705 if ( !nodes.isEmpty() )
706 {
707 QgsDebugMsgLevel( QStringLiteral( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
708 lstRecent->setCurrentItem( nodes.first() );
709 }
710 else
711 {
712 QgsDebugMsgLevel( QStringLiteral( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
713 lstRecent->clearSelection();
714 lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
715 }
716 }
717 else
718 {
719 lstRecent->clearSelection();
720 lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
721 }
722 }
723 else
724 {
725 // Not a CRS - remove the highlight so the user doesn't get too confused
726 current->setSelected( false );
727 teProjection->clear();
728 lstRecent->clearSelection();
729 emit hasValidSelectionChanged( false );
730 }
731}
732
733void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked( QTreeWidgetItem *current, int column )
734{
735 Q_UNUSED( column )
736
737 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
738
739 if ( !current )
740 {
741 QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
742 return;
743 }
744
745 // If the item has children, it's not an end node in the tree, and
746 // hence is just a grouping thingy, not an actual CRS.
747 if ( current->childCount() == 0 )
749}
750
751void QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
752{
753 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
754
755 if ( !current )
756 {
757 QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
758 return;
759 }
760
761 lstRecent->scrollToItem( current );
762
763 QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
764 if ( !nodes.isEmpty() )
765 lstCoordinateSystems->setCurrentItem( nodes.first() );
766}
767
768void QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked( QTreeWidgetItem *current, int column )
769{
770 Q_UNUSED( column )
771
772 QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
773
774 if ( !current )
775 {
776 QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
777 return;
778 }
779
780 QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
781 if ( !nodes.isEmpty() )
783}
784
785void QgsProjectionSelectionTreeWidget::updateFilter()
786{
787 QString filterTxtCopy = QgsStringUtils::qRegExpEscape( leSearch->text() );
788 filterTxtCopy.replace( QRegularExpression( "\\s+" ), QStringLiteral( ".*" ) );
789 const QRegularExpression re( filterTxtCopy, QRegularExpression::PatternOption::CaseInsensitiveOption );
790
791 const bool hideDeprecated = cbxHideDeprecated->isChecked();
792
793 auto filterTreeWidget = [ = ]( QTreeWidget * tree )
794 {
795 QTreeWidgetItemIterator itr( tree );
796 while ( *itr )
797 {
798 if ( ( *itr )->childCount() == 0 ) // it's an end node aka a projection
799 {
800 if ( hideDeprecated && ( *itr )->data( 0, RoleDeprecated ).toBool() )
801 {
802 ( *itr )->setHidden( true );
803 if ( ( *itr )->isSelected() )
804 {
805 ( *itr )->setSelected( false );
806 teProjection->clear();
807 }
808 }
809 else if ( ( *itr )->text( NameColumn ).contains( re )
810 || ( *itr )->text( AuthidColumn ).contains( re )
811 )
812 {
813 ( *itr )->setHidden( false );
814 QTreeWidgetItem *parent = ( *itr )->parent();
815 while ( parent )
816 {
817 parent->setExpanded( true );
818 parent->setHidden( false );
819 parent = parent->parent();
820 }
821 }
822 else
823 {
824 ( *itr )->setHidden( true );
825 }
826 }
827 else
828 {
829 ( *itr )->setHidden( true );
830 }
831 ++itr;
832 }
833 };
834
835 // filter recent crs's
836 filterTreeWidget( lstRecent );
837
838 // filter crs's
839 filterTreeWidget( lstCoordinateSystems );
840}
841
843{
844}
845
846long QgsProjectionSelectionTreeWidget::getLargestCrsIdMatch( const QString &sql )
847{
848 long srsId = 0;
849
850 //
851 // Now perform the actual search
852 //
853
854 sqlite3 *database = nullptr;
855 const char *tail = nullptr;
856 sqlite3_stmt *stmt = nullptr;
857 int result;
858
859 // first we search the users db as any srsid there will be definition be greater than in sys db
860
861 //check the db is available
862 QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
863 if ( QFileInfo::exists( databaseFileName ) ) //only bother trying to open if the file exists
864 {
865 result = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
866 if ( result )
867 {
868 // XXX This will likely never happen since on open, sqlite creates the
869 // database if it does not exist. But we checked earlier for its existence
870 // and aborted in that case. This is because we may be running from read only
871 // media such as live cd and don't want to force trying to create a db.
872 showDBMissingWarning( databaseFileName );
873 return 0;
874 }
875
876 result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
877 // XXX Need to free memory from the error msg if one is set
878 if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
879 {
880 QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
881 srsId = srsIdString.toLong();
882 // close the sqlite3 statement
883 sqlite3_finalize( stmt );
884 sqlite3_close( database );
885 return srsId;
886 }
887 }
888 else
889 {
890 //only bother looking in srs.db if it wasn't found above
891 result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
892 if ( result )
893 {
894 QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
895 //no need for assert because user db may not have been created yet
896 return 0;
897 }
898 }
899
900 result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
901 // XXX Need to free memory from the error msg if one is set
902 if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
903 {
904 QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
905 srsId = srsIdString.toLong();
906 }
907
908 // close the sqlite3 statement
909 sqlite3_finalize( stmt );
910 sqlite3_close( database );
911
912 return srsId;
913}
914
915void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
916{
917 QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
918 if ( !lvi || ( lvi->text( QgisCrsIdColumn ).isEmpty() && !lvi->data( 0, RoleWkt ).isValid() ) )
919 return;
920
921 QgsCoordinateReferenceSystem currentCrs = crs();
922 if ( !currentCrs.isValid() )
923 return;
924
925 QgsRectangle rect = currentCrs.bounds();
926 QString extentString = tr( "Extent not known" );
927 mAreaCanvas->setPreviewRect( rect );
928 if ( !qgsDoubleNear( rect.area(), 0.0 ) )
929 {
930 extentString = QStringLiteral( "%1, %2, %3, %4" )
931 .arg( rect.xMinimum(), 0, 'f', 2 )
932 .arg( rect.yMinimum(), 0, 'f', 2 )
933 .arg( rect.xMaximum(), 0, 'f', 2 )
934 .arg( rect.yMaximum(), 0, 'f', 2 );
935 }
936
937 QStringList properties;
938 if ( currentCrs.isGeographic() )
939 properties << tr( "Geographic (uses latitude and longitude for coordinates)" );
940 else
941 {
942 properties << tr( "Units: %1" ).arg( QgsUnitTypes::toString( currentCrs.mapUnits() ) );
943 }
944 properties << ( currentCrs.isDynamic() ? tr( "Dynamic (relies on a datum which is not plate-fixed)" ) : tr( "Static (relies on a datum which is plate-fixed)" ) );
945
946 try
947 {
948 const QString celestialBody = currentCrs.celestialBodyName();
949 if ( !celestialBody.isEmpty() )
950 {
951 properties << tr( "Celestial body: %1" ).arg( celestialBody );
952 }
953 }
954 catch ( QgsNotSupportedException & )
955 {
956
957 }
958
959 try
960 {
961 const QgsDatumEnsemble ensemble = currentCrs.datumEnsemble();
962 if ( ensemble.isValid() )
963 {
964 QString id;
965 if ( !ensemble.code().isEmpty() )
966 id = QStringLiteral( "<i>%1</i> (%2:%3)" ).arg( ensemble.name(), ensemble.authority(), ensemble.code() );
967 else
968 id = QStringLiteral( "<i>%</i>”" ).arg( ensemble.name() );
969 if ( ensemble.accuracy() > 0 )
970 {
971 properties << tr( "Based on %1, which has a limited accuracy of <b>at best %2 meters</b>." ).arg( id ).arg( ensemble.accuracy() );
972 }
973 else
974 {
975 properties << tr( "Based on %1, which has a limited accuracy." ).arg( id );
976 }
977 }
978 }
979 catch ( QgsNotSupportedException & )
980 {
981
982 }
983
984 const QgsProjOperation operation = currentCrs.operation();
985 properties << tr( "Method: %1" ).arg( operation.description() );
986
987 const QString propertiesString = QStringLiteral( "<dt><b>%1</b></dt><dd><ul><li>%2</li></ul></dd>" ).arg( tr( "Properties" ),
988 properties.join( QLatin1String( "</li><li>" ) ) );
989
990 const QString extentHtml = QStringLiteral( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Extent" ), extentString );
991 const QString wktString = QStringLiteral( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "WKT" ), currentCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED, true ).replace( '\n', QLatin1String( "<br>" ) ).replace( ' ', QLatin1String( "&nbsp;" ) ) );
992 const QString proj4String = QStringLiteral( "<dt><b>%1</b></dt><dd><code>%2</code></dd>" ).arg( tr( "Proj4" ), currentCrs.toProj() );
993
994#ifdef Q_OS_WIN
995 const int smallerPointSize = std::max( font().pointSize() - 1, 8 ); // bit less on windows, due to poor rendering of small point sizes
996#else
997 const int smallerPointSize = std::max( font().pointSize() - 2, 6 );
998#endif
999
1000 teProjection->setText( QStringLiteral( "<div style=\"font-size: %1pt\"><h3>%2</h3><dl>" ).arg( smallerPointSize ).arg( selectedName() ) + propertiesString + wktString + proj4String + extentHtml + QStringLiteral( "</dl></div>" ) );
1001}
1002
1003QStringList QgsProjectionSelectionTreeWidget::authorities()
1004{
1005 sqlite3 *database = nullptr;
1006 const char *tail = nullptr;
1007 sqlite3_stmt *stmt = nullptr;
1008
1009 int result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
1010 if ( result )
1011 {
1012 QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
1013 //no need for assert because user db may not have been created yet
1014 return QStringList();
1015 }
1016
1017 QString sql = QStringLiteral( "select distinct auth_name from tbl_srs" );
1018 result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
1019
1020 QStringList authorities;
1021 if ( result == SQLITE_OK )
1022 {
1023 while ( sqlite3_step( stmt ) == SQLITE_ROW )
1024 {
1025 authorities << QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
1026 }
1027
1028 }
1029
1030 // close the sqlite3 statement
1031 sqlite3_finalize( stmt );
1032 sqlite3_close( database );
1033
1034 return authorities;
1035}
1036
1037QString QgsProjectionSelectionTreeWidget::sqlSafeString( const QString &theSQL ) const
1038{
1039 QString retval = theSQL;
1040 retval.replace( '\\', QLatin1String( "\\\\" ) );
1041 retval.replace( '\"', QLatin1String( "\\\"" ) );
1042 retval.replace( '\'', QLatin1String( "\\'" ) );
1043 retval.replace( '%', QLatin1String( "\\%" ) );
1044 return retval;
1045}
1046
1047void QgsProjectionSelectionTreeWidget::showDBMissingWarning( const QString &fileName )
1048{
1049
1050 QMessageBox::critical( this, tr( "Resource Location Error" ),
1051 tr( "Error reading database file from: \n %1\n"
1052 "Because of this the projection selector will not work…" )
1053 .arg( fileName ) );
1054}
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QgsCoordinateReferenceSystemRegistry * coordinateReferenceSystemRegistry()
Returns the application's coordinate reference system (CRS) registry, which handles known CRS definit...
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
QList< QgsCoordinateReferenceSystemRegistry::UserCrsDetails > userCrsList() const
Returns a list containing the details of all registered custom (user-defined) CRSes.
This class represents a coordinate reference system (CRS).
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
QString toProj() const
Returns a Proj string representation of this CRS.
static void pushRecentCoordinateReferenceSystem(const QgsCoordinateReferenceSystem &crs)
Pushes a recently used CRS to the top of the recent CRS list.
QgsDatumEnsemble datumEnsemble() const SIP_THROW(QgsNotSupportedException)
Attempts to retrieve datum ensemble details from the CRS.
bool isDynamic() const
Returns true if the CRS is a dynamic CRS.
static QgsCoordinateReferenceSystem fromProj(const QString &proj)
Creates a CRS from a proj style formatted string.
static QList< QgsCoordinateReferenceSystem > recentCoordinateReferenceSystems()
Returns a list of recently used CRS.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
static QgsCoordinateReferenceSystem fromWkt(const QString &wkt)
Creates a CRS from a WKT spatial ref sys definition string.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QgsProjOperation operation() const
Returns information about the PROJ operation associated with the coordinate reference system,...
Q_GADGET QgsUnitTypes::DistanceUnit mapUnits
long srsid() const
Returns the internal CRS ID, if available.
QString celestialBodyName() const SIP_THROW(QgsNotSupportedException)
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
Contains information about a datum ensemble.
Definition: qgsdatums.h:95
QString code() const
Identification code, e.g.
Definition: qgsdatums.h:122
QString authority() const
Authority name, e.g.
Definition: qgsdatums.h:117
bool isValid() const
Returns true if the datum ensemble is a valid object, or false if it is a null/invalid object.
Definition: qgsdatums.h:102
QString name() const
Display name of datum ensemble.
Definition: qgsdatums.h:107
double accuracy() const
Positional accuracy (in meters).
Definition: qgsdatums.h:112
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.
Definition: qgsexception.h:118
Contains information about a PROJ operation.
QString description() const
Description.
void resizeEvent(QResizeEvent *event) override
void setPreviewRect(const QgsRectangle &rect)
Sets the initial "preview" rectangle for the bounds overview map.
void setShowBoundsMap(bool show)
Sets whether to show the bounds preview map.
void crsSelected()
Emitted when a projection is selected in the widget.
Q_DECL_DEPRECATED void pushProjectionToFront()
Marks the current selected projection for push to front of recent projections list.
QgsCoordinateReferenceSystem crs() const
Returns the CRS currently selected in the widget.
bool showNoProjection() const
Returns whether the "no/invalid" projection option is shown.
void setShowNoProjection(bool show)
Sets whether a "no/invalid" projection option should be shown.
bool showBoundsMap() const
Returns whether the bounds preview map is shown.
QgsRectangle previewRect() const
The initial "preview" rectangle for the bounds overview map.
QgsProjectionSelectionTreeWidget(QWidget *parent=nullptr)
Constructor for QgsProjectionSelectionTreeWidget.
void projectionDoubleClicked()
Emitted when a projection is double clicked in the list.
bool hasValidSelection() const
Returns true if the current selection in the widget is a valid choice.
void setOgcWmsCrsFilter(const QSet< QString > &crsFilter)
filters this widget by the given CRSs
void setNotSetText(const QString &text)
Sets the text to show for the not set option.
void initialized()
Notifies others that the widget is now fully initialized, including deferred selection of projection.
void hasValidSelectionChanged(bool isValid)
Emitted when the selection in the tree is changed from a valid selection to an invalid selection,...
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the initial crs to show within the dialog.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
double area() const SIP_HOLDGIL
Returns the area of the rectangle.
Definition: qgsrectangle.h:239
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
static QString qRegExpEscape(const QString &string)
Returns an escaped string matching the behavior of QRegExp::escape.
static Q_INVOKABLE QString toString(QgsUnitTypes::DistanceUnit unit)
Returns a translated string representing a distance unit.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:2527
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/....
Definition: qgis.h:3009
struct sqlite3 sqlite3
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs