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