QGIS API Documentation  3.6.0-Noosa (5873452)
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"
24 #include "qgsrubberband.h"
25 #include "qgsvectorlayer.h"
26 #include "qgsmaptoolpan.h"
27 #include "qgsvertexmarker.h"
28 
29 //qt includes
30 #include <QFileInfo>
31 #include <QHeaderView>
32 #include <QResizeEvent>
33 #include <QMessageBox>
34 
36  : QWidget( parent )
37 {
38  setupUi( this );
39 
40  leSearch->setShowSearchIcon( true );
41 
42  connect( lstCoordinateSystems, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked );
43  connect( lstRecent, &QTreeWidget::itemDoubleClicked, this, &QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked );
44  connect( lstCoordinateSystems, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged );
45  connect( lstRecent, &QTreeWidget::currentItemChanged, this, &QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged );
46  connect( cbxHideDeprecated, &QCheckBox::stateChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
47  connect( leSearch, &QgsFilterLineEdit::textChanged, this, &QgsProjectionSelectionTreeWidget::updateFilter );
48 
49  mPreviewBand = new QgsRubberBand( mAreaCanvas, QgsWkbTypes::PolygonGeometry );
50  mPreviewBand->setWidth( 4 );
51 
52  mPreviewBand2 = new QgsRubberBand( mAreaCanvas, QgsWkbTypes::PolygonGeometry );
53  mPreviewBand2->setWidth( 4 );
54  QColor rectColor = QColor( 185, 84, 210, 60 );
55  mPreviewBand2->setColor( rectColor );
56 
57  mVertexMarker = new QgsVertexMarker( mAreaCanvas );
58  mVertexMarker->setIconType( QgsVertexMarker::ICON_CROSS );
59  mVertexMarker->setColor( QColor( 185, 84, 210 ) );
60  mVertexMarker->setPenWidth( 3 );
61 
63  mAreaCanvas->setDestinationCrs( srs );
64 
65  QString layerPath = QgsApplication::pkgDataPath() + QStringLiteral( "/resources/data/world_map.shp" );
66  mLayers << new QgsVectorLayer( layerPath );
67  mAreaCanvas->setLayers( mLayers );
68  mAreaCanvas->setMapTool( new QgsMapToolPan( mAreaCanvas ) );
69  mAreaCanvas->setPreviewJobsEnabled( true );
70  mAreaCanvas->setVisible( mShowMap );
71 
72  if ( QDialog *dlg = qobject_cast<QDialog *>( parent ) )
73  {
74  // mark selected projection for push to front if parent dialog is accepted
75  connect( dlg, &QDialog::accepted, this, &QgsProjectionSelectionTreeWidget::pushProjectionToFront );
76  }
77 
78  // Get the full path name to the sqlite3 spatial reference database.
79  mSrsDatabaseFileName = QgsApplication::srsDatabaseFilePath();
80 
81  lstCoordinateSystems->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
82  lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
83  lstCoordinateSystems->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
84 
85  // Hide (internal) ID column
86  lstCoordinateSystems->setColumnHidden( QgisCrsIdColumn, true );
87 
88  lstRecent->header()->setSectionResizeMode( AuthidColumn, QHeaderView::Stretch );
89  lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
90  lstRecent->header()->setSectionResizeMode( QgisCrsIdColumn, QHeaderView::Fixed );
91 
92  // Hide (internal) ID column
93  lstRecent->setColumnHidden( QgisCrsIdColumn, true );
94 
96 
97  mCheckBoxNoProjection->setHidden( true );
98  connect( mCheckBoxNoProjection, &QCheckBox::toggled, this, &QgsProjectionSelectionTreeWidget::crsSelected );
99  connect( mCheckBoxNoProjection, &QCheckBox::toggled, mFrameProjections, &QFrame::setDisabled );
100 }
101 
103 {
104  qDeleteAll( mLayers );
105  delete mPreviewBand;
106  delete mPreviewBand2;
107  delete mVertexMarker;
108 
109  if ( !mPushProjectionToFront )
110  {
111  return;
112  }
113 
114  // Push current projection to front, only if set
115  long crsId = selectedCrsId();
116  if ( crsId == 0 )
117  return;
118 
119  // Save persistent list of projects
120  mRecentProjections.removeAll( QString::number( crsId ) );
121  mRecentProjections.prepend( QString::number( crsId ) );
122  // Prune size of list
123  while ( mRecentProjections.size() > 8 )
124  {
125  mRecentProjections.removeLast();
126  }
127 
128  // Save to file *** Should be removed sometims in the future ***
129  QgsSettings settings;
130  settings.setValue( QStringLiteral( "/UI/recentProjections" ), mRecentProjections );
131 
132  // Convert to EPSG and proj4, and save those values also
133 
134  QStringList projectionsProj4;
135  QStringList projectionsAuthId;
136  for ( int i = 0; i < mRecentProjections.size(); i++ )
137  {
138  // Create a crs from the crsId
139  QgsCoordinateReferenceSystem crs( mRecentProjections.at( i ).toLong(), QgsCoordinateReferenceSystem::InternalCrsId );
140  if ( ! crs.isValid() )
141  {
142  // No? Skip this entry
143  continue;
144  }
145  projectionsProj4 << crs.toProj4();
146  projectionsAuthId << crs.authid();
147  }
148  settings.setValue( QStringLiteral( "/UI/recentProjectionsProj4" ), projectionsProj4 );
149  settings.setValue( QStringLiteral( "/UI/recentProjectionsAuthId" ), projectionsAuthId );
150 }
151 
153 {
154  lstCoordinateSystems->header()->resizeSection( NameColumn, event->size().width() - 240 );
155  lstCoordinateSystems->header()->resizeSection( AuthidColumn, 240 );
156  lstCoordinateSystems->header()->resizeSection( QgisCrsIdColumn, 0 );
157 
158  lstRecent->header()->resizeSection( NameColumn, event->size().width() - 240 );
159  lstRecent->header()->resizeSection( AuthidColumn, 240 );
160  lstRecent->header()->resizeSection( QgisCrsIdColumn, 0 );
161 }
162 
164 {
165  // ensure the projection list view is actually populated
166  // before we show this widget
167  loadCrsList( &mCrsFilter );
168  loadUserCrsList( &mCrsFilter );
169 
170  if ( !mRecentProjListDone )
171  {
172  for ( int i = mRecentProjections.size() - 1; i >= 0; i-- )
173  insertRecent( mRecentProjections.at( i ).toLong() );
174  mRecentProjListDone = true;
175  }
176 
177  // apply deferred selection
178  applySelection();
179 
180  emit initialized();
181 
182  // Pass up the inheritance hierarchy
183  QWidget::showEvent( event );
184 }
185 
186 QString QgsProjectionSelectionTreeWidget::ogcWmsCrsFilterAsSqlExpression( QSet<QString> *crsFilter )
187 {
188  QString sqlExpression = QStringLiteral( "1" ); // it's "SQL" for "true"
189  QMap<QString, QStringList> authParts;
190 
191  if ( !crsFilter )
192  return sqlExpression;
193 
194  /*
195  Ref: WMS 1.3.0, section 6.7.3 "Layer CRS":
196 
197  Every Layer CRS has an identifier that is a character string. Two types of
198  Layer CRS identifiers are permitted: "label" and "URL" identifiers:
199 
200  Label: The identifier includes a namespace prefix, a colon, a numeric or
201  string code, and in some instances a comma followed by additional
202  parameters. This International Standard defines three namespaces:
203  CRS, EpsgCrsId and AUTO2 [...]
204 
205  URL: The identifier is a fully-qualified Uniform Resource Locator that
206  references a publicly-accessible file containing a definition of the CRS
207  that is compliant with ISO 19111.
208  */
209 
210  // iterate through all incoming CRSs
211 
212  Q_FOREACH ( const QString &auth_id, *crsFilter )
213  {
214  QStringList parts = auth_id.split( ':' );
215 
216  if ( parts.size() < 2 )
217  continue;
218 
219  authParts[ parts.at( 0 ).toUpper()].append( parts.at( 1 ).toUpper() );
220  }
221 
222  if ( authParts.isEmpty() )
223  return sqlExpression;
224 
225  if ( !authParts.isEmpty() )
226  {
227  QString prefix = QStringLiteral( " AND (" );
228  for ( auto it = authParts.constBegin(); it != authParts.constEnd(); ++it )
229  {
230  sqlExpression += QStringLiteral( "%1(upper(auth_name)='%2' AND upper(auth_id) IN ('%3'))" )
231  .arg( prefix,
232  it.key(),
233  it.value().join( QStringLiteral( "','" ) ) );
234  prefix = QStringLiteral( " OR " );
235  }
236  sqlExpression += ')';
237  }
238 
239  QgsDebugMsgLevel( "exiting with '" + sqlExpression + "'.", 4 );
240 
241  return sqlExpression;
242 }
243 
244 void QgsProjectionSelectionTreeWidget::applySelection( int column, QString value )
245 {
246  if ( !mProjListDone || !mUserProjListDone )
247  {
248  // defer selection until loaded
249  mSearchColumn = column;
250  mSearchValue = value;
251  return;
252  }
253 
254  if ( column == QgsProjectionSelectionTreeWidget::None )
255  {
256  // invoked deferred selection
257  column = mSearchColumn;
258  value = mSearchValue;
259 
260  mSearchColumn = QgsProjectionSelectionTreeWidget::None;
261  mSearchValue.clear();
262  }
263 
264  if ( column == QgsProjectionSelectionTreeWidget::None )
265  return;
266 
267  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( value, Qt::MatchExactly | Qt::MatchRecursive, column );
268  if ( !nodes.isEmpty() )
269  {
270  QgsDebugMsgLevel( QStringLiteral( "found %1,%2" ).arg( column ).arg( value ), 4 );
271  lstCoordinateSystems->setCurrentItem( nodes.first() );
272  }
273  else
274  {
275  QgsDebugMsgLevel( QStringLiteral( "nothing found for %1,%2" ).arg( column ).arg( value ), 4 );
276  // deselect the selected item to avoid confusing the user
277  lstCoordinateSystems->clearSelection();
278  lstRecent->clearSelection();
279  teProjection->clear();
280  }
281 }
282 
283 void QgsProjectionSelectionTreeWidget::insertRecent( long crsId )
284 {
285  if ( !mProjListDone || !mUserProjListDone )
286  return;
287 
288  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( QString::number( crsId ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
289  if ( nodes.isEmpty() )
290  return;
291 
292  lstRecent->insertTopLevelItem( 0, new QTreeWidgetItem( lstRecent, QStringList()
293  << nodes.first()->text( NameColumn )
294  << nodes.first()->text( AuthidColumn )
295  << nodes.first()->text( QgisCrsIdColumn ) ) );
296 }
297 
298 //note this line just returns the projection name!
299 QString QgsProjectionSelectionTreeWidget::selectedName()
300 {
301  // return the selected wkt name from the list view
302  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
303  return lvi ? lvi->text( NameColumn ) : QString();
304 }
305 
307 {
308  if ( !crs.isValid() )
309  {
310  mCheckBoxNoProjection->setChecked( true );
311  }
312  else
313  {
314  mCheckBoxNoProjection->setChecked( false );
315  applySelection( AuthidColumn, crs.authid() );
316  }
317 }
318 
320 {
321  mPreviewRect = rect;
322  mPreviewBand2->setToGeometry( QgsGeometry::fromRect( mPreviewRect ), nullptr );
323  mPreviewBand2->show();
324  mVertexMarker->setCenter( rect.center() );
325  mVertexMarker->show();
326 }
327 
329 {
330  return mPreviewRect;
331 }
332 
333 // Returns the whole proj4 string for the selected projection node
334 QString QgsProjectionSelectionTreeWidget::selectedProj4String()
335 {
336  // Only return the projection if there is a node in the tree
337  // selected that has an srid. This prevents error if the user
338  // selects a top-level node rather than an actual coordinate
339  // system
340  //
341  // Get the selected node
342  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
343  if ( !item || item->text( QgisCrsIdColumn ).isEmpty() )
344  return QString();
345 
346  QString srsId = item->text( QgisCrsIdColumn );
347 
348  QgsDebugMsgLevel( "srsId = " + srsId, 4 );
349  QgsDebugMsgLevel( "USER_CRS_START_ID = " + QString::number( USER_CRS_START_ID ), 4 );
350 
351  //
352  // Determine if this is a user projection or a system on
353  // user projection defs all have srs_id >= 100000
354  //
355  QString databaseFileName;
356  if ( srsId.toLong() >= USER_CRS_START_ID )
357  {
358  databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
359  if ( !QFileInfo::exists( databaseFileName ) ) //its unlikely that this condition will ever be reached
360  return QString();
361  }
362  else //must be a system projection then
363  {
364  databaseFileName = mSrsDatabaseFileName;
365  }
366 
367  QgsDebugMsgLevel( "db = " + databaseFileName, 4 );
368 
369  sqlite3 *database = nullptr;
370  int rc = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
371  if ( rc )
372  {
373  showDBMissingWarning( databaseFileName );
374  return QString();
375  }
376 
377  // prepare the sql statement
378  const char *tail = nullptr;
379  sqlite3_stmt *stmt = nullptr;
380  QString sql = QStringLiteral( "select parameters from tbl_srs where srs_id=%1" ).arg( srsId );
381 
382  QgsDebugMsgLevel( "Selection sql: " + sql, 4 );
383 
384  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
385  // XXX Need to free memory from the error msg if one is set
386  QString projString;
387  if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
388  {
389  projString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
390  }
391 
392  // close the statement
393  sqlite3_finalize( stmt );
394  // close the database
395  sqlite3_close( database );
396 
397  Q_ASSERT( !projString.isEmpty() );
398 
399  return projString;
400 }
401 
402 QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const
403 {
404  // Only return the attribute if there is a node in the tree
405  // selected that has an srs_id. This prevents error if the user
406  // selects a top-level node rather than an actual coordinate
407  // system
408  //
409  // Get the selected node and make sure it is a srs andx
410  // not a top-level projection node
411  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
412  if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
413  return QString();
414 
415  //
416  // Determine if this is a user projection or a system on
417  // user projection defs all have srs_id >= 100000
418  //
419  QString databaseFileName;
420  if ( lvi->text( QgisCrsIdColumn ).toLong() >= USER_CRS_START_ID )
421  {
422  databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
423  if ( !QFileInfo::exists( databaseFileName ) )
424  {
425  return QString();
426  }
427  }
428  else
429  {
430  databaseFileName = mSrsDatabaseFileName;
431  }
432 
433  //
434  // set up the database
435  // XXX We could probabaly hold the database open for the life of this object,
436  // assuming that it will never be used anywhere else. Given the low overhead,
437  // opening it each time seems to be a reasonable approach at this time.
438  sqlite3 *database = nullptr;
439  int rc = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
440  if ( rc )
441  {
442  QgsMessageLog::logMessage( tr( "Resource Location Error" ), tr( "Error reading database file from: \n %1\n"
443  "Because of this the projection selector will not work…" ).arg( databaseFileName ),
444  Qgis::Critical );
445  return QString();
446  }
447 
448  // prepare the sql statement
449  const char *tail = nullptr;
450  sqlite3_stmt *stmt = nullptr;
451  QString sql = QStringLiteral( "select %1 from tbl_srs where srs_id=%2" )
452  .arg( expression,
453  lvi->text( QgisCrsIdColumn ) );
454 
455  QgsDebugMsgLevel( QStringLiteral( "Finding selected attribute using : %1" ).arg( sql ), 4 );
456  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
457  // XXX Need to free memory from the error msg if one is set
458  QString attributeValue;
459  if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
460  {
461  // get the first row of the result set
462  attributeValue = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
463  }
464 
465  // close the statement
466  sqlite3_finalize( stmt );
467  // close the database
468  sqlite3_close( database );
469 
470  // return the srs
471  return attributeValue;
472 }
473 
475 {
476  if ( mCheckBoxNoProjection->isChecked() )
478 
479  int srid = getSelectedExpression( QStringLiteral( "srs_id" ) ).toLong();
480  if ( srid >= USER_CRS_START_ID )
481  return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) );
482  else
483  return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) );
484 }
485 
487 {
488  mCheckBoxNoProjection->setHidden( !show );
489 }
490 
492 {
493  mShowMap = show;
494  mAreaCanvas->setVisible( show );
495 
496 }
497 
499 {
500  return !mCheckBoxNoProjection->isHidden();
501 }
502 
504 {
505  return mShowMap;
506 }
507 
509 {
510  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
511  if ( mCheckBoxNoProjection->isChecked() )
512  return true;
513  else
514  return item && !item->text( QgisCrsIdColumn ).isEmpty();
515 }
516 
517 long QgsProjectionSelectionTreeWidget::selectedCrsId()
518 {
519  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
520 
521  if ( item && !item->text( QgisCrsIdColumn ).isEmpty() )
522  return lstCoordinateSystems->currentItem()->text( QgisCrsIdColumn ).toLong();
523  else
524  return 0;
525 }
526 
527 
528 void QgsProjectionSelectionTreeWidget::setOgcWmsCrsFilter( const QSet<QString> &crsFilter )
529 {
530  mCrsFilter = crsFilter;
531  mProjListDone = false;
532  mUserProjListDone = false;
533  lstCoordinateSystems->clear();
534 }
535 
536 void QgsProjectionSelectionTreeWidget::loadUserCrsList( QSet<QString> *crsFilter )
537 {
538  if ( mUserProjListDone )
539  return;
540 
541  QgsDebugMsgLevel( QStringLiteral( "Fetching user projection list..." ), 4 );
542 
543  // convert our Coordinate Reference System filter into the SQL expression
544  QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
545 
546  // User defined coordinate system node
547  // Make in an italic font to distinguish them from real projections
548  mUserProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "User Defined Coordinate Systems" ) ) );
549 
550  QFont fontTemp = mUserProjList->font( 0 );
551  fontTemp.setItalic( true );
552  fontTemp.setBold( true );
553  mUserProjList->setFont( 0, fontTemp );
554  mUserProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
555 
556  //determine where the user proj database lives for this user. If none is found an empty
557  //now only will be shown
558  QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
559  // first we look for ~/.qgis/qgis.db
560  // if it doesn't exist we copy it in from the global resources dir
561 
562  //return straight away if the user has not created any custom projections
563  if ( !QFileInfo::exists( databaseFileName ) )
564  {
565  QgsDebugMsg( QStringLiteral( "Users qgis.db not found...skipping" ) );
566  mUserProjListDone = true;
567  return;
568  }
569 
570  sqlite3 *database = nullptr;
571  const char *tail = nullptr;
572  sqlite3_stmt *stmt = nullptr;
573  //check the db is available
574  int result = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
575  if ( result )
576  {
577  // XXX This will likely never happen since on open, sqlite creates the
578  // database if it does not exist. But we checked earlier for its existence
579  // and aborted in that case. This is because we may be running from read only
580  // media such as live cd and don't want to force trying to create a db.
581  showDBMissingWarning( databaseFileName );
582  return;
583  }
584 
585  // Set up the query to retrieve the projection information needed to populate the list
586  QString sql = QStringLiteral( "select description, srs_id from vw_srs where %1" ).arg( sqlFilter );
587 
588  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
589  // XXX Need to free memory from the error msg if one is set
590  if ( result == SQLITE_OK )
591  {
592  QTreeWidgetItem *newItem = nullptr;
593  while ( sqlite3_step( stmt ) == SQLITE_ROW )
594  {
595  newItem = new QTreeWidgetItem( mUserProjList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
596  // EpsgCrsId for user projections is not always defined in some dbases.
597  // It's also not written from customprojections dialog.
598  // display the epsg (field 2) in the second column of the list view
599  // newItem->setText( EPSG_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( stmt, 2 ) ) );
600  // display the qgis srs_id (field 1) in the third column of the list view
601  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
602  newItem->setText( AuthidColumn, QStringLiteral( "USER:%1" ).arg( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ).toInt() ) );
603  }
604  }
605  // close the sqlite3 statement
606  sqlite3_finalize( stmt );
607  sqlite3_close( database );
608 
609  mUserProjListDone = true;
610 }
611 
612 void QgsProjectionSelectionTreeWidget::loadCrsList( QSet<QString> *crsFilter )
613 {
614  if ( mProjListDone )
615  return;
616 
617  // convert our Coordinate Reference System filter into the SQL expression
618  QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
619 
620  // Create the top-level nodes for the list view of projections
621  // Make in an italic font to distinguish them from real projections
622  //
623  // Geographic coordinate system node
624  mGeoList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Geographic Coordinate Systems" ) ) );
625 
626  QFont fontTemp = mGeoList->font( 0 );
627  fontTemp.setItalic( true );
628  fontTemp.setBold( true );
629  mGeoList->setFont( 0, fontTemp );
630  mGeoList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) ) );
631 
632  // Projected coordinate system node
633  mProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Projected Coordinate Systems" ) ) );
634 
635  fontTemp = mProjList->font( 0 );
636  fontTemp.setItalic( true );
637  fontTemp.setBold( true );
638  mProjList->setFont( 0, fontTemp );
639  mProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) );
640 
641  //bail out in case the projections db does not exist
642  //this is necessary in case the pc is running linux with a
643  //read only filesystem because otherwise sqlite will try
644  //to create the db file on the fly
645 
646  if ( !QFileInfo::exists( mSrsDatabaseFileName ) )
647  {
648  mProjListDone = true;
649  return;
650  }
651 
652  // open the database containing the spatial reference data
653  sqlite3 *database = nullptr;
654  int rc = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
655  if ( rc )
656  {
657  // XXX This will likely never happen since on open, sqlite creates the
658  // database if it does not exist.
659  showDBMissingWarning( mSrsDatabaseFileName );
660  return;
661  }
662 
663  const char *tail = nullptr;
664  sqlite3_stmt *stmt = nullptr;
665  // Set up the query to retrieve the projection information needed to populate the list
666  //note I am giving the full field names for clarity here and in case someone
667  //changes the underlying view TS
668  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" )
669  .arg( sqlFilter );
670 
671  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
672  // XXX Need to free memory from the error msg if one is set
673  if ( rc == SQLITE_OK )
674  {
675  QTreeWidgetItem *newItem = nullptr;
676  // Cache some stuff to speed up creating of the list of projected
677  // spatial reference systems
678  QString previousSrsType;
679  QTreeWidgetItem *previousSrsTypeNode = nullptr;
680 
681  while ( sqlite3_step( stmt ) == SQLITE_ROW )
682  {
683  // check to see if the srs is geographic
684  int isGeo = sqlite3_column_int( stmt, 3 );
685  if ( isGeo )
686  {
687  // this is a geographic coordinate system
688  // Add it to the tree (field 0)
689  newItem = new QTreeWidgetItem( mGeoList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
690 
691  // display the authority name (field 2) in the second column of the list view
692  newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
693 
694  // display the qgis srs_id (field 1) in the third column of the list view
695  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
696  }
697  else
698  {
699  // This is a projected srs
700  QTreeWidgetItem *node = nullptr;
701  QString srsType = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 4 ) );
702  // Find the node for this type and add the projection to it
703  // If the node doesn't exist, create it
704  if ( srsType == previousSrsType )
705  {
706  node = previousSrsTypeNode;
707  }
708  else
709  {
710  // Different from last one, need to search
711  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( srsType, Qt::MatchExactly | Qt::MatchRecursive, NameColumn );
712  if ( nodes.isEmpty() )
713  {
714  // the node doesn't exist -- create it
715  // Make in an italic font to distinguish them from real projections
716  node = new QTreeWidgetItem( mProjList, QStringList( srsType ) );
717  QFont fontTemp = node->font( 0 );
718  fontTemp.setItalic( true );
719  node->setFont( 0, fontTemp );
720  }
721  else
722  {
723  node = nodes.first();
724  }
725  // Update the cache.
726  previousSrsType = srsType;
727  previousSrsTypeNode = node;
728  }
729  // add the item, setting the projection name in the first column of the list view
730  newItem = new QTreeWidgetItem( node, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
731  // display the authority id (field 2) in the second column of the list view
732  newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
733  // display the qgis srs_id (field 1) in the third column of the list view
734  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
735  // expand also parent node
736  newItem->parent()->setExpanded( true );
737  }
738 
739  // display the qgis deprecated in the user data of the item
740  newItem->setData( 0, RoleDeprecated, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 6 ) ) );
741  newItem->setHidden( cbxHideDeprecated->isChecked() );
742  }
743  mProjList->setExpanded( true );
744  }
745 
746  // close the sqlite3 statement
747  sqlite3_finalize( stmt );
748  // close the database
749  sqlite3_close( database );
750 
751  mProjListDone = true;
752 }
753 
754 // New coordinate system selected from the list
755 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
756 {
757  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
758 
759  if ( !current )
760  {
761  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
762  return;
763  }
764 
765  lstCoordinateSystems->scrollToItem( current );
766 
767  // If the item has children, it's not an end node in the tree, and
768  // hence is just a grouping thingy, not an actual CRS.
769  if ( current->childCount() == 0 )
770  {
771  // Found a real CRS
772  emit crsSelected();
773 
774  updateBoundsPreview();
775 
776  QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn );
777  if ( !nodes.isEmpty() )
778  {
779  QgsDebugMsgLevel( QStringLiteral( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
780  lstRecent->setCurrentItem( nodes.first() );
781  }
782  else
783  {
784  QgsDebugMsgLevel( QStringLiteral( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
785  lstRecent->clearSelection();
786  lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
787  }
788  }
789  else
790  {
791  // Not a CRS - remove the highlight so the user doesn't get too confused
792  current->setSelected( false );
793  teProjection->clear();
794  lstRecent->clearSelection();
795  }
796 }
797 
798 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked( QTreeWidgetItem *current, int column )
799 {
800  Q_UNUSED( column );
801 
802  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
803 
804  if ( !current )
805  {
806  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
807  return;
808  }
809 
810  // If the item has children, it's not an end node in the tree, and
811  // hence is just a grouping thingy, not an actual CRS.
812  if ( current->childCount() == 0 )
814 }
815 
816 void QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
817 {
818  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
819 
820  if ( !current )
821  {
822  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
823  return;
824  }
825 
826  lstRecent->scrollToItem( current );
827 
828  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
829  if ( !nodes.isEmpty() )
830  lstCoordinateSystems->setCurrentItem( nodes.first() );
831 }
832 
833 void QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked( QTreeWidgetItem *current, int column )
834 {
835  Q_UNUSED( column );
836 
837  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
838 
839  if ( !current )
840  {
841  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
842  return;
843  }
844 
845  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
846  if ( !nodes.isEmpty() )
848 }
849 
850 void QgsProjectionSelectionTreeWidget::updateFilter()
851 {
852  QString filterTxtCopy = leSearch->text();
853  filterTxtCopy.replace( QRegExp( "\\s+" ), QStringLiteral( ".*" ) );
854  QRegExp re( filterTxtCopy, Qt::CaseInsensitive );
855 
856  const bool hideDeprecated = cbxHideDeprecated->isChecked();
857 
858  auto filterTreeWidget = [ = ]( QTreeWidget * tree )
859  {
860  QTreeWidgetItemIterator itr( tree );
861  while ( *itr )
862  {
863  if ( ( *itr )->childCount() == 0 ) // it's an end node aka a projection
864  {
865  if ( hideDeprecated && ( *itr )->data( 0, RoleDeprecated ).toBool() )
866  {
867  ( *itr )->setHidden( true );
868  if ( ( *itr )->isSelected() )
869  {
870  ( *itr )->setSelected( false );
871  teProjection->clear();
872  }
873  }
874  else if ( ( *itr )->text( NameColumn ).contains( re )
875  || ( *itr )->text( AuthidColumn ).contains( re )
876  )
877  {
878  ( *itr )->setHidden( false );
879  QTreeWidgetItem *parent = ( *itr )->parent();
880  while ( parent )
881  {
882  parent->setExpanded( true );
883  parent->setHidden( false );
884  parent = parent->parent();
885  }
886  }
887  else
888  {
889  ( *itr )->setHidden( true );
890  }
891  }
892  else
893  {
894  ( *itr )->setHidden( true );
895  }
896  ++itr;
897  }
898  };
899 
900  // filter recent crs's
901  filterTreeWidget( lstRecent );
902 
903  // filter crs's
904  filterTreeWidget( lstCoordinateSystems );
905 }
906 
907 
909 {
910  // set flag to push selected projection to front in destructor
911  mPushProjectionToFront = true;
912 }
913 
914 
915 long QgsProjectionSelectionTreeWidget::getLargestCrsIdMatch( const QString &sql )
916 {
917  long srsId = 0;
918 
919  //
920  // Now perform the actual search
921  //
922 
923  sqlite3 *database = nullptr;
924  const char *tail = nullptr;
925  sqlite3_stmt *stmt = nullptr;
926  int result;
927 
928  // first we search the users db as any srsid there will be definition be greater than in sys db
929 
930  //check the db is available
931  QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
932  if ( QFileInfo::exists( databaseFileName ) ) //only bother trying to open if the file exists
933  {
934  result = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
935  if ( result )
936  {
937  // XXX This will likely never happen since on open, sqlite creates the
938  // database if it does not exist. But we checked earlier for its existence
939  // and aborted in that case. This is because we may be running from read only
940  // media such as live cd and don't want to force trying to create a db.
941  showDBMissingWarning( databaseFileName );
942  return 0;
943  }
944 
945  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
946  // XXX Need to free memory from the error msg if one is set
947  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
948  {
949  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
950  srsId = srsIdString.toLong();
951  // close the sqlite3 statement
952  sqlite3_finalize( stmt );
953  sqlite3_close( database );
954  return srsId;
955  }
956  }
957  else
958  {
959  //only bother looking in srs.db if it wasn't found above
960  result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
961  if ( result )
962  {
963  QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
964  //no need for assert because user db may not have been created yet
965  return 0;
966  }
967  }
968 
969  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
970  // XXX Need to free memory from the error msg if one is set
971  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
972  {
973  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
974  srsId = srsIdString.toLong();
975  }
976 
977  // close the sqlite3 statement
978  sqlite3_finalize( stmt );
979  sqlite3_close( database );
980 
981  return srsId;
982 }
983 
984 void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
985 {
986  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
987  if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
988  return;
989 
990  QgsCoordinateReferenceSystem currentCrs = crs();
991  if ( !currentCrs.isValid() )
992  return;
993 
994  QgsRectangle rect = currentCrs.bounds();
995  QString extentString = tr( "Extent not known" );
996  if ( !qgsDoubleNear( rect.area(), 0.0 ) )
997  {
998  QgsGeometry geom;
999  if ( rect.xMinimum() > rect.xMaximum() )
1000  {
1001  QgsRectangle rect1 = QgsRectangle( -180, rect.yMinimum(), rect.xMaximum(), rect.yMaximum() );
1002  QgsRectangle rect2 = QgsRectangle( rect.xMinimum(), rect.yMinimum(), 180, rect.yMaximum() );
1003  geom = QgsGeometry::fromRect( rect1 );
1004  geom.addPart( QgsGeometry::fromRect( rect2 ) );
1005  }
1006  else
1007  {
1008  geom = QgsGeometry::fromRect( rect );
1009  }
1010  mPreviewBand->setToGeometry( geom, nullptr );
1011  mPreviewBand->setColor( QColor( 255, 0, 0, 65 ) );
1012  QgsRectangle extent = geom.boundingBox();
1013  extent.scale( 1.1 );
1014  mAreaCanvas->setExtent( extent );
1015  mAreaCanvas->refresh();
1016  mPreviewBand->show();
1017  extentString = QStringLiteral( "%1, %2, %3, %4" )
1018  .arg( rect.xMinimum(), 0, 'f', 2 )
1019  .arg( rect.yMinimum(), 0, 'f', 2 )
1020  .arg( rect.xMaximum(), 0, 'f', 2 )
1021  .arg( rect.yMaximum(), 0, 'f', 2 );
1022 
1023  }
1024  else
1025  {
1026  mPreviewBand->hide();
1027  mAreaCanvas->zoomToFullExtent();
1028  }
1029 
1030  QString extentHtml = QStringLiteral( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Extent" ), extentString );
1031  QString proj4String = tr( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Proj4" ), selectedProj4String() );
1032  teProjection->setText( QStringLiteral( "<h3>%1</h3><dl>" ).arg( selectedName() ) + extentHtml + proj4String + QLatin1String( "</dl>" ) );
1033 }
1034 
1035 QStringList QgsProjectionSelectionTreeWidget::authorities()
1036 {
1037  sqlite3 *database = nullptr;
1038  const char *tail = nullptr;
1039  sqlite3_stmt *stmt = nullptr;
1040 
1041  int result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
1042  if ( result )
1043  {
1044  QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
1045  //no need for assert because user db may not have been created yet
1046  return QStringList();
1047  }
1048 
1049  QString sql = QStringLiteral( "select distinct auth_name from tbl_srs" );
1050  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
1051 
1052  QStringList authorities;
1053  if ( result == SQLITE_OK )
1054  {
1055  while ( sqlite3_step( stmt ) == SQLITE_ROW )
1056  {
1057  authorities << QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
1058  }
1059 
1060  }
1061 
1062  // close the sqlite3 statement
1063  sqlite3_finalize( stmt );
1064  sqlite3_close( database );
1065 
1066  return authorities;
1067 }
1068 
1069 QString QgsProjectionSelectionTreeWidget::sqlSafeString( const QString &theSQL ) const
1070 {
1071  QString retval = theSQL;
1072  retval.replace( '\\', QLatin1String( "\\\\" ) );
1073  retval.replace( '\"', QLatin1String( "\\\"" ) );
1074  retval.replace( '\'', QLatin1String( "\\'" ) );
1075  retval.replace( '%', QLatin1String( "\\%" ) );
1076  return retval;
1077 }
1078 
1079 void QgsProjectionSelectionTreeWidget::showDBMissingWarning( const QString &fileName )
1080 {
1081 
1082  QMessageBox::critical( this, tr( "Resource Location Error" ),
1083  tr( "Error reading database file from: \n %1\n"
1084  "Because of this the projection selector will not work…" )
1085  .arg( fileName ) );
1086 }
QgsCoordinateReferenceSystem crs() const
Returns the CRS currently selected in the widget.
void setWidth(int width)
Sets the width of the line.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:235
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
QString toProj4() const
Returns a Proj4 string representation of this CRS.
void setPenWidth(int width)
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
void resizeEvent(QResizeEvent *event) override
bool showBoundsMap() const
Returns whether the bounds preview map is shown.
void projectionDoubleClicked()
Emitted when a projection is double clicked in the list.
Internal ID used by QGIS in the local SQLite database.
void setToGeometry(const QgsGeometry &geom, QgsVectorLayer *layer)
Sets this rubber band to geom.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
QgsProjectionSelectionTreeWidget(QWidget *parent=nullptr)
Constructor for QgsProjectionSelectionTreeWidget.
QgsRectangle previewRect() const
The initial "preview" rectangle for the bounds overview map.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
static QStringList recentProjections()
Returns a list of recently used projections.
A class for drawing transient features (e.g.
Definition: qgsrubberband.h:40
void setCenter(const QgsPointXY &point)
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
bool hasValidSelection() const
Returns true if the current selection in the widget is a valid choice.
bool showNoProjection() const
Returns whether the "no/invalid" projection option is shown.
A class for marking vertices of features using e.g.
void pushProjectionToFront()
Marks the current selected projection for push to front of recent projections list.
static QString pkgDataPath()
Returns the common root path of all application data directories.
struct sqlite3 sqlite3
double area() const
Returns the area of the rectangle.
Definition: qgsrectangle.h:218
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
void initialized()
Notifies others that the widget is now fully initialized, including deferred selection of projection...
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
void setShowNoProjection(bool show)
Sets whether a "no/invalid" projection option should be shown.
void crsSelected()
Emitted when a projection is selected in the widget.
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/...
Definition: qgis.h:559
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
void setOgcWmsCrsFilter(const QSet< QString > &crsFilter)
filters this widget by the given CRSs
void setPreviewRect(const QgsRectangle &rect)
Sets the initial "preview" rectangle for the bounds overview map.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
void setColor(const QColor &color)
Sets the color for the rubberband.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the initial crs to show within the dialog.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:230
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
void setColor(const QColor &color)
Sets the stroke color for the marker.
Represents a vector layer which manages a vector based data sets.
void setIconType(int iconType)
QString authid() const
Returns the authority identifier for the CRS.
A map tool for panning the map.
Definition: qgsmaptoolpan.h:29
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
void setShowBoundsMap(bool show)
Sets whether to show the bounnds preview map.