QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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  const auto authIds { *crsFilter };
213  for ( const QString &auth_id : authIds )
214  {
215  QStringList parts = auth_id.split( ':' );
216 
217  if ( parts.size() < 2 )
218  continue;
219 
220  authParts[ parts.at( 0 ).toUpper()].append( parts.at( 1 ).toUpper() );
221  }
222 
223  if ( authParts.isEmpty() )
224  return sqlExpression;
225 
226  if ( !authParts.isEmpty() )
227  {
228  QString prefix = QStringLiteral( " AND (" );
229  for ( auto it = authParts.constBegin(); it != authParts.constEnd(); ++it )
230  {
231  sqlExpression += QStringLiteral( "%1(upper(auth_name)='%2' AND upper(auth_id) IN ('%3'))" )
232  .arg( prefix,
233  it.key(),
234  it.value().join( QStringLiteral( "','" ) ) );
235  prefix = QStringLiteral( " OR " );
236  }
237  sqlExpression += ')';
238  }
239 
240  QgsDebugMsgLevel( "exiting with '" + sqlExpression + "'.", 4 );
241 
242  return sqlExpression;
243 }
244 
245 void QgsProjectionSelectionTreeWidget::applySelection( int column, QString value )
246 {
247  if ( !mProjListDone || !mUserProjListDone )
248  {
249  // defer selection until loaded
250  mSearchColumn = column;
251  mSearchValue = value;
252  return;
253  }
254 
255  if ( column == QgsProjectionSelectionTreeWidget::None )
256  {
257  // invoked deferred selection
258  column = mSearchColumn;
259  value = mSearchValue;
260 
261  mSearchColumn = QgsProjectionSelectionTreeWidget::None;
262  mSearchValue.clear();
263  }
264 
265  if ( column == QgsProjectionSelectionTreeWidget::None )
266  return;
267 
268  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( value, Qt::MatchExactly | Qt::MatchRecursive, column );
269  if ( !nodes.isEmpty() )
270  {
271  QgsDebugMsgLevel( QStringLiteral( "found %1,%2" ).arg( column ).arg( value ), 4 );
272  lstCoordinateSystems->setCurrentItem( nodes.first() );
273  }
274  else
275  {
276  QgsDebugMsgLevel( QStringLiteral( "nothing found for %1,%2" ).arg( column ).arg( value ), 4 );
277  // deselect the selected item to avoid confusing the user
278  lstCoordinateSystems->clearSelection();
279  lstRecent->clearSelection();
280  teProjection->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 QString();
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().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
372  if ( rc )
373  {
374  showDBMissingWarning( databaseFileName );
375  return QString();
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  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().constData(), &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( QStringLiteral( "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( QStringLiteral( "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( QStringLiteral( "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().constData(), &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;
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  if ( srsType.isEmpty() )
702  srsType = tr( "Other" );
703 
704  // Find the node for this type and add the projection to it
705  // If the node doesn't exist, create it
706  if ( srsType == previousSrsType )
707  {
708  node = previousSrsTypeNode;
709  }
710  else
711  {
712  // Different from last one, need to search
713  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( srsType, Qt::MatchExactly | Qt::MatchRecursive, NameColumn );
714  if ( nodes.isEmpty() )
715  {
716  // the node doesn't exist -- create it
717  // Make in an italic font to distinguish them from real projections
718  node = new QTreeWidgetItem( mProjList, QStringList( srsType ) );
719  QFont fontTemp = node->font( 0 );
720  fontTemp.setItalic( true );
721  node->setFont( 0, fontTemp );
722  }
723  else
724  {
725  node = nodes.first();
726  }
727  // Update the cache.
728  previousSrsType = srsType;
729  previousSrsTypeNode = node;
730  }
731  // add the item, setting the projection name in the first column of the list view
732  newItem = new QTreeWidgetItem( node, QStringList( QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) ) ) );
733  // display the authority id (field 2) in the second column of the list view
734  newItem->setText( AuthidColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 2 ) ) );
735  // display the qgis srs_id (field 1) in the third column of the list view
736  newItem->setText( QgisCrsIdColumn, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 1 ) ) );
737  // expand also parent node
738  newItem->parent()->setExpanded( true );
739  }
740 
741  // display the qgis deprecated in the user data of the item
742  newItem->setData( 0, RoleDeprecated, QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 6 ) ) );
743  newItem->setHidden( cbxHideDeprecated->isChecked() );
744  }
745  mProjList->setExpanded( true );
746  }
747 
748  // close the sqlite3 statement
749  sqlite3_finalize( stmt );
750  // close the database
751  sqlite3_close( database );
752 
753  mProjListDone = true;
754 }
755 
756 // New coordinate system selected from the list
757 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
758 {
759  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
760 
761  if ( !current )
762  {
763  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
764  return;
765  }
766 
767  lstCoordinateSystems->scrollToItem( current );
768 
769  // If the item has children, it's not an end node in the tree, and
770  // hence is just a grouping thingy, not an actual CRS.
771  if ( current->childCount() == 0 )
772  {
773  // Found a real CRS
774  emit crsSelected();
775 
776  updateBoundsPreview();
777 
778  QList<QTreeWidgetItem *> nodes = lstRecent->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly, QgisCrsIdColumn );
779  if ( !nodes.isEmpty() )
780  {
781  QgsDebugMsgLevel( QStringLiteral( "found srs %1 in recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
782  lstRecent->setCurrentItem( nodes.first() );
783  }
784  else
785  {
786  QgsDebugMsgLevel( QStringLiteral( "srs %1 not recent" ).arg( current->text( QgisCrsIdColumn ) ), 4 );
787  lstRecent->clearSelection();
788  lstCoordinateSystems->setFocus( Qt::OtherFocusReason );
789  }
790  }
791  else
792  {
793  // Not a CRS - remove the highlight so the user doesn't get too confused
794  current->setSelected( false );
795  teProjection->clear();
796  lstRecent->clearSelection();
797  }
798 }
799 
800 void QgsProjectionSelectionTreeWidget::lstCoordinateSystems_itemDoubleClicked( QTreeWidgetItem *current, int column )
801 {
802  Q_UNUSED( column )
803 
804  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
805 
806  if ( !current )
807  {
808  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
809  return;
810  }
811 
812  // If the item has children, it's not an end node in the tree, and
813  // hence is just a grouping thingy, not an actual CRS.
814  if ( current->childCount() == 0 )
816 }
817 
818 void QgsProjectionSelectionTreeWidget::lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem * )
819 {
820  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
821 
822  if ( !current )
823  {
824  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
825  return;
826  }
827 
828  lstRecent->scrollToItem( current );
829 
830  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
831  if ( !nodes.isEmpty() )
832  lstCoordinateSystems->setCurrentItem( nodes.first() );
833 }
834 
835 void QgsProjectionSelectionTreeWidget::lstRecent_itemDoubleClicked( QTreeWidgetItem *current, int column )
836 {
837  Q_UNUSED( column )
838 
839  QgsDebugMsgLevel( QStringLiteral( "Entered." ), 4 );
840 
841  if ( !current )
842  {
843  QgsDebugMsgLevel( QStringLiteral( "no current item" ), 4 );
844  return;
845  }
846 
847  QList<QTreeWidgetItem *> nodes = lstCoordinateSystems->findItems( current->text( QgisCrsIdColumn ), Qt::MatchExactly | Qt::MatchRecursive, QgisCrsIdColumn );
848  if ( !nodes.isEmpty() )
850 }
851 
852 void QgsProjectionSelectionTreeWidget::updateFilter()
853 {
854  QString filterTxtCopy = leSearch->text();
855  filterTxtCopy.replace( QRegExp( "\\s+" ), QStringLiteral( ".*" ) );
856  QRegExp re( filterTxtCopy, Qt::CaseInsensitive );
857 
858  const bool hideDeprecated = cbxHideDeprecated->isChecked();
859 
860  auto filterTreeWidget = [ = ]( QTreeWidget * tree )
861  {
862  QTreeWidgetItemIterator itr( tree );
863  while ( *itr )
864  {
865  if ( ( *itr )->childCount() == 0 ) // it's an end node aka a projection
866  {
867  if ( hideDeprecated && ( *itr )->data( 0, RoleDeprecated ).toBool() )
868  {
869  ( *itr )->setHidden( true );
870  if ( ( *itr )->isSelected() )
871  {
872  ( *itr )->setSelected( false );
873  teProjection->clear();
874  }
875  }
876  else if ( ( *itr )->text( NameColumn ).contains( re )
877  || ( *itr )->text( AuthidColumn ).contains( re )
878  )
879  {
880  ( *itr )->setHidden( false );
881  QTreeWidgetItem *parent = ( *itr )->parent();
882  while ( parent )
883  {
884  parent->setExpanded( true );
885  parent->setHidden( false );
886  parent = parent->parent();
887  }
888  }
889  else
890  {
891  ( *itr )->setHidden( true );
892  }
893  }
894  else
895  {
896  ( *itr )->setHidden( true );
897  }
898  ++itr;
899  }
900  };
901 
902  // filter recent crs's
903  filterTreeWidget( lstRecent );
904 
905  // filter crs's
906  filterTreeWidget( lstCoordinateSystems );
907 }
908 
909 
911 {
912  // set flag to push selected projection to front in destructor
913  mPushProjectionToFront = true;
914 }
915 
916 
917 long QgsProjectionSelectionTreeWidget::getLargestCrsIdMatch( const QString &sql )
918 {
919  long srsId = 0;
920 
921  //
922  // Now perform the actual search
923  //
924 
925  sqlite3 *database = nullptr;
926  const char *tail = nullptr;
927  sqlite3_stmt *stmt = nullptr;
928  int result;
929 
930  // first we search the users db as any srsid there will be definition be greater than in sys db
931 
932  //check the db is available
933  QString databaseFileName = QgsApplication::qgisUserDatabaseFilePath();
934  if ( QFileInfo::exists( databaseFileName ) ) //only bother trying to open if the file exists
935  {
936  result = sqlite3_open_v2( databaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
937  if ( result )
938  {
939  // XXX This will likely never happen since on open, sqlite creates the
940  // database if it does not exist. But we checked earlier for its existence
941  // and aborted in that case. This is because we may be running from read only
942  // media such as live cd and don't want to force trying to create a db.
943  showDBMissingWarning( databaseFileName );
944  return 0;
945  }
946 
947  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
948  // XXX Need to free memory from the error msg if one is set
949  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
950  {
951  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
952  srsId = srsIdString.toLong();
953  // close the sqlite3 statement
954  sqlite3_finalize( stmt );
955  sqlite3_close( database );
956  return srsId;
957  }
958  }
959  else
960  {
961  //only bother looking in srs.db if it wasn't found above
962  result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
963  if ( result )
964  {
965  QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
966  //no need for assert because user db may not have been created yet
967  return 0;
968  }
969  }
970 
971  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
972  // XXX Need to free memory from the error msg if one is set
973  if ( result == SQLITE_OK && sqlite3_step( stmt ) == SQLITE_ROW )
974  {
975  QString srsIdString = QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
976  srsId = srsIdString.toLong();
977  }
978 
979  // close the sqlite3 statement
980  sqlite3_finalize( stmt );
981  sqlite3_close( database );
982 
983  return srsId;
984 }
985 
986 void QgsProjectionSelectionTreeWidget::updateBoundsPreview()
987 {
988  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
989  if ( !lvi || lvi->text( QgisCrsIdColumn ).isEmpty() )
990  return;
991 
992  QgsCoordinateReferenceSystem currentCrs = crs();
993  if ( !currentCrs.isValid() )
994  return;
995 
996  QgsRectangle rect = currentCrs.bounds();
997  QString extentString = tr( "Extent not known" );
998  if ( !qgsDoubleNear( rect.area(), 0.0 ) )
999  {
1000  QgsGeometry geom;
1001  if ( rect.xMinimum() > rect.xMaximum() )
1002  {
1003  QgsRectangle rect1 = QgsRectangle( -180, rect.yMinimum(), rect.xMaximum(), rect.yMaximum() );
1004  QgsRectangle rect2 = QgsRectangle( rect.xMinimum(), rect.yMinimum(), 180, rect.yMaximum() );
1005  geom = QgsGeometry::fromRect( rect1 );
1006  geom.addPart( QgsGeometry::fromRect( rect2 ) );
1007  }
1008  else
1009  {
1010  geom = QgsGeometry::fromRect( rect );
1011  }
1012  mPreviewBand->setToGeometry( geom, nullptr );
1013  mPreviewBand->setColor( QColor( 255, 0, 0, 65 ) );
1014  QgsRectangle extent = geom.boundingBox();
1015  extent.scale( 1.1 );
1016  mAreaCanvas->setExtent( extent );
1017  mAreaCanvas->refresh();
1018  mPreviewBand->show();
1019  extentString = QStringLiteral( "%1, %2, %3, %4" )
1020  .arg( rect.xMinimum(), 0, 'f', 2 )
1021  .arg( rect.yMinimum(), 0, 'f', 2 )
1022  .arg( rect.xMaximum(), 0, 'f', 2 )
1023  .arg( rect.yMaximum(), 0, 'f', 2 );
1024 
1025  }
1026  else
1027  {
1028  mPreviewBand->hide();
1029  mAreaCanvas->zoomToFullExtent();
1030  }
1031 
1032  QString extentHtml = QStringLiteral( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Extent" ), extentString );
1033  QString proj4String = tr( "<dt><b>%1</b></dt><dd>%2</dd>" ).arg( tr( "Proj4" ), selectedProj4String() );
1034  teProjection->setText( QStringLiteral( "<h3>%1</h3><dl>" ).arg( selectedName() ) + extentHtml + proj4String + QLatin1String( "</dl>" ) );
1035 }
1036 
1037 QStringList QgsProjectionSelectionTreeWidget::authorities()
1038 {
1039  sqlite3 *database = nullptr;
1040  const char *tail = nullptr;
1041  sqlite3_stmt *stmt = nullptr;
1042 
1043  int result = sqlite3_open_v2( mSrsDatabaseFileName.toUtf8().constData(), &database, SQLITE_OPEN_READONLY, nullptr );
1044  if ( result )
1045  {
1046  QgsDebugMsg( QStringLiteral( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( database ) ) );
1047  //no need for assert because user db may not have been created yet
1048  return QStringList();
1049  }
1050 
1051  QString sql = QStringLiteral( "select distinct auth_name from tbl_srs" );
1052  result = sqlite3_prepare( database, sql.toUtf8(), sql.toUtf8().length(), &stmt, &tail );
1053 
1054  QStringList authorities;
1055  if ( result == SQLITE_OK )
1056  {
1057  while ( sqlite3_step( stmt ) == SQLITE_ROW )
1058  {
1059  authorities << QString::fromUtf8( ( char * )sqlite3_column_text( stmt, 0 ) );
1060  }
1061 
1062  }
1063 
1064  // close the sqlite3 statement
1065  sqlite3_finalize( stmt );
1066  sqlite3_close( database );
1067 
1068  return authorities;
1069 }
1070 
1071 QString QgsProjectionSelectionTreeWidget::sqlSafeString( const QString &theSQL ) const
1072 {
1073  QString retval = theSQL;
1074  retval.replace( '\\', QLatin1String( "\\\\" ) );
1075  retval.replace( '\"', QLatin1String( "\\\"" ) );
1076  retval.replace( '\'', QLatin1String( "\\'" ) );
1077  retval.replace( '%', QLatin1String( "\\%" ) );
1078  return retval;
1079 }
1080 
1081 void QgsProjectionSelectionTreeWidget::showDBMissingWarning( const QString &fileName )
1082 {
1083 
1084  QMessageBox::critical( this, tr( "Resource Location Error" ),
1085  tr( "Error reading database file from: \n %1\n"
1086  "Because of this the projection selector will not work…" )
1087  .arg( fileName ) );
1088 }
QgsCoordinateReferenceSystem crs() const
Returns the CRS currently selected in the widget.
void setWidth(int width)
Sets the width of the line.
A rectangle specified with double values.
Definition: qgsrectangle.h:41
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
static QString qgisUserDatabaseFilePath()
Returns the path to the user qgis.db file.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void scale(double scaleFactor, const QgsPointXY *c=nullptr)
Scale the rectangle around its center point.
Definition: qgsrectangle.h:235
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
QString toProj4() const
Returns a Proj4 string representation of this CRS.
void setPenWidth(int width)
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h: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.
void projectionDoubleClicked()
Emitted when a projection is double clicked in the list.
Internal ID used by QGIS in the local SQLite database.
void setToGeometry(const QgsGeometry &geom, QgsVectorLayer *layer)
Sets this rubber band to geom.
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
QgsProjectionSelectionTreeWidget(QWidget *parent=nullptr)
Constructor for QgsProjectionSelectionTreeWidget.
QgsRectangle previewRect() const
The initial "preview" rectangle for the bounds overview map.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
static QStringList recentProjections()
Returns a list of recently used projections.
A class for drawing transient features (e.g.
Definition: qgsrubberband.h:40
void setCenter(const QgsPointXY &point)
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
bool hasValidSelection() const
Returns true if the current selection in the widget is a valid choice.
bool showNoProjection() const
Returns whether the "no/invalid" projection option is shown.
A class for marking vertices of features using e.g.
void pushProjectionToFront()
Marks the current selected projection for push to front of recent projections list.
static QString pkgDataPath()
Returns the common root path of all application data directories.
struct sqlite3 sqlite3
double area() const
Returns the area of the rectangle.
Definition: qgsrectangle.h:218
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
void initialized()
Notifies others that the widget is now fully initialized, including deferred selection of projection...
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
void setShowNoProjection(bool show)
Sets whether a "no/invalid" projection option should be shown.
void crsSelected()
Emitted when a projection is selected in the widget.
const int USER_CRS_START_ID
Magick number that determines whether a projection crsid is a system (srs.db) or user (~/...
Definition: qgis.h:559
static QgsCoordinateReferenceSystem fromOgcWmsCrs(const QString &ogcCrs)
Creates a CRS from a given OGC WMS-format Coordinate Reference System string.
void setOgcWmsCrsFilter(const QSet< QString > &crsFilter)
filters this widget by the given CRSs
void setPreviewRect(const QgsRectangle &rect)
Sets the initial "preview" rectangle for the bounds overview map.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
This class represents a coordinate reference system (CRS).
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
static QString srsDatabaseFilePath()
Returns the path to the srs.db file.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:172
void setColor(const QColor &color)
Sets the color for the rubberband.
void setCrs(const QgsCoordinateReferenceSystem &crs)
Sets the initial crs to show within the dialog.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:230
QgsRectangle bounds() const
Returns the approximate bounds for the region the CRS is usable within.
void setColor(const QColor &color)
Sets the stroke color for the marker.
Represents a vector layer which manages a vector based data sets.
void setIconType(int iconType)
QString authid() const
Returns the authority identifier for the CRS.
A map tool for panning the map.
Definition: qgsmaptoolpan.h:29
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
void setShowBoundsMap(bool show)
Sets whether to show the bounnds preview map.