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