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