QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties 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 : [email protected]
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 <QTemporaryFile>
24 #include <QDomNode>
25 #include <QDomElement>
26 #include <QFileInfo>
27 #include <QRegExp>
28 #include <QTextStream>
29 #include <QFile>
30 #include <QSettings>
31 
32 #include "qgsapplication.h"
33 #include "qgscrscache.h"
34 #include "qgslogger.h"
35 #include "qgsmessagelog.h"
36 #include "qgis.h" //const vals declared here
37 
38 #include <sqlite3.h>
39 #include <proj_api.h>
40 
41 //gdal and ogr includes (needed for == operator)
42 #include <ogr_srs_api.h>
43 #include <cpl_error.h>
44 #include <cpl_conv.h>
45 
47 
48 //--------------------------
49 
51  : mSrsId( 0 )
52  , mGeoFlag( false )
53  , mMapUnits( QGis::UnknownUnit )
54  , mSRID( 0 )
55  , mIsValidFlag( 0 )
56  , mValidationHint( "" )
57  , mAxisInverted( false )
58 {
59  mCRS = OSRNewSpatialReference( NULL );
60 }
61 
63  : mSrsId( 0 )
64  , mGeoFlag( false )
65  , mMapUnits( QGis::UnknownUnit )
66  , mSRID( 0 )
67  , mIsValidFlag( 0 )
68  , mValidationHint( "" )
69  , mAxisInverted( false )
70 {
71  mCRS = OSRNewSpatialReference( NULL );
72  createFromString( theDefinition );
73 }
74 
75 
77  : mSrsId( 0 )
78  , mGeoFlag( false )
79  , mMapUnits( QGis::UnknownUnit )
80  , mSRID( 0 )
81  , mIsValidFlag( 0 )
82  , mValidationHint( "" )
83  , mAxisInverted( false )
84 {
85  mCRS = OSRNewSpatialReference( NULL );
86  createFromId( theId, theType );
87 }
88 
90 {
91  OSRDestroySpatialReference( mCRS );
92 }
93 
94 bool QgsCoordinateReferenceSystem::createFromId( const long theId, CrsType theType )
95 {
96  bool result = false;
97  switch ( theType )
98  {
99  case InternalCrsId:
100  result = createFromSrsId( theId );
101  break;
102  case PostgisCrsId:
103  result = createFromSrid( theId );
104  break;
105  case EpsgCrsId:
106  result = createFromOgcWmsCrs( QString( "EPSG:%1" ).arg( theId ) );
107  break;
108  default:
109  //THIS IS BAD...THIS PART OF CODE SHOULD NEVER BE REACHED...
110  QgsDebugMsg( "Unexpected case reached!" );
111  };
112  return result;
113 }
114 
115 bool QgsCoordinateReferenceSystem::createFromString( const QString theDefinition )
116 {
117  bool result = false;
118  QRegExp reCrsId( "^(epsg|postgis|internal)\\:(\\d+)$", Qt::CaseInsensitive );
119  if ( reCrsId.indexIn( theDefinition ) == 0 )
120  {
121  QString authName = reCrsId.cap( 1 ).toLower();
122  CrsType type = InternalCrsId;
123  if ( authName == "epsg" )
124  type = EpsgCrsId;
125  if ( authName == "postgis" )
126  type = PostgisCrsId;
127  long id = reCrsId.cap( 2 ).toLong();
128  result = createFromId( id, type );
129  }
130  else
131  {
132  QRegExp reCrsStr( "^(?:(wkt|proj4)\\:)?(.+)$", Qt::CaseInsensitive );
133  if ( reCrsStr.indexIn( theDefinition ) == 0 )
134  {
135  if ( reCrsStr.cap( 1 ).toLower() == "proj4" )
136  {
137  result = createFromProj4( reCrsStr.cap( 2 ) );
138  //TODO: createFromProj4 used to save to the user database any new CRS
139  // this behavior was changed in order to separate creation and saving.
140  // Not sure if it necessary to save it here, should be checked by someone
141  // familiar with the code (should also give a more descriptive name to the generated CRS)
142  if ( srsid() == 0 )
143  {
144  QString myName = QString( " * %1 (%2)" )
145  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ) )
146  .arg( toProj4() );
147  saveAsUserCRS( myName );
148  }
149  }
150  else
151  {
152  result = createFromWkt( reCrsStr.cap( 2 ) );
153  }
154  }
155  }
156  return result;
157 }
158 
159 bool QgsCoordinateReferenceSystem::createFromUserInput( const QString theDefinition )
160 {
161  QString theWkt;
162  char *wkt = NULL;
163  OGRSpatialReferenceH crs = OSRNewSpatialReference( NULL );
164 
165  // make sure towgs84 parameter is loaded if using an ESRI definition and gdal >= 1.9
166 #if GDAL_VERSION_NUM >= 1900
167  if ( theDefinition.startsWith( "ESRI::" ) )
168  {
169  setupESRIWktFix();
170  }
171 #endif
172 
173  if ( OSRSetFromUserInput( crs, theDefinition.toLocal8Bit().constData() ) == OGRERR_NONE )
174  {
175  if ( OSRExportToWkt( crs, &wkt ) == OGRERR_NONE )
176  {
177  theWkt = wkt;
178  OGRFree( wkt );
179  }
180  OSRDestroySpatialReference( crs );
181  }
182  //QgsDebugMsg( "theDefinition: " + theDefinition + " theWkt = " + theWkt );
183  return createFromWkt( theWkt );
184 }
185 
187 {
188  // make sure towgs84 parameter is loaded if gdal >= 1.9
189  // this requires setting GDAL_FIX_ESRI_WKT=GEOGCS (see qgis bug #5598 and gdal bug #4673)
190 #if GDAL_VERSION_NUM >= 1900
191  const char* configOld = CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" );
192  const char* configNew = "GEOGCS";
193  // only set if it was not set, to let user change the value if needed
194  if ( strcmp( configOld, "" ) == 0 )
195  {
196  CPLSetConfigOption( "GDAL_FIX_ESRI_WKT", configNew );
197  if ( strcmp( configNew, CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) != 0 )
198  QgsLogger::warning( QString( "GDAL_FIX_ESRI_WKT could not be set to %1 : %2"
199  ).arg( configNew ).arg( CPLGetConfigOption( "GDAL_FIX_ESRI_WKT", "" ) ) ) ;
200  QgsDebugMsg( QString( "set GDAL_FIX_ESRI_WKT : %1" ).arg( configNew ) );
201  }
202  else
203  {
204  QgsDebugMsg( QString( "GDAL_FIX_ESRI_WKT was already set : %1" ).arg( configNew ) );
205  }
206 #endif
207 }
208 
210 {
211  QRegExp re( "urn:ogc:def:crs:([^:]+).+([^:]+)", Qt::CaseInsensitive );
212  if ( re.exactMatch( theCrs ) )
213  {
214  theCrs = re.cap( 1 ) + ":" + re.cap( 2 );
215  }
216  else
217  {
218  re.setPattern( "(user|custom|qgis):(\\d+)" );
219  if ( re.exactMatch( theCrs ) && createFromSrsId( re.cap( 2 ).toInt() ) )
220  {
221  return true;
222  }
223  }
224 
225  if ( loadFromDb( QgsApplication::srsDbFilePath(), "lower(auth_name||':'||auth_id)", theCrs.toLower() ) )
226  return true;
227 
228  // NAD27
229  if ( theCrs.compare( "CRS:27", Qt::CaseInsensitive ) == 0 ||
230  theCrs.compare( "OGC:CRS27", Qt::CaseInsensitive ) == 0 )
231  {
232  // TODO: verify same axis orientation
233  return createFromOgcWmsCrs( "EPSG:4267" );
234  }
235 
236  // NAD83
237  if ( theCrs.compare( "CRS:83", Qt::CaseInsensitive ) == 0 ||
238  theCrs.compare( "OGC:CRS83", Qt::CaseInsensitive ) == 0 )
239  {
240  // TODO: verify same axis orientation
241  return createFromOgcWmsCrs( "EPSG:4269" );
242  }
243 
244  // WGS84
245  if ( theCrs.compare( "CRS:84", Qt::CaseInsensitive ) == 0 ||
246  theCrs.compare( "OGC:CRS84", Qt::CaseInsensitive ) == 0 )
247  {
248  createFromOgcWmsCrs( "EPSG:4326" );
249  mAxisInverted = 0;
250  return mIsValidFlag;
251  }
252 
253  return false;
254 }
255 
257 {
258  mCRS = OSRNewSpatialReference( NULL );
259  *this = srs;
260 }
261 
262 // Assignment operator
264 {
265  if ( &srs != this )
266  {
267  mSrsId = srs.mSrsId;
271  mGeoFlag = srs.mGeoFlag;
273  mMapUnits = srs.mMapUnits;
274  mSRID = srs.mSRID;
275  mAuthId = srs.mAuthId;
278  mWkt = srs.mWkt;
279  if ( mIsValidFlag )
280  {
281  OSRDestroySpatialReference( mCRS );
282  mCRS = OSRClone( srs.mCRS );
283  }
284  }
285  return *this;
286 }
287 
288 // Misc helper functions -----------------------
289 
290 
292 {
293  if ( mIsValidFlag )
294  return;
295 
296  // try to validate using custom validation routines
297  if ( mCustomSrsValidation )
298  mCustomSrsValidation( *this );
299 
300  if ( !mIsValidFlag )
301  {
303  }
304 }
305 
307 {
308  return loadFromDb( QgsApplication::srsDbFilePath(), "srid", QString::number( id ) );
309 }
310 
312 {
315  "srs_id", QString::number( id ) );
316 }
317 
318 bool QgsCoordinateReferenceSystem::loadFromDb( QString db, QString expression, QString value )
319 {
320  QgsDebugMsgLevel( "load CRS from " + db + " where " + expression + " is " + value, 3 );
321  mIsValidFlag = false;
322  mWkt.clear();
323 
324  QFileInfo myInfo( db );
325  if ( !myInfo.exists() )
326  {
327  QgsDebugMsg( "failed : " + db + " does not exist!" );
328  return mIsValidFlag;
329  }
330 
331  sqlite3 *myDatabase;
332  const char *myTail;
333  sqlite3_stmt *myPreparedStatement;
334  int myResult;
335  //check the db is available
336  myResult = openDb( db, &myDatabase );
337  if ( myResult != SQLITE_OK )
338  {
339  QgsDebugMsg( "failed : " + db + " could not be opened!" );
340  return mIsValidFlag;
341  }
342 
343  /*
344  srs_id INTEGER PRIMARY KEY,
345  description text NOT NULL,
346  projection_acronym text NOT NULL,
347  ellipsoid_acronym NOT NULL,
348  parameters text NOT NULL,
349  srid integer NOT NULL,
350  auth_name varchar NOT NULL,
351  auth_id integer NOT NULL,
352  is_geo integer NOT NULL);
353  */
354 
355  QString mySql = "select srs_id,description,projection_acronym,"
356  "ellipsoid_acronym,parameters,srid,auth_name||':'||auth_id,is_geo "
357  "from tbl_srs where " + expression + "=" + quotedValue( value ) + " order by deprecated";
358  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(),
359  mySql.toUtf8().length(),
360  &myPreparedStatement, &myTail );
361  // XXX Need to free memory from the error msg if one is set
362  if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
363  {
364  mSrsId = QString::fromUtf8(( char * )sqlite3_column_text(
365  myPreparedStatement, 0 ) ).toLong();
366  mDescription = QString::fromUtf8(( char * )sqlite3_column_text(
367  myPreparedStatement, 1 ) );
368  mProjectionAcronym = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 2 ) );
369  mEllipsoidAcronym = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 3 ) );
370  QString toProj4 = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 4 ) );
371  mSRID = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 5 ) ).toLong();
372  mAuthId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 6 ) );
373  mGeoFlag = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 7 ) ).toInt() != 0;
374  mAxisInverted = -1;
375 
376  if ( mSrsId >= USER_CRS_START_ID && mAuthId.isEmpty() )
377  {
378  mAuthId = QString( "USER:%1" ).arg( mSrsId );
379  }
380  else if ( mAuthId.startsWith( "EPSG:", Qt::CaseInsensitive ) )
381  {
382  OSRDestroySpatialReference( mCRS );
383  mCRS = OSRNewSpatialReference( NULL );
384  mIsValidFlag = OSRSetFromUserInput( mCRS, mAuthId.toLower().toAscii() ) == OGRERR_NONE;
385  setMapUnits();
386  }
387 
388  if ( !mIsValidFlag )
389  {
390  setProj4String( toProj4 );
391  }
392  }
393  else
394  {
395  QgsDebugMsg( "failed : " + mySql );
396  }
397  sqlite3_finalize( myPreparedStatement );
398  sqlite3_close( myDatabase );
399  return mIsValidFlag;
400 }
401 
403 {
404  if ( mAxisInverted == -1 )
405  {
406  OGRAxisOrientation orientation;
407  const char *axis0 = OSRGetAxis( mCRS, mGeoFlag ? "GEOGCS" : "PROJCS", 0, &orientation );
409  ? ( orientation == OAO_East || orientation == OAO_West || orientation == OAO_Other )
410  : ( orientation == OAO_North || orientation == OAO_South );
411  QgsDebugMsg( QString( "srid:%1 axis0:%2 orientation:%3 inverted:%4" ).arg( mSRID ).arg( axis0 ).arg( OSRAxisEnumToName( orientation ) ).arg( mAxisInverted ) );
412  Q_UNUSED( axis0 );
413  }
414 
415  return mAxisInverted != 0;
416 }
417 
419 {
420  mIsValidFlag = false;
421  mWkt.clear();
422 
423  if ( theWkt.isEmpty() )
424  {
425  QgsDebugMsg( "theWkt is uninitialised, operation failed" );
426  return mIsValidFlag;
427  }
428  QgsDebugMsg( "wkt: " + theWkt );
429  QByteArray ba = theWkt.toLatin1();
430  const char *pWkt = ba.data();
431 
432  OGRErr myInputResult = OSRImportFromWkt( mCRS, ( char ** ) & pWkt );
433 
434  if ( myInputResult != OGRERR_NONE )
435  {
436  QgsDebugMsg( "\n---------------------------------------------------------------" );
437  QgsDebugMsg( "This CRS could *** NOT *** be set from the supplied Wkt " );
438  QgsDebugMsg( "INPUT: " + theWkt );
439  QgsDebugMsg( QString( "UNUSED WKT: %1" ).arg( pWkt ) );
440  QgsDebugMsg( "---------------------------------------------------------------\n" );
441  return mIsValidFlag;
442  }
443 
444  if ( OSRAutoIdentifyEPSG( mCRS ) == OGRERR_NONE )
445  {
446  QString authid = QString( "%1:%2" )
447  .arg( OSRGetAuthorityName( mCRS, NULL ) )
448  .arg( OSRGetAuthorityCode( mCRS, NULL ) );
449  QgsDebugMsg( "authid recognized as " + authid );
450  return createFromOgcWmsCrs( authid );
451  }
452 
453  // always morph from esri as it doesn't hurt anything
454  // FW: Hey, that's not right! It can screw stuff up! Disable
455  //myOgrSpatialRef.morphFromESRI();
456 
457  // create the proj4 structs needed for transforming
458  char *proj4src = NULL;
459  OSRExportToProj4( mCRS, &proj4src );
460 
461  //now that we have the proj4string, delegate to createFromProj4 so
462  // that we can try to fill in the remaining class members...
463  //create from Proj will set the isValidFlag
464  if ( !createFromProj4( proj4src ) )
465  {
466  CPLFree( proj4src );
467 
468  // try fixed up version
469  OSRFixup( mCRS );
470 
471  OSRExportToProj4( mCRS, &proj4src );
472 
473  createFromProj4( proj4src );
474  }
475  //TODO: createFromProj4 used to save to the user database any new CRS
476  // this behavior was changed in order to separate creation and saving.
477  // Not sure if it necessary to save it here, should be checked by someone
478  // familiar with the code (should also give a more descriptive name to the generated CRS)
479  if ( mSrsId == 0 )
480  {
481  QString myName = QString( " * %1 (%2)" )
482  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ) )
483  .arg( toProj4() );
484  saveAsUserCRS( myName );
485  }
486 
487  CPLFree( proj4src );
488 
489  return mIsValidFlag;
490  //setMapunits will be called by createfromproj above
491 }
492 
494 {
495  return mIsValidFlag;
496 }
497 
498 bool QgsCoordinateReferenceSystem::createFromProj4( const QString theProj4String )
499 {
500  //
501  // Examples:
502  // +proj=tmerc +lat_0=0 +lon_0=-62 +k=0.999500 +x_0=400000 +y_0=0
503  // +ellps=clrk80 +towgs84=-255,-15,71,0,0,0,0 +units=m +no_defs
504  //
505  // +proj=lcc +lat_1=46.8 +lat_0=46.8 +lon_0=2.337229166666664 +k_0=0.99987742
506  // +x_0=600000 +y_0=2200000 +a=6378249.2 +b=6356515.000000472 +units=m +no_defs
507  //
508  QString myProj4String = theProj4String.trimmed();
509  QgsDebugMsg( "proj4: " + myProj4String );
510  mIsValidFlag = false;
511  mWkt.clear();
512 
513  QRegExp myProjRegExp( "\\+proj=(\\S+)" );
514  int myStart = myProjRegExp.indexIn( myProj4String );
515  if ( myStart == -1 )
516  {
517  QgsDebugMsg( "proj string supplied has no +proj argument" );
518  return mIsValidFlag;
519  }
520 
521  mProjectionAcronym = myProjRegExp.cap( 1 );
522 
523  QRegExp myEllipseRegExp( "\\+ellps=(\\S+)" );
524  myStart = myEllipseRegExp.indexIn( myProj4String );
525  if ( myStart == -1 )
526  {
527  QgsDebugMsg( "proj string supplied has no +ellps argument" );
528  mEllipsoidAcronym = "";
529  }
530  else
531  {
532  mEllipsoidAcronym = myEllipseRegExp.cap( 1 );
533  }
534 
535  QRegExp myAxisRegExp( "\\+a=(\\S+)" );
536  myStart = myAxisRegExp.indexIn( myProj4String );
537  if ( myStart == -1 )
538  {
539  QgsDebugMsg( "proj string supplied has no +a argument" );
540  }
541 
542  /*
543  * We try to match the proj string to and srsid using the following logic:
544  *
545  * - perform a whole text search on srs name (if not null). The srs name will
546  * have been set if this method has been delegated to from createFromWkt.
547  * Normally we wouldnt expect this to work, but its worth trying first
548  * as its quicker than methods below..
549  */
550  long mySrsId = 0;
552 
553  /*
554  * - if the above does not match perform a whole text search on proj4 string (if not null)
555  */
556  // QgsDebugMsg( "wholetext match on name failed, trying proj4string match" );
557  myRecord = getRecord( "select * from tbl_srs where parameters=" + quotedValue( myProj4String ) + " order by deprecated" );
558  if ( myRecord.empty() )
559  {
560  // Ticket #722 - aaronr
561  // Check if we can swap the lat_1 and lat_2 params (if they exist) to see if we match...
562  // First we check for lat_1 and lat_2
563  QRegExp myLat1RegExp( "\\+lat_1=\\S+" );
564  QRegExp myLat2RegExp( "\\+lat_2=\\S+" );
565  int myStart1 = 0;
566  int myLength1 = 0;
567  int myStart2 = 0;
568  int myLength2 = 0;
569  QString lat1Str = "";
570  QString lat2Str = "";
571  myStart1 = myLat1RegExp.indexIn( myProj4String, myStart1 );
572  myStart2 = myLat2RegExp.indexIn( myProj4String, myStart2 );
573  if ( myStart1 != -1 && myStart2 != -1 )
574  {
575  myLength1 = myLat1RegExp.matchedLength();
576  myLength2 = myLat2RegExp.matchedLength();
577  lat1Str = myProj4String.mid( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN );
578  lat2Str = myProj4String.mid( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN );
579  }
580  // If we found the lat_1 and lat_2 we need to swap and check to see if we can find it...
581  if ( lat1Str != "" && lat2Str != "" )
582  {
583  // Make our new string to check...
584  QString theProj4StringModified = myProj4String;
585  // First just swap in the lat_2 value for lat_1 value
586  theProj4StringModified.replace( myStart1 + LAT_PREFIX_LEN, myLength1 - LAT_PREFIX_LEN, lat2Str );
587  // Now we have to find the lat_2 location again since it has potentially moved...
588  myStart2 = 0;
589  myStart2 = myLat2RegExp.indexIn( theProj4String, myStart2 );
590  theProj4StringModified.replace( myStart2 + LAT_PREFIX_LEN, myLength2 - LAT_PREFIX_LEN, lat1Str );
591  QgsDebugMsg( "trying proj4string match with swapped lat_1,lat_2" );
592  myRecord = getRecord( "select * from tbl_srs where parameters=" + quotedValue( theProj4StringModified.trimmed() ) + " order by deprecated" );
593  }
594  }
595 
596  if ( myRecord.empty() )
597  {
598  // match all parameters individually:
599  // - order of parameters doesn't matter
600  // - found definition may have more parameters (like +towgs84 in GDAL)
601  // - retry without datum, if no match is found (looks like +datum<>WGS84 was dropped in GDAL)
602 
603  QString sql = "SELECT * FROM tbl_srs WHERE ";
604  QString delim = "";
605  QString datum;
606 
607  // split on spaces followed by a plus sign (+) to deal
608  // also with parameters containing spaces (e.g. +nadgrids)
609  // make sure result is trimmed (#5598)
610  foreach ( QString param, myProj4String.split( QRegExp( "\\s+(?=\\+)" ), QString::SkipEmptyParts ) )
611  {
612  QString arg = QString( "' '||parameters||' ' LIKE %1" ).arg( quotedValue( QString( "% %1 %" ).arg( param.trimmed() ) ) );
613  if ( param.startsWith( "+datum=" ) )
614  {
615  datum = arg;
616  }
617  else
618  {
619  sql += delim + arg;
620  delim = " AND ";
621  }
622  }
623 
624  if ( !datum.isEmpty() )
625  {
626  myRecord = getRecord( sql + delim + datum + " order by deprecated" );
627  }
628 
629  if ( myRecord.empty() )
630  {
631  // datum might have disappeared in definition - retry without it
632  myRecord = getRecord( sql + " order by deprecated" );
633  }
634  }
635 
636  if ( !myRecord.empty() )
637  {
638  mySrsId = myRecord["srs_id"].toLong();
639  QgsDebugMsg( "proj4string param match search for srsid returned srsid: " + QString::number( mySrsId ) );
640  if ( mySrsId > 0 )
641  {
642  createFromSrsId( mySrsId );
643  }
644  }
645  else
646  {
647  // Last ditch attempt to piece together what we know of the projection to find a match...
648  QgsDebugMsg( "globbing search for srsid from this proj string" );
649  setProj4String( myProj4String );
650  mySrsId = findMatchingProj();
651  QgsDebugMsg( "globbing search for srsid returned srsid: " + QString::number( mySrsId ) );
652  if ( mySrsId > 0 )
653  {
654  createFromSrsId( mySrsId );
655  }
656  else
657  {
658  mIsValidFlag = false;
659  }
660  }
661 
662  // if we failed to look up the projection in database, don't worry. we can still use it :)
663  if ( !mIsValidFlag )
664  {
665  QgsDebugMsg( "Projection is not found in databases." );
666  //setProj4String will set mIsValidFlag to true if there is no issue
667  setProj4String( myProj4String );
668  }
669 
670  return mIsValidFlag;
671 }
672 
673 //private method meant for internal use by this class only
675 {
676  QString myDatabaseFileName;
678  QString myFieldName;
679  QString myFieldValue;
680  sqlite3 *myDatabase;
681  const char *myTail;
682  sqlite3_stmt *myPreparedStatement;
683  int myResult;
684 
685  QgsDebugMsg( "running query: " + theSql );
686  // Get the full path name to the sqlite3 spatial reference database.
687  myDatabaseFileName = QgsApplication::srsDbFilePath();
688  QFileInfo myInfo( myDatabaseFileName );
689  if ( !myInfo.exists() )
690  {
691  QgsDebugMsg( "failed : " + myDatabaseFileName + " does not exist!" );
692  return myMap;
693  }
694 
695  //check the db is available
696  myResult = openDb( myDatabaseFileName, &myDatabase );
697  if ( myResult != SQLITE_OK )
698  {
699  return myMap;
700  }
701 
702  myResult = sqlite3_prepare( myDatabase, theSql.toUtf8(), theSql.toUtf8().length(), &myPreparedStatement, &myTail );
703  // XXX Need to free memory from the error msg if one is set
704  if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
705  {
706  QgsDebugMsg( "trying system srs.db" );
707  int myColumnCount = sqlite3_column_count( myPreparedStatement );
708  //loop through each column in the record adding its expression name and value to the map
709  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
710  {
711  myFieldName = QString::fromUtf8(( char * )sqlite3_column_name( myPreparedStatement, myColNo ) );
712  myFieldValue = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, myColNo ) );
713  myMap[myFieldName] = myFieldValue;
714  }
715  if ( sqlite3_step( myPreparedStatement ) != SQLITE_DONE )
716  {
717  QgsDebugMsg( "Multiple records found in srs.db" );
718  myMap.clear();
719  }
720  }
721  else
722  {
723  QgsDebugMsg( "failed : " + theSql );
724  }
725 
726  if ( myMap.empty() )
727  {
728  QgsDebugMsg( "trying user qgis.db" );
729  sqlite3_finalize( myPreparedStatement );
730  sqlite3_close( myDatabase );
731 
732  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
733  QFileInfo myFileInfo;
734  myFileInfo.setFile( myDatabaseFileName );
735  if ( !myFileInfo.exists( ) )
736  {
737  QgsDebugMsg( "user qgis.db not found" );
738  return myMap;
739  }
740 
741  //check the db is available
742  myResult = openDb( myDatabaseFileName, &myDatabase );
743  if ( myResult != SQLITE_OK )
744  {
745  return myMap;
746  }
747 
748  myResult = sqlite3_prepare( myDatabase, theSql.toUtf8(), theSql.toUtf8().length(), &myPreparedStatement, &myTail );
749  // XXX Need to free memory from the error msg if one is set
750  if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
751  {
752  int myColumnCount = sqlite3_column_count( myPreparedStatement );
753  //loop through each column in the record adding its field name and value to the map
754  for ( int myColNo = 0; myColNo < myColumnCount; myColNo++ )
755  {
756  myFieldName = QString::fromUtf8(( char * )sqlite3_column_name( myPreparedStatement, myColNo ) );
757  myFieldValue = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, myColNo ) );
758  myMap[myFieldName] = myFieldValue;
759  }
760 
761  if ( sqlite3_step( myPreparedStatement ) != SQLITE_DONE )
762  {
763  QgsDebugMsg( "Multiple records found in srs.db" );
764  myMap.clear();
765  }
766  }
767  else
768  {
769  QgsDebugMsg( "failed : " + theSql );
770  }
771  }
772  sqlite3_finalize( myPreparedStatement );
773  sqlite3_close( myDatabase );
774 
775 #ifdef QGISDEBUG
776  QgsDebugMsg( "retrieved: " + theSql );
777  RecordMap::Iterator it;
778  for ( it = myMap.begin(); it != myMap.end(); ++it )
779  {
780  QgsDebugMsgLevel( it.key() + " => " + it.value(), 2 );
781  }
782 #endif
783 
784  return myMap;
785 }
786 
787 // Accessors -----------------------------------
788 
790 {
791  return mSrsId;
792 }
793 
795 {
796 
797  return mSRID;
798 
799 }
800 
802 {
803  return mAuthId;
804 }
805 
807 {
808  if ( mDescription.isNull() )
809  {
810  return "";
811  }
812  else
813  {
814  return mDescription;
815  }
816 }
817 
819 {
820  if ( mProjectionAcronym.isNull() )
821  {
822  return "";
823  }
824  else
825  {
826  return mProjectionAcronym;
827  }
828 }
829 
831 {
832  if ( mEllipsoidAcronym.isNull() )
833  {
834  return "";
835  }
836  else
837  {
838  return mEllipsoidAcronym;
839  }
840 }
841 
843 {
844  if ( !mIsValidFlag )
845  return "";
846 
847  QString toProj4;
848  char *proj4src = NULL;
849  OSRExportToProj4( mCRS, &proj4src );
850  toProj4 = proj4src;
851  CPLFree( proj4src );
852 
853  // Stray spaces at the end?
854  return toProj4.trimmed();
855 }
856 
858 {
859  return mGeoFlag;
860 }
861 
863 {
864  return mMapUnits;
865 }
866 
867 
868 // Mutators -----------------------------------
869 
870 
872 {
873  mSrsId = theSrsId;
874 }
876 {
877  mAuthId = authId;
878 }
880 {
881  mSRID = theSrid;
882 }
883 void QgsCoordinateReferenceSystem::setDescription( QString theDescription )
884 {
885  mDescription = theDescription;
886 }
887 void QgsCoordinateReferenceSystem::setProj4String( QString theProj4String )
888 {
889  char *oldlocale = setlocale( LC_NUMERIC, NULL );
890  /* the next setlocale() invalides the return of previous setlocale() */
891  if ( oldlocale )
892  oldlocale = strdup( oldlocale );
893 
894  setlocale( LC_NUMERIC, "C" );
895  OSRDestroySpatialReference( mCRS );
896  mCRS = OSRNewSpatialReference( NULL );
897  mIsValidFlag =
898  OSRImportFromProj4( mCRS, theProj4String.trimmed().toLatin1().constData() )
899  == OGRERR_NONE;
900  mWkt.clear();
901  setMapUnits();
902 
903 #if defined(QGISDEBUG) && QGISDEBUG>=3
904  debugPrint();
905 #endif
906 
907  setlocale( LC_NUMERIC, oldlocale );
908  free( oldlocale );
909 }
911 {
912  mGeoFlag = theGeoFlag;
913 }
915 {
916  mAuthId = QString( "EPSG:%1" ).arg( theEpsg );
917 }
918 void QgsCoordinateReferenceSystem::setProjectionAcronym( QString theProjectionAcronym )
919 {
920  mProjectionAcronym = theProjectionAcronym;
921 }
922 void QgsCoordinateReferenceSystem::setEllipsoidAcronym( QString theEllipsoidAcronym )
923 {
924  mEllipsoidAcronym = theEllipsoidAcronym;
925 }
926 
928 {
929  if ( !mIsValidFlag )
930  {
932  return;
933  }
934 
935  char *unitName;
936 
937  // Of interest to us is that this call adds in a unit parameter if
938  // one doesn't already exist.
939  OSRFixup( mCRS );
940 
941  if ( OSRIsProjected( mCRS ) )
942  {
943  double toMeter = OSRGetLinearUnits( mCRS, &unitName );
944  QString unit( unitName );
945 
946  // If the units parameter was created during the Fixup() call
947  // above, the name of the units is likely to be 'unknown'. Try to
948  // do better than that ... (but perhaps ogr should be enhanced to
949  // do this instead?).
950 
951  static const double feetToMeter = 0.3048;
952  static const double smallNum = 1e-3;
953 
954  if ( qAbs( toMeter - feetToMeter ) < smallNum )
955  unit = "Foot";
956 
957  QgsDebugMsg( "Projection has linear units of " + unit );
958 
959  if ( qgsDoubleNear( toMeter, 1.0 ) ) //Unit name for meters would be "metre"
961  else if ( unit == "Foot" )
963  else
964  {
965  QgsDebugMsg( "Unsupported map units of " + unit );
967  }
968  }
969  else
970  {
971  OSRGetAngularUnits( mCRS, &unitName );
972  QString unit( unitName );
973  if ( unit == "degree" )
975  else
976  {
977  QgsDebugMsg( "Unsupported map units of " + unit );
979  }
980  QgsDebugMsgLevel( "Projection has angular units of " + unit, 3 );
981  }
982 }
983 
984 /*
985 * check if srs is a geocs or a proj cs (using ogr isGeographic)
986 * then sequentially walk through the database (first users qgis.db srs tbl then
987 * system srs.db tbl), converting each entry into an ogr srs and using isSame
988 * or isSameGeocs (essentially calling the == overloaded operator). We'll try to
989 * be smart about this and first parse out the proj and ellpse strings and only
990 * check for a match in entities that have the same ellps and proj entries so
991 * that it doesnt munch yer cpu so much.
992 */
994 {
995  QgsDebugMsg( "entered." );
996  if ( mEllipsoidAcronym.isNull() || mProjectionAcronym.isNull()
997  || !mIsValidFlag )
998  {
999  QgsDebugMsg( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1000  "work if prj acr ellipsoid acr and proj4string are set"
1001  " and the current projection is valid!" );
1002  return 0;
1003  }
1004 
1005  sqlite3 *myDatabase;
1006  const char *myTail;
1007  sqlite3_stmt *myPreparedStatement;
1008  int myResult;
1009 
1010  // Set up the query to retrieve the projection information
1011  // needed to populate the list
1012  QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1013  "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1014  .arg( quotedValue( mProjectionAcronym ) )
1015  .arg( quotedValue( mEllipsoidAcronym ) );
1016  // Get the full path name to the sqlite3 spatial reference database.
1017  QString myDatabaseFileName = QgsApplication::srsDbFilePath();
1018 
1019  //check the db is available
1020  myResult = openDb( myDatabaseFileName, &myDatabase );
1021  if ( myResult != SQLITE_OK )
1022  {
1023  return 0;
1024  }
1025 
1026  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1027 // XXX Need to free memory from the error msg if one is set
1028  if ( myResult == SQLITE_OK )
1029  {
1030 
1031  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1032  {
1033  QString mySrsId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
1034  QString myProj4String = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 1 ) );
1035  if ( toProj4() == myProj4String.trimmed() )
1036  {
1037  QgsDebugMsg( "-------> MATCH FOUND in srs.db srsid: " + mySrsId );
1038  // close the sqlite3 statement
1039  sqlite3_finalize( myPreparedStatement );
1040  sqlite3_close( myDatabase );
1041  return mySrsId.toLong();
1042  }
1043  else
1044  {
1045 // QgsDebugMsg(QString(" Not matched : %1").arg(myProj4String));
1046  }
1047  }
1048  }
1049  QgsDebugMsg( "no match found in srs.db, trying user db now!" );
1050  // close the sqlite3 statement
1051  sqlite3_finalize( myPreparedStatement );
1052  sqlite3_close( myDatabase );
1053  //
1054  // Try the users db now
1055  //
1056 
1057  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
1058  //check the db is available
1059  myResult = openDb( myDatabaseFileName, &myDatabase );
1060  if ( myResult != SQLITE_OK )
1061  {
1062  return 0;
1063  }
1064 
1065  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1066 // XXX Need to free memory from the error msg if one is set
1067  if ( myResult == SQLITE_OK )
1068  {
1069 
1070  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1071  {
1072  QString mySrsId = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
1073  QString myProj4String = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 1 ) );
1074  if ( toProj4() == myProj4String.trimmed() )
1075  {
1076  QgsDebugMsg( "-------> MATCH FOUND in user qgis.db srsid: " + mySrsId );
1077  // close the sqlite3 statement
1078  sqlite3_finalize( myPreparedStatement );
1079  sqlite3_close( myDatabase );
1080  return mySrsId.toLong();
1081  }
1082  else
1083  {
1084 // QgsDebugMsg(QString(" Not matched : %1").arg(myProj4String));
1085  }
1086  }
1087  }
1088  QgsDebugMsg( "no match found in user db" );
1089 
1090  // close the sqlite3 statement
1091  sqlite3_finalize( myPreparedStatement );
1092  sqlite3_close( myDatabase );
1093  return 0;
1094 }
1095 
1097 {
1098  return mIsValidFlag && theSrs.mIsValidFlag && theSrs.authid() == authid();
1099 }
1100 
1102 {
1103  return !( *this == theSrs );
1104 }
1105 
1107 {
1108  if ( mWkt.isEmpty() )
1109  {
1110  char *wkt;
1111  if ( OSRExportToWkt( mCRS, &wkt ) == OGRERR_NONE )
1112  {
1113  mWkt = wkt;
1114  OGRFree( wkt );
1115  }
1116  }
1117  return mWkt;
1118 }
1119 
1120 bool QgsCoordinateReferenceSystem::readXML( QDomNode & theNode )
1121 {
1122  QgsDebugMsg( "Reading Spatial Ref Sys from xml ------------------------!" );
1123  QDomNode srsNode = theNode.namedItem( "spatialrefsys" );
1124 
1125  if ( ! srsNode.isNull() )
1126  {
1127  bool initialized = false;
1128 
1129  long srsid = srsNode.namedItem( "srsid" ).toElement().text().toLong();
1130 
1131  QDomNode myNode;
1132 
1133  if ( srsid < USER_CRS_START_ID )
1134  {
1135  myNode = srsNode.namedItem( "authid" );
1136  if ( !myNode.isNull() )
1137  {
1138  operator=( QgsCRSCache::instance()->crsByAuthId( myNode.toElement().text() ) );
1139  if ( isValid() )
1140  {
1141  initialized = true;
1142  }
1143  }
1144 
1145  if ( !initialized )
1146  {
1147  myNode = srsNode.namedItem( "epsg" );
1148  if ( !myNode.isNull() )
1149  {
1150  operator=( QgsCRSCache::instance()->crsByEpsgId( myNode.toElement().text().toLong() ) );
1151  if ( isValid() )
1152  {
1153  initialized = true;
1154  }
1155  }
1156  }
1157  }
1158  else
1159  {
1160  QgsDebugMsg( "Ignoring authid/epsg for user crs." );
1161  }
1162 
1163  if ( initialized )
1164  {
1165  QgsDebugMsg( "Set from auth id" );
1166  }
1167  else
1168  {
1169  myNode = srsNode.namedItem( "proj4" );
1170 
1171  if ( createFromProj4( myNode.toElement().text() ) )
1172  {
1173  // createFromProj4() sets everything, including map units
1174  QgsDebugMsg( "Setting from proj4 string" );
1175  }
1176  else
1177  {
1178  QgsDebugMsg( "Setting from elements one by one" );
1179 
1180  myNode = srsNode.namedItem( "proj4" );
1181  setProj4String( myNode.toElement().text() );
1182 
1183  myNode = srsNode.namedItem( "srsid" );
1184  setInternalId( myNode.toElement().text().toLong() );
1185 
1186  myNode = srsNode.namedItem( "srid" );
1187  setSrid( myNode.toElement().text().toLong() );
1188 
1189  myNode = srsNode.namedItem( "authid" );
1190  setAuthId( myNode.toElement().text() );
1191 
1192  myNode = srsNode.namedItem( "description" );
1193  setDescription( myNode.toElement().text() );
1194 
1195  myNode = srsNode.namedItem( "projectionacronym" );
1196  setProjectionAcronym( myNode.toElement().text() );
1197 
1198  myNode = srsNode.namedItem( "ellipsoidacronym" );
1199  setEllipsoidAcronym( myNode.toElement().text() );
1200 
1201  myNode = srsNode.namedItem( "geographicflag" );
1202  if ( myNode.toElement().text().compare( "true" ) )
1203  {
1204  setGeographicFlag( true );
1205  }
1206  else
1207  {
1208  setGeographicFlag( false );
1209  }
1210 
1211  //make sure the map units have been set
1212  setMapUnits();
1213 
1214  //@TODO this srs needs to be validated!!!
1215  mIsValidFlag = true; //shamelessly hard coded for now
1216  }
1217  //TODO: createFromProj4 used to save to the user database any new CRS
1218  // this behavior was changed in order to separate creation and saving.
1219  // Not sure if it necessary to save it here, should be checked by someone
1220  // familiar with the code (should also give a more descriptive name to the generated CRS)
1221  if ( mSrsId == 0 )
1222  {
1223  QString myName = QString( " * %1 (%2)" )
1224  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ) )
1225  .arg( toProj4() );
1226  saveAsUserCRS( myName );
1227  }
1228 
1229  }
1230  }
1231  else
1232  {
1233  // Return default CRS if none was found in the XML.
1235  }
1236  return true;
1237 }
1238 
1239 bool QgsCoordinateReferenceSystem::writeXML( QDomNode & theNode, QDomDocument & theDoc ) const
1240 {
1241 
1242  QDomElement myLayerNode = theNode.toElement();
1243  QDomElement mySrsElement = theDoc.createElement( "spatialrefsys" );
1244 
1245  QDomElement myProj4Element = theDoc.createElement( "proj4" );
1246  myProj4Element.appendChild( theDoc.createTextNode( toProj4() ) );
1247  mySrsElement.appendChild( myProj4Element );
1248 
1249  QDomElement mySrsIdElement = theDoc.createElement( "srsid" );
1250  mySrsIdElement.appendChild( theDoc.createTextNode( QString::number( srsid() ) ) );
1251  mySrsElement.appendChild( mySrsIdElement );
1252 
1253  QDomElement mySridElement = theDoc.createElement( "srid" );
1254  mySridElement.appendChild( theDoc.createTextNode( QString::number( postgisSrid() ) ) );
1255  mySrsElement.appendChild( mySridElement );
1256 
1257  QDomElement myEpsgElement = theDoc.createElement( "authid" );
1258  myEpsgElement.appendChild( theDoc.createTextNode( authid() ) );
1259  mySrsElement.appendChild( myEpsgElement );
1260 
1261  QDomElement myDescriptionElement = theDoc.createElement( "description" );
1262  myDescriptionElement.appendChild( theDoc.createTextNode( description() ) );
1263  mySrsElement.appendChild( myDescriptionElement );
1264 
1265  QDomElement myProjectionAcronymElement = theDoc.createElement( "projectionacronym" );
1266  myProjectionAcronymElement.appendChild( theDoc.createTextNode( projectionAcronym() ) );
1267  mySrsElement.appendChild( myProjectionAcronymElement );
1268 
1269  QDomElement myEllipsoidAcronymElement = theDoc.createElement( "ellipsoidacronym" );
1270  myEllipsoidAcronymElement.appendChild( theDoc.createTextNode( ellipsoidAcronym() ) );
1271  mySrsElement.appendChild( myEllipsoidAcronymElement );
1272 
1273  QDomElement myGeographicFlagElement = theDoc.createElement( "geographicflag" );
1274  QString myGeoFlagText = "false";
1275  if ( geographicFlag() )
1276  {
1277  myGeoFlagText = "true";
1278  }
1279 
1280  myGeographicFlagElement.appendChild( theDoc.createTextNode( myGeoFlagText ) );
1281  mySrsElement.appendChild( myGeographicFlagElement );
1282 
1283  myLayerNode.appendChild( mySrsElement );
1284 
1285  return true;
1286 }
1287 
1288 
1289 
1290 //
1291 // Static helper methods below this point only please!
1292 //
1293 
1294 
1295 // Returns the whole proj4 string for the selected srsid
1296 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
1298 {
1299 
1300  QString myDatabaseFileName;
1301  QString myProjString;
1302  QString mySql = QString( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( theSrsId );
1303 
1304  QgsDebugMsg( "mySrsId = " + QString::number( theSrsId ) );
1305  QgsDebugMsg( "USER_CRS_START_ID = " + QString::number( USER_CRS_START_ID ) );
1306  QgsDebugMsg( "Selection sql : " + mySql );
1307 
1308  //
1309  // Determine if this is a user projection or a system on
1310  // user projection defs all have srs_id >= 100000
1311  //
1312  if ( theSrsId >= USER_CRS_START_ID )
1313  {
1314  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
1315  QFileInfo myFileInfo;
1316  myFileInfo.setFile( myDatabaseFileName );
1317  if ( !myFileInfo.exists( ) ) //its unlikely that this condition will ever be reached
1318  {
1319  QgsDebugMsg( "users qgis.db not found" );
1320  return NULL;
1321  }
1322  }
1323  else //must be a system projection then
1324  {
1325  myDatabaseFileName = QgsApplication::srsDbFilePath();
1326  }
1327  QgsDebugMsg( "db = " + myDatabaseFileName );
1328 
1329  sqlite3 *db;
1330  int rc;
1331  rc = openDb( myDatabaseFileName, &db );
1332  if ( rc )
1333  {
1334  return QString();
1335  }
1336  // prepare the sql statement
1337  const char *pzTail;
1338  sqlite3_stmt *ppStmt;
1339 
1340  rc = sqlite3_prepare( db, mySql.toUtf8(), mySql.toUtf8().length(), &ppStmt, &pzTail );
1341  // XXX Need to free memory from the error msg if one is set
1342 
1343  if ( rc == SQLITE_OK )
1344  {
1345  if ( sqlite3_step( ppStmt ) == SQLITE_ROW )
1346  {
1347  myProjString = QString::fromUtf8(( char* )sqlite3_column_text( ppStmt, 0 ) );
1348  }
1349  }
1350  // close the statement
1351  sqlite3_finalize( ppStmt );
1352  // close the database
1353  sqlite3_close( db );
1354 
1355  //Q_ASSERT(myProjString.length() > 0);
1356  return myProjString;
1357 }
1358 
1359 int QgsCoordinateReferenceSystem::openDb( QString path, sqlite3 **db, bool readonly )
1360 {
1361  QgsDebugMsgLevel( "path = " + path, 3 );
1362  int myResult = readonly
1363  ? sqlite3_open_v2( path.toUtf8().data(), db, SQLITE_OPEN_READONLY, NULL )
1364  : sqlite3_open( path.toUtf8().data(), db );
1365 
1366  if ( myResult != SQLITE_OK )
1367  {
1368  QgsDebugMsg( "Can't open database: " + QString( sqlite3_errmsg( *db ) ) );
1369  // XXX This will likely never happen since on open, sqlite creates the
1370  // database if it does not exist.
1371  // ... unfortunately it happens on Windows
1372  QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
1373  .arg( path )
1374  .arg( myResult )
1375  .arg( sqlite3_errmsg( *db ) ), QObject::tr( "CRS" ) );
1376  }
1377  return myResult;
1378 }
1379 
1381 {
1383 }
1384 
1386 {
1387  return mCustomSrsValidation;
1388 }
1389 
1391 {
1392  QgsDebugMsg( "***SpatialRefSystem***" );
1393  QgsDebugMsg( "* Valid : " + ( mIsValidFlag ? QString( "true" ) : QString( "false" ) ) );
1394  QgsDebugMsg( "* SrsId : " + QString::number( mSrsId ) );
1395  QgsDebugMsg( "* Proj4 : " + toProj4() );
1396  QgsDebugMsg( "* WKT : " + toWkt() );
1397  QgsDebugMsg( "* Desc. : " + mDescription );
1398  if ( mapUnits() == QGis::Meters )
1399  {
1400  QgsDebugMsg( "* Units : meters" );
1401  }
1402  else if ( mapUnits() == QGis::Feet )
1403  {
1404  QgsDebugMsg( "* Units : feet" );
1405  }
1406  else if ( mapUnits() == QGis::Degrees )
1407  {
1408  QgsDebugMsg( "* Units : degrees" );
1409  }
1410 }
1411 
1413 {
1414  mValidationHint = html;
1415 }
1416 
1418 {
1419  return mValidationHint;
1420 }
1421 
1424 
1426 {
1427  if ( ! mIsValidFlag )
1428  {
1429  QgsDebugMsg( "Can't save an invalid CRS!" );
1430  return false;
1431  }
1432 
1433  QString mySql;
1434 
1435  //if this is the first record we need to ensure that its srs_id is 10000. For
1436  //any rec after that sqlite3 will take care of the autonumering
1437  //this was done to support sqlite 3.0 as it does not yet support
1438  //the autoinc related system tables.
1439  if ( getRecordCount() == 0 )
1440  {
1441  mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1442  + QString::number( USER_CRS_START_ID )
1443  + "," + quotedValue( name )
1444  + "," + quotedValue( projectionAcronym() )
1445  + "," + quotedValue( ellipsoidAcronym() )
1446  + "," + quotedValue( toProj4() )
1447  + ",0)"; // <-- is_geo shamelessly hard coded for now
1448  }
1449  else
1450  {
1451  mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1452  + quotedValue( name )
1453  + "," + quotedValue( projectionAcronym() )
1454  + "," + quotedValue( ellipsoidAcronym() )
1455  + "," + quotedValue( toProj4() )
1456  + ",0)"; // <-- is_geo shamelessly hard coded for now
1457  }
1458  sqlite3 *myDatabase;
1459  const char *myTail;
1460  sqlite3_stmt *myPreparedStatement;
1461  int myResult;
1462  //check the db is available
1463  myResult = sqlite3_open( QgsApplication::qgisUserDbFilePath().toUtf8().data(), &myDatabase );
1464  if ( myResult != SQLITE_OK )
1465  {
1466  QgsDebugMsg( QString( "Can't open or create database %1: %2" )
1468  .arg( sqlite3_errmsg( myDatabase ) ) );
1469  return false;
1470  }
1471  QgsDebugMsg( QString( "Update or insert sql \n%1" ).arg( mySql ) );
1472  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1473  sqlite3_step( myPreparedStatement );
1474 
1475  QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( toProj4() ), QObject::tr( "CRS" ) );
1476 
1477  int return_id;
1478  if ( myResult == SQLITE_OK )
1479  {
1480  return_id = sqlite3_last_insert_rowid( myDatabase );
1481  setInternalId( return_id );
1482 
1483  //We add the just created user CRS to the list of recently used CRS
1484  QSettings settings;
1485  //QStringList recentProjections = settings.value( "/UI/recentProjections" ).toStringList();
1486  QStringList projectionsProj4 = settings.value( "/UI/recentProjectionsProj4" ).toStringList();
1487  QStringList projectionsAuthId = settings.value( "/UI/recentProjectionsAuthId" ).toStringList();
1488  //recentProjections.append();
1489  //settings.setValue( "/UI/recentProjections", recentProjections );
1490  projectionsProj4.append( toProj4() );
1491  projectionsAuthId.append( authid() );
1492  settings.setValue( "/UI/recentProjectionsProj4", projectionsProj4 );
1493  settings.setValue( "/UI/recentProjectionsAuthId", projectionsAuthId );
1494 
1495  }
1496  else
1497  return_id = -1;
1498  return return_id;
1499 }
1500 
1502 {
1503  sqlite3 *myDatabase;
1504  const char *myTail;
1505  sqlite3_stmt *myPreparedStatement;
1506  int myResult;
1507  long myRecordCount = 0;
1508  //check the db is available
1509  myResult = sqlite3_open_v2( QgsApplication::qgisUserDbFilePath().toUtf8().data(), &myDatabase, SQLITE_OPEN_READONLY, NULL );
1510  if ( myResult != SQLITE_OK )
1511  {
1512  QgsDebugMsg( QString( "Can't open database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
1513  return 0;
1514  }
1515  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
1516  QString mySql = "select count(*) from tbl_srs";
1517  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1518  // XXX Need to free memory from the error msg if one is set
1519  if ( myResult == SQLITE_OK )
1520  {
1521  if ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1522  {
1523  QString myRecordCountString = QString::fromUtf8(( char * )sqlite3_column_text( myPreparedStatement, 0 ) );
1524  myRecordCount = myRecordCountString.toLong();
1525  }
1526  }
1527  // close the sqlite3 statement
1528  sqlite3_finalize( myPreparedStatement );
1529  sqlite3_close( myDatabase );
1530  return myRecordCount;
1531 }
1532 
1534 {
1535  value.replace( "'", "''" );
1536  return value.prepend( "'" ).append( "'" );
1537 }
1538 
1539 // adapted from gdal/ogr/ogr_srs_dict.cpp
1540 bool QgsCoordinateReferenceSystem::loadWkts( QHash<int, QString> &wkts, const char *filename )
1541 {
1542  qDebug( "Loading %s", filename );
1543  const char *pszFilename = CPLFindFile( "gdal", filename );
1544  if ( !pszFilename )
1545  return false;
1546 
1547  QFile csv( pszFilename );
1548  if ( !csv.open( QIODevice::ReadOnly ) )
1549  return false;
1550 
1551  QTextStream lines( &csv );
1552 
1553  for ( ;; )
1554  {
1555  QString line = lines.readLine();
1556  if ( line.isNull() )
1557  break;
1558 
1559  if ( line.startsWith( '#' ) )
1560  {
1561  continue;
1562  }
1563  else if ( line.startsWith( "include " ) )
1564  {
1565  if ( !loadWkts( wkts, line.mid( 8 ).toUtf8() ) )
1566  break;
1567  }
1568  else
1569  {
1570  int pos = line.indexOf( "," );
1571  if ( pos < 0 )
1572  return false;
1573 
1574  bool ok;
1575  int epsg = line.left( pos ).toInt( &ok );
1576  if ( !ok )
1577  return false;
1578 
1579  wkts.insert( epsg, line.mid( pos + 1 ) );
1580  }
1581  }
1582 
1583  csv.close();
1584 
1585  return true;
1586 }
1587 
1588 bool QgsCoordinateReferenceSystem::loadIDs( QHash<int, QString> &wkts )
1589 {
1590  OGRSpatialReferenceH crs = OSRNewSpatialReference( NULL );
1591 
1592  foreach ( QString csv, QStringList() << "gcs.csv" << "pcs.csv" << "vertcs.csv" << "compdcs.csv" << "geoccs.csv" )
1593  {
1594  QString filename = CPLFindFile( "gdal", csv.toUtf8() );
1595 
1596  QFile f( filename );
1597  if ( !f.open( QIODevice::ReadOnly ) )
1598  continue;
1599 
1600  QTextStream lines( &f );
1601  int l = 0, n = 0;
1602 
1603  lines.readLine();
1604  for ( ;; )
1605  {
1606  l++;
1607  QString line = lines.readLine();
1608  if ( line.isNull() )
1609  break;
1610 
1611  int pos = line.indexOf( "," );
1612  if ( pos < 0 )
1613  continue;
1614 
1615  bool ok;
1616  int epsg = line.left( pos ).toInt( &ok );
1617  if ( !ok )
1618  continue;
1619 
1620  // some CRS are known to fail (see http://trac.osgeo.org/gdal/ticket/2900)
1621  if ( epsg == 2218 || epsg == 2221 || epsg == 2296 || epsg == 2297 || epsg == 2298 || epsg == 2299 || epsg == 2300 || epsg == 2301 || epsg == 2302 ||
1622  epsg == 2303 || epsg == 2304 || epsg == 2305 || epsg == 2306 || epsg == 2307 || epsg == 2963 || epsg == 2985 || epsg == 2986 || epsg == 3052 ||
1623  epsg == 3053 || epsg == 3139 || epsg == 3144 || epsg == 3145 || epsg == 3173 || epsg == 3295 || epsg == 3993 || epsg == 4087 || epsg == 4088 ||
1624  epsg == 5017 || epsg == 5221 || epsg == 5224 || epsg == 5225 || epsg == 5514 || epsg == 5515 || epsg == 5516 || epsg == 5819 || epsg == 5820 ||
1625  epsg == 5821 || epsg == 32600 || epsg == 32663 || epsg == 32700 )
1626  continue;
1627 
1628  if ( OSRImportFromEPSG( crs, epsg ) != OGRERR_NONE )
1629  {
1630  qDebug( "EPSG %d: not imported", epsg );
1631  continue;
1632  }
1633 
1634  char *wkt = 0;
1635  if ( OSRExportToWkt( crs, &wkt ) != OGRERR_NONE )
1636  {
1637  qWarning( "EPSG %d: not exported to WKT", epsg );
1638  continue;
1639  }
1640 
1641  wkts.insert( epsg, wkt );
1642  n++;
1643 
1644  OGRFree( wkt );
1645  }
1646 
1647  f.close();
1648 
1649  qDebug( "Loaded %d/%d from %s", n, l, filename.toUtf8().constData() );
1650  }
1651 
1652  OSRDestroySpatialReference( crs );
1653 
1654  return true;
1655 }
1656 
1658 {
1659  int inserted = 0, updated = 0, deleted = 0, errors = 0;
1660 
1661  sqlite3 *database;
1662  if ( sqlite3_open( QgsApplication::srsDbFilePath().toUtf8().constData(), &database ) != SQLITE_OK )
1663  {
1664  qCritical( "Could not open database: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1665  return -1;
1666  }
1667 
1668  if ( sqlite3_exec( database, "BEGIN TRANSACTION", 0, 0, 0 ) != SQLITE_OK )
1669  {
1670  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1671  return -1;
1672 
1673  }
1674 
1675  sqlite3_exec( database, "UPDATE tbl_srs SET srid=141001 WHERE srid=41001 AND auth_name='OSGEO' AND auth_id='41001'", 0, 0, 0 );
1676 
1677  OGRSpatialReferenceH crs = OSRNewSpatialReference( NULL );
1678  const char *tail;
1679  sqlite3_stmt *select;
1680  char *errMsg = NULL;
1681 
1682  QString proj4;
1683  QString sql;
1684  QHash<int, QString> wkts;
1685  loadIDs( wkts );
1686  loadWkts( wkts, "epsg.wkt" );
1687 
1688  qDebug( "%d WKTs loaded", wkts.count() );
1689 
1690  for ( QHash<int, QString>::const_iterator it = wkts.constBegin(); it != wkts.constEnd(); ++it )
1691  {
1692  QByteArray ba( it.value().toUtf8() );
1693  char *psz = ba.data();
1694  OGRErr ogrErr = OSRImportFromWkt( crs, &psz );
1695  if ( ogrErr != OGRERR_NONE )
1696  {
1697  continue;
1698  }
1699 
1700  if ( OSRExportToProj4( crs, &psz ) != OGRERR_NONE )
1701  continue;
1702 
1703  proj4 = psz;
1704  proj4 = proj4.trimmed();
1705 
1706  CPLFree( psz );
1707 
1708  if ( proj4.isEmpty() )
1709  {
1710  continue;
1711  }
1712 
1713  sql = QString( "SELECT parameters,noupdate FROM tbl_srs WHERE auth_name='EPSG' AND auth_id='%1'" ).arg( it.key() );
1714  if ( sqlite3_prepare( database, sql.toAscii(), sql.size(), &select, &tail ) != SQLITE_OK )
1715  {
1716  qCritical( "Could not prepare: %s [%s]\n", sql.toAscii().constData(), sqlite3_errmsg( database ) );
1717  continue;
1718  }
1719 
1720  QString srsProj4;
1721  if ( sqlite3_step( select ) == SQLITE_ROW )
1722  {
1723  srsProj4 = ( const char * ) sqlite3_column_text( select, 0 );
1724 
1725  if ( QString::fromUtf8(( char * )sqlite3_column_text( select, 1 ) ).toInt() != 0 )
1726  continue;
1727  }
1728 
1729  sqlite3_finalize( select );
1730 
1731  if ( !srsProj4.isEmpty() )
1732  {
1733  if ( proj4 != srsProj4 )
1734  {
1735  errMsg = NULL;
1736  sql = QString( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name='EPSG' AND auth_id=%2" ).arg( quotedValue( proj4 ) ).arg( it.key() );
1737 
1738  if ( sqlite3_exec( database, sql.toUtf8(), 0, 0, &errMsg ) != SQLITE_OK )
1739  {
1740  qCritical( "Could not execute: %s [%s/%s]\n",
1741  sql.toLocal8Bit().constData(),
1742  sqlite3_errmsg( database ),
1743  errMsg ? errMsg : "(unknown error)" );
1744  errors++;
1745  }
1746  else
1747  {
1748  updated++;
1749  QgsDebugMsgLevel( QString( "SQL: %1\n OLD:%2\n NEW:%3" ).arg( sql ).arg( srsProj4 ).arg( proj4 ), 3 );
1750  }
1751  }
1752  }
1753  else
1754  {
1755  QRegExp projRegExp( "\\+proj=(\\S+)" );
1756  if ( projRegExp.indexIn( proj4 ) < 0 )
1757  {
1758  QgsDebugMsg( QString( "EPSG %1: no +proj argument found [%2]" ).arg( it.key() ).arg( proj4 ) );
1759  continue;
1760  }
1761 
1762  QRegExp ellipseRegExp( "\\+ellps=(\\S+)" );
1763  QString ellps;
1764  if ( ellipseRegExp.indexIn( proj4 ) >= 0 )
1765  {
1766  ellps = ellipseRegExp.cap( 1 );
1767  }
1768 
1769  QString name( OSRIsGeographic( crs ) ? OSRGetAttrValue( crs, "GEOCS", 0 ) : OSRGetAttrValue( crs, "PROJCS", 0 ) );
1770  if ( name.isEmpty() )
1771  name = QObject::tr( "Imported from GDAL" );
1772 
1773  sql = QString( "INSERT INTO tbl_srs(description,projection_acronym,ellipsoid_acronym,parameters,srid,auth_name,auth_id,is_geo,deprecated) VALUES (%1,%2,%3,%4,%5,'EPSG',%5,%6,0)" )
1774  .arg( quotedValue( name ) )
1775  .arg( quotedValue( projRegExp.cap( 1 ) ) )
1776  .arg( quotedValue( ellps ) )
1777  .arg( quotedValue( proj4 ) )
1778  .arg( it.key() )
1779  .arg( OSRIsGeographic( crs ) );
1780 
1781  errMsg = NULL;
1782  if ( sqlite3_exec( database, sql.toUtf8(), 0, 0, &errMsg ) == SQLITE_OK )
1783  {
1784  inserted++;
1785  }
1786  else
1787  {
1788  qCritical( "Could not execute: %s [%s/%s]\n",
1789  sql.toLocal8Bit().constData(),
1790  sqlite3_errmsg( database ),
1791  errMsg ? errMsg : "(unknown error)" );
1792  errors++;
1793 
1794  if ( errMsg )
1795  sqlite3_free( errMsg );
1796  }
1797  }
1798  }
1799 
1800  sql = "DELETE FROM tbl_srs WHERE auth_name='EPSG' AND NOT auth_id IN (";
1801  QString delim;
1802  foreach ( int i, wkts.keys() )
1803  {
1804  sql += delim + QString::number( i );
1805  delim = ",";
1806  }
1807  sql += ") AND NOT noupdate";
1808 
1809  if ( sqlite3_exec( database, sql.toUtf8(), 0, 0, 0 ) == SQLITE_OK )
1810  {
1811  deleted = sqlite3_changes( database );
1812  }
1813  else
1814  {
1815  errors++;
1816  qCritical( "Could not execute: %s [%s]\n",
1817  sql.toLocal8Bit().constData(),
1818  sqlite3_errmsg( database ) );
1819  }
1820 
1821 #if !defined(PJ_VERSION) || PJ_VERSION!=470
1822  sql = QString( "select auth_name,auth_id,parameters from tbl_srs WHERE auth_name<>'EPSG' AND NOT deprecated AND NOT noupdate" );
1823  if ( sqlite3_prepare( database, sql.toAscii(), sql.size(), &select, &tail ) == SQLITE_OK )
1824  {
1825  while ( sqlite3_step( select ) == SQLITE_ROW )
1826  {
1827  const char *auth_name = ( const char * ) sqlite3_column_text( select, 0 );
1828  const char *auth_id = ( const char * ) sqlite3_column_text( select, 1 );
1829  const char *params = ( const char * ) sqlite3_column_text( select, 2 );
1830 
1831  QString input = QString( "+init=%1:%2" ).arg( QString( auth_name ).toLower() ).arg( auth_id );
1832  projPJ pj = pj_init_plus( input.toAscii() );
1833  if ( !pj )
1834  {
1835  input = QString( "+init=%1:%2" ).arg( QString( auth_name ).toUpper() ).arg( auth_id );
1836  pj = pj_init_plus( input.toAscii() );
1837  }
1838 
1839  if ( pj )
1840  {
1841  char *def = pj_get_def( pj, 0 );
1842  if ( def )
1843  {
1844  proj4 = def;
1845  pj_dalloc( def );
1846 
1847  input.prepend( ' ' ).append( ' ' );
1848  if ( proj4.startsWith( input ) )
1849  {
1850  proj4 = proj4.mid( input.size() );
1851  proj4 = proj4.trimmed();
1852  }
1853 
1854  if ( proj4 != params )
1855  {
1856  sql = QString( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name=%2 AND auth_id=%3" )
1857  .arg( quotedValue( proj4 ) )
1858  .arg( quotedValue( auth_name ) )
1859  .arg( quotedValue( auth_id ) );
1860 
1861  if ( sqlite3_exec( database, sql.toUtf8(), 0, 0, &errMsg ) == SQLITE_OK )
1862  {
1863  updated++;
1864  QgsDebugMsgLevel( QString( "SQL: %1\n OLD:%2\n NEW:%3" ).arg( sql ).arg( params ).arg( proj4 ), 3 );
1865  }
1866  else
1867  {
1868  qCritical( "Could not execute: %s [%s/%s]\n",
1869  sql.toLocal8Bit().constData(),
1870  sqlite3_errmsg( database ),
1871  errMsg ? errMsg : "(unknown error)" );
1872  errors++;
1873  }
1874  }
1875  }
1876  else
1877  {
1878  QgsDebugMsg( QString( "could not retrieve proj string for %1 from PROJ" ).arg( input ) );
1879  }
1880  }
1881  else
1882  {
1883  QgsDebugMsgLevel( QString( "could not retrieve crs for %1 from PROJ" ).arg( input ), 3 );
1884  }
1885 
1886  pj_free( pj );
1887  }
1888  }
1889  else
1890  {
1891  errors++;
1892  qCritical( "Could not execute: %s [%s]\n",
1893  sql.toLocal8Bit().constData(),
1894  sqlite3_errmsg( database ) );
1895  }
1896 #endif
1897 
1898  OSRDestroySpatialReference( crs );
1899 
1900  if ( sqlite3_exec( database, "COMMIT", 0, 0, 0 ) != SQLITE_OK )
1901  {
1902  qCritical( "Could not commit transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1903  return -1;
1904  }
1905 
1906  sqlite3_close( database );
1907 
1908  qWarning( "CRS update (inserted:%d updated:%d deleted:%d errors:%d)", inserted, updated, deleted, errors );
1909 
1910  if ( errors > 0 )
1911  return -errors;
1912  else
1913  return updated + inserted;
1914 }