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