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