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