Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgsprojectionselector.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  * qgsprojectionselector.cpp *
3  * Copyright (C) 2005 by Tim Sutton *
4  * tim@linfiniti.com *
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  ***************************************************************************/
11 /* $Id$ */
12 #include <qgsprojectionselector.h>
13 
14 //standard includes
15 #include <cassert>
16 #include <sqlite3.h>
17 
18 //qgis includes
19 #include "qgis.h" //magic numbers here
20 #include "qgsapplication.h"
21 #include "qgslogger.h"
23 
24 //qt includes
25 #include <QDir>
26 #include <QFileInfo>
27 #include <QTextStream>
28 #include <QHeaderView>
29 #include <QResizeEvent>
30 #include <QMessageBox>
31 #include <QSettings>
32 
33 const int NAME_COLUMN = 0;
34 const int AUTHID_COLUMN = 1;
35 const int QGIS_CRS_ID_COLUMN = 2;
36 
37 QgsProjectionSelector::QgsProjectionSelector( QWidget* parent, const char *name, Qt::WFlags fl )
38  : QWidget( parent, fl )
39  , mProjListDone( false )
40  , mUserProjListDone( false )
41  , mRecentProjListDone( false )
42  , mCRSNameSelectionPending( false )
43  , mCRSIDSelectionPending( false )
44  , mAuthIDSelectionPending( false )
45 {
46  Q_UNUSED( name );
47  setupUi( this );
48  connect( lstCoordinateSystems, SIGNAL( currentItemChanged( QTreeWidgetItem*, QTreeWidgetItem* ) ),
49  this, SLOT( coordinateSystemSelected( QTreeWidgetItem* ) ) );
50 
51  // Get the full path name to the sqlite3 spatial reference database.
53  lstCoordinateSystems->header()->setResizeMode( AUTHID_COLUMN, QHeaderView::Stretch );
54  lstCoordinateSystems->header()->resizeSection( QGIS_CRS_ID_COLUMN, 0 );
55  lstCoordinateSystems->header()->setResizeMode( QGIS_CRS_ID_COLUMN, QHeaderView::Fixed );
56 
57  lstRecent->header()->setResizeMode( AUTHID_COLUMN, QHeaderView::Stretch );
58  lstRecent->header()->resizeSection( QGIS_CRS_ID_COLUMN, 0 );
59  lstRecent->header()->setResizeMode( QGIS_CRS_ID_COLUMN, QHeaderView::Fixed );
60 
61  cbxAuthority->addItem( tr( "All" ) );
62  cbxAuthority->addItems( authorities() );
63 
64  // Read settings from persistent storage
65  QSettings settings;
66  mRecentProjections = settings.value( "/UI/recentProjections" ).toStringList();
67  /*** The reading (above) of internal id from persistent storage should be removed sometime in the future */
68  /*** This is kept now for backwards compatibility */
69 
70  QStringList projectionsProj4 = settings.value( "/UI/recentProjectionsProj4" ).toStringList();
71  QStringList projectionsAuthId = settings.value( "/UI/recentProjectionsAuthId" ).toStringList();
72  if ( projectionsAuthId.size() >= mRecentProjections.size() )
73  {
74  // We had saved state with AuthId and Proj4. Use that instead
75  // to find out the crs id
76  QgsDebugMsg( "Use popular projection list from AuthId/Proj4 saved state" );
77  mRecentProjections.clear();
78  for ( int i = 0; i < projectionsAuthId.size(); i++ )
79  {
80  // Create a crs from the EPSG
82  crs.createFromOgcWmsCrs( projectionsAuthId.at( i ) );
83  if ( ! crs.isValid() )
84  {
85  // Couldn't create from EPSG, try the Proj4 string instead
86  if ( ! crs.createFromProj4( projectionsProj4.at( i ) ) )
87  {
88  // No? Skip this entry
89  continue;
90  }
91  }
92  mRecentProjections << QString::number( crs.srsid() );
93  }
94  }
95 }
96 
97 
99 {
100  // Save persistent list of projects
101  QSettings settings;
102  long crsId;
103 
104  // Push current projection to front, only if set
105  crsId = selectedCrsId();
106  if ( crsId )
107  {
108  mRecentProjections.removeAll( QString::number( crsId ) );
109  mRecentProjections.prepend( QString::number( crsId ) );
110  // Prune size of list
111  while ( mRecentProjections.size() > 4 )
112  {
113  mRecentProjections.removeLast();
114  }
115  // Save to file *** Should be removed sometims in the future ***
116  settings.setValue( "/UI/recentProjections", mRecentProjections );
117 
118  // Convert to EPSG and proj4, and save those values also
119 
120  QStringList projectionsProj4;
121  QStringList projectionsAuthId;
122  for ( int i = 0; i < mRecentProjections.size(); i++ )
123  {
124  // Create a crs from the crsId
126  if ( ! crs.isValid() )
127  {
128  // No? Skip this entry
129  continue;
130  }
131  projectionsProj4 << crs.toProj4();
132  projectionsAuthId << crs.authid();
133  }
134  settings.setValue( "/UI/recentProjectionsProj4", projectionsProj4 );
135  settings.setValue( "/UI/recentProjectionsAuthId", projectionsAuthId );
136  }
137 }
138 
139 
140 void QgsProjectionSelector::resizeEvent( QResizeEvent * theEvent )
141 {
142  lstCoordinateSystems->header()->resizeSection( NAME_COLUMN, theEvent->size().width() - 240 );
143  lstCoordinateSystems->header()->resizeSection( AUTHID_COLUMN, 240 );
144  lstCoordinateSystems->header()->resizeSection( QGIS_CRS_ID_COLUMN, 0 );
145 
146  lstRecent->header()->resizeSection( NAME_COLUMN, theEvent->size().width() - 240 );
147  lstRecent->header()->resizeSection( AUTHID_COLUMN, 240 );
148  lstRecent->header()->resizeSection( QGIS_CRS_ID_COLUMN, 0 );
149 }
150 
151 void QgsProjectionSelector::showEvent( QShowEvent * theEvent )
152 {
153  // ensure the projection list view is actually populated
154  // before we show this widget
155 
156  if ( !mProjListDone )
157  {
159  }
160 
161  if ( !mUserProjListDone )
162  {
164  }
165 
166  // check if a paricular projection is waiting
167  // to be pre-selected, and if so, to select it now.
169  {
170  applySelection();
171  }
172 
173  if ( !mRecentProjListDone )
174  {
175  for ( int i = mRecentProjections.size() - 1; i >= 0; i-- )
176  insertRecent( mRecentProjections.at( i ).toLong() );
177  mRecentProjListDone = true;
178  }
179 
180  // Pass up the inheritance hierarchy
181  QWidget::showEvent( theEvent );
182 }
183 
184 QString QgsProjectionSelector::ogcWmsCrsFilterAsSqlExpression( QSet<QString> * crsFilter )
185 {
186  QString sqlExpression = "1"; // it's "SQL" for "true"
187  QMap<QString, QStringList> authParts;
188 
189  if ( !crsFilter )
190  {
191  return sqlExpression;
192  }
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  foreach( QString auth_id, crsFilter->values() )
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.size() > 0 )
226  {
227  QString prefix = " AND (";
228  foreach( QString auth_name, authParts.keys() )
229  {
230  sqlExpression += QString( "%1(upper(auth_name)='%2' AND upper(auth_id) IN ('%3'))" )
231  .arg( prefix )
232  .arg( auth_name )
233  .arg( authParts[auth_name].join( "','" ) );
234  prefix = " OR ";
235  }
236  sqlExpression += ")";
237  }
238 
239  QgsDebugMsg( "exiting with '" + sqlExpression + "'." );
240 
241  return sqlExpression;
242 }
243 
244 
246 {
247  mCRSNameSelection = theCRSName;
249  mCRSIDSelectionPending = false; // only one type can be pending at a time
251 
252  if ( isVisible() )
253  {
254  applySelection();
255  }
256  // else we will wait for the projection selector to
257  // become visible (with the showEvent()) and set the
258  // selection there
259 }
260 
261 
263 {
264  mCRSIDSelection = theCRSID;
265  mCRSIDSelectionPending = true;
266  mCRSNameSelectionPending = false; // only one type can be pending at a time
267  mAuthIDSelectionPending = false;
268 
269  if ( isVisible() )
270  {
271  applySelection();
272  }
273  // else we will wait for the projection selector to
274  // become visible (with the showEvent()) and set the
275  // selection there
276 }
277 
279 {
280  setSelectedAuthId( QString( "EPSG:%1" ).arg( id ) );
281 }
282 
284 {
285  mAuthIDSelection = id;
286  mCRSIDSelectionPending = false;
288  mCRSNameSelectionPending = false; // only one type can be pending at a time
289 }
290 
292 {
293  if ( !mProjListDone || !mUserProjListDone )
294  return;
295 
296  QList<QTreeWidgetItem*> nodes;
298  {
299  //get the srid given the wkt so we can pick the correct list item
300  QgsDebugMsg( "called with " + mCRSNameSelection );
301  nodes = lstCoordinateSystems->findItems( mCRSNameSelection, Qt::MatchExactly | Qt::MatchRecursive, 0 );
302 
303  mCRSNameSelectionPending = false;
304  }
305 
307  {
308  //get the srid given the wkt so we can pick the correct list item
309  QgsDebugMsg( "called with " + mAuthIDSelection );
310  nodes = lstCoordinateSystems->findItems( mAuthIDSelection, Qt::MatchExactly | Qt::MatchRecursive, AUTHID_COLUMN );
311 
312  mAuthIDSelectionPending = false;
313  }
314 
316  {
317  QString myCRSIDString = QString::number( mCRSIDSelection );
318 
319  nodes = lstCoordinateSystems->findItems( myCRSIDString, Qt::MatchExactly | Qt::MatchRecursive, QGIS_CRS_ID_COLUMN );
320 
321  mCRSIDSelectionPending = false;
322  }
323 
324  if ( nodes.count() > 0 )
325  {
326  lstCoordinateSystems->setCurrentItem( nodes.first() );
327  lstCoordinateSystems->scrollToItem( lstCoordinateSystems->currentItem(), QAbstractItemView::PositionAtCenter );
328  }
329  else // unselect the selected item to avoid confusing the user
330  {
331  lstCoordinateSystems->clearSelection();
332  teProjection->setText( "" );
333  }
334 }
335 
337 {
338  if ( !mProjListDone || !mUserProjListDone )
339  return;
340 
341  QList<QTreeWidgetItem*> nodes = lstCoordinateSystems->findItems( QString::number( theCrsId ), Qt::MatchExactly | Qt::MatchRecursive, QGIS_CRS_ID_COLUMN );
342 
343  if ( nodes.count() == 0 )
344  return;
345 
346  lstRecent->insertTopLevelItem( 0, new QTreeWidgetItem( lstRecent, QStringList()
347  << nodes.first()->text( NAME_COLUMN )
348  << nodes.first()->text( AUTHID_COLUMN )
349  << nodes.first()->text( QGIS_CRS_ID_COLUMN ) ) );
350 }
351 
352 //note this line just returns the projection name!
354 {
355  // return the selected wkt name from the list view
356  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
357  if ( lvi )
358  {
359  return lvi->text( 0 );
360  }
361  else
362  {
363  return QString::null;
364  }
365 }
366 // Returns the whole proj4 string for the selected projection node
368 {
369  // Only return the projection if there is a node in the tree
370  // selected that has an srid. This prevents error if the user
371  // selects a top-level node rather than an actual coordinate
372  // system
373  //
374  // Get the selected node
375  QTreeWidgetItem *myItem = lstCoordinateSystems->currentItem();
376  if ( myItem )
377  {
378 
379  if ( myItem->text( QGIS_CRS_ID_COLUMN ).length() > 0 )
380  {
381  QString myDatabaseFileName;
382  QString mySrsId = myItem->text( QGIS_CRS_ID_COLUMN );
383 
384  QgsDebugMsg( "mySrsId = " + mySrsId );
385  QgsDebugMsg( "USER_CRS_START_ID = " + QString::number( USER_CRS_START_ID ) );
386  //
387  // Determine if this is a user projection or a system on
388  // user projection defs all have srs_id >= 100000
389  //
390  if ( mySrsId.toLong() >= USER_CRS_START_ID )
391  {
392  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
393  QFileInfo myFileInfo;
394  myFileInfo.setFile( myDatabaseFileName );
395  if ( !myFileInfo.exists( ) ) //its unlikely that this condition will ever be reached
396  {
397  QgsDebugMsg( "users qgis.db not found" );
398  return QString( "" );
399  }
400  else
401  {
402  QgsDebugMsg( "users qgis.db found" );
403  }
404  }
405  else //must be a system projection then
406  {
407  myDatabaseFileName = mSrsDatabaseFileName;
408  }
409  QgsDebugMsg( "db = " + myDatabaseFileName );
410 
411 
412  sqlite3 *db;
413  int rc;
414  rc = sqlite3_open( myDatabaseFileName.toUtf8().data(), &db );
415  if ( rc )
416  {
417  showDBMissingWarning( myDatabaseFileName );
418  return QString( "" );
419  }
420  // prepare the sql statement
421  const char *pzTail;
422  sqlite3_stmt *ppStmt;
423  QString sql = QString( "select parameters from tbl_srs where srs_id = %1" ).arg( mySrsId );
424 
425  QgsDebugMsg( "Selection sql: " + sql );
426 
427  rc = sqlite3_prepare( db, sql.toUtf8(), sql.toUtf8().length(), &ppStmt, &pzTail );
428  // XXX Need to free memory from the error msg if one is set
429  QString myProjString;
430  if ( rc == SQLITE_OK )
431  {
432  if ( sqlite3_step( ppStmt ) == SQLITE_ROW )
433  {
434  myProjString = QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 0 ) );
435  }
436  }
437  // close the statement
438  sqlite3_finalize( ppStmt );
439  // close the database
440  sqlite3_close( db );
441  assert( myProjString.length() > 0 );
442  return myProjString;
443  }
444  else
445  {
446  // No node is selected, return null
447  return QString( "" );
448  }
449  }
450  else
451  {
452  // No node is selected, return null
453  return QString( "" );
454  }
455 
456 }
457 
458 QString QgsProjectionSelector::getSelectedExpression( QString expression )
459 {
460  // Only return the attribute if there is a node in the tree
461  // selected that has an srs_id. This prevents error if the user
462  // selects a top-level node rather than an actual coordinate
463  // system
464  //
465  // Get the selected node
466  QTreeWidgetItem *lvi = lstCoordinateSystems->currentItem();
467  if ( lvi )
468  {
469  // Make sure the selected node is a srs and not a top-level projection node
470  if ( lvi->text( QGIS_CRS_ID_COLUMN ).length() > 0 )
471  {
472  QString myDatabaseFileName;
473  //
474  // Determine if this is a user projection or a system on
475  // user projection defs all have srs_id >= 100000
476  //
477  if ( lvi->text( QGIS_CRS_ID_COLUMN ).toLong() >= USER_CRS_START_ID )
478  {
479  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
480  QFileInfo myFileInfo;
481  myFileInfo.setFile( myDatabaseFileName );
482  if ( !myFileInfo.exists( ) )
483  {
484  QgsDebugMsg( " Projection selector : users qgis.db not found" );
485  return 0;
486  }
487  }
488  else //must be a system projection then
489  {
490  myDatabaseFileName = mSrsDatabaseFileName;
491  }
492  //
493  // set up the database
494  // XXX We could probabaly hold the database open for the life of this object,
495  // assuming that it will never be used anywhere else. Given the low overhead,
496  // opening it each time seems to be a reasonable approach at this time.
497  sqlite3 *db;
498  int rc;
499  rc = sqlite3_open( myDatabaseFileName.toUtf8().data(), &db );
500  if ( rc )
501  {
502  showDBMissingWarning( myDatabaseFileName );
503  return 0;
504  }
505  // prepare the sql statement
506  const char *pzTail;
507  sqlite3_stmt *ppStmt;
508  QString sql = QString( "select %1 from tbl_srs where srs_id=%2" )
509  .arg( expression )
510  .arg( lvi->text( QGIS_CRS_ID_COLUMN ) );
511 
512  QgsDebugMsg( QString( "Finding selected attribute using : %1" ).arg( sql ) );
513  rc = sqlite3_prepare( db, sql.toUtf8(), sql.toUtf8().length(), &ppStmt, &pzTail );
514  // XXX Need to free memory from the error msg if one is set
515  QString myAttributeValue;
516  if ( rc == SQLITE_OK )
517  {
518  // get the first row of the result set
519  if ( sqlite3_step( ppStmt ) == SQLITE_ROW )
520  {
521  // get the attribute
522  myAttributeValue = QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 0 ) );
523  }
524  }
525  // close the statement
526  sqlite3_finalize( ppStmt );
527  // close the database
528  sqlite3_close( db );
529  // return the srs
530  return myAttributeValue;
531  }
532  }
533 
534  // No node is selected, return null
535  return 0;
536 }
537 
539 {
540  if ( getSelectedExpression( "auth_name" ).compare( "EPSG", Qt::CaseInsensitive ) == 0 )
541  {
542  return getSelectedExpression( "auth_id" ).toLong();
543  }
544  else
545  {
546  QgsDebugMsg( "selected projection is NOT EPSG" );
547  return 0;
548  }
549 }
550 
552 {
553  return getSelectedExpression( "srid" ).toLong();
554 }
555 
556 
558 {
559  int srid = getSelectedExpression( "srs_id" ).toLong();
560  if ( srid >= USER_CRS_START_ID )
561  return QString( "USER:%1" ).arg( srid );
562  else
563  return getSelectedExpression( "upper(auth_name||':'||auth_id)" );
564 }
565 
566 
568 {
569  QTreeWidgetItem* item = lstCoordinateSystems->currentItem();
570 
571  if ( item != NULL && item->text( QGIS_CRS_ID_COLUMN ).length() > 0 )
572  {
573  return lstCoordinateSystems->currentItem()->text( QGIS_CRS_ID_COLUMN ).toLong();
574  }
575  else
576  {
577  return 0;
578  }
579 }
580 
581 
582 void QgsProjectionSelector::setOgcWmsCrsFilter( QSet<QString> crsFilter )
583 {
584  mCrsFilter = crsFilter;
585  mProjListDone = false;
586  mUserProjListDone = false;
587  lstCoordinateSystems->clear();
588 }
589 
590 
591 void QgsProjectionSelector::loadUserCrsList( QSet<QString> * crsFilter )
592 {
593  QgsDebugMsg( "Fetching user projection list..." );
594 
595  // convert our Coordinate Reference System filter into the SQL expression
596  QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
597 
598  // User defined coordinate system node
599  // Make in an italic font to distinguish them from real projections
600  mUserProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "User Defined Coordinate Systems" ) ) );
601 
602  QFont fontTemp = mUserProjList->font( 0 );
603  fontTemp.setItalic( true );
604  fontTemp.setBold( true );
605  mUserProjList->setFont( 0, fontTemp );
606  mUserProjList->setIcon( 0, QIcon( QgsApplication::activeThemePath() + "user.png" ) );
607 
608  //determine where the user proj database lives for this user. If none is found an empty
609  //now only will be shown
610  QString myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
611  // first we look for ~/.qgis/qgis.db
612  // if it doesnt exist we copy it in from the global resources dir
613  QFileInfo myFileInfo;
614  myFileInfo.setFile( myDatabaseFileName );
615  //return straight away if the user has not created any custom projections
616  if ( !myFileInfo.exists( ) )
617  {
618  QgsDebugMsg( "Users qgis.db not found...skipping" );
619 
620  mUserProjListDone = true;
621  return;
622  }
623 
624  sqlite3 *myDatabase;
625  const char *myTail;
626  sqlite3_stmt *myPreparedStatement;
627  int myResult;
628  //check the db is available
629  myResult = sqlite3_open( QString( myDatabaseFileName ).toUtf8().data(), &myDatabase );
630  if ( myResult )
631  {
632  // XXX This will likely never happen since on open, sqlite creates the
633  // database if it does not exist. But we checked earlier for its existance
634  // and aborted in that case. This is because we may be runnig from read only
635  // media such as live cd and don't want to force trying to create a db.
636  showDBMissingWarning( myDatabaseFileName );
637  return;
638  }
639 
640  // Set up the query to retrieve the projection information needed to populate the list
641  QString mySql = QString( "select description, srs_id from vw_srs where %1" ).arg( sqlFilter );
642 
643  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
644  // XXX Need to free memory from the error msg if one is set
645  if ( myResult == SQLITE_OK )
646  {
647  QTreeWidgetItem *newItem;
648  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
649  {
650  newItem = new QTreeWidgetItem( mUserProjList, QStringList( QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) ) ) );
651  // EpsgCrsId for user projections is not always defined in some dbases.
652  // It's also not written from customprojections dialog.
653  // display the epsg (field 2) in the second column of the list view
654  // newItem->setText( EPSG_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 2 ) ) );
655  // display the qgis srs_id (field 1) in the third column of the list view
656  newItem->setText( QGIS_CRS_ID_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 1 ) ) );
657  }
658  }
659  // close the sqlite3 statement
660  sqlite3_finalize( myPreparedStatement );
661  sqlite3_close( myDatabase );
662 
663  mUserProjListDone = true;
664 }
665 
666 void QgsProjectionSelector::loadCrsList( QSet<QString> *crsFilter )
667 {
668  // convert our Coordinate Reference System filter into the SQL expression
669  QString sqlFilter = ogcWmsCrsFilterAsSqlExpression( crsFilter );
670 
671  // Create the top-level nodes for the list view of projections
672  // Make in an italic font to distinguish them from real projections
673  //
674  // Geographic coordinate system node
675  mGeoList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Geographic Coordinate Systems" ) ) );
676 
677  QFont fontTemp = mGeoList->font( 0 );
678  fontTemp.setItalic( true );
679  fontTemp.setBold( true );
680  mGeoList->setFont( 0, fontTemp );
681  mGeoList->setIcon( 0, QIcon( QgsApplication::activeThemePath() + "geographic.png" ) );
682 
683  // Projected coordinate system node
684  mProjList = new QTreeWidgetItem( lstCoordinateSystems, QStringList( tr( "Projected Coordinate Systems" ) ) );
685 
686  fontTemp = mProjList->font( 0 );
687  fontTemp.setItalic( true );
688  fontTemp.setBold( true );
689  mProjList->setFont( 0, fontTemp );
690  mProjList->setIcon( 0, QIcon( QgsApplication::activeThemePath() + "transformed.png" ) );
691 
692  //bail out in case the projections db does not exist
693  //this is necessary in case the pc is running linux with a
694  //read only filesystem because otherwise sqlite will try
695  //to create the db file on the fly
696 
697  QFileInfo myFileInfo;
698  myFileInfo.setFile( mSrsDatabaseFileName );
699  if ( !myFileInfo.exists( ) )
700  {
701  mProjListDone = true;
702  return;
703  }
704 
705  // open the database containing the spatial reference data
706  sqlite3 *db;
707  int rc;
708  rc = sqlite3_open( mSrsDatabaseFileName.toUtf8().data(), &db );
709  if ( rc )
710  {
711  // XXX This will likely never happen since on open, sqlite creates the
712  // database if it does not exist.
714  return ;
715  }
716  // prepare the sql statement
717  const char *pzTail;
718  sqlite3_stmt *ppStmt;
719  // get total count of records in the projection table
720  QString sql = "select count(*) from tbl_srs";
721 
722  rc = sqlite3_prepare( db, sql.toUtf8(), sql.toUtf8().length(), &ppStmt, &pzTail );
723  assert( rc == SQLITE_OK );
724  sqlite3_step( ppStmt );
725 
726  sqlite3_finalize( ppStmt );
727 
728  // Set up the query to retrieve the projection information needed to populate the list
729  //note I am giving the full field names for clarity here and in case someone
730  //changes the underlying view TS
731  sql = QString( "select description, srs_id, upper(auth_name||':'||auth_id), is_geo, name, parameters, deprecated from vw_srs where %1 order by name,description" )
732  .arg( sqlFilter );
733 
734  rc = sqlite3_prepare( db, sql.toUtf8(), sql.toUtf8().length(), &ppStmt, &pzTail );
735  // XXX Need to free memory from the error msg if one is set
736  if ( rc == SQLITE_OK )
737  {
738  QTreeWidgetItem *newItem;
739  // Cache some stuff to speed up creating of the list of projected
740  // spatial reference systems
741  QString previousSrsType( "" );
742  QTreeWidgetItem* previousSrsTypeNode = NULL;
743 
744  while ( sqlite3_step( ppStmt ) == SQLITE_ROW )
745  {
746  // check to see if the srs is geographic
747  int isGeo = sqlite3_column_int( ppStmt, 3 );
748  if ( isGeo )
749  {
750  // this is a geographic coordinate system
751  // Add it to the tree (field 0)
752  newItem = new QTreeWidgetItem( mGeoList, QStringList( QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 0 ) ) ) );
753 
754  // display the authority name (field 2) in the second column of the list view
755  newItem->setText( AUTHID_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 2 ) ) );
756 
757  // display the qgis srs_id (field 1) in the third column of the list view
758  newItem->setText( QGIS_CRS_ID_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 1 ) ) );
759  }
760  else
761  {
762  // This is a projected srs
763  QTreeWidgetItem *node;
764  QString srsType = QString::fromUtf8(( char* )sqlite3_column_text( ppStmt, 4 ) );
765  // Find the node for this type and add the projection to it
766  // If the node doesn't exist, create it
767  if ( srsType == previousSrsType )
768  {
769  node = previousSrsTypeNode;
770  }
771  else
772  { // Different from last one, need to search
773  QList<QTreeWidgetItem*> nodes = lstCoordinateSystems->findItems( srsType, Qt::MatchExactly | Qt::MatchRecursive, 0 );
774  if ( nodes.count() == 0 )
775  {
776  // the node doesn't exist -- create it
777  // Make in an italic font to distinguish them from real projections
778  node = new QTreeWidgetItem( mProjList, QStringList( srsType ) );
779 
780  QFont fontTemp = node->font( 0 );
781  fontTemp.setItalic( true );
782  node->setFont( 0, fontTemp );
783  }
784  else
785  {
786  node = nodes.first();
787  }
788  // Update the cache.
789  previousSrsType = srsType;
790  previousSrsTypeNode = node;
791  }
792  // add the item, setting the projection name in the first column of the list view
793  newItem = new QTreeWidgetItem( node, QStringList( QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 0 ) ) ) );
794  // display the authority id (field 2) in the second column of the list view
795  newItem->setText( AUTHID_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 2 ) ) );
796  // display the qgis srs_id (field 1) in the third column of the list view
797  newItem->setText( QGIS_CRS_ID_COLUMN, QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 1 ) ) );
798 
799  }
800 
801  // display the qgis deprecated in the user data of the item
802  newItem->setData( 0, Qt::UserRole, QString::fromUtf8(( char * )sqlite3_column_text( ppStmt, 6 ) ) );
803  newItem->setHidden( cbxHideDeprecated->isChecked() );
804  }
805  mProjList->setExpanded( true );
806  }
807  // close the sqlite3 statement
808  sqlite3_finalize( ppStmt );
809  // close the database
810  sqlite3_close( db );
811 
812  mProjListDone = true;
813 }
814 
815 
816 // New coordinate system selected from the list
817 void QgsProjectionSelector::coordinateSystemSelected( QTreeWidgetItem * theItem )
818 {
819  // If the item has children, it's not an end node in the tree, and
820  // hence is just a grouping thingy, not an actual CRS.
821  if ( theItem && theItem->childCount() == 0 )
822  {
823  // Found a real CRS
824  QString myDescription;
825  emit sridSelected( QString::number( selectedCrsId() ) );
826  QString myProjString = selectedProj4String();
827  lstCoordinateSystems->scrollToItem( theItem );
828  teProjection->setText( myProjString );
829 
830  lstRecent->clearSelection();
831  }
832  else
833  {
834  // Not an CRS - remove the highlight so the user doesn't get too confused
835  if ( theItem )
836  theItem->setSelected( false );
837  teProjection->setText( "" );
838  }
839 }
840 
841 void QgsProjectionSelector::hideDeprecated( QTreeWidgetItem *item )
842 {
843  if ( item->data( 0, Qt::UserRole ).toBool() )
844  {
845  item->setHidden( cbxHideDeprecated->isChecked() );
846  if ( item->isSelected() && item->isHidden() )
847  {
848  item->setSelected( false );
849  teProjection->setText( "" );
850  }
851  }
852 
853  for ( int i = 0; i < item->childCount(); i++ )
854  hideDeprecated( item->child( i ) );
855 }
856 
858 {
859  for ( int i = 0; i < lstCoordinateSystems->topLevelItemCount(); i++ )
860  hideDeprecated( lstCoordinateSystems->topLevelItem( i ) );
861 }
862 
863 void QgsProjectionSelector::on_lstRecent_currentItemChanged( QTreeWidgetItem *current, QTreeWidgetItem *previous )
864 {
865  Q_UNUSED( previous );
866  if ( current )
867  setSelectedCrsId( current->text( QGIS_CRS_ID_COLUMN ).toLong() );
868 }
869 
871 {
872  QgsDebugMsg( "pbnFind..." );
873 
874  QString mySearchString( sqlSafeString( leSearch->text() ) );
875 
876  // Set up the query to retrieve the projection information needed to populate the list
877  QString mySql = "select srs_id from tbl_srs where ";
878  if ( cbxAuthority->currentIndex() > 0 )
879  {
880  mySql += QString( "auth_name='%1' AND " ).arg( cbxAuthority->currentText() );
881  }
882 
883  if ( cbxHideDeprecated->isChecked() )
884  {
885  mySql += "not deprecated AND ";
886  }
887 
888  if ( cbxMode->currentIndex() == 0 )
889  {
890  mySql += QString( "auth_id='%1'" ).arg( mySearchString );
891  }
892  else
893  {
894  mySql += "upper(description) like '%" + mySearchString.toUpper() + "%' ";
895 
896  long myLargestSrsId = getLargestCRSIDMatch( QString( "%1 order by srs_id desc limit 1" ).arg( mySql ) );
897  QgsDebugMsg( QString( "Largest CRSID%1" ).arg( myLargestSrsId ) );
898 
899  //a name search is ambiguous, so we find the first srsid after the current selected srsid
900  // each time the find button is pressed. This means we can loop through all matches.
901  if ( myLargestSrsId <= selectedCrsId() )
902  {
903  mySql = QString( "%1 order by srs_id limit 1" ).arg( mySql );
904  }
905  else
906  {
907  // search ahead of the current position
908  mySql = QString( "%1 and srs_id > %2 order by srs_id limit 1" ).arg( mySql ).arg( selectedCrsId() );
909  }
910  }
911  QgsDebugMsg( QString( " Search sql: %1" ).arg( mySql ) );
912 
913  //
914  // Now perform the actual search
915  //
916 
917  sqlite3 *myDatabase;
918  const char *myTail;
919  sqlite3_stmt *myPreparedStatement;
920  int myResult;
921  //check the db is available
922  myResult = sqlite3_open( mSrsDatabaseFileName.toUtf8().data(), &myDatabase );
923  if ( myResult )
924  {
925  // XXX This will likely never happen since on open, sqlite creates the
926  // database if it does not exist. But we checked earlier for its existance
927  // and aborted in that case. This is because we may be runnig from read only
928  // media such as live cd and don't want to force trying to create a db.
930  return;
931  }
932 
933  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
934  // XXX Need to free memory from the error msg if one is set
935  if ( myResult == SQLITE_OK )
936  {
937  myResult = sqlite3_step( myPreparedStatement );
938  if ( myResult == SQLITE_ROW )
939  {
940  QString mySrsId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
941  setSelectedCrsId( mySrsId.toLong() );
942  // close the sqlite3 statement
943  sqlite3_finalize( myPreparedStatement );
944  sqlite3_close( myDatabase );
945  return;
946  }
947  }
948  //search the users db
949  QString myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
950  QFileInfo myFileInfo;
951  myFileInfo.setFile( myDatabaseFileName );
952  if ( !myFileInfo.exists( ) ) //its not critical if this happens
953  {
954  QgsDebugMsg( QString( "%1\nUser db does not exist" ).arg( myDatabaseFileName ) );
955  return ;
956  }
957  myResult = sqlite3_open( myDatabaseFileName.toUtf8().data(), &myDatabase );
958  if ( myResult )
959  {
960  QgsDebugMsg( QString( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
961  //no need for assert because user db may not have been created yet
962  return;
963  }
964 
965  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
966  // XXX Need to free memory from the error msg if one is set
967  if ( myResult == SQLITE_OK )
968  {
969  myResult = sqlite3_step( myPreparedStatement );
970  if ( myResult == SQLITE_ROW )
971  {
972  QString mySrsId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
973  setSelectedCrsId( mySrsId.toLong() );
974  // close the sqlite3 statement
975  sqlite3_finalize( myPreparedStatement );
976  sqlite3_close( myDatabase );
977  return;
978  }
979  }
980 
981  QMessageBox::information( this, tr( "Find projection" ), tr( "No matching projection found." ) );
982  lstCoordinateSystems->clearSelection();
983  teProjection->setText( "" );
984 }
985 
987 {
988  long mySrsId = 0;
989  //
990  // Now perform the actual search
991  //
992 
993  sqlite3 *myDatabase;
994  const char *myTail;
995  sqlite3_stmt *myPreparedStatement;
996  int myResult;
997 
998  // first we search the users db as any srsid there will be definition be greater than in sys db
999 
1000  //check the db is available
1001  QString myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
1002  QFileInfo myFileInfo;
1003  myFileInfo.setFile( myDatabaseFileName );
1004  if ( myFileInfo.exists( ) ) //only bother trying to open if the file exists
1005  {
1006  myResult = sqlite3_open( myDatabaseFileName.toUtf8().data(), &myDatabase );
1007  if ( myResult )
1008  {
1009  // XXX This will likely never happen since on open, sqlite creates the
1010  // database if it does not exist. But we checked earlier for its existance
1011  // and aborted in that case. This is because we may be runnig from read only
1012  // media such as live cd and don't want to force trying to create a db.
1013  showDBMissingWarning( myDatabaseFileName );
1014  return 0;
1015  }
1016  else
1017  {
1018  myResult = sqlite3_prepare( myDatabase, theSql.toUtf8(), theSql.toUtf8().length(), &myPreparedStatement, &myTail );
1019  // XXX Need to free memory from the error msg if one is set
1020  if ( myResult == SQLITE_OK )
1021  {
1022  myResult = sqlite3_step( myPreparedStatement );
1023  if ( myResult == SQLITE_ROW )
1024  {
1025  QString mySrsIdString = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
1026  mySrsId = mySrsIdString.toLong();
1027  // close the sqlite3 statement
1028  sqlite3_finalize( myPreparedStatement );
1029  sqlite3_close( myDatabase );
1030  return mySrsId;
1031  }
1032  }
1033  }
1034  }
1035 
1036  //only bother looking in srs.db if it wasnt found above
1037 
1038  myResult = sqlite3_open( mSrsDatabaseFileName.toUtf8().data(), &myDatabase );
1039  if ( myResult )
1040  {
1041  QgsDebugMsg( QString( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
1042  //no need for assert because user db may not have been created yet
1043  return 0;
1044  }
1045 
1046  myResult = sqlite3_prepare( myDatabase, theSql.toUtf8(), theSql.toUtf8().length(), &myPreparedStatement, &myTail );
1047  // XXX Need to free memory from the error msg if one is set
1048  if ( myResult == SQLITE_OK )
1049  {
1050  myResult = sqlite3_step( myPreparedStatement );
1051  if ( myResult == SQLITE_ROW )
1052  {
1053  QString mySrsIdString = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
1054  mySrsId = mySrsIdString.toLong();
1055  // close the sqlite3 statement
1056  sqlite3_finalize( myPreparedStatement );
1057  sqlite3_close( myDatabase );
1058  }
1059  }
1060  return mySrsId;
1061 }
1062 
1064 {
1065  sqlite3 *myDatabase;
1066  const char *myTail;
1067  sqlite3_stmt *myPreparedStatement;
1068  int myResult;
1069 
1070  myResult = sqlite3_open( mSrsDatabaseFileName.toUtf8().data(), &myDatabase );
1071  if ( myResult )
1072  {
1073  QgsDebugMsg( QString( "Can't open * user * database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
1074  //no need for assert because user db may not have been created yet
1075  return QStringList();
1076  }
1077 
1078  QString theSql = "select distinct auth_name from tbl_srs";
1079  myResult = sqlite3_prepare( myDatabase, theSql.toUtf8(), theSql.toUtf8().length(), &myPreparedStatement, &myTail );
1080 
1081  QStringList authorities;
1082 
1083  if ( myResult == SQLITE_OK )
1084  {
1085  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1086  {
1087  authorities << QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
1088  }
1089 
1090  // close the sqlite3 statement
1091  sqlite3_finalize( myPreparedStatement );
1092  sqlite3_close( myDatabase );
1093  }
1094 
1095  return authorities;
1096 }
1097 
1107 const QString QgsProjectionSelector::sqlSafeString( const QString theSQL )
1108 {
1109  QString myRetval = theSQL;
1110  myRetval.replace( "\\", "\\\\" );
1111  myRetval.replace( '\"', "\\\"" );
1112  myRetval.replace( "\'", "\\'" );
1113  myRetval.replace( "%", "\\%" );
1114  return myRetval;
1115 }
1116 
1117 void QgsProjectionSelector::showDBMissingWarning( const QString theFileName )
1118 {
1119 
1120  QMessageBox::critical( this, tr( "Resource Location Error" ),
1121  tr( "Error reading database file from: \n %1\n"
1122  "Because of this the projection selector will not work..." )
1123  .arg( theFileName ) );
1124 }