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