QGIS API Documentation  3.2.0-Bonn (bc43194)
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( QString( "found %1,%2" ).arg( column ).arg( value ), 4 );
271  lstCoordinateSystems->setCurrentItem( nodes.first() );
272  }
273  else
274  {
275  QgsDebugMsgLevel( QString( "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  teSelected->clear();
281  }
282 }
283 
284 void QgsProjectionSelectionTreeWidget::insertRecent( long crsId )
285 {
286  if ( !mProjListDone || !mUserProjListDone )
287  return;
288 
289  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( QString::number( crsId ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
290  if ( nodes.isEmpty() )
291  return;
292 
293  lstRecent->insertTopLevelItem( 0, new QTreeWidgetItem( lstRecent, QStringList()
294  << nodes.first()->text( NameColumn )
295  << nodes.first()->text( AuthidColumn )
296  << nodes.first()->text( QgisCrsIdColumn ) ) );
297 }
298 
299 //note this line just returns the projection name!
300 QString QgsProjectionSelectionTreeWidget::selectedName()
301 {
302  // return the selected wkt name from the list view
303  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
304  return lvi ? lvi->text( NameColumn ) : QString();
305 }
306 
308 {
309  if ( !crs.isValid() )
310  {
311  mCheckBoxNoProjection->setChecked( true );
312  }
313  else
314  {
315  mCheckBoxNoProjection->setChecked( false );
316  applySelection( AuthidColumn, crs.authid() );
317  }
318 }
319 
321 {
322  mPreviewRect = rect;
323  mPreviewBand2->setToGeometry( QgsGeometry::fromRect( mPreviewRect ), nullptr );
324  mPreviewBand2->show();
325  mVertexMarker->setCenter( rect.center() );
326  mVertexMarker->show();
327 }
328 
330 {
331  return mPreviewRect;
332 }
333 
334 // Returns the whole proj4 string for the selected projection node
335 QString QgsProjectionSelectionTreeWidget::selectedProj4String()
336 {
337  // Only return the projection if there is a node in the tree
338  // selected that has an srid. This prevents error if the user
339  // selects a top-level node rather than an actual coordinate
340  // system
341  //
342  // Get the selected node
343  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
344  if ( !item || item->text( QgisCrsIdColumn ).isEmpty() )
345  return QLatin1String( "" );
346 
347  QString srsId = item->text( QgisCrsIdColumn );
348 
349  QgsDebugMsgLevel( "srsId = " + srsId, 4 );
350  QgsDebugMsgLevel( "USER_CRS_START_ID = " + QString::number( USER_CRS_START_ID ), 4 );
351 
352  //
353  // Determine if this is a user projection or a system on
354  // user projection defs all have srs_id >= 100000
355  //
356  QString databaseFileName;
357  if ( srsId.toLong() >= USER_CRS_START_ID )
358  {
359  databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
360  if ( !QFileInfo::exists( databaseFileName ) ) //its unlikely that this condition will ever be reached
361  return QString();
362  }
363  else //must be a system projection then
364  {
365  databaseFileName = mSrsDatabaseFileName;
366  }
367 
368  QgsDebugMsgLevel( "db = " + databaseFileName, 4 );
369 
370  sqlite3 *database = nullptr;
371  int rc = sqlite3_open_v2( databaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
372  if ( rc )
373  {
374  showDBMissingWarning( databaseFileName );
375  return QLatin1String( "" );
376  }
377 
378  // prepare the sql statement
379  const char *tail = nullptr;
380  sqlite3_stmt *stmt = nullptr;
381  QString sql = QStringLiteral( "select parameters from tbl_srs where srs_id=%1" ).arg( srsId );
382 
383  QgsDebugMsgLevel( "Selection sql: " + sql, 4 );
384 
385  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
386  // XXX Need to free memory from the error msg if one is set
387  QString projString;
388  if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
389  {
390  projString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
391  }
392 
393  // close the statement
394  sqlite3_finalize( stmt );
395  // close the database
396  sqlite3_close( database );
397 
398  Q_ASSERT( !projString.isEmpty() );
399 
400  return projString;
401 }
402 
403 QString QgsProjectionSelectionTreeWidget::getSelectedExpression( const QString &expression ) const
404 {
405  // Only return the attribute if there is a node in the tree
406  // selected that has an srs_id. This prevents error if the user
407  // selects a top-level node rather than an actual coordinate
408  // system
409  //
410  // Get the selected node and make sure it is a srs andx
411  // not a top-level projection node
412  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
413  if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
414  return QString();
415 
416  //
417  // Determine if this is a user projection or a system on
418  // user projection defs all have srs_id >= 100000
419  //
420  QString databaseFileName;
421  if ( lvi->text( QgisCrsIdColumn ).toLong() >= USER_CRS_START_ID )
422  {
423  databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
424  if ( !QFileInfo::exists( databaseFileName ) )
425  {
426  return QString();
427  }
428  }
429  else
430  {
431  databaseFileName = mSrsDatabaseFileName;
432  }
433 
434  //
435  // set up the database
436  // XXX We could probabaly hold the database open for the life of this object,
437  // assuming that it will never be used anywhere else. Given the low overhead,
438  // opening it each time seems to be a reasonable approach at this time.
439  sqlite3 *database = nullptr;
440  int rc = sqlite3_open_v2( databaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
441  if ( rc )
442  {
443  QgsMessageLog::logMessage( tr( "Resource Location Error" ), tr( "Error reading database file from: \n %1\n"
444  "Because of this the projection selector will not work…" ).arg( databaseFileName ),
445  Qgis::Critical );
446  return QString();
447  }
448 
449  // prepare the sql statement
450  const char *tail = nullptr;
451  sqlite3_stmt *stmt = nullptr;
452  QString sql = QStringLiteral( "select %1 from tbl_srs where srs_id=%2" )
453  .arg( expression,
454  lvi->text( QgisCrsIdColumn ) );
455 
456  QgsDebugMsgLevel( QString( "Finding selected attribute using : %1" ).arg( sql ), 4 );
457  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
458  // XXX Need to free memory from the error msg if one is set
459  QString attributeValue;
460  if ( rc == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
461  {
462  // get the first row of the result set
463  attributeValue = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
464  }
465 
466  // close the statement
467  sqlite3_finalize( stmt );
468  // close the database
469  sqlite3_close( database );
470 
471  // return the srs
472  return attributeValue;
473 }
474 
476 {
477  if ( mCheckBoxNoProjection->isChecked() )
479 
480  int srid = getSelectedExpression( QStringLiteral( "srs_id" ) ).toLong();
481  if ( srid >= USER_CRS_START_ID )
482  return QgsCoordinateReferenceSystem::fromOgcWmsCrs( QStringLiteral( "USER:%1" ).arg( srid ) );
483  else
484  return QgsCoordinateReferenceSystem::fromOgcWmsCrs( getSelectedExpression( QStringLiteral( "upper(auth_name||':'||auth_id)" ) ) );
485 }
486 
488 {
489  mCheckBoxNoProjection->setHidden( !show );
490 }
491 
493 {
494  mShowMap = show;
495  mAreaCanvas->setVisible( show );
496 
497 }
498 
500 {
501  return !mCheckBoxNoProjection->isHidden();
502 }
503 
505 {
506  return mShowMap;
507 }
508 
510 {
511  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
512  if ( mCheckBoxNoProjection->isChecked() )
513  return true;
514  else
515  return item && !item->text( QgisCrsIdColumn ).isEmpty();
516 }
517 
518 long QgsProjectionSelectionTreeWidget::selectedCrsId()
519 {
520  QTreeWidgetItem *item = lstCoordinateSystems->currentItem();
521 
522  if ( item && !item->text( QgisCrsIdColumn ).isEmpty() )
523  return lstCoordinateSystems->currentItem()->text( QgisCrsIdColumn ).toLong();
524  else
525  return 0;
526 }
527 
528 
529 void QgsProjectionSelectionTreeWidget::setOgcWmsCrsFilter( const QSet<QString> &crsFilter )
530 {
531  mCrsFilter = crsFilter;
532  mProjListDone = false;
533  mUserProjListDone = false;
534  lstCoordinateSystems->clear();
535 }
536 
537 void QgsProjectionSelectionTreeWidget::loadUserCrsList( QSet<QString> *crsFilter )
538 {
539  if ( mUserProjListDone )
540  return;
541 
542  QgsDebugMsgLevel( "Fetching user projection list...", 4 );
543 
544  // convert our Coordinate Reference System filter into the SQL expression
545  QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
546 
547  // User defined coordinate system node
548  // Make in an italic font to distinguish them from real projections
549  mUserProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "User Defined Coordinate Systems" ) ) );
550 
551  QFont fontTemp = mUserProjList->font( 0 );
552  fontTemp.setItalic( true );
553  fontTemp.setBold( true );
554  mUserProjList->setFont( 0, fontTemp );
555  mUserProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/user.svg" ) ) );
556 
557  //determine where the user proj database lives for this user. If none is found an empty
558  //now only will be shown
559  QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
560  // first we look for ~/.qgis/qgis.db
561  // if it doesn't exist we copy it in from the global resources dir
562 
563  //return straight away if the user has not created any custom projections
564  if ( !QFileInfo::exists( databaseFileName ) )
565  {
566  QgsDebugMsg( "Users qgis.db not found...skipping" );
567  mUserProjListDone = true;
568  return;
569  }
570 
571  sqlite3 *database = nullptr;
572  const char *tail = nullptr;
573  sqlite3_stmt *stmt = nullptr;
574  //check the db is available
575  int result = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
576  if ( result )
577  {
578  // XXX This will likely never happen since on open, sqlite creates the
579  // database if it does not exist. But we checked earlier for its existence
580  // and aborted in that case. This is because we may be running from read only
581  // media such as live cd and don't want to force trying to create a db.
582  showDBMissingWarning( databaseFileName );
583  return;
584  }
585 
586  // Set up the query to retrieve the projection information needed to populate the list
587  QString sql = QStringLiteral( "select description, srs_id from vw_srs where %1" ).arg( sqlFilter );
588 
589  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
590  // XXX Need to free memory from the error msg if one is set
591  if ( result == SQLITE_OK )
592  {
593  QTreeWidgetItem *newItem = nullptr;
594  while ( sqlite3_step( stmt ) == SQLITE_ROW )
595  {
596  newItem = new QTreeWidgetItem( mUserProjList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
597  // EpsgCrsId for user projections is not always defined in some dbases.
598  // It's also not written from customprojections dialog.
599  // display the epsg (field 2) in the second column of the list view
600  // newItem->setText( EPSG_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( stmt, 2 ) ) );
601  // display the qgis srs_id (field 1) in the third column of the list view
602  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
603  newItem->setText( AuthidColumn, QStringLiteral( "USER:%1" ).arg( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ).toInt() ) );
604  }
605  }
606  // close the sqlite3 statement
607  sqlite3_finalize( stmt );
608  sqlite3_close( database );
609 
610  mUserProjListDone = true;
611 }
612 
613 void QgsProjectionSelectionTreeWidget::loadCrsList( QSet<QString> *crsFilter )
614 {
615  if ( mProjListDone )
616  return;
617 
618  // convert our Coordinate Reference System filter into the SQL expression
619  QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
620 
621  // Create the top-level nodes for the list view of projections
622  // Make in an italic font to distinguish them from real projections
623  //
624  // Geographic coordinate system node
625  mGeoList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Geographic Coordinate Systems" ) ) );
626 
627  QFont fontTemp = mGeoList->font( 0 );
628  fontTemp.setItalic( true );
629  fontTemp.setBold( true );
630  mGeoList->setFont( 0, fontTemp );
631  mGeoList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/mIconProjectionEnabled.svg" ) ) );
632 
633  // Projected coordinate system node
634  mProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Projected Coordinate Systems" ) ) );
635 
636  fontTemp = mProjList->font( 0 );
637  fontTemp.setItalic( true );
638  fontTemp.setBold( true );
639  mProjList->setFont( 0, fontTemp );
640  mProjList->setIcon( 0, QgsApplication::getThemeIcon( QStringLiteral( "/transformed.svg" ) ) );
641 
642  //bail out in case the projections db does not exist
643  //this is necessary in case the pc is running linux with a
644  //read only filesystem because otherwise sqlite will try
645  //to create the db file on the fly
646 
647  if ( !QFileInfo::exists( mSrsDatabaseFileName ) )
648  {
649  mProjListDone = true;
650  return;
651  }
652 
653  // open the database containing the spatial reference data
654  sqlite3 *database = nullptr;
655  int rc = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
656  if ( rc )
657  {
658  // XXX This will likely never happen since on open, sqlite creates the
659  // database if it does not exist.
660  showDBMissingWarning( mSrsDatabaseFileName );
661  return;
662  }
663 
664  const char *tail = nullptr;
665  sqlite3_stmt *stmt = nullptr;
666  // Set up the query to retrieve the projection information needed to populate the list
667  //note I am giving the full field names for clarity here and in case someone
668  //changes the underlying view TS
669  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" )
670  .arg( sqlFilter );
671 
672  rc = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
673  // XXX Need to free memory from the error msg if one is set
674  if ( rc == SQLITE_OK )
675  {
676  QTreeWidgetItem *newItem = nullptr;
677  // Cache some stuff to speed up creating of the list of projected
678  // spatial reference systems
679  QString previousSrsType( QLatin1String( "" ) );
680  QTreeWidgetItem *previousSrsTypeNode = nullptr;
681 
682  while ( sqlite3_step( stmt ) == SQLITE_ROW )
683  {
684  // check to see if the srs is geographic
685  int isGeo = sqlite3_column_int( stmt, 3 );
686  if ( isGeo )
687  {
688  // this is a geographic coordinate system
689  // Add it to the tree (field 0)
690  newItem = new QTreeWidgetItem( mGeoList, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
691 
692  // display the authority name (field 2) in the second column of the list view
693  newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
694 
695  // display the qgis srs_id (field 1) in the third column of the list view
696  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
697  }
698  else
699  {
700  // This is a projected srs
701  QTreeWidgetItem *node = nullptr;
702  QString srsType = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 4 ) );
703  // Find the node for this type and add the projection to it
704  // If the node doesn't exist, create it
705  if ( srsType == previousSrsType )
706  {
707  node = previousSrsTypeNode;
708  }
709  else
710  {
711  // Different from last one, need to search
712  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( srsType, Qt::MatchExactly | Qt::MatchRecursive, NameColumn );
713  if ( nodes.isEmpty() )
714  {
715  // the node doesn't exist -- create it
716  // Make in an italic font to distinguish them from real projections
717  node = new QTreeWidgetItem( mProjList, QStringList( srsType ) );
718  QFont fontTemp = node->font( 0 );
719  fontTemp.setItalic( true );
720  node->setFont( 0, fontTemp );
721  }
722  else
723  {
724  node = nodes.first();
725  }
726  // Update the cache.
727  previousSrsType = srsType;
728  previousSrsTypeNode = node;
729  }
730  // add the item, setting the projection name in the first column of the list view
731  newItem = new QTreeWidgetItem( node, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
732  // display the authority id (field 2) in the second column of the list view
733  newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
734  // display the qgis srs_id (field 1) in the third column of the list view
735  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
736  // expand also parent node
737  newItem->parent()->setExpanded( true );
738  }
739 
740  // display the qgis deprecated in the user data of the item
741  newItem->setData( 0, RoleDeprecated, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 6 ) ) );
742  newItem->setHidden( cbxHideDeprecated->isChecked() );
743  }
744  mProjList->setExpanded( true );
745  }
746 
747  // close the sqlite3 statement
748  sqlite3_finalize( stmt );
749  // close the database
750  sqlite3_close( database );
751 
752  mProjListDone = true;
753 }
754 
755 // New coordinate system selected from the list
756 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
757 {
758  QgsDebugMsgLevel( "Entered.", 4 );
759 
760  if ( !current )
761  {
762  QgsDebugMsgLevel( "no current item", 4 );
763  return;
764  }
765 
766  lstCoordinateSystems->scrollToItem( current );
767 
768  // If the item has children, it's not an end node in the tree, and
769  // hence is just a grouping thingy, not an actual CRS.
770  if ( current->childCount() == 0 )
771  {
772  // Found a real CRS
773  emit crsSelected();
774 
775  teSelected->setText( selectedName() );
776  updateBoundsPreview();
777 
778  QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn );
779  if ( !nodes.isEmpty() )
780  {
781  QgsDebugMsgLevel( QString( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
782  lstRecent->setCurrentItem( nodes.first() );
783  }
784  else
785  {
786  QgsDebugMsgLevel( QString( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
787  lstRecent->clearSelection();
788  lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
789  }
790  }
791  else
792  {
793  // Not an CRS - remove the highlight so the user doesn't get too confused
794  current->setSelected( false );
795  teProjection->clear();
796  teSelected->clear();
797  lstRecent->clearSelection();
798  }
799 }
800 
801 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked( QTreeWidgetItem *current, int column )
802 {
803  Q_UNUSED( column );
804 
805  QgsDebugMsgLevel( "Entered.", 4 );
806 
807  if ( !current )
808  {
809  QgsDebugMsgLevel( "no current item", 4 );
810  return;
811  }
812 
813  // If the item has children, it's not an end node in the tree, and
814  // hence is just a grouping thingy, not an actual CRS.
815  if ( current->childCount() == 0 )
817 }
818 
819 void QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
820 {
821  QgsDebugMsgLevel( "Entered.", 4 );
822 
823  if ( !current )
824  {
825  QgsDebugMsgLevel( "no current item", 4 );
826  return;
827  }
828 
829  lstRecent->scrollToItem( current );
830 
831  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
832  if ( !nodes.isEmpty() )
833  lstCoordinateSystems->setCurrentItem( nodes.first() );
834 }
835 
836 void QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked( QTreeWidgetItem *current, int column )
837 {
838  Q_UNUSED( column );
839 
840  QgsDebugMsgLevel( "Entered.", 4 );
841 
842  if ( !current )
843  {
844  QgsDebugMsgLevel( "no current item", 4 );
845  return;
846  }
847 
848  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
849  if ( !nodes.isEmpty() )
851 }
852 
853 void QgsProjectionSelectionTreeWidget::updateFilter()
854 {
855  QString filterTxtCopy = leSearch->text();
856  filterTxtCopy.replace( QRegExp( "\\s+" ), QStringLiteral( ".*" ) );
857  QRegExp re( filterTxtCopy, Qt::CaseInsensitive );
858 
859  const bool hideDeprecated = cbxHideDeprecated->isChecked();
860 
861  auto filterTreeWidget = [ = ]( QTreeWidget * tree )
862  {
863  QTreeWidgetItemIterator itr( tree );
864  while ( *itr )
865  {
866  if ( ( *itr )->childCount() == 0 ) // it's an end node aka a projection
867  {
868  if ( hideDeprecated && ( *itr )->data( 0, RoleDeprecated ).toBool() )
869  {
870  ( *itr )->setHidden( true );
871  if ( ( *itr )->isSelected() )
872  {
873  ( *itr )->setSelected( false );
874  teProjection->clear();
875  teSelected->clear();
876  }
877  }
878  else if ( ( *itr )->text( NameColumn ).contains( re )
879  || ( *itr )->text( AuthidColumn ).contains( re )
880  )
881  {
882  ( *itr )->setHidden( false );
883  QTreeWidgetItem *parent = ( *itr )->parent();
884  while ( parent )
885  {
886  parent->setExpanded( true );
887  parent->setHidden( false );
888  parent = parent->parent();
889  }
890  }
891  else
892  {
893  ( *itr )->setHidden( true );
894  }
895  }
896  else
897  {
898  ( *itr )->setHidden( true );
899  }
900  ++itr;
901  }
902  };
903 
904  // filter recent crs's
905  filterTreeWidget( lstRecent );
906 
907  // filter crs's
908  filterTreeWidget( lstCoordinateSystems );
909 }
910 
911 
913 {
914  // set flag to push selected projection to front in destructor
915  mPushProjectionToFront = true;
916 }
917 
918 
919 long QgsProjectionSelectionTreeWidget::getLargestCrsIdMatch( const QString &sql )
920 {
921  long srsId = 0;
922 
923  //
924  // Now perform the actual search
925  //
926 
927  sqlite3 *database = nullptr;
928  const char *tail = nullptr;
929  sqlite3_stmt *stmt = nullptr;
930  int result;
931 
932  // first we search the users db as any srsid there will be definition be greater than in sys db
933 
934  //check the db is available
935  QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
936  if ( QFileInfo::exists( databaseFileName ) ) //only bother trying to open if the file exists
937  {
938  result = sqlite3_open_v2( databaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
939  if ( result )
940  {
941  // XXX This will likely never happen since on open, sqlite creates the
942  // database if it does not exist. But we checked earlier for its existence
943  // and aborted in that case. This is because we may be running from read only
944  // media such as live cd and don't want to force trying to create a db.
945  showDBMissingWarning( databaseFileName );
946  return 0;
947  }
948 
949  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
950  // XXX Need to free memory from the error msg if one is set
951  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
952  {
953  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
954  srsId = srsIdString.toLong();
955  // close the sqlite3 statement
956  sqlite3_finalize( stmt );
957  sqlite3_close( database );
958  return srsId;
959  }
960  }
961  else
962  {
963  //only bother looking in srs.db if it wasn't found above
964  result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
965  if ( result )
966  {
967  QgsDebugMsg( QString( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
968  //no need for assert because user db may not have been created yet
969  return 0;
970  }
971  }
972 
973  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
974  // XXX Need to free memory from the error msg if one is set
975  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
976  {
977  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
978  srsId = srsIdString.toLong();
979  }
980 
981  // close the sqlite3 statement
982  sqlite3_finalize( stmt );
983  sqlite3_close( database );
984 
985  return srsId;
986 }
987 
988 void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
989 {
990  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
991  if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
992  return;
993 
994  QgsCoordinateReferenceSystem currentCrs = crs();
995  if ( !currentCrs.isValid() )
996  return;
997 
998  QgsRectangle rect = currentCrs.bounds();
999  if ( !qgsDoubleNear( rect.area(), 0.0 ) )
1000  {
1001  QgsGeometry geom;
1002  if ( rect.xMinimum() > rect.xMaximum() )
1003  {
1004  QgsRectangle rect1 = QgsRectangle( -180, rect.yMinimum(), rect.xMaximum(), rect.yMaximum() );
1005  QgsRectangle rect2 = QgsRectangle( rect.xMinimum(), rect.yMinimum(), 180, rect.yMaximum() );
1006  geom = QgsGeometry::fromRect( rect1 );
1007  geom.addPart( QgsGeometry::fromRect( rect2 ) );
1008  }
1009  else
1010  {
1011  geom = QgsGeometry::fromRect( rect );
1012  }
1013  mPreviewBand->setToGeometry( geom, nullptr );
1014  mPreviewBand->setColor( QColor( 255, 0, 0, 65 ) );
1015  QgsRectangle extent = geom.boundingBox();
1016  extent.scale( 1.1 );
1017  mAreaCanvas->setExtent( extent );
1018  mAreaCanvas->refresh();
1019  mPreviewBand->show();
1020  QString extentString = tr( "Extent: %1, %2, %3, %4" )
1021  .arg( rect.xMinimum(), 0, 'f', 2 )
1022  .arg( rect.yMinimum(), 0, 'f', 2 )
1023  .arg( rect.xMaximum(), 0, 'f', 2 )
1024  .arg( rect.yMaximum(), 0, 'f', 2 );
1025  QString proj4String = tr( "Proj4: %1" ).arg( selectedProj4String() );
1026  teProjection->setText( extentString + "\n" + proj4String );
1027  }
1028  else
1029  {
1030  mPreviewBand->hide();
1031  mAreaCanvas->zoomToFullExtent();
1032  QString extentString = tr( "Extent: Extent not known" );
1033  QString proj4String = tr( "Proj4: %1" ).arg( selectedProj4String() );
1034  teProjection->setText( extentString + "\n" + proj4String );
1035  }
1036 }
1037 
1038 QStringList QgsProjectionSelectionTreeWidget::authorities()
1039 {
1040  sqlite3 *database = nullptr;
1041  const char *tail = nullptr;
1042  sqlite3_stmt *stmt = nullptr;
1043 
1044  int result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().data(), &database, SQLITE_OPEN_READONLY, nullptr );
1045  if ( result )
1046  {
1047  QgsDebugMsg( QString( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
1048  //no need for assert because user db may not have been created yet
1049  return QStringList();
1050  }
1051 
1052  QString sql = QStringLiteral( "select distinct auth_name from tbl_srs" );
1053  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
1054 
1055  QStringList authorities;
1056  if ( result == SQLITE_OK )
1057  {
1058  while ( sqlite3_step( stmt ) == SQLITE_ROW )
1059  {
1060  authorities << QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
1061  }
1062 
1063  }
1064 
1065  // close the sqlite3 statement
1066  sqlite3_finalize( stmt );
1067  sqlite3_close( database );
1068 
1069  return authorities;
1070 }
1071 
1072 QString QgsProjectionSelectionTreeWidget::sqlSafeString( const QString &theSQL ) const
1073 {
1074  QString retval = theSQL;
1075  retval.replace( '\\', QLatin1String( "\\\\" ) );
1076  retval.replace( '\"', QLatin1String( "\\\"" ) );
1077  retval.replace( '\'', QLatin1String( "\\'" ) );
1078  retval.replace( '%', QLatin1String( "\\%" ) );
1079  return retval;
1080 }
1081 
1082 void QgsProjectionSelectionTreeWidget::showDBMissingWarning( const QString &fileName )
1083 {
1084 
1085  QMessageBox::critical( this, tr( "Resource Location Error" ),
1086  tr( "Error reading database file from: \n %1\n"
1087  "Because of this the projection selector will not work…" )
1088  .arg( fileName ) );
1089 }
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:40
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: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:251
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:104
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 the geometry of an existing feature.
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:37
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:217
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:176
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:161
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:473
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:166
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:171
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:229
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.