Quantum GIS API Documentation  1.7.5-Wroclaw
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
qgscoordinatereferencesystem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinatereferencesystem.cpp
3 
4  -------------------
5  begin : 2007
6  copyright : (C) 2007 by Gary E. Sherman
7  email : sherman@mrcc.com
8 ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
19 
20 #include <cmath>
21 
22 #include <QDir>
23 #include <QDomNode>
24 #include <QDomElement>
25 #include <QFileInfo>
26 #include <QRegExp>
27 #include <QTextStream>
28 
29 #include "qgsapplication.h"
30 #include "qgslogger.h"
31 #include "qgsmessageoutput.h"
32 #include "qgis.h" //const vals declared here
33 
34 #include <sqlite3.h>
35 
36 //gdal and ogr includes (needed for == operator)
37 #include <ogr_srs_api.h>
38 #include <cpl_error.h>
39 #include <cpl_conv.h>
40 
42 
43 //--------------------------
44 
46  : mMapUnits( QGis::UnknownUnit )
47  , mIsValidFlag( 0 )
48  , mValidationHint( "" )
49 {
50  mCRS = OSRNewSpatialReference( NULL );
51 }
52 
54  : mMapUnits( QGis::UnknownUnit )
55  , mIsValidFlag( 0 )
56  , mValidationHint( "" )
57 {
58  mCRS = OSRNewSpatialReference( NULL );
59  createFromString( theDefinition );
60 }
61 
62 
64  : mMapUnits( QGis::UnknownUnit )
65  , mIsValidFlag( 0 )
66  , mValidationHint( "" )
67 {
68  mCRS = OSRNewSpatialReference( NULL );
69  createFromId( theId, theType );
70 }
71 
73 {
74  OSRDestroySpatialReference( mCRS );
75 }
76 
77 bool QgsCoordinateReferenceSystem::createFromId( const long theId, CrsType theType )
78 {
79  bool result = false;
80  switch ( theType )
81  {
82  case InternalCrsId:
83  result = createFromSrsId( theId );
84  break;
85  case PostgisCrsId:
86  result = createFromSrid( theId );
87  break;
88  case EpsgCrsId:
89  result = createFromOgcWmsCrs( QString( "EPSG:%1" ).arg( theId ) );
90  break;
91  default:
92  //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
93  QgsDebugMsg( "Unexpected case reached!" );
94  };
95  return result;
96 }
97 
98 bool QgsCoordinateReferenceSystem::createFromString( const QString theDefinition )
99 {
100  bool result = false;
101  QRegExp reCrsId( "^(epsg|postgis|internal)\\:(\\d+)$", Qt::CaseInsensitive );
102  if ( reCrsId.indexIn( theDefinition ) == 0 )
103  {
104  QString authName = reCrsId.cap( 1 ).toLower();
105  CrsType type = InternalCrsId;
106  if ( authName == "epsg" ) type = EpsgCrsId;
107  if ( authName == "postgis" ) type = PostgisCrsId;
108  long id = reCrsId.cap( 2 ).toLong();
109  result = createFromId( id, type );
110  }
111  else
112  {
113  QRegExp reCrsStr( "^(?:(wkt|proj4)\\:)?(.+)$", Qt::CaseInsensitive );
114  if ( reCrsStr.indexIn( theDefinition ) == 0 )
115  {
116  if ( reCrsStr.cap( 1 ).toLower() == "proj4" )
117  {
118  result = createFromProj4( reCrsStr.cap( 2 ) );
119  }
120  else
121  {
122  result = createFromWkt( reCrsStr.cap( 2 ) );
123  }
124  }
125  }
126  return result;
127 }
128 
130 {
131  QRegExp re( "(user|custom|qgis):(\\d+)", Qt::CaseInsensitive );
132  if ( re.exactMatch( theCrs ) && createFromSrsId( re.cap( 2 ).toInt() ) )
133  {
134  return true;
135  }
136 
137  if ( loadFromDb( QgsApplication::srsDbFilePath(), "lower(auth_name||':'||auth_id)", theCrs.toLower() ) )
138  return true;
139 
140  if ( theCrs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 )
141  {
143  return true;
144  }
145 
146  return false;
147 }
148 
150 {
151  mCRS = OSRNewSpatialReference( NULL );
152  *this = srs;
153 }
154 
155 // Assignment operator
157 {
158  if ( &srs != this )
159  {
160  mSrsId = srs.mSrsId;
164  mGeoFlag = srs.mGeoFlag;
165  mMapUnits = srs.mMapUnits;
166  mSRID = srs.mSRID;
167  mAuthId = srs.mAuthId;
170  if ( mIsValidFlag )
171  {
172  OSRDestroySpatialReference( mCRS );
173  mCRS = OSRClone( srs.mCRS );
174  }
175  }
176  return *this;
177 }
178 
179 // Misc helper functions -----------------------
180 
181 
183 {
184  if ( mIsValidFlag )
185  return;
186 
187  // try to validate using custom validation routines
188  if ( mCustomSrsValidation )
189  mCustomSrsValidation( this );
190 
191  if ( !mIsValidFlag )
192  // set the default
194 }
195 
197 {
198  return loadFromDb( QgsApplication::srsDbFilePath(), "srid", QString::number( id ) );
199 }
200 
202 {
203  return createFromOgcWmsCrs( QString( "EPSG:%1" ).arg( id ) );
204 }
205 
207 {
209  "srs_id", QString::number( id ) );
210 }
211 
212 bool QgsCoordinateReferenceSystem::loadFromDb( QString db, QString expression, QString value )
213 {
214  QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
215  mIsValidFlag = false;
216 
217  QFileInfo myInfo( db );
218  if ( !myInfo.exists() )
219  {
220  QgsDebugMsg( "failed : " + db + " does not exist!" );
221  return mIsValidFlag;
222  }
223 
224  sqlite3 *myDatabase;
225  const char *myTail;
226  sqlite3_stmt *myPreparedStatement;
227  int myResult;
228  //check the db is available
229  myResult = openDb( db, &myDatabase );
230  if ( myResult != SQLITE_OK )
231  {
232  QgsDebugMsg( "failed : " + db + " could not be opened!" );
233  return mIsValidFlag;
234  }
235 
236  /*
237  srs_id INTEGER PRIMARY KEY,
238  description text NOT NULL,
239  projection_acronym text NOT NULL,
240  ellipsoid_acronym NOT NULL,
241  parameters text NOT NULL,
242  srid integer NOT NULL,
243  auth_name varchar NOT NULL,
244  auth_id integer NOT NULL,
245  is_geo integer NOT NULL);
246  */
247 
248  QString mySql = "select srs_id,description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo from tbl_srs where " + expression + "=" + quotedValue( value );
249  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
250  // XXX Need to free memory from the error msg if one is set
251  if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
252  {
253  mSrsId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) ).toLong();
254  mDescription = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 1 ) );
255  mProjectionAcronym = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 2 ) );
256  mEllipsoidAcronym = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 3 ) );
257  QString toProj4 = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 4 ) );
258  mSRID = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 5 ) ).toLong();
259  mAuthId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 6 ) );
260  mGeoFlag = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 7 ) ).toInt() != 0;
261 
262  if ( mSrsId >= USER_CRS_START_ID && mAuthId.isEmpty() )
263  {
264  mAuthId = QString( "USER:%1" ).arg( mSrsId );
265  }
266  else if ( mAuthId.startsWith( "EPSG:", Qt::CaseInsensitive ) )
267  {
268  OSRDestroySpatialReference( mCRS );
269  mCRS = OSRNewSpatialReference( NULL );
270  mIsValidFlag = OSRSetFromUserInput( mCRS, mAuthId.toLower().toAscii() ) == OGRERR_NONE;
271  setMapUnits();
272  }
273 
274  if ( !mIsValidFlag )
275  {
276  setProj4String( toProj4 );
277  }
278  }
279  else
280  {
281  QgsDebugMsg( "failed : " + mySql );
282  }
283  sqlite3_finalize( myPreparedStatement );
284  sqlite3_close( myDatabase );
285  return mIsValidFlag;
286 }
287 
289 {
290  mIsValidFlag = false;
291 
292  if ( theWkt.isEmpty() )
293  {
294  QgsDebugMsg( "theWkt is uninitialised, operation failed" );
295  return mIsValidFlag;
296  }
297  QgsDebugMsg( "wkt: " + theWkt );
298  QByteArray ba = theWkt.toLatin1();
299  const char *pWkt = ba.data();
300 
301  OGRErr myInputResult = OSRImportFromWkt( mCRS, ( char ** ) & pWkt );
302 
303  if ( myInputResult != OGRERR_NONE )
304  {
305  QgsDebugMsg( "\n---------------------------------------------------------------" );
306  QgsDebugMsg( "This CRS could *** NOT *** be set from the supplied Wkt " );
307  QgsDebugMsg( "INPUT: " + theWkt );
308  QgsDebugMsg( QString( "UNUSED WKT: %1" ).arg( pWkt ) );
309  QgsDebugMsg( "---------------------------------------------------------------\n" );
310  return mIsValidFlag;
311  }
312 
313  if ( OSRAutoIdentifyEPSG( mCRS ) == OGRERR_NONE )
314  {
315  QString authid = QString( "%1:%2" )
316  .arg( OSRGetAuthorityName( mCRS, NULL ) )
317  .arg( OSRGetAuthorityCode( mCRS, NULL ) );
318  QgsDebugMsg( "authid recognized as " + authid );
319  return createFromOgcWmsCrs( authid );
320  }
321 
322  // always morph from esri as it doesn't hurt anything
323  // FW: Hey, that's not right! It can screw stuff up! Disable
324  //myOgrSpatialRef.morphFromESRI();
325 
326  // create the proj4 structs needed for transforming
327  char *proj4src = NULL;
328  OSRExportToProj4( mCRS, &proj4src );
329 
330  //now that we have the proj4string, delegate to createFromProj4 so
331  // that we can try to fill in the remaining class members...
332  //create from Proj will set the isValidFlag
333  if ( !createFromProj4( proj4src ) )
334  {
335  CPLFree( proj4src );
336 
337  // try fixed up version
338  OSRFixup( mCRS );
339 
340  OSRExportToProj4( mCRS, &proj4src );
341 
342  createFromProj4( proj4src );
343  }
344 
345  CPLFree( proj4src );
346 
347  return mIsValidFlag;
348  //setMapunits will be called by createfromproj above
349 }
350 
352 {
353  return mIsValidFlag;
354 }
355 
356 bool QgsCoordinateReferenceSystem::createFromProj4( const QString theProj4String )
357 {
358  //
359  // Examples:
360  // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
361  // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
362  //
363  // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
364  // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
365  //
366  QgsDebugMsg( "proj4: " + theProj4String );
367  mIsValidFlag = false;
368 
369  QRegExp myProjRegExp( "\\+proj=(\\S+)" );
370  int myStart = myProjRegExp.indexIn( theProj4String );
371  if ( myStart == -1 )
372  {
373  QgsDebugMsg( "proj string supplied has no +proj argument" );
374  return mIsValidFlag;
375  }
376 
377  mProjectionAcronym = myProjRegExp.cap( 1 );
378 
379  QRegExp myEllipseRegExp( "\\+ellps=(\\S+)" );
380  myStart = myEllipseRegExp.indexIn( theProj4String );
381  if ( myStart == -1 )
382  {
383  QgsDebugMsg( "proj string supplied has no +ellps argument" );
384  mEllipsoidAcronym = "";
385  }
386  else
387  {
388  mEllipsoidAcronym = myEllipseRegExp.cap( 1 );
389  }
390 
391  QRegExp myAxisRegExp( "\\+a=(\\S+)" );
392  myStart = myAxisRegExp.indexIn( theProj4String );
393  if ( myStart == -1 )
394  {
395  QgsDebugMsg( "proj string supplied has no +a argument" );
396  }
397 
398  /*
399  * We try to match the proj string to and srsid using the following logic:
400  *
401  * - perform a whole text search on srs name (if not null). The srs name will
402  * have been set if this method has been delegated to from createFromWkt.
403  * Normally we wouldnt expect this to work, but its worth trying first
404  * as its quicker than methods below..
405  */
406  long mySrsId = 0;
408 
409  /*
410  * - if the above does not match perform a whole text search on proj4 string (if not null)
411  */
412  // QgsDebugMsg( "wholetext match on name failed, trying proj4string match" );
413  myRecord = getRecord( "select * from tbl_srs where parameters=" + quotedValue( theProj4String.trimmed() ) );
414  if ( myRecord.empty() )
415  {
416  // Ticket #722 - aaronr
417  // Check if we can swap the lat_1 and lat_2 params (if they exist) to see if we match...
418  // First we check for lat_1 and lat_2
419  QRegExp myLat1RegExp( "\\+lat_1=\\S+" );
420  QRegExp myLat2RegExp( "\\+lat_2=\\S+" );
421  int myStart1 = 0;
422  int myLength1 = 0;
423  int myStart2 = 0;
424  int myLength2 = 0;
425  QString lat1Str = "";
426  QString lat2Str = "";
427  myStart1 = myLat1RegExp.indexIn( theProj4String, myStart1 );
428  myStart2 = myLat2RegExp.indexIn( theProj4String, myStart2 );
429  if ( myStart1 != -1 && myStart2 != -1 )
430  {
431  myLength1 = myLat1RegExp.matchedLength();
432  myLength2 = myLat2RegExp.matchedLength();
433  lat1Str = theProj4String.mid( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN );
434  lat2Str = theProj4String.mid( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN );
435  }
436  // If we found the lat_1 and lat_2 we need to swap and check to see if we can find it...
437  if ( lat1Str != "" && lat2Str != "" )
438  {
439  // Make our new string to check...
440  QString theProj4StringModified = theProj4String;
441  // First just swap in the lat_2 value for lat_1 value
442  theProj4StringModified.replace( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN, lat2Str );
443  // Now we have to find the lat_2 location again since it has potentially moved...
444  myStart2 = 0;
445  myStart2 = myLat2RegExp.indexIn( theProj4String, myStart2 );
446  theProj4StringModified.replace( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN, lat1Str );
447  QgsDebugMsg( "trying proj4string match with swapped lat_1,lat_2" );
448  myRecord = getRecord( "select * from tbl_srs where parameters=" + quotedValue( theProj4StringModified.trimmed() ) );
449  }
450  }
451 
452  if ( myRecord.empty() )
453  {
454  // match all arameters individually:
455  // - order of parameters doesn't matter
456  // - found definition may have more parameters (like +towgs84 in GDAL)
457  // - retry without datum, if no match is found (looks like +datum<>WGS84 was dropped in GDAL)
458 
459  QString sql = "SELECT * FROM tbl_srs WHERE ";
460  QString delim = "";
461  QString datum;
462  foreach( QString param, theProj4String.split( " ", QString::SkipEmptyParts ) )
463  {
464  QString arg = QString( "' '||parameters||' ' LIKE %1" ).arg( quotedValue( QString( "% %1 %" ).arg( param ) ) );
465  if ( param.startsWith( "+datum=" ) )
466  {
467  datum = arg;
468  }
469  else
470  {
471  sql += delim + arg;
472  delim = " AND ";
473  }
474  }
475 
476  if ( !datum.isEmpty() )
477  {
478  myRecord = getRecord( sql + delim + datum );
479  }
480 
481  if ( myRecord.empty() )
482  {
483  // datum might have disappeared in definition - retry without it
484  myRecord = getRecord( sql );
485  }
486  }
487 
488  if ( !myRecord.empty() )
489  {
490  mySrsId = myRecord["srs_id"].toLong();
491  QgsDebugMsg( "proj4string param match search for srsid returned srsid: " + QString::number( mySrsId ) );
492  if ( mySrsId > 0 )
493  {
494  createFromSrsId( mySrsId );
495  }
496  }
497  else
498  {
499  // Last ditch attempt to piece together what we know of the projection to find a match...
500  QgsDebugMsg( "globbing search for srsid from this proj string" );
501  setProj4String( theProj4String );
502  mySrsId = findMatchingProj();
503  QgsDebugMsg( "globbing search for srsid returned srsid: " + QString::number( mySrsId ) );
504  if ( mySrsId > 0 )
505  {
506  createFromSrsId( mySrsId );
507  }
508  else
509  {
510  mIsValidFlag = false;
511  }
512  }
513 
514  // if we failed to look up the projection in database, don't worry. we can still use it :)
515  if ( !mIsValidFlag )
516  {
517  QgsDebugMsg( "Projection is not found in databases." );
518  setProj4String( theProj4String );
519 
520  // Is the SRS is valid now, we know it's a decent +proj string that can be entered into the srs.db
521  if ( mIsValidFlag )
522  {
523  // but the proj.4 parsed string might already be in our database
524  myRecord = getRecord( "select * from tbl_srs where parameters=" + quotedValue( toProj4() ) );
525  if ( myRecord.empty() )
526  {
527  // It's not, so try to add it
528  QgsDebugMsg( "Projection appears to be valid. Save to database!" );
530 
531  if ( mIsValidFlag )
532  {
533  // but validate that it's there afterwards
534  myRecord = getRecord( "select * from tbl_srs where parameters=" + quotedValue( toProj4() ) );
535  }
536  }
537 
538  if ( !myRecord.empty() )
539  {
540  // take the srid from the record
541  mySrsId = myRecord["srs_id"].toLong();
542  QgsDebugMsg( "proj4string match search for srsid returned srsid: " + QString::number( mySrsId ) );
543  if ( mySrsId > 0 )
544  {
545  createFromSrsId( mySrsId );
546  }
547  else
548  {
549  QgsDebugMsg( QString( "invalid srid %1 found" ).arg( mySrsId ) );
550  mIsValidFlag = false;
551  }
552  }
553  else
554  {
555  QgsDebugMsg( "Couldn't find newly added proj string?" );
556  mIsValidFlag = false;
557  }
558  }
559  }
560 
561 
562  return mIsValidFlag;
563 }
564 
565 //private method meant for internal use by this class only
567 {
568  QString myDatabaseFileName;
570  QString myFieldName;
571  QString myFieldValue;
572  sqlite3 *myDatabase;
573  const char *myTail;
574  sqlite3_stmt *myPreparedStatement;
575  int myResult;
576 
577  QgsDebugMsg( "running query: " + theSql );
578  // Get the full path name to the sqlite3 spatial reference database.
579  myDatabaseFileName = QgsApplication::srsDbFilePath();
580  QFileInfo myInfo( myDatabaseFileName );
581  if ( !myInfo.exists() )
582  {
583  QgsDebugMsg( "failed : " + myDatabaseFileName +
584  " does not exist!" );
585  return myMap;
586  }
587 
588  //check the db is available
589  myResult = openDb( myDatabaseFileName, &myDatabase );
590  if ( myResult != SQLITE_OK )
591  {
592  return myMap;
593  }
594 
595  myResult = sqlite3_prepare( myDatabase, theSql.toUtf8(), theSql.toUtf8().length(), &myPreparedStatement, &myTail );
596  // XXX Need to free memory from the error msg if one is set
597  if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
598  {
599  QgsDebugMsg( "trying system srs.db" );
600  int myColumnCount = sqlite3_column_count( myPreparedStatement );
601  //loop through each column in the record adding its expression name and value to the map
602  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
603  {
604  myFieldName = QString::fromUtf8(( char * )sqlite3_column_name( myPreparedStatement, myColNo ) );
605  myFieldValue = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, myColNo ) );
606  myMap[myFieldName] = myFieldValue;
607  }
608  }
609  else
610  {
611  QgsDebugMsg( "trying user qgis.db" );
612  sqlite3_finalize( myPreparedStatement );
613  sqlite3_close( myDatabase );
614 
615  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
616  QFileInfo myFileInfo;
617  myFileInfo.setFile( myDatabaseFileName );
618  if ( !myFileInfo.exists( ) )
619  {
620  QgsDebugMsg( "user qgis.db not found" );
621  return myMap;
622  }
623 
624  //check the db is available
625  myResult = openDb( myDatabaseFileName, &myDatabase );
626  if ( myResult != SQLITE_OK )
627  {
628  return myMap;
629  }
630 
631  myResult = sqlite3_prepare( myDatabase, theSql.toUtf8(), theSql.toUtf8().length(), &myPreparedStatement, &myTail );
632  // XXX Need to free memory from the error msg if one is set
633  if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
634  {
635  int myColumnCount = sqlite3_column_count( myPreparedStatement );
636  //loop through each column in the record adding its field name and value to the map
637  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
638  {
639  myFieldName = QString::fromUtf8(( char * )sqlite3_column_name( myPreparedStatement, myColNo ) );
640  myFieldValue = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, myColNo ) );
641  myMap[myFieldName] = myFieldValue;
642  }
643  }
644  else
645  {
646  QgsDebugMsg( "failed : " + theSql );
647 
648  }
649  }
650  sqlite3_finalize( myPreparedStatement );
651  sqlite3_close( myDatabase );
652 
653 #ifdef QGISDEBUG
654  QgsDebugMsg( "retrieved: " + theSql );
655  RecordMap::Iterator it;
656  for ( it = myMap.begin(); it != myMap.end(); ++it )
657  {
658  QgsDebugMsgLevel( it.key() + " => " + it.value(), 2 );
659  }
660 #endif
661 
662  return myMap;
663 }
664 
665 // Accessors -----------------------------------
666 
668 {
669  return mSrsId;
670 }
671 
673 {
674 
675  return mSRID;
676 
677 }
678 
680 {
681  if ( mAuthId.startsWith( "EPSG:", Qt::CaseInsensitive ) )
682  return mAuthId.mid( 5 ).toLong();
683  else
684  return 0;
685 }
686 
688 {
689  return mAuthId;
690 }
691 
693 {
694  if ( mDescription.isNull() )
695  {
696  return "";
697  }
698  else
699  {
700  return mDescription;
701  }
702 }
703 
705 {
706  if ( mProjectionAcronym.isNull() )
707  {
708  return "";
709  }
710  else
711  {
712  return mProjectionAcronym;
713  }
714 }
715 
717 {
718  if ( mEllipsoidAcronym.isNull() )
719  {
720  return "";
721  }
722  else
723  {
724  return mEllipsoidAcronym;
725  }
726 }
727 
729 {
730  if ( !mIsValidFlag )
731  return "";
732 
733  QString toProj4;
734  char *proj4src = NULL;
735  OSRExportToProj4( mCRS, &proj4src );
736  toProj4 = proj4src;
737  CPLFree( proj4src );
738 
739  // Stray spaces at the end?
740  return toProj4.trimmed();
741 }
742 
744 {
745  return mGeoFlag;
746 }
747 
749 {
750  return mMapUnits;
751 }
752 
753 
754 // Mutators -----------------------------------
755 
756 
758 {
759  mSrsId = theSrsId;
760 }
762 {
763  mAuthId = authId;
764 }
766 {
767  mSRID = theSrid;
768 }
769 void QgsCoordinateReferenceSystem::setDescription( QString theDescription )
770 {
771  mDescription = theDescription;
772 }
773 void QgsCoordinateReferenceSystem::setProj4String( QString theProj4String )
774 {
775  const char *oldlocale = setlocale( LC_NUMERIC, NULL );
776 
777  setlocale( LC_NUMERIC, "C" );
778  OSRDestroySpatialReference( mCRS );
779  mCRS = OSRNewSpatialReference( NULL );
780  mIsValidFlag = OSRImportFromProj4( mCRS, theProj4String.toLatin1().constData() ) == OGRERR_NONE;
781  setMapUnits();
782 
783 #if defined(QGISDEBUG) && QGISDEBUG>=3
784  debugPrint();
785 #endif
786 
787  setlocale( LC_NUMERIC, oldlocale );
788 }
790 {
791  mGeoFlag = theGeoFlag;
792 }
794 {
795  mAuthId = QString( "EPSG:%1" ).arg( theEpsg );
796 }
797 void QgsCoordinateReferenceSystem::setProjectionAcronym( QString theProjectionAcronym )
798 {
799  mProjectionAcronym = theProjectionAcronym;
800 }
801 void QgsCoordinateReferenceSystem::setEllipsoidAcronym( QString theEllipsoidAcronym )
802 {
803  mEllipsoidAcronym = theEllipsoidAcronym;
804 }
805 
807 {
808  if ( !mIsValidFlag )
809  {
811  return;
812  }
813 
814  char *unitName;
815 
816  // Of interest to us is that this call adds in a unit parameter if
817  // one doesn't already exist.
818  OSRFixup( mCRS );
819 
820  if ( OSRIsProjected( mCRS ) )
821  {
822  double toMeter = OSRGetLinearUnits( mCRS, &unitName );
823  QString unit( unitName );
824 
825  // If the units parameter was created during the Fixup() call
826  // above, the name of the units is likely to be 'unknown'. Try to
827  // do better than that ... (but perhaps ogr should be enhanced to
828  // do this instead?).
829 
830  static const double feetToMeter = 0.3048;
831  static const double smallNum = 1e-3;
832 
833  if ( qAbs( toMeter - feetToMeter ) < smallNum )
834  unit = "Foot";
835 
836  QgsDebugMsg( "Projection has linear units of " + unit );
837 
838  if ( doubleNear( toMeter, 1.0 ) ) //Unit name for meters would be "metre"
840  else if ( unit == "Foot" )
842  else
843  {
844  QgsDebugMsg( "Unsupported map units of " + unit );
846  }
847  }
848  else
849  {
850  OSRGetAngularUnits( mCRS, &unitName );
851  QString unit( unitName );
852  if ( unit == "degree" )
854  else
855  {
856  QgsDebugMsg( "Unsupported map units of " + unit );
858  }
859  QgsDebugMsgLevel( "Projection has angular units of " + unit, 3 );
860  }
861 }
862 
863 /*
864 * check if srs is a geocs or a proj cs (using ogr isGeographic)
865 * then sequentially walk through the database (first users qgis.db srs tbl then
866 * system srs.db tbl), converting each entry into an ogr srs and using isSame
867 * or isSameGeocs (essentially calling the == overloaded operator). We'll try to
868 * be smart about this and first parse out the proj and ellpse strings and only
869 * check for a match in entities that have the same ellps and proj entries so
870 * that it doesnt munch yer cpu so much.
871 */
873 {
874  QgsDebugMsg( "entered." );
875  if ( mEllipsoidAcronym.isNull() || mProjectionAcronym.isNull() || !mIsValidFlag )
876  {
877  QgsDebugMsg( "QgsCoordinateReferenceSystem::findMatchingProj will only work if prj acr ellipsoid acr and proj4string are set"
878  " and the current projection is valid!" );
879  return 0;
880  }
881 
882  sqlite3 *myDatabase;
883  const char *myTail;
884  sqlite3_stmt *myPreparedStatement;
885  int myResult;
886 
887  // Set up the query to retrieve the projection information needed to populate the list
888  QString mySql = QString( "select srs_id,parameters from tbl_srs where projection_acronym=%1 and ellipsoid_acronym=%2" )
890  .arg( quotedValue( mEllipsoidAcronym ) );
891  // Get the full path name to the sqlite3 spatial reference database.
892  QString myDatabaseFileName = QgsApplication::srsDbFilePath();
893 
894  //check the db is available
895  myResult = openDb( myDatabaseFileName, &myDatabase );
896  if ( myResult != SQLITE_OK )
897  {
898  return 0;
899  }
900 
901  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
902 // XXX Need to free memory from the error msg if one is set
903  if ( myResult == SQLITE_OK )
904  {
905 
906  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
907  {
908  QString mySrsId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
909  QString myProj4String = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 1 ) );
910  if ( equals( myProj4String ) )
911  {
912  QgsDebugMsg( "-------> MATCH FOUND in srs.db srsid: " + mySrsId );
913  // close the sqlite3 statement
914  sqlite3_finalize( myPreparedStatement );
915  sqlite3_close( myDatabase );
916  return mySrsId.toLong();
917  }
918  else
919  {
920 // QgsDebugMsg(QString(" Not matched : %1").arg(myProj4String));
921  }
922  }
923  }
924  QgsDebugMsg( "no match found in srs.db, trying user db now!" );
925  // close the sqlite3 statement
926  sqlite3_finalize( myPreparedStatement );
927  sqlite3_close( myDatabase );
928  //
929  // Try the users db now
930  //
931 
932  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
933  //check the db is available
934  myResult = openDb( myDatabaseFileName, &myDatabase );
935  if ( myResult != SQLITE_OK )
936  {
937  return 0;
938  }
939 
940  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
941 // XXX Need to free memory from the error msg if one is set
942  if ( myResult == SQLITE_OK )
943  {
944 
945  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
946  {
947  QString mySrsId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
948  QString myProj4String = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 1 ) );
949  if ( equals( myProj4String ) )
950  {
951  QgsDebugMsg( "-------> MATCH FOUND in user qgis.db srsid: " + mySrsId );
952  // close the sqlite3 statement
953  sqlite3_finalize( myPreparedStatement );
954  sqlite3_close( myDatabase );
955  return mySrsId.toLong();
956  }
957  else
958  {
959 // QgsDebugMsg(QString(" Not matched : %1").arg(myProj4String));
960  }
961  }
962  }
963  QgsDebugMsg( "no match found in user db" );
964 
965  // close the sqlite3 statement
966  sqlite3_finalize( myPreparedStatement );
967  sqlite3_close( myDatabase );
968  return 0;
969 }
970 
972 {
973  if ( !mIsValidFlag || !theSrs.mIsValidFlag )
974  {
975  return false;
976  }
977  char *thisStr;
978  char *otherStr;
979 
980  // OSRIsSame is not relaibel when it comes to comparing +towgs84 parameters
981  // Use string compare on WKT instead.
982  if (( OSRExportToWkt( mCRS, &thisStr ) == OGRERR_NONE ) )
983  {
984  if ( OSRExportToWkt( theSrs.mCRS, &otherStr ) == OGRERR_NONE )
985  {
986  QgsDebugMsgLevel( QString( "Comparing " ) + thisStr, 3 );
987  QgsDebugMsgLevel( QString( " with " ) + otherStr, 3 );
988  if ( !strcmp( thisStr, otherStr ) )
989  {
990  QgsDebugMsgLevel( QString( "MATCHED!" ) + otherStr, 3 );
991  CPLFree( thisStr );
992  CPLFree( otherStr );
993  return true;
994  }
995  CPLFree( otherStr );
996  }
997  CPLFree( thisStr );
998  }
999  return false;
1000 }
1001 
1003 {
1004  return !( *this == theSrs );
1005 }
1006 
1007 bool QgsCoordinateReferenceSystem::equals( QString theProj4String )
1008 {
1010  r.setProj4String( theProj4String );
1011  return *this == r;
1012 }
1013 
1015 {
1016  QString myWkt;
1017  char* Wkt;
1018  if ( OSRExportToWkt( mCRS, &Wkt ) == OGRERR_NONE )
1019  {
1020  myWkt = Wkt;
1021  OGRFree( Wkt );
1022  }
1023 
1024  return myWkt;
1025 }
1026 
1027 bool QgsCoordinateReferenceSystem::readXML( QDomNode & theNode )
1028 {
1029  QgsDebugMsg( "Reading Spatial Ref Sys from xml ------------------------!" );
1030  QDomNode srsNode = theNode.namedItem( "spatialrefsys" );
1031 
1032  if ( ! srsNode.isNull() )
1033  {
1034  bool initialized = false;
1035 
1036  QDomNode myNode = srsNode.namedItem( "authid" );
1037  if ( !myNode.isNull() )
1038  {
1039  initialized = createFromOgcWmsCrs( myNode.toElement().text() );
1040  }
1041 
1042  if ( !initialized )
1043  {
1044  myNode = srsNode.namedItem( "epsg" );
1045  if ( !myNode.isNull() )
1046  initialized = createFromOgcWmsCrs( QString( "EPSG:%1" ).arg( myNode.toElement().text().toLong() ) );
1047  }
1048 
1049  if ( initialized )
1050  {
1051  QgsDebugMsg( "Set from auth id" );
1052  }
1053  else
1054  {
1055  myNode = srsNode.namedItem( "proj4" );
1056 
1057  if ( createFromProj4( myNode.toElement().text() ) )
1058  {
1059  // createFromProj4() sets everything, including map units
1060  QgsDebugMsg( "Setting from proj4 string" );
1061  }
1062  else
1063  {
1064  QgsDebugMsg( "Setting from elements one by one" );
1065 
1066  myNode = srsNode.namedItem( "proj4" );
1067  setProj4String( myNode.toElement().text() );
1068 
1069  myNode = srsNode.namedItem( "srsid" );
1070  setInternalId( myNode.toElement().text().toLong() );
1071 
1072  myNode = srsNode.namedItem( "srid" );
1073  setSrid( myNode.toElement().text().toLong() );
1074 
1075  myNode = srsNode.namedItem( "authid" );
1076  setAuthId( myNode.toElement().text() );
1077 
1078  myNode = srsNode.namedItem( "description" );
1079  setDescription( myNode.toElement().text() );
1080 
1081  myNode = srsNode.namedItem( "projectionacronym" );
1082  setProjectionAcronym( myNode.toElement().text() );
1083 
1084  myNode = srsNode.namedItem( "ellipsoidacronym" );
1085  setEllipsoidAcronym( myNode.toElement().text() );
1086 
1087  myNode = srsNode.namedItem( "geographicflag" );
1088  if ( myNode.toElement().text().compare( "true" ) )
1089  {
1090  setGeographicFlag( true );
1091  }
1092  else
1093  {
1094  setGeographicFlag( false );
1095  }
1096 
1097  //make sure the map units have been set
1098  setMapUnits();
1099 
1100  //@TODO this srs needs to be validated!!!
1101  mIsValidFlag = true;//shamelessly hard coded for now
1102  }
1103  }
1104  }
1105  else
1106  {
1107  // Return default CRS if none was found in the XML.
1109  }
1110  return true;
1111 }
1112 
1113 bool QgsCoordinateReferenceSystem::writeXML( QDomNode & theNode, QDomDocument & theDoc ) const
1114 {
1115 
1116  QDomElement myLayerNode = theNode.toElement();
1117  QDomElement mySrsElement = theDoc.createElement( "spatialrefsys" );
1118 
1119  QDomElement myProj4Element = theDoc.createElement( "proj4" );
1120  myProj4Element.appendChild( theDoc.createTextNode( toProj4() ) );
1121  mySrsElement.appendChild( myProj4Element );
1122 
1123  QDomElement mySrsIdElement = theDoc.createElement( "srsid" );
1124  mySrsIdElement.appendChild( theDoc.createTextNode( QString::number( srsid() ) ) );
1125  mySrsElement.appendChild( mySrsIdElement );
1126 
1127  QDomElement mySridElement = theDoc.createElement( "srid" );
1128  mySridElement.appendChild( theDoc.createTextNode( QString::number( postgisSrid() ) ) );
1129  mySrsElement.appendChild( mySridElement );
1130 
1131  QDomElement myEpsgElement = theDoc.createElement( "authid" );
1132  myEpsgElement.appendChild( theDoc.createTextNode( authid() ) );
1133  mySrsElement.appendChild( myEpsgElement );
1134 
1135  QDomElement myDescriptionElement = theDoc.createElement( "description" );
1136  myDescriptionElement.appendChild( theDoc.createTextNode( description() ) );
1137  mySrsElement.appendChild( myDescriptionElement );
1138 
1139  QDomElement myProjectionAcronymElement = theDoc.createElement( "projectionacronym" );
1140  myProjectionAcronymElement.appendChild( theDoc.createTextNode( projectionAcronym() ) );
1141  mySrsElement.appendChild( myProjectionAcronymElement );
1142 
1143  QDomElement myEllipsoidAcronymElement = theDoc.createElement( "ellipsoidacronym" );
1144  myEllipsoidAcronymElement.appendChild( theDoc.createTextNode( ellipsoidAcronym() ) );
1145  mySrsElement.appendChild( myEllipsoidAcronymElement );
1146 
1147  QDomElement myGeographicFlagElement = theDoc.createElement( "geographicflag" );
1148  QString myGeoFlagText = "false";
1149  if ( geographicFlag() )
1150  {
1151  myGeoFlagText = "true";
1152  }
1153 
1154  myGeographicFlagElement.appendChild( theDoc.createTextNode( myGeoFlagText ) );
1155  mySrsElement.appendChild( myGeographicFlagElement );
1156 
1157  myLayerNode.appendChild( mySrsElement );
1158 
1159  return true;
1160 }
1161 
1162 
1163 
1164 //
1165 // Static helper methods below this point only please!
1166 //
1167 
1168 
1169 // Returns the whole proj4 string for the selected srsid
1170 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
1172 {
1173 
1174  QString myDatabaseFileName;
1175  QString myProjString;
1176  QString mySql = "select parameters from tbl_srs where srs_id = ";
1177  mySql += QString::number( theSrsId );
1178 
1179  QgsDebugMsg( "mySrsId = " + QString::number( theSrsId ) );
1180  QgsDebugMsg( "USER_CRS_START_ID = " + QString::number( USER_CRS_START_ID ) );
1181  QgsDebugMsg( "Selection sql : " + mySql );
1182 
1183  //
1184  // Determine if this is a user projection or a system on
1185  // user projection defs all have srs_id >= 100000
1186  //
1187  if ( theSrsId >= USER_CRS_START_ID )
1188  {
1189  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
1190  QFileInfo myFileInfo;
1191  myFileInfo.setFile( myDatabaseFileName );
1192  if ( !myFileInfo.exists( ) ) //its unlikely that this condition will ever be reached
1193  {
1194  QgsDebugMsg( "users qgis.db not found" );
1195  return NULL;
1196  }
1197  }
1198  else //must be a system projection then
1199  {
1200  myDatabaseFileName = QgsApplication::srsDbFilePath();
1201  }
1202  QgsDebugMsg( "db = " + myDatabaseFileName );
1203 
1204  sqlite3 *db;
1205  int rc;
1206  rc = openDb( myDatabaseFileName, &db );
1207  if ( rc )
1208  {
1209  return QString();
1210  }
1211  // prepare the sql statement
1212  const char *pzTail;
1213  sqlite3_stmt *ppStmt;
1214 
1215  rc = sqlite3_prepare( db, mySql.toUtf8(), mySql.toUtf8().length(), &ppStmt, &pzTail );
1216  // XXX Need to free memory from the error msg if one is set
1217 
1218  if ( rc == SQLITE_OK )
1219  {
1220  if ( sqlite3_step( ppStmt ) == SQLITE_ROW )
1221  {
1222  myProjString = QString::fromUtf8(( char* )sqlite3_column_text( ppStmt, 0 ) );
1223  }
1224  }
1225  // close the statement
1226  sqlite3_finalize( ppStmt );
1227  // close the database
1228  sqlite3_close( db );
1229 
1230  //Q_ASSERT(myProjString.length() > 0);
1231  return myProjString;
1232 }
1233 
1235 {
1236  QgsDebugMsgLevel( "path = " + path, 3 );
1237  int myResult = sqlite3_open( path.toUtf8().data(), db );
1238 
1239  if ( myResult != SQLITE_OK )
1240  {
1241  QgsDebugMsg( "Can't open database: " + QString( sqlite3_errmsg( *db ) ) );
1242  // XXX This will likely never happen since on open, sqlite creates the
1243  // database if it does not exist.
1244  // ... unfortunately it happens on Windows
1246  output->setTitle( "Error" );
1247  output->setMessage( QObject::tr( "Could not open CRS database %1<br>Error(%2): %3" )
1248  .arg( path )
1249  .arg( myResult )
1250  .arg( sqlite3_errmsg( *db ) ), QgsMessageOutput::MessageText );
1251  output->showMessage();
1252  }
1253  return myResult;
1254 }
1255 
1257 {
1259 }
1260 
1262 {
1263  return mCustomSrsValidation;
1264 }
1265 
1267 {
1268  QgsDebugMsg( "***SpatialRefSystem***" );
1269  QgsDebugMsg( "* Valid : " + ( mIsValidFlag ? QString( "true" ) : QString( "false" ) ) );
1270  QgsDebugMsg( "* SrsId : " + QString::number( mSrsId ) );
1271  QgsDebugMsg( "* Proj4 : " + toProj4() );
1272  QgsDebugMsg( "* WKT : " + toWkt() );
1273  QgsDebugMsg( "* Desc. : " + mDescription );
1274  if ( mapUnits() == QGis::Meters )
1275  {
1276  QgsDebugMsg( "* Units : meters" );
1277  }
1278  else if ( mapUnits() == QGis::Feet )
1279  {
1280  QgsDebugMsg( "* Units : feet" );
1281  }
1282  else if ( mapUnits() == QGis::Degrees )
1283  {
1284  QgsDebugMsg( "* Units : degrees" );
1285  }
1286 }
1287 
1289 {
1290  mValidationHint = html;
1291 }
1292 
1294 {
1295  return mValidationHint;
1296 }
1297 
1300 
1302 {
1303  if ( ! mIsValidFlag )
1304  {
1305  QgsDebugMsg( "Can't save an invalid CRS!" );
1306  return false;
1307  }
1308 
1309  QString mySql;
1310  QString myName = QString( " * %1 (%2)" )
1311  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ) )
1312  .arg( toProj4() );
1313 
1314  //if this is the first record we need to ensure that its srs_id is 10000. For
1315  //any rec after that sqlite3 will take care of the autonumering
1316  //this was done to support sqlite 3.0 as it does not yet support
1317  //the autoinc related system tables.
1318  if ( getRecordCount() == 0 )
1319  {
1320  mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1321  + QString::number( USER_CRS_START_ID )
1322  + "," + quotedValue( myName )
1323  + "," + quotedValue( projectionAcronym() )
1324  + "," + quotedValue( ellipsoidAcronym() )
1325  + "," + quotedValue( toProj4() )
1326  + ",0)"; // <-- is_geo shamelessly hard coded for now
1327  }
1328  else
1329  {
1330  mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1331  + quotedValue( myName )
1332  + "," + quotedValue( projectionAcronym() )
1333  + "," + quotedValue( ellipsoidAcronym() )
1334  + "," + quotedValue( toProj4() )
1335  + ",0)"; // <-- is_geo shamelessly hard coded for now
1336  }
1337  sqlite3 *myDatabase;
1338  const char *myTail;
1339  sqlite3_stmt *myPreparedStatement;
1340  int myResult;
1341  //check the db is available
1342  myResult = sqlite3_open( QgsApplication::qgisUserDbFilePath().toUtf8().data(), &myDatabase );
1343  if ( myResult != SQLITE_OK )
1344  {
1345  QgsDebugMsg( QString( "Can't open or create database %1: %2" )
1347  .arg( sqlite3_errmsg( myDatabase ) ) );
1348  return false;
1349  }
1350  QgsDebugMsg( QString( "Update or insert sql \n%1" ).arg( mySql ) );
1351  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1352  sqlite3_step( myPreparedStatement );
1353  // XXX Need to free memory from the error msg if one is set
1354  return myResult == SQLITE_OK;
1355 }
1356 
1358 {
1359  sqlite3 *myDatabase;
1360  const char *myTail;
1361  sqlite3_stmt *myPreparedStatement;
1362  int myResult;
1363  long myRecordCount = 0;
1364  //check the db is available
1365  myResult = sqlite3_open( QgsApplication::qgisUserDbFilePath().toUtf8().data(), &myDatabase );
1366  if ( myResult != SQLITE_OK )
1367  {
1368  QgsDebugMsg( QString( "Can't open database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
1369  return 0;
1370  }
1371  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
1372  QString mySql = "select count(*) from tbl_srs";
1373  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1374  // XXX Need to free memory from the error msg if one is set
1375  if ( myResult == SQLITE_OK )
1376  {
1377  if ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1378  {
1379  QString myRecordCountString = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
1380  myRecordCount = myRecordCountString.toLong();
1381  }
1382  }
1383  // close the sqlite3 statement
1384  sqlite3_finalize( myPreparedStatement );
1385  sqlite3_close( myDatabase );
1386  return myRecordCount;
1387 }
1388 
1390 {
1391  value.replace( "'", "''" );
1392  return value.prepend( "'" ).append( "'" );
1393 }