QGIS API Documentation  2.14.11-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 : sherman@mrcc.com
8 ***************************************************************************/
9 
10 /***************************************************************************
11  * *
12  * This program is free software; you can redistribute it and/or modify *
13  * it under the terms of the GNU General Public License as published by *
14  * the Free Software Foundation; either version 2 of the License, or *
15  * (at your option) any later version. *
16  * *
17  ***************************************************************************/
19 
20 #include <cmath>
21 
22 #include <QDir>
23 #include <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  // OSRImportFromProj4() may accept strings that are not valid proj.4 strings,
931  // e.g if they lack a +ellps parameter, it will automatically add +ellps=WGS84, but as
932  // we use the original mProj4 with QgsCoordinateTransform, it will fail to initialize
933  // so better detect it now.
934  projPJ theProj = pj_init_plus( theProj4String.trimmed().toLatin1().constData() );
935  if ( !theProj )
936  {
937  QgsDebugMsg( "proj.4 string rejected by pj_init_plus()" );
938  mIsValidFlag = false;
939  }
940  else
941  {
942  pj_free( theProj );
943  }
944  mWkt.clear();
945  setMapUnits();
946 
947 #if defined(QGISDEBUG) && QGISDEBUG>=3
948  debugPrint();
949 #endif
950 }
951 void QgsCoordinateReferenceSystem::setGeographicFlag( bool theGeoFlag )
952 {
953  mGeoFlag = theGeoFlag;
954 }
955 void QgsCoordinateReferenceSystem::setEpsg( long theEpsg )
956 {
957  mAuthId = QString( "EPSG:%1" ).arg( theEpsg );
958 }
959 void QgsCoordinateReferenceSystem::setProjectionAcronym( const QString& theProjectionAcronym )
960 {
961  mProjectionAcronym = theProjectionAcronym;
962 }
963 void QgsCoordinateReferenceSystem::setEllipsoidAcronym( const QString& theEllipsoidAcronym )
964 {
965  mEllipsoidAcronym = theEllipsoidAcronym;
966 }
967 
968 void QgsCoordinateReferenceSystem::setMapUnits()
969 {
970  if ( !mIsValidFlag )
971  {
972  mMapUnits = QGis::UnknownUnit;
973  return;
974  }
975 
976  char *unitName;
977 
978  // Of interest to us is that this call adds in a unit parameter if
979  // one doesn't already exist.
980  OSRFixup( mCRS );
981 
982  if ( OSRIsProjected( mCRS ) )
983  {
984  double toMeter = OSRGetLinearUnits( mCRS, &unitName );
985  QString unit( unitName );
986 
987  // If the units parameter was created during the Fixup() call
988  // above, the name of the units is likely to be 'unknown'. Try to
989  // do better than that ... (but perhaps ogr should be enhanced to
990  // do this instead?).
991 
992  static const double feetToMeter = 0.3048;
993  static const double smallNum = 1e-3;
994 
995  if ( qAbs( toMeter - feetToMeter ) < smallNum )
996  unit = "Foot";
997 
998  QgsDebugMsg( "Projection has linear units of " + unit );
999 
1000  if ( qgsDoubleNear( toMeter, 1.0 ) ) //Unit name for meters would be "metre"
1001  mMapUnits = QGis::Meters;
1002  else if ( unit == "Foot" )
1003  mMapUnits = QGis::Feet;
1004  else
1005  {
1006  QgsDebugMsg( "Unsupported map units of " + unit );
1007  mMapUnits = QGis::UnknownUnit;
1008  }
1009  }
1010  else
1011  {
1012  OSRGetAngularUnits( mCRS, &unitName );
1013  QString unit( unitName );
1014  if ( unit == "degree" )
1015  mMapUnits = QGis::Degrees;
1016  else
1017  {
1018  QgsDebugMsg( "Unsupported map units of " + unit );
1019  mMapUnits = QGis::UnknownUnit;
1020  }
1021  QgsDebugMsgLevel( "Projection has angular units of " + unit, 3 );
1022  }
1023 }
1024 
1025 /*
1026 * check if srs is a geocs or a proj cs (using ogr isGeographic)
1027 * then sequentially walk through the database (first users qgis.db srs tbl then
1028 * system srs.db tbl), converting each entry into an ogr srs and using isSame
1029 * or isSameGeocs (essentially calling the == overloaded operator). We'll try to
1030 * be smart about this and first parse out the proj and ellpse strings and only
1031 * check for a match in entities that have the same ellps and proj entries so
1032 * that it doesnt munch yer cpu so much.
1033 */
1035 {
1036  QgsDebugMsg( "entered." );
1037  if ( mEllipsoidAcronym.isNull() || mProjectionAcronym.isNull()
1038  || !mIsValidFlag )
1039  {
1040  QgsDebugMsg( "QgsCoordinateReferenceSystem::findMatchingProj will only "
1041  "work if prj acr ellipsoid acr and proj4string are set"
1042  " and the current projection is valid!" );
1043  return 0;
1044  }
1045 
1046  sqlite3 *myDatabase;
1047  const char *myTail;
1048  sqlite3_stmt *myPreparedStatement;
1049  int myResult;
1050 
1051  // Set up the query to retrieve the projection information
1052  // needed to populate the list
1053  QString mySql = QString( "select srs_id,parameters from tbl_srs where "
1054  "projection_acronym=%1 and ellipsoid_acronym=%2 order by deprecated" )
1055  .arg( quotedValue( mProjectionAcronym ),
1056  quotedValue( mEllipsoidAcronym ) );
1057  // Get the full path name to the sqlite3 spatial reference database.
1058  QString myDatabaseFileName = QgsApplication::srsDbFilePath();
1059 
1060  //check the db is available
1061  myResult = openDb( myDatabaseFileName, &myDatabase );
1062  if ( myResult != SQLITE_OK )
1063  {
1064  return 0;
1065  }
1066 
1067  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1068 // XXX Need to free memory from the error msg if one is set
1069  if ( myResult == SQLITE_OK )
1070  {
1071 
1072  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1073  {
1074  QString mySrsId = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( myPreparedStatement, 0 ) ) );
1075  QString myProj4String = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( myPreparedStatement, 1 ) ) );
1076  if ( toProj4() == myProj4String.trimmed() )
1077  {
1078  QgsDebugMsg( "-------> MATCH FOUND in srs.db srsid: " + mySrsId );
1079  // close the sqlite3 statement
1080  sqlite3_finalize( myPreparedStatement );
1081  sqlite3_close( myDatabase );
1082  return mySrsId.toLong();
1083  }
1084  else
1085  {
1086 // QgsDebugMsg(QString(" Not matched : %1").arg(myProj4String));
1087  }
1088  }
1089  }
1090  QgsDebugMsg( "no match found in srs.db, trying user db now!" );
1091  // close the sqlite3 statement
1092  sqlite3_finalize( myPreparedStatement );
1093  sqlite3_close( myDatabase );
1094  //
1095  // Try the users db now
1096  //
1097 
1098  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
1099  //check the db is available
1100  myResult = openDb( myDatabaseFileName, &myDatabase );
1101  if ( myResult != SQLITE_OK )
1102  {
1103  return 0;
1104  }
1105 
1106  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1107 // XXX Need to free memory from the error msg if one is set
1108  if ( myResult == SQLITE_OK )
1109  {
1110 
1111  while ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1112  {
1113  QString mySrsId = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( myPreparedStatement, 0 ) ) );
1114  QString myProj4String = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( myPreparedStatement, 1 ) ) );
1115  if ( toProj4() == myProj4String.trimmed() )
1116  {
1117  QgsDebugMsg( "-------> MATCH FOUND in user qgis.db srsid: " + mySrsId );
1118  // close the sqlite3 statement
1119  sqlite3_finalize( myPreparedStatement );
1120  sqlite3_close( myDatabase );
1121  return mySrsId.toLong();
1122  }
1123  else
1124  {
1125 // QgsDebugMsg(QString(" Not matched : %1").arg(myProj4String));
1126  }
1127  }
1128  }
1129  QgsDebugMsg( "no match found in user db" );
1130 
1131  // close the sqlite3 statement
1132  sqlite3_finalize( myPreparedStatement );
1133  sqlite3_close( myDatabase );
1134  return 0;
1135 }
1136 
1138 {
1139  return ( !mIsValidFlag && !theSrs.mIsValidFlag ) ||
1140  ( mIsValidFlag && theSrs.mIsValidFlag && theSrs.authid() == authid() );
1141 }
1142 
1144 {
1145  return !( *this == theSrs );
1146 }
1147 
1149 {
1150  if ( mWkt.isEmpty() )
1151  {
1152  char *wkt;
1153  if ( OSRExportToWkt( mCRS, &wkt ) == OGRERR_NONE )
1154  {
1155  mWkt = wkt;
1156  OGRFree( wkt );
1157  }
1158  }
1159  return mWkt;
1160 }
1161 
1163 {
1164  QgsDebugMsg( "Reading Spatial Ref Sys from xml ------------------------!" );
1165  bool result = true;
1166  QDomNode srsNode = theNode.namedItem( "spatialrefsys" );
1167 
1168  if ( ! srsNode.isNull() )
1169  {
1170  bool initialized = false;
1171 
1172  long srsid = srsNode.namedItem( "srsid" ).toElement().text().toLong();
1173 
1174  QDomNode myNode;
1175 
1176  if ( srsid < USER_CRS_START_ID )
1177  {
1178  myNode = srsNode.namedItem( "authid" );
1179  if ( !myNode.isNull() )
1180  {
1181  operator=( QgsCRSCache::instance()->crsByAuthId( myNode.toElement().text() ) );
1182  if ( isValid() )
1183  {
1184  initialized = true;
1185  }
1186  }
1187 
1188  if ( !initialized )
1189  {
1190  myNode = srsNode.namedItem( "epsg" );
1191  if ( !myNode.isNull() )
1192  {
1193  operator=( QgsCRSCache::instance()->crsByEpsgId( myNode.toElement().text().toLong() ) );
1194  if ( isValid() )
1195  {
1196  initialized = true;
1197  }
1198  }
1199  }
1200  }
1201  else
1202  {
1203  QgsDebugMsg( "Ignoring authid/epsg for user crs." );
1204  }
1205 
1206  if ( initialized )
1207  {
1208  QgsDebugMsg( "Set from auth id" );
1209  }
1210  else
1211  {
1212  myNode = srsNode.namedItem( "proj4" );
1213 
1214  if ( createFromProj4( myNode.toElement().text() ) )
1215  {
1216  // createFromProj4() sets everything, including map units
1217  QgsDebugMsg( "Setting from proj4 string" );
1218  }
1219  else
1220  {
1221  QgsDebugMsg( "Setting from elements one by one" );
1222 
1223  myNode = srsNode.namedItem( "proj4" );
1224  setProj4String( myNode.toElement().text() );
1225 
1226  myNode = srsNode.namedItem( "srsid" );
1227  setInternalId( myNode.toElement().text().toLong() );
1228 
1229  myNode = srsNode.namedItem( "srid" );
1230  setSrid( myNode.toElement().text().toLong() );
1231 
1232  myNode = srsNode.namedItem( "authid" );
1233  setAuthId( myNode.toElement().text() );
1234 
1235  myNode = srsNode.namedItem( "description" );
1236  setDescription( myNode.toElement().text() );
1237 
1238  myNode = srsNode.namedItem( "projectionacronym" );
1239  setProjectionAcronym( myNode.toElement().text() );
1240 
1241  myNode = srsNode.namedItem( "ellipsoidacronym" );
1242  setEllipsoidAcronym( myNode.toElement().text() );
1243 
1244  myNode = srsNode.namedItem( "geographicflag" );
1245  if ( myNode.toElement().text().compare( "true" ) )
1246  {
1247  setGeographicFlag( true );
1248  }
1249  else
1250  {
1251  setGeographicFlag( false );
1252  }
1253 
1254  //make sure the map units have been set
1255  setMapUnits();
1256 
1257  //@TODO this srs needs to be validated!!!
1258  mIsValidFlag = true; //shamelessly hard coded for now
1259  }
1260  //TODO: createFromProj4 used to save to the user database any new CRS
1261  // this behavior was changed in order to separate creation and saving.
1262  // Not sure if it necessary to save it here, should be checked by someone
1263  // familiar with the code (should also give a more descriptive name to the generated CRS)
1264  if ( mSrsId == 0 )
1265  {
1266  QString myName = QString( " * %1 (%2)" )
1267  .arg( QObject::tr( "Generated CRS", "A CRS automatically generated from layer info get this prefix for description" ),
1268  toProj4() );
1269  saveAsUserCRS( myName );
1270  }
1271 
1272  }
1273  }
1274  else
1275  {
1276  // Return default CRS if none was found in the XML.
1278  result = false;
1279  }
1280  return result;
1281 }
1282 
1284 {
1285 
1286  QDomElement myLayerNode = theNode.toElement();
1287  QDomElement mySrsElement = theDoc.createElement( "spatialrefsys" );
1288 
1289  QDomElement myProj4Element = theDoc.createElement( "proj4" );
1290  myProj4Element.appendChild( theDoc.createTextNode( toProj4() ) );
1291  mySrsElement.appendChild( myProj4Element );
1292 
1293  QDomElement mySrsIdElement = theDoc.createElement( "srsid" );
1294  mySrsIdElement.appendChild( theDoc.createTextNode( QString::number( srsid() ) ) );
1295  mySrsElement.appendChild( mySrsIdElement );
1296 
1297  QDomElement mySridElement = theDoc.createElement( "srid" );
1298  mySridElement.appendChild( theDoc.createTextNode( QString::number( postgisSrid() ) ) );
1299  mySrsElement.appendChild( mySridElement );
1300 
1301  QDomElement myEpsgElement = theDoc.createElement( "authid" );
1302  myEpsgElement.appendChild( theDoc.createTextNode( authid() ) );
1303  mySrsElement.appendChild( myEpsgElement );
1304 
1305  QDomElement myDescriptionElement = theDoc.createElement( "description" );
1306  myDescriptionElement.appendChild( theDoc.createTextNode( description() ) );
1307  mySrsElement.appendChild( myDescriptionElement );
1308 
1309  QDomElement myProjectionAcronymElement = theDoc.createElement( "projectionacronym" );
1310  myProjectionAcronymElement.appendChild( theDoc.createTextNode( projectionAcronym() ) );
1311  mySrsElement.appendChild( myProjectionAcronymElement );
1312 
1313  QDomElement myEllipsoidAcronymElement = theDoc.createElement( "ellipsoidacronym" );
1314  myEllipsoidAcronymElement.appendChild( theDoc.createTextNode( ellipsoidAcronym() ) );
1315  mySrsElement.appendChild( myEllipsoidAcronymElement );
1316 
1317  QDomElement myGeographicFlagElement = theDoc.createElement( "geographicflag" );
1318  QString myGeoFlagText = "false";
1319  if ( geographicFlag() )
1320  {
1321  myGeoFlagText = "true";
1322  }
1323 
1324  myGeographicFlagElement.appendChild( theDoc.createTextNode( myGeoFlagText ) );
1325  mySrsElement.appendChild( myGeographicFlagElement );
1326 
1327  myLayerNode.appendChild( mySrsElement );
1328 
1329  return true;
1330 }
1331 
1332 
1333 
1334 //
1335 // Static helper methods below this point only please!
1336 //
1337 
1338 
1339 // Returns the whole proj4 string for the selected srsid
1340 //this is a static method! NOTE I've made it private for now to reduce API clutter TS
1341 QString QgsCoordinateReferenceSystem::proj4FromSrsId( const int theSrsId )
1342 {
1343 
1344  QString myDatabaseFileName;
1345  QString myProjString;
1346  QString mySql = QString( "select parameters from tbl_srs where srs_id = %1 order by deprecated" ).arg( theSrsId );
1347 
1348  QgsDebugMsg( "mySrsId = " + QString::number( theSrsId ) );
1349  QgsDebugMsg( "USER_CRS_START_ID = " + QString::number( USER_CRS_START_ID ) );
1350  QgsDebugMsg( "Selection sql : " + mySql );
1351 
1352  //
1353  // Determine if this is a user projection or a system on
1354  // user projection defs all have srs_id >= 100000
1355  //
1356  if ( theSrsId >= USER_CRS_START_ID )
1357  {
1358  myDatabaseFileName = QgsApplication::qgisUserDbFilePath();
1359  QFileInfo myFileInfo;
1360  myFileInfo.setFile( myDatabaseFileName );
1361  if ( !myFileInfo.exists() ) //its unlikely that this condition will ever be reached
1362  {
1363  QgsDebugMsg( "users qgis.db not found" );
1364  return nullptr;
1365  }
1366  }
1367  else //must be a system projection then
1368  {
1369  myDatabaseFileName = QgsApplication::srsDbFilePath();
1370  }
1371  QgsDebugMsg( "db = " + myDatabaseFileName );
1372 
1373  sqlite3 *db;
1374  int rc;
1375  rc = openDb( myDatabaseFileName, &db );
1376  if ( rc )
1377  {
1378  return QString();
1379  }
1380  // prepare the sql statement
1381  const char *pzTail;
1382  sqlite3_stmt *ppStmt;
1383 
1384  rc = sqlite3_prepare( db, mySql.toUtf8(), mySql.toUtf8().length(), &ppStmt, &pzTail );
1385  // XXX Need to free memory from the error msg if one is set
1386 
1387  if ( rc == SQLITE_OK )
1388  {
1389  if ( sqlite3_step( ppStmt ) == SQLITE_ROW )
1390  {
1391  myProjString = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( ppStmt, 0 ) ) );
1392  }
1393  }
1394  // close the statement
1395  sqlite3_finalize( ppStmt );
1396  // close the database
1397  sqlite3_close( db );
1398 
1399  //Q_ASSERT(myProjString.length() > 0);
1400  return myProjString;
1401 }
1402 
1403 int QgsCoordinateReferenceSystem::openDb( const QString& path, sqlite3 **db, bool readonly )
1404 {
1405  QgsDebugMsgLevel( "path = " + path, 3 );
1406  int myResult = readonly
1407  ? sqlite3_open_v2( path.toUtf8().data(), db, SQLITE_OPEN_READONLY, nullptr )
1408  : sqlite3_open( path.toUtf8().data(), db );
1409 
1410  if ( myResult != SQLITE_OK )
1411  {
1412  QgsDebugMsg( "Can't open database: " + QString( sqlite3_errmsg( *db ) ) );
1413  // XXX This will likely never happen since on open, sqlite creates the
1414  // database if it does not exist.
1415  // ... unfortunately it happens on Windows
1416  QgsMessageLog::logMessage( QObject::tr( "Could not open CRS database %1\nError(%2): %3" )
1417  .arg( path )
1418  .arg( myResult )
1419  .arg( sqlite3_errmsg( *db ) ), QObject::tr( "CRS" ) );
1420  }
1421  return myResult;
1422 }
1423 
1425 {
1426  mCustomSrsValidation = f;
1427 }
1428 
1430 {
1431  return mCustomSrsValidation;
1432 }
1433 
1434 void QgsCoordinateReferenceSystem::debugPrint()
1435 {
1436  QgsDebugMsg( "***SpatialRefSystem***" );
1437  QgsDebugMsg( "* Valid : " + ( mIsValidFlag ? QString( "true" ) : QString( "false" ) ) );
1438  QgsDebugMsg( "* SrsId : " + QString::number( mSrsId ) );
1439  QgsDebugMsg( "* Proj4 : " + toProj4() );
1440  QgsDebugMsg( "* WKT : " + toWkt() );
1441  QgsDebugMsg( "* Desc. : " + mDescription );
1442  if ( mapUnits() == QGis::Meters )
1443  {
1444  QgsDebugMsg( "* Units : meters" );
1445  }
1446  else if ( mapUnits() == QGis::Feet )
1447  {
1448  QgsDebugMsg( "* Units : feet" );
1449  }
1450  else if ( mapUnits() == QGis::Degrees )
1451  {
1452  QgsDebugMsg( "* Units : degrees" );
1453  }
1454 }
1455 
1457 {
1458  mValidationHint = html;
1459 }
1460 
1462 {
1463  return mValidationHint;
1464 }
1465 
1468 
1470 {
1471  if ( ! mIsValidFlag )
1472  {
1473  QgsDebugMsg( "Can't save an invalid CRS!" );
1474  return false;
1475  }
1476 
1477  QString mySql;
1478 
1479  QString proj4String = mProj4;
1480  if ( proj4String.isEmpty() )
1481  {
1482  proj4String = toProj4();
1483  }
1484 
1485  //if this is the first record we need to ensure that its srs_id is 10000. For
1486  //any rec after that sqlite3 will take care of the autonumering
1487  //this was done to support sqlite 3.0 as it does not yet support
1488  //the autoinc related system tables.
1489  if ( getRecordCount() == 0 )
1490  {
1491  mySql = "insert into tbl_srs (srs_id,description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1493  + ',' + quotedValue( name )
1494  + ',' + quotedValue( projectionAcronym() )
1495  + ',' + quotedValue( ellipsoidAcronym() )
1496  + ',' + quotedValue( toProj4() )
1497  + ",0)"; // <-- is_geo shamelessly hard coded for now
1498  }
1499  else
1500  {
1501  mySql = "insert into tbl_srs (description,projection_acronym,ellipsoid_acronym,parameters,is_geo) values ("
1502  + quotedValue( name )
1503  + ',' + quotedValue( projectionAcronym() )
1504  + ',' + quotedValue( ellipsoidAcronym() )
1505  + ',' + quotedValue( toProj4() )
1506  + ",0)"; // <-- is_geo shamelessly hard coded for now
1507  }
1508  sqlite3 *myDatabase;
1509  const char *myTail;
1510  sqlite3_stmt *myPreparedStatement;
1511  int myResult;
1512  //check the db is available
1513  myResult = sqlite3_open( QgsApplication::qgisUserDbFilePath().toUtf8().data(), &myDatabase );
1514  if ( myResult != SQLITE_OK )
1515  {
1516  QgsDebugMsg( QString( "Can't open or create database %1: %2" )
1518  sqlite3_errmsg( myDatabase ) ) );
1519  return false;
1520  }
1521  QgsDebugMsg( QString( "Update or insert sql \n%1" ).arg( mySql ) );
1522  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1523 
1524  qint64 return_id;
1525  if ( myResult == SQLITE_OK && sqlite3_step( myPreparedStatement ) == SQLITE_DONE )
1526  {
1527  QgsMessageLog::logMessage( QObject::tr( "Saved user CRS [%1]" ).arg( toProj4() ), QObject::tr( "CRS" ) );
1528 
1529  return_id = sqlite3_last_insert_rowid( myDatabase );
1530  setInternalId( return_id );
1531 
1532  //We add the just created user CRS to the list of recently used CRS
1533  QSettings settings;
1534  //QStringList recentProjections = settings.value( "/UI/recentProjections" ).toStringList();
1535  QStringList projectionsProj4 = settings.value( "/UI/recentProjectionsProj4" ).toStringList();
1536  QStringList projectionsAuthId = settings.value( "/UI/recentProjectionsAuthId" ).toStringList();
1537  //recentProjections.append();
1538  //settings.setValue( "/UI/recentProjections", recentProjections );
1539  projectionsProj4.append( toProj4() );
1540  projectionsAuthId.append( authid() );
1541  settings.setValue( "/UI/recentProjectionsProj4", projectionsProj4 );
1542  settings.setValue( "/UI/recentProjectionsAuthId", projectionsAuthId );
1543 
1544  }
1545  else
1546  return_id = -1;
1547  return return_id;
1548 }
1549 
1550 long QgsCoordinateReferenceSystem::getRecordCount()
1551 {
1552  sqlite3 *myDatabase;
1553  const char *myTail;
1554  sqlite3_stmt *myPreparedStatement;
1555  int myResult;
1556  long myRecordCount = 0;
1557  //check the db is available
1558  myResult = sqlite3_open_v2( QgsApplication::qgisUserDbFilePath().toUtf8().data(), &myDatabase, SQLITE_OPEN_READONLY, nullptr );
1559  if ( myResult != SQLITE_OK )
1560  {
1561  QgsDebugMsg( QString( "Can't open database: %1" ).arg( sqlite3_errmsg( myDatabase ) ) );
1562  return 0;
1563  }
1564  // Set up the query to retrieve the projection information needed to populate the ELLIPSOID list
1565  QString mySql = "select count(*) from tbl_srs";
1566  myResult = sqlite3_prepare( myDatabase, mySql.toUtf8(), mySql.toUtf8().length(), &myPreparedStatement, &myTail );
1567  // XXX Need to free memory from the error msg if one is set
1568  if ( myResult == SQLITE_OK )
1569  {
1570  if ( sqlite3_step( myPreparedStatement ) == SQLITE_ROW )
1571  {
1572  QString myRecordCountString = QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( myPreparedStatement, 0 ) ) );
1573  myRecordCount = myRecordCountString.toLong();
1574  }
1575  }
1576  // close the sqlite3 statement
1577  sqlite3_finalize( myPreparedStatement );
1578  sqlite3_close( myDatabase );
1579  return myRecordCount;
1580 }
1581 
1582 QString QgsCoordinateReferenceSystem::quotedValue( QString value )
1583 {
1584  value.replace( '\'', "''" );
1585  return value.prepend( '\'' ).append( '\'' );
1586 }
1587 
1588 // adapted from gdal/ogr/ogr_srs_dict.cpp
1589 bool QgsCoordinateReferenceSystem::loadWkts( QHash<int, QString> &wkts, const char *filename )
1590 {
1591  qDebug( "Loading %s", filename );
1592  const char *pszFilename = CPLFindFile( "gdal", filename );
1593  if ( !pszFilename )
1594  return false;
1595 
1596  QFile csv( pszFilename );
1597  if ( !csv.open( QIODevice::ReadOnly ) )
1598  return false;
1599 
1600  QTextStream lines( &csv );
1601 
1602  for ( ;; )
1603  {
1604  QString line = lines.readLine();
1605  if ( line.isNull() )
1606  break;
1607 
1608  if ( line.startsWith( '#' ) )
1609  {
1610  continue;
1611  }
1612  else if ( line.startsWith( "include " ) )
1613  {
1614  if ( !loadWkts( wkts, line.mid( 8 ).toUtf8() ) )
1615  break;
1616  }
1617  else
1618  {
1619  int pos = line.indexOf( ',' );
1620  if ( pos < 0 )
1621  return false;
1622 
1623  bool ok;
1624  int epsg = line.left( pos ).toInt( &ok );
1625  if ( !ok )
1626  return false;
1627 
1628  wkts.insert( epsg, line.mid( pos + 1 ) );
1629  }
1630  }
1631 
1632  csv.close();
1633 
1634  return true;
1635 }
1636 
1637 bool QgsCoordinateReferenceSystem::loadIDs( QHash<int, QString> &wkts )
1638 {
1639  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
1640 
1641  Q_FOREACH ( const QString& csv, QStringList() << "gcs.csv" << "pcs.csv" << "vertcs.csv" << "compdcs.csv" << "geoccs.csv" )
1642  {
1643  QString filename = CPLFindFile( "gdal", csv.toUtf8() );
1644 
1645  QFile f( filename );
1646  if ( !f.open( QIODevice::ReadOnly ) )
1647  continue;
1648 
1649  QTextStream lines( &f );
1650  int l = 0, n = 0;
1651 
1652  lines.readLine();
1653  for ( ;; )
1654  {
1655  l++;
1656  QString line = lines.readLine();
1657  if ( line.isNull() )
1658  break;
1659 
1660  int pos = line.indexOf( ',' );
1661  if ( pos < 0 )
1662  continue;
1663 
1664  bool ok;
1665  int epsg = line.left( pos ).toInt( &ok );
1666  if ( !ok )
1667  continue;
1668 
1669  // some CRS are known to fail (see http://trac.osgeo.org/gdal/ticket/2900)
1670  if ( epsg == 2218 || epsg == 2221 || epsg == 2296 || epsg == 2297 || epsg == 2298 || epsg == 2299 || epsg == 2300 || epsg == 2301 || epsg == 2302 ||
1671  epsg == 2303 || epsg == 2304 || epsg == 2305 || epsg == 2306 || epsg == 2307 || epsg == 2963 || epsg == 2985 || epsg == 2986 || epsg == 3052 ||
1672  epsg == 3053 || epsg == 3139 || epsg == 3144 || epsg == 3145 || epsg == 3173 || epsg == 3295 || epsg == 3993 || epsg == 4087 || epsg == 4088 ||
1673  epsg == 5017 || epsg == 5221 || epsg == 5224 || epsg == 5225 || epsg == 5514 || epsg == 5515 || epsg == 5516 || epsg == 5819 || epsg == 5820 ||
1674  epsg == 5821 || epsg == 6200 || epsg == 6201 || epsg == 6202 || epsg == 6244 || epsg == 6245 || epsg == 6246 || epsg == 6247 || epsg == 6248 ||
1675  epsg == 6249 || epsg == 6250 || epsg == 6251 || epsg == 6252 || epsg == 6253 || epsg == 6254 || epsg == 6255 || epsg == 6256 || epsg == 6257 ||
1676  epsg == 6258 || epsg == 6259 || epsg == 6260 || epsg == 6261 || epsg == 6262 || epsg == 6263 || epsg == 6264 || epsg == 6265 || epsg == 6266 ||
1677  epsg == 6267 || epsg == 6268 || epsg == 6269 || epsg == 6270 || epsg == 6271 || epsg == 6272 || epsg == 6273 || epsg == 6274 || epsg == 6275 ||
1678  epsg == 32600 || epsg == 32663 || epsg == 32700 )
1679  continue;
1680 
1681  if ( OSRImportFromEPSG( crs, epsg ) != OGRERR_NONE )
1682  {
1683  qDebug( "EPSG %d: not imported", epsg );
1684  continue;
1685  }
1686 
1687  char *wkt = nullptr;
1688  if ( OSRExportToWkt( crs, &wkt ) != OGRERR_NONE )
1689  {
1690  qWarning( "EPSG %d: not exported to WKT", epsg );
1691  continue;
1692  }
1693 
1694  wkts.insert( epsg, wkt );
1695  n++;
1696 
1697  OGRFree( wkt );
1698  }
1699 
1700  f.close();
1701 
1702  qDebug( "Loaded %d/%d from %s", n, l, filename.toUtf8().constData() );
1703  }
1704 
1705  OSRDestroySpatialReference( crs );
1706 
1707  return true;
1708 }
1709 
1711 {
1712  QString dbFilePath = QgsApplication::srsDbFilePath();
1713  syncDatumTransform( dbFilePath );
1714 
1715  int inserted = 0, updated = 0, deleted = 0, errors = 0;
1716 
1717  qDebug( "Load srs db from: %s", QgsApplication::srsDbFilePath().toLocal8Bit().constData() );
1718 
1719  sqlite3 *database;
1720  if ( sqlite3_open( dbFilePath.toUtf8().constData(), &database ) != SQLITE_OK )
1721  {
1722  qCritical( "Could not open database: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1723  return -1;
1724  }
1725 
1726  if ( sqlite3_exec( database, "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
1727  {
1728  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1729  return -1;
1730  }
1731 
1732  // fix up database, if not done already //
1733  if ( sqlite3_exec( database, "alter table tbl_srs add noupdate boolean", nullptr, nullptr, nullptr ) == SQLITE_OK )
1734  ( 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 );
1735 
1736  ( void )sqlite3_exec( database, "UPDATE tbl_srs SET srid=141001 WHERE srid=41001 AND auth_name='OSGEO' AND auth_id='41001'", nullptr, 0, 0 );
1737 
1738  OGRSpatialReferenceH crs = OSRNewSpatialReference( nullptr );
1739  const char *tail;
1740  sqlite3_stmt *select;
1741  char *errMsg = nullptr;
1742 
1743  QString proj4;
1744  QString sql;
1745  QHash<int, QString> wkts;
1746  loadIDs( wkts );
1747  loadWkts( wkts, "epsg.wkt" );
1748 
1749  qDebug( "%d WKTs loaded", wkts.count() );
1750 
1751  for ( QHash<int, QString>::const_iterator it = wkts.constBegin(); it != wkts.constEnd(); ++it )
1752  {
1753  QByteArray ba( it.value().toUtf8() );
1754  char *psz = ba.data();
1755  OGRErr ogrErr = OSRImportFromWkt( crs, &psz );
1756  if ( ogrErr != OGRERR_NONE )
1757  continue;
1758 
1759  if ( OSRExportToProj4( crs, &psz ) != OGRERR_NONE )
1760  continue;
1761 
1762  proj4 = psz;
1763  proj4 = proj4.trimmed();
1764 
1765  CPLFree( psz );
1766 
1767  if ( proj4.isEmpty() )
1768  continue;
1769 
1770  sql = QString( "SELECT parameters,noupdate FROM tbl_srs WHERE auth_name='EPSG' AND auth_id='%1'" ).arg( it.key() );
1771  if ( sqlite3_prepare( database, sql.toAscii(), sql.size(), &select, &tail ) != SQLITE_OK )
1772  {
1773  qCritical( "Could not prepare: %s [%s]\n", sql.toAscii().constData(), sqlite3_errmsg( database ) );
1774  continue;
1775  }
1776 
1777  QString srsProj4;
1778  if ( sqlite3_step( select ) == SQLITE_ROW )
1779  {
1780  srsProj4 = reinterpret_cast< const char * >( sqlite3_column_text( select, 0 ) );
1781 
1782  if ( QString::fromUtf8( reinterpret_cast< const char * >( sqlite3_column_text( select, 1 ) ) ).toInt() != 0 )
1783  continue;
1784  }
1785 
1786  sqlite3_finalize( select );
1787 
1788  if ( !srsProj4.isEmpty() )
1789  {
1790  if ( proj4 != srsProj4 )
1791  {
1792  errMsg = nullptr;
1793  sql = QString( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name='EPSG' AND auth_id=%2" ).arg( quotedValue( proj4 ) ).arg( it.key() );
1794 
1795  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, &errMsg ) != SQLITE_OK )
1796  {
1797  qCritical( "Could not execute: %s [%s/%s]\n",
1798  sql.toLocal8Bit().constData(),
1799  sqlite3_errmsg( database ),
1800  errMsg ? errMsg : "(unknown error)" );
1801  errors++;
1802  }
1803  else
1804  {
1805  updated++;
1806  QgsDebugMsgLevel( QString( "SQL: %1\n OLD:%2\n NEW:%3" ).arg( sql, srsProj4, proj4 ), 3 );
1807  }
1808  }
1809  }
1810  else
1811  {
1812  QRegExp projRegExp( "\\+proj=(\\S+)" );
1813  if ( projRegExp.indexIn( proj4 ) < 0 )
1814  {
1815  QgsDebugMsg( QString( "EPSG %1: no +proj argument found [%2]" ).arg( it.key() ).arg( proj4 ) );
1816  continue;
1817  }
1818 
1819  QRegExp ellipseRegExp( "\\+ellps=(\\S+)" );
1820  QString ellps;
1821  if ( ellipseRegExp.indexIn( proj4 ) >= 0 )
1822  {
1823  ellps = ellipseRegExp.cap( 1 );
1824  }
1825 
1826  QString name( OSRIsGeographic( crs ) ? OSRGetAttrValue( crs, "GEOCS", 0 ) : OSRGetAttrValue( crs, "PROJCS", 0 ) );
1827  if ( name.isEmpty() )
1828  name = QObject::tr( "Imported from GDAL" );
1829 
1830  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)" )
1831  .arg( quotedValue( name ),
1832  quotedValue( projRegExp.cap( 1 ) ),
1833  quotedValue( ellps ),
1834  quotedValue( proj4 ) )
1835  .arg( it.key() )
1836  .arg( OSRIsGeographic( crs ) );
1837 
1838  errMsg = nullptr;
1839  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
1840  {
1841  inserted++;
1842  }
1843  else
1844  {
1845  qCritical( "Could not execute: %s [%s/%s]\n",
1846  sql.toLocal8Bit().constData(),
1847  sqlite3_errmsg( database ),
1848  errMsg ? errMsg : "(unknown error)" );
1849  errors++;
1850 
1851  if ( errMsg )
1852  sqlite3_free( errMsg );
1853  }
1854  }
1855  }
1856 
1857  sql = "DELETE FROM tbl_srs WHERE auth_name='EPSG' AND NOT auth_id IN (";
1858  QString delim;
1860  for ( ; it != wkts.constEnd(); ++it )
1861  {
1862  sql += delim + QString::number( it.key() );
1863  delim = ',';
1864  }
1865  sql += ") AND NOT noupdate";
1866 
1867  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, nullptr ) == SQLITE_OK )
1868  {
1869  deleted = sqlite3_changes( database );
1870  }
1871  else
1872  {
1873  errors++;
1874  qCritical( "Could not execute: %s [%s]\n",
1875  sql.toLocal8Bit().constData(),
1876  sqlite3_errmsg( database ) );
1877  }
1878 
1879 #if !defined(PJ_VERSION) || PJ_VERSION!=470
1880  sql = QString( "select auth_name,auth_id,parameters from tbl_srs WHERE auth_name<>'EPSG' AND NOT deprecated AND NOT noupdate" );
1881  if ( sqlite3_prepare( database, sql.toAscii(), sql.size(), &select, &tail ) == SQLITE_OK )
1882  {
1883  while ( sqlite3_step( select ) == SQLITE_ROW )
1884  {
1885  const char *auth_name = reinterpret_cast< const char * >( sqlite3_column_text( select, 0 ) );
1886  const char *auth_id = reinterpret_cast< const char * >( sqlite3_column_text( select, 1 ) );
1887  const char *params = reinterpret_cast< const char * >( sqlite3_column_text( select, 2 ) );
1888 
1889  QString input = QString( "+init=%1:%2" ).arg( QString( auth_name ).toLower(), auth_id );
1890  projPJ pj = pj_init_plus( input.toAscii() );
1891  if ( !pj )
1892  {
1893  input = QString( "+init=%1:%2" ).arg( QString( auth_name ).toUpper(), auth_id );
1894  pj = pj_init_plus( input.toAscii() );
1895  }
1896 
1897  if ( pj )
1898  {
1899  char *def = pj_get_def( pj, 0 );
1900  if ( def )
1901  {
1902  proj4 = def;
1903  pj_dalloc( def );
1904 
1905  input.prepend( ' ' ).append( ' ' );
1906  if ( proj4.startsWith( input ) )
1907  {
1908  proj4 = proj4.mid( input.size() );
1909  proj4 = proj4.trimmed();
1910  }
1911 
1912  if ( proj4 != params )
1913  {
1914  sql = QString( "UPDATE tbl_srs SET parameters=%1 WHERE auth_name=%2 AND auth_id=%3" )
1915  .arg( quotedValue( proj4 ),
1916  quotedValue( auth_name ),
1917  quotedValue( auth_id ) );
1918 
1919  if ( sqlite3_exec( database, sql.toUtf8(), nullptr, nullptr, &errMsg ) == SQLITE_OK )
1920  {
1921  updated++;
1922  QgsDebugMsgLevel( QString( "SQL: %1\n OLD:%2\n NEW:%3" ).arg( sql, params, proj4 ), 3 );
1923  }
1924  else
1925  {
1926  qCritical( "Could not execute: %s [%s/%s]\n",
1927  sql.toLocal8Bit().constData(),
1928  sqlite3_errmsg( database ),
1929  errMsg ? errMsg : "(unknown error)" );
1930  errors++;
1931  }
1932  }
1933  }
1934  else
1935  {
1936  QgsDebugMsg( QString( "could not retrieve proj string for %1 from PROJ" ).arg( input ) );
1937  }
1938  }
1939  else
1940  {
1941  QgsDebugMsgLevel( QString( "could not retrieve crs for %1 from PROJ" ).arg( input ), 3 );
1942  }
1943 
1944  pj_free( pj );
1945  }
1946  }
1947  else
1948  {
1949  errors++;
1950  qCritical( "Could not execute: %s [%s]\n",
1951  sql.toLocal8Bit().constData(),
1952  sqlite3_errmsg( database ) );
1953  }
1954 #endif
1955 
1956  OSRDestroySpatialReference( crs );
1957 
1958  if ( sqlite3_exec( database, "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
1959  {
1960  qCritical( "Could not commit transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( database ) );
1961  return -1;
1962  }
1963 
1964  sqlite3_close( database );
1965 
1966  qWarning( "CRS update (inserted:%d updated:%d deleted:%d errors:%d)", inserted, updated, deleted, errors );
1967 
1968  if ( errors > 0 )
1969  return -errors;
1970  else
1971  return updated + inserted;
1972 }
1973 
1974 bool QgsCoordinateReferenceSystem::syncDatumTransform( const QString& dbPath )
1975 {
1976  const char *filename = CSVFilename( "datum_shift.csv" );
1977  FILE *fp = VSIFOpen( filename, "rb" );
1978  if ( !fp )
1979  {
1980  return false;
1981  }
1982 
1983  char **fieldnames = CSVReadParseLine( fp );
1984 
1985  // "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"
1986 
1987  struct
1988  {
1989  const char *src;
1990  const char *dst;
1991  int idx;
1992  } map[] =
1993  {
1994  // { "SEQ_KEY", "", -1 },
1995  { "SOURCE_CRS_CODE", "source_crs_code", -1 },
1996  { "TARGET_CRS_CODE", "target_crs_code", -1 },
1997  { "REMARKS", "remarks", -1 },
1998  { "COORD_OP_SCOPE", "scope", -1 },
1999  { "AREA_OF_USE_CODE", "area_of_use_code", -1 },
2000  // { "AREA_SOUTH_BOUND_LAT", "", -1 },
2001  // { "AREA_NORTH_BOUND_LAT", "", -1 },
2002  // { "AREA_WEST_BOUND_LON", "", -1 },
2003  // { "AREA_EAST_BOUND_LON", "", -1 },
2004  // { "SHOW_OPERATION", "", -1 },
2005  { "DEPRECATED", "deprecated", -1 },
2006  { "COORD_OP_METHOD_CODE", "coord_op_method_code", -1 },
2007  { "DX", "p1", -1 },
2008  { "DY", "p2", -1 },
2009  { "DZ", "p3", -1 },
2010  { "RX", "p4", -1 },
2011  { "RY", "p5", -1 },
2012  { "RZ", "p6", -1 },
2013  { "DS", "p7", -1 },
2014  { "PREFERRED", "preferred", -1 },
2015  { "COORD_OP_CODE", "coord_op_code", -1 },
2016  };
2017 
2018  QString update = "UPDATE tbl_datum_transform SET ";
2019  QString insert, values;
2020 
2021  int n = CSLCount( fieldnames );
2022 
2023  int idxid = -1, idxrx = -1, idxry = -1, idxrz = -1, idxmcode = -1;
2024  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2025  {
2026  bool last = i == sizeof( map ) / sizeof( *map ) - 1;
2027 
2028  map[i].idx = CSLFindString( fieldnames, map[i].src );
2029  if ( map[i].idx < 0 )
2030  {
2031  qWarning( "field %s not found", map[i].src );
2032  CSLDestroy( fieldnames );
2033  fclose( fp );
2034  return false;
2035  }
2036 
2037  if ( strcmp( map[i].src, "COORD_OP_CODE" ) == 0 )
2038  idxid = i;
2039  if ( strcmp( map[i].src, "RX" ) == 0 )
2040  idxrx = i;
2041  if ( strcmp( map[i].src, "RY" ) == 0 )
2042  idxry = i;
2043  if ( strcmp( map[i].src, "RZ" ) == 0 )
2044  idxrz = i;
2045  if ( strcmp( map[i].src, "COORD_OP_METHOD_CODE" ) == 0 )
2046  idxmcode = i;
2047 
2048  if ( i > 0 )
2049  {
2050  insert += ',';
2051  values += ',';
2052 
2053  if ( last )
2054  {
2055  update += " WHERE ";
2056  }
2057  else
2058  {
2059  update += ',';
2060  }
2061  }
2062 
2063  update += QString( "%1=%%2" ).arg( map[i].dst ).arg( i + 1 );
2064 
2065  insert += map[i].dst;
2066  values += QString( "%%1" ).arg( i + 1 );
2067  }
2068 
2069  insert = "INSERT INTO tbl_datum_transform(" + insert + ") VALUES (" + values + ')';
2070 
2071  QgsDebugMsgLevel( QString( "insert:%1" ).arg( insert ), 4 );
2072  QgsDebugMsgLevel( QString( "update:%1" ).arg( update ), 4 );
2073 
2074  CSLDestroy( fieldnames );
2075 
2076  Q_ASSERT( idxid >= 0 );
2077  Q_ASSERT( idxrx >= 0 );
2078  Q_ASSERT( idxry >= 0 );
2079  Q_ASSERT( idxrz >= 0 );
2080 
2081  sqlite3 *db;
2082  int openResult = sqlite3_open( dbPath.toUtf8().constData(), &db );
2083  if ( openResult != SQLITE_OK )
2084  {
2085  fclose( fp );
2086  return false;
2087  }
2088 
2089  if ( sqlite3_exec( db, "BEGIN TRANSACTION", nullptr, nullptr, nullptr ) != SQLITE_OK )
2090  {
2091  qCritical( "Could not begin transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( db ) );
2092  sqlite3_close( db );
2093  fclose( fp );
2094  return false;
2095  }
2096 
2097  QStringList v;
2098  v.reserve( sizeof( map ) / sizeof( *map ) );
2099 
2100  while ( !feof( fp ) )
2101  {
2102  char **values = CSVReadParseLine( fp );
2103 
2104  v.clear();
2105 
2106  if ( CSLCount( values ) < n )
2107  {
2108  qWarning( "Only %d columns", CSLCount( values ) );
2109  continue;
2110  }
2111 
2112  for ( unsigned int i = 0; i < sizeof( map ) / sizeof( *map ); i++ )
2113  {
2114  int idx = map[i].idx;
2115  Q_ASSERT( idx != -1 );
2116  Q_ASSERT( idx < n );
2117  v.insert( i, *values[ idx ] ? quotedValue( values[idx] ) : "NULL" );
2118  }
2119 
2120  //switch sign of rotation parameters. See http://trac.osgeo.org/proj/wiki/GenParms#towgs84-DatumtransformationtoWGS84
2121  if ( v.at( idxmcode ).compare( QLatin1String( "'9607'" ) ) == 0 )
2122  {
2123  v[ idxmcode ] = "'9606'";
2124  v[ idxrx ] = '\'' + qgsDoubleToString( -( v[ idxrx ].remove( '\'' ).toDouble() ) ) + '\'';
2125  v[ idxry ] = '\'' + qgsDoubleToString( -( v[ idxry ].remove( '\'' ).toDouble() ) ) + '\'';
2126  v[ idxrz ] = '\'' + qgsDoubleToString( -( v[ idxrz ].remove( '\'' ).toDouble() ) ) + '\'';
2127  }
2128 
2129  //entry already in db?
2130  sqlite3_stmt *stmt;
2131  QString cOpCode;
2132  QString sql = QString( "SELECT coord_op_code FROM tbl_datum_transform WHERE coord_op_code=%1" ).arg( v[ idxid ] );
2133  int prepareRes = sqlite3_prepare( db, sql.toAscii(), sql.size(), &stmt, nullptr );
2134  if ( prepareRes != SQLITE_OK )
2135  continue;
2136 
2137  if ( sqlite3_step( stmt ) == SQLITE_ROW )
2138  {
2139  cOpCode = reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) );
2140  }
2141  sqlite3_finalize( stmt );
2142 
2143  sql = cOpCode.isEmpty() ? insert : update;
2144  for ( int i = 0; i < v.size(); i++ )
2145  {
2146  sql = sql.arg( v[i] );
2147  }
2148 
2149  if ( sqlite3_exec( db, sql.toUtf8(), nullptr, nullptr, nullptr ) != SQLITE_OK )
2150  {
2151  qCritical( "SQL: %s", sql.toUtf8().constData() );
2152  qCritical( "Error: %s", sqlite3_errmsg( db ) );
2153  }
2154  }
2155 
2156  if ( sqlite3_exec( db, "COMMIT", nullptr, nullptr, nullptr ) != SQLITE_OK )
2157  {
2158  qCritical( "Could not commit transaction: %s [%s]\n", QgsApplication::srsDbFilePath().toLocal8Bit().constData(), sqlite3_errmsg( db ) );
2159  return false;
2160  }
2161 
2162  sqlite3_close( db );
2163  return true;
2164 }
2165 
2167 {
2168  if ( geographicFlag() )
2169  {
2170  return mAuthId;
2171  }
2172  else if ( mCRS )
2173  {
2174  return OSRGetAuthorityName( mCRS, "GEOGCS" ) + QLatin1String( ":" ) + OSRGetAuthorityCode( mCRS, "GEOGCS" );
2175  }
2176  else
2177  {
2178  return "";
2179  }
2180 }
2181 
2183 {
2184  QStringList projections;
2185 
2186  // Read settings from persistent storage
2187  QSettings settings;
2188  projections = settings.value( "/UI/recentProjections" ).toStringList();
2189  /*** The reading (above) of internal id from persistent storage should be removed sometime in the future */
2190  /*** This is kept now for backwards compatibility */
2191 
2192  QStringList projectionsProj4 = settings.value( "/UI/recentProjectionsProj4" ).toStringList();
2193  QStringList projectionsAuthId = settings.value( "/UI/recentProjectionsAuthId" ).toStringList();
2194  if ( projectionsAuthId.size() >= projections.size() )
2195  {
2196  // We had saved state with AuthId and Proj4. Use that instead
2197  // to find out the crs id
2198  projections.clear();
2199  for ( int i = 0; i < projectionsAuthId.size(); i++ )
2200  {
2201  // Create a crs from the EPSG
2203  crs.createFromOgcWmsCrs( projectionsAuthId.at( i ) );
2204  if ( ! crs.isValid() )
2205  {
2206  // Couldn't create from EPSG, try the Proj4 string instead
2207  if ( i >= projectionsProj4.size() || !crs.createFromProj4( projectionsProj4.at( i ) ) )
2208  {
2209  // No? Skip this entry
2210  continue;
2211  }
2212  //If the CRS can be created but do not correspond to a CRS in the database, skip it (for example a deleted custom CRS)
2213  if ( crs.srsid() == 0 )
2214  {
2215  continue;
2216  }
2217  }
2218  projections << QString::number( crs.srsid() );
2219  }
2220  }
2221  return projections;
2222 }
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
void validate()
Perform some validation on this 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
int size() const
QString toProj4() const
Returns a Proj4 string representation of this CRS.
bool createFromId(const long theId, CrsType theType=PostgisCrsId)
The QGis class provides global constants for use throughout the application.
Definition: qgis.h:36
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.
QString ellipsoidAcronym() const
Returns the ellipsoid acronym for the ellipsoid used by the CRS.
void append(const T &value)
QString fromUtf8(const char *str, int size)
const_iterator constEnd() const
QString text() const
QString geographicCRSAuthId() const
Returns auth id of related geographic CRS.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:34
bool operator!=(const QgsCoordinateReferenceSystem &theSrs) const
Overloaded != operator used to compare to CRS&#39;s.
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.
long postgisSrid() const
Returns PostGIS SRID for the CRS.
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
QGis::UnitType mapUnits() const
Returns the units for the projection used by the CRS.
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:361
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)
QString description() const
Returns the descriptive name of the CRS, eg "WGS 84" or "GDA 94 / Vicgrid94".
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()
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
bool operator==(const QgsCoordinateReferenceSystem &theSrs) const
Overloaded == operator used to compare to CRS&#39;s.
QString projectionAcronym() const
Returns the projection acronym for the projection used by the CRS.
virtual void close()
bool isNull() const
long toLong(bool *ok, int base) const
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:374
QByteArray toLatin1() const
QString mid(int position, int n) const
QStringList toStringList() const
void insert(int i, const T &value)
Class for storing a coordinate reference system (CRS)
QString toWkt() const
Returns a WKT representation of this CRS.
const int LAT_PREFIX_LEN
The length of the string "+lat_1=".
Definition: qgis.h:371
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.
typedef Iterator
void(* CUSTOM_CRS_VALIDATION)(QgsCoordinateReferenceSystem &)
bool writeXML(QDomNode &theNode, QDomDocument &theDoc) const
Stores state to the given Dom node in the given document.
QDomElement createElement(const QString &tagName)
long srsid() const
Returns the SrsId, if available.
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
QString authid() const
Returns the authority identifier for the CRS, which includes both the authority (eg EPSG) and the CRS...
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
bool geographicFlag() const
Returns whether the CRS is a geographic CRS.
bool axisInverted() const
Returns whether axis is inverted (eg.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QByteArray toUtf8() const