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