QGIS API Documentation
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  [email protected] 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 == 6200 || epsg == 6201 || epsg == 6202 || epsg == 6244 || epsg == 6245 || epsg == 6246 || epsg == 6247 || epsg == 6248 ||
1661  epsg == 6249 || epsg == 6250 || epsg == 6251 || epsg == 6252 || epsg == 6253 || epsg == 6254 || epsg == 6255 || epsg == 6256 || epsg == 6257 ||
1662  epsg == 6258 || epsg == 6259 || epsg == 6260 || epsg == 6261 || epsg == 6262 || epsg == 6263 || epsg == 6264 || epsg == 6265 || epsg == 6266 ||
1663  epsg == 6267 || epsg == 6268 || epsg == 6269 || epsg == 6270 || epsg == 6271 || epsg == 6272 || epsg == 6273 || epsg == 6274 || epsg == 6275 ||
1664  epsg == 32600 || epsg == 32663 || epsg == 32700 )
1665  continue;
1666 
1667  if ( OSRImportFromEPSG( crs, epsg ) != OGRERR_NONE )
1668  {
1669  qDebug( "EPSG %d: not imported", epsg );
1670  continue;
1671  }
1672 
1673  char *wkt = nullptr;
1674  if ( OSRExportToWkt( crs, &wkt ) != OGRERR_NONE )
1675  {
1676  qWarning( "EPSG %d: not exported to WKT", epsg );
1677  continue;
1678  }
1679 
1680  wkts.insert( epsg, wkt );
1681  n++;
1682 
1683  OGRFree( wkt );
1684  }
1685 
1686  f.close();
1687 
1688  qDebug( "Loaded %d/%d from %s", n, l, filename.toUtf8().constData() );
1689  }
1690 
1691  OSRDestroySpatialReference( crs );
1692 
1693  return true;
1694 }
1695 
1697 {
1698  QString dbFilePath = QgsApplication::srsDbFilePath();
1699  syncDatumTransform( dbFilePath );
1700 
1701  int inserted = 0, updated = 0, deleted = 0, errors = 0;
1702 
1703  qDebug( "Load srs db from: %s", QgsApplication::srsDbFilePath().toLocal8Bit().constData() );
1704 
1705  sqlite3 *database;
1706  if ( sqlite3_open( dbFilePath.toUtf8().constData(), &database ) != SQLITE_OK )
1707  {
1708  qCritical( "Could not open database: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1709  return -1;
1710  }
1711 
1712  if ( sqlite3_exec( database, "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
1713  {
1714  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1715  return -1;
1716  }
1717 
1718  // fix up database, if not done already //
1719  if ( sqlite3_exec( database, "alter table tbl_srs add noupdate boolean", nullptr, nullptr, nullptr ) == SQLITE_OK )
1720  ( 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 );
1721 
1722  ( void )sqlite3_exec( database, "UPDATE tbl_srs SET srid=141001 WHERE srid=41001 AND auth_name='OSGEO' AND auth_id='41001'", nullptr, 0, 0 );
1723 
1724  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
1725  const char *tail;
1726  sqlite3_stmt *select;
1727  char *errMsg = nullptr;
1728 
1729  QString proj4;
1730  QString sql;
1731  QHash<int, QString> wkts;
1732  loadIDs( wkts );
1733  loadWkts( wkts, "epsg.wkt" );
1734 
1735  qDebug( "%d WKTs loaded", wkts.count() );
1736 
1737  for ( QHash<int, QString>::const_iterator it = wkts.constBegin(); it != wkts.constEnd(); ++it )
1738  {
1739  QByteArray ba( it.value().toUtf8() );
1740  char *psz = ba.data();
1741  OGRErr ogrErr = OSRImportFromWkt( crs, &psz );
1742  if ( ogrErr != OGRERR_NONE )
1743  continue;
1744 
1745  if ( OSRExportToProj4( crs, &psz ) != OGRERR_NONE )
1746  continue;
1747 
1748  proj4 = psz;
1749  proj4 = proj4.trimmed();
1750 
1751  CPLFree( psz );
1752 
1753  if ( proj4.isEmpty() )
1754  continue;
1755 
1756  sql = QString( "SELECT parameters,noupdate FROM tbl_srs WHERE auth_name='EPSG' AND auth_id='%1'" ).arg( it.key() );
1757  if ( sqlite3_prepare( database, sql.toAscii(), sql.size(), &select, &tail ) != SQLITE_OK )
1758  {
1759  qCritical( "Could not prepare: %s [%s]\n", sql.toAscii().constData(), sqlite3_errmsg( database ) );
1760  continue;
1761  }
1762 
1763  QString srsProj4;
1764  if ( sqlite3_step( select ) == SQLITE_ROW )
1765  {
1766  srsProj4 = reinterpret_cast< const char * >( sqlite3_column_text( select, 0 ) );
1767 
1768  if ( QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( select, 1 ) ) ).toInt() != 0 )
1769  continue;
1770  }
1771 
1772  sqlite3_finalize( select );
1773 
1774  if ( !srsProj4.isEmpty() )
1775  {
1776  if ( proj4 != srsProj4 )
1777  {
1778  errMsg = nullptr;
1779  sql = QString( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name='EPSG' AND auth_id=%2" ).arg( quotedValue( proj4 ) ).arg( it.key() );
1780 
1781  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
1782  {
1783  qCritical( "Could not execute: %s [%s/%s]\n",
1784  sql.toLocal8Bit().constData(),
1785  sqlite3_errmsg( database ),
1786  errMsg ? errMsg : "(unknown error)" );
1787  errors++;
1788  }
1789  else
1790  {
1791  updated++;
1792  QgsDebugMsgLevel( QString( "SQL: %1\n OLD:%2\n NEW:%3" ).arg( sql, srsProj4, proj4 ), 3 );
1793  }
1794  }
1795  }
1796  else
1797  {
1798  QRegExp projRegExp( "\\+proj=(\\S+)" );
1799  if ( projRegExp.indexIn( proj4 ) < 0 )
1800  {
1801  QgsDebugMsg( QString( "EPSG %1: no +proj argument found [%2]" ).arg( it.key() ).arg( proj4 ) );
1802  continue;
1803  }
1804 
1805  QRegExp ellipseRegExp( "\\+ellps=(\\S+)" );
1806  QString ellps;
1807  if ( ellipseRegExp.indexIn( proj4 ) >= 0 )
1808  {
1809  ellps = ellipseRegExp.cap( 1 );
1810  }
1811 
1812  QString name( OSRIsGeographic( crs ) ? OSRGetAttrValue( crs, "GEOCS", 0 ) : OSRGetAttrValue( crs, "PROJCS", 0 ) );
1813  if ( name.isEmpty() )
1814  name = QObject::tr( "Imported from GDAL" );
1815 
1816  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)" )
1817  .arg( quotedValue( name ),
1818  quotedValue( projRegExp.cap( 1 ) ),
1819  quotedValue( ellps ),
1820  quotedValue( proj4 ) )
1821  .arg( it.key() )
1822  .arg( OSRIsGeographic( crs ) );
1823 
1824  errMsg = nullptr;
1825  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
1826  {
1827  inserted++;
1828  }
1829  else
1830  {
1831  qCritical( "Could not execute: %s [%s/%s]\n",
1832  sql.toLocal8Bit().constData(),
1833  sqlite3_errmsg( database ),
1834  errMsg ? errMsg : "(unknown error)" );
1835  errors++;
1836 
1837  if ( errMsg )
1838  sqlite3_free( errMsg );
1839  }
1840  }
1841  }
1842 
1843  sql = "DELETE FROM tbl_srs WHERE auth_name='EPSG' AND NOT auth_id IN (";
1844  QString delim;
1846  for ( ; it != wkts.constEnd(); ++it )
1847  {
1848  sql += delim + QString::number( it.key() );
1849  delim = ',';
1850  }
1851  sql += ") AND NOT noupdate";
1852 
1853  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
1854  {
1855  deleted = sqlite3_changes( database );
1856  }
1857  else
1858  {
1859  errors++;
1860  qCritical( "Could not execute: %s [%s]\n",
1861  sql.toLocal8Bit().constData(),
1862  sqlite3_errmsg( database ) );
1863  }
1864 
1865 #if !defined(PJ_VERSION) || PJ_VERSION!=470
1866  sql = QString( "select auth_name,auth_id,parameters from tbl_srs WHERE auth_name<>'EPSG' AND NOT deprecated AND NOT noupdate" );
1867  if ( sqlite3_prepare( database, sql.toAscii(), sql.size(), &select, &tail ) == SQLITE_OK )
1868  {
1869  while ( sqlite3_step( select ) == SQLITE_ROW )
1870  {
1871  const char *auth_name = reinterpret_cast< const char * >( sqlite3_column_text( select, 0 ) );
1872  const char *auth_id = reinterpret_cast< const char * >( sqlite3_column_text( select, 1 ) );
1873  const char *params = reinterpret_cast< const char * >( sqlite3_column_text( select, 2 ) );
1874 
1875  QString input = QString( "+init=%1:%2" ).arg( QString( auth_name ).toLower(), auth_id );
1876  projPJ pj = pj_init_plus( input.toAscii() );
1877  if ( !pj )
1878  {
1879  input = QString( "+init=%1:%2" ).arg( QString( auth_name ).toUpper(), auth_id );
1880  pj = pj_init_plus( input.toAscii() );
1881  }
1882 
1883  if ( pj )
1884  {
1885  char *def = pj_get_def( pj, 0 );
1886  if ( def )
1887  {
1888  proj4 = def;
1889  pj_dalloc( def );
1890 
1891  input.prepend( ' ' ).append( ' ' );
1892  if ( proj4.startsWith( input ) )
1893  {
1894  proj4 = proj4.mid( input.size() );
1895  proj4 = proj4.trimmed();
1896  }
1897 
1898  if ( proj4 != params )
1899  {
1900  sql = QString( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name=%2 AND auth_id=%3" )
1901  .arg( quotedValue( proj4 ),
1902  quotedValue( auth_name ),
1903  quotedValue( auth_id ) );
1904 
1905  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
1906  {
1907  updated++;
1908  QgsDebugMsgLevel( QString( "SQL: %1\n OLD:%2\n NEW:%3" ).arg( sql, params, proj4 ), 3 );
1909  }
1910  else
1911  {
1912  qCritical( "Could not execute: %s [%s/%s]\n",
1913  sql.toLocal8Bit().constData(),
1914  sqlite3_errmsg( database ),
1915  errMsg ? errMsg : "(unknown error)" );
1916  errors++;
1917  }
1918  }
1919  }
1920  else
1921  {
1922  QgsDebugMsg( QString( "could not retrieve proj string for %1 from PROJ" ).arg( input ) );
1923  }
1924  }
1925  else
1926  {
1927  QgsDebugMsgLevel( QString( "could not retrieve crs for %1 from PROJ" ).arg( input ), 3 );
1928  }
1929 
1930  pj_free( pj );
1931  }
1932  }
1933  else
1934  {
1935  errors++;
1936  qCritical( "Could not execute: %s [%s]\n",
1937  sql.toLocal8Bit().constData(),
1938  sqlite3_errmsg( database ) );
1939  }
1940 #endif
1941 
1942  OSRDestroySpatialReference( crs );
1943 
1944  if ( sqlite3_exec( database, "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
1945  {
1946  qCritical( "Could not commit transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1947  return -1;
1948  }
1949 
1950  sqlite3_close( database );
1951 
1952  qWarning( "CRS update (inserted:%d updated:%d deleted:%d errors:%d)", inserted, updated, deleted, errors );
1953 
1954  if ( errors > 0 )
1955  return -errors;
1956  else
1957  return updated + inserted;
1958 }
1959 
1960 bool QgsCoordinateReferenceSystem::syncDatumTransform( const QString& dbPath )
1961 {
1962  const char *filename = CSVFilename( "datum_shift.csv" );
1963  FILE *fp = VSIFOpen( filename, "rb" );
1964  if ( !fp )
1965  {
1966  return false;
1967  }
1968 
1969  char **fieldnames = CSVReadParseLine( fp );
1970 
1971  // "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"
1972 
1973  struct
1974  {
1975  const char *src;
1976  const char *dst;
1977  int idx;
1978  } map[] =
1979  {
1980  // { "SEQ_KEY", "", -1 },
1981  { "SOURCE_CRS_CODE", "source_crs_code", -1 },
1982  { "TARGET_CRS_CODE", "target_crs_code", -1 },
1983  { "REMARKS", "remarks", -1 },
1984  { "COORD_OP_SCOPE", "scope", -1 },
1985  { "AREA_OF_USE_CODE", "area_of_use_code", -1 },
1986  // { "AREA_SOUTH_BOUND_LAT", "", -1 },
1987  // { "AREA_NORTH_BOUND_LAT", "", -1 },
1988  // { "AREA_WEST_BOUND_LON", "", -1 },
1989  // { "AREA_EAST_BOUND_LON", "", -1 },
1990  // { "SHOW_OPERATION", "", -1 },
1991  { "DEPRECATED", "deprecated", -1 },
1992  { "COORD_OP_METHOD_CODE", "coord_op_method_code", -1 },
1993  { "DX", "p1", -1 },
1994  { "DY", "p2", -1 },
1995  { "DZ", "p3", -1 },
1996  { "RX", "p4", -1 },
1997  { "RY", "p5", -1 },
1998  { "RZ", "p6", -1 },
1999  { "DS", "p7", -1 },
2000  { "PREFERRED", "preferred", -1 },
2001  { "COORD_OP_CODE", "coord_op_code", -1 },
2002  };
2003 
2004  QString update = "UPDATE tbl_datum_transform SET ";
2005  QString insert, values;
2006 
2007  int n = CSLCount( fieldnames );
2008 
2009  int idxid = -1, idxrx = -1, idxry = -1, idxrz = -1, idxmcode = -1;
2010  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2011  {
2012  bool last = i == sizeof( map ) / sizeof( *map ) - 1;
2013 
2014  map[i].idx = CSLFindString( fieldnames, map[i].src );
2015  if ( map[i].idx < 0 )
2016  {
2017  qWarning( "field %s not found", map[i].src );
2018  CSLDestroy( fieldnames );
2019  fclose( fp );
2020  return false;
2021  }
2022 
2023  if ( strcmp( map[i].src, "COORD_OP_CODE" ) == 0 )
2024  idxid = i;
2025  if ( strcmp( map[i].src, "RX" ) == 0 )
2026  idxrx = i;
2027  if ( strcmp( map[i].src, "RY" ) == 0 )
2028  idxry = i;
2029  if ( strcmp( map[i].src, "RZ" ) == 0 )
2030  idxrz = i;
2031  if ( strcmp( map[i].src, "COORD_OP_METHOD_CODE" ) == 0 )
2032  idxmcode = i;
2033 
2034  if ( i > 0 )
2035  {
2036  insert += ',';
2037  values += ',';
2038 
2039  if ( last )
2040  {
2041  update += " WHERE ";
2042  }
2043  else
2044  {
2045  update += ',';
2046  }
2047  }
2048 
2049  update += QString( "%1=%%2" ).arg( map[i].dst ).arg( i + 1 );
2050 
2051  insert += map[i].dst;
2052  values += QString( "%%1" ).arg( i + 1 );
2053  }
2054 
2055  insert = "INSERT INTO tbl_datum_transform(" + insert + ") VALUES (" + values + ')';
2056 
2057  QgsDebugMsgLevel( QString( "insert:%1" ).arg( insert ), 4 );
2058  QgsDebugMsgLevel( QString( "update:%1" ).arg( update ), 4 );
2059 
2060  CSLDestroy( fieldnames );
2061 
2062  Q_ASSERT( idxid >= 0 );
2063  Q_ASSERT( idxrx >= 0 );
2064  Q_ASSERT( idxry >= 0 );
2065  Q_ASSERT( idxrz >= 0 );
2066 
2067  sqlite3 *db;
2068  int openResult = sqlite3_open( dbPath.toUtf8().constData(), &db );
2069  if ( openResult != SQLITE_OK )
2070  {
2071  fclose( fp );
2072  return false;
2073  }
2074 
2075  if ( sqlite3_exec( db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2076  {
2077  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( db ) );
2078  sqlite3_close( db );
2079  fclose( fp );
2080  return false;
2081  }
2082 
2083  QStringList v;
2084  v.reserve( sizeof( map ) / sizeof( *map ) );
2085 
2086  while ( !feof( fp ) )
2087  {
2088  char **values = CSVReadParseLine( fp );
2089 
2090  v.clear();
2091 
2092  if ( CSLCount( values ) < n )
2093  {
2094  qWarning( "Only %d columns", CSLCount( values ) );
2095  continue;
2096  }
2097 
2098  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2099  {
2100  int idx = map[i].idx;
2101  Q_ASSERT( idx != -1 );
2102  Q_ASSERT( idx < n );
2103  v.insert( i, *values[ idx ] ? quotedValue( values[idx] ) : "NULL" );
2104  }
2105 
2106  //switch sign of rotation parameters. See http://trac.osgeo.org/proj/wiki/GenParms#towgs84-DatumtransformationtoWGS84
2107  if ( v.at( idxmcode ).compare( QLatin1String( "'9607'" ) ) == 0 )
2108  {
2109  v[ idxmcode ] = "'9606'";
2110  v[ idxrx ] = '\'' + qgsDoubleToString( -( v[ idxrx ].remove( '\'' ).toDouble() ) ) + '\'';
2111  v[ idxry ] = '\'' + qgsDoubleToString( -( v[ idxry ].remove( '\'' ).toDouble() ) ) + '\'';
2112  v[ idxrz ] = '\'' + qgsDoubleToString( -( v[ idxrz ].remove( '\'' ).toDouble() ) ) + '\'';
2113  }
2114 
2115  //entry already in db?
2116  sqlite3_stmt *stmt;
2117  QString cOpCode;
2118  QString sql = QString( "SELECT coord_op_code FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( v[ idxid ] );
2119  int prepareRes = sqlite3_prepare( db, sql.toAscii(), sql.size(), &stmt, nullptr );
2120  if ( prepareRes != SQLITE_OK )
2121  continue;
2122 
2123  if ( sqlite3_step( stmt ) == SQLITE_ROW )
2124  {
2125  cOpCode = reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) );
2126  }
2127  sqlite3_finalize( stmt );
2128 
2129  sql = cOpCode.isEmpty() ? insert : update;
2130  for ( int i = 0; i < v.size(); i++ )
2131  {
2132  sql = sql.arg( v[i] );
2133  }
2134 
2135  if ( sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, nullptr ) != SQLITE_OK )
2136  {
2137  qCritical( "SQL: %s", sql.toUtf8().constData() );
2138  qCritical( "Error: %s", sqlite3_errmsg( db ) );
2139  }
2140  }
2141 
2142  if ( sqlite3_exec( db, "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2143  {
2144  qCritical( "Could not commit transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( db ) );
2145  return false;
2146  }
2147 
2148  sqlite3_close( db );
2149  return true;
2150 }
2151 
2153 {
2154  if ( geographicFlag() )
2155  {
2156  return mAuthId;
2157  }
2158  else if ( mCRS )
2159  {
2160  return OSRGetAuthorityName( mCRS, "GEOGCS" ) + QLatin1String( ":" ) + OSRGetAuthorityCode( mCRS, "GEOGCS" );
2161  }
2162  else
2163  {
2164  return "";
2165  }
2166 }
2167 
2169 {
2170  QStringList projections;
2171 
2172  // Read settings from persistent storage
2173  QSettings settings;
2174  projections = settings.value( "/UI/recentProjections" ).toStringList();
2175  /*** The reading (above) of internal id from persistent storage should be removed sometime in the future */
2176  /*** This is kept now for backwards compatibility */
2177 
2178  QStringList projectionsProj4 = settings.value( "/UI/recentProjectionsProj4" ).toStringList();
2179  QStringList projectionsAuthId = settings.value( "/UI/recentProjectionsAuthId" ).toStringList();
2180  if ( projectionsAuthId.size() >= projections.size() )
2181  {
2182  // We had saved state with AuthId and Proj4. Use that instead
2183  // to find out the crs id
2184  projections.clear();
2185  for ( int i = 0; i < projectionsAuthId.size(); i++ )
2186  {
2187  // Create a crs from the EPSG
2189  crs.createFromOgcWmsCrs( projectionsAuthId.at( i ) );
2190  if ( ! crs.isValid() )
2191  {
2192  // Couldn't create from EPSG, try the Proj4 string instead
2193  if ( i >= projectionsProj4.size() || !crs.createFromProj4( projectionsProj4.at( i ) ) )
2194  {
2195  // No? Skip this entry
2196  continue;
2197  }
2198  //If the CRS can be created but do not correspond to a CRS in the database, skip it (for example a deleted custom CRS)
2199  if ( crs.srsid() == 0 )
2200  {
2201  continue;
2202  }
2203  }
2204  projections << QString::number( crs.srsid() );
2205  }
2206  }
2207  return projections;
2208 }
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)
Compare two doubles (but allow some difference)
Definition: qgis.h:348
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)
Returns a string representation of a double.
Definition: qgis.h:336
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:420
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:433
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:430
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.