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