QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgscoordinatetransform_p.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscoordinatetransform_p.cpp
3  ----------------------------
4  begin : May 2017
5  copyright : (C) 2017 Nyall Dawson
6  email : nyall dot dawson at gmail dot com
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
19 #include "qgslogger.h"
20 #include "qgsapplication.h"
21 #include "qgsreadwritelocker.h"
22 #include "qgsmessagelog.h"
23 
24 #if PROJ_VERSION_MAJOR>=6
25 #include "qgsprojutils.h"
26 #include <proj.h>
27 #else
28 #include <proj_api.h>
29 #endif
30 
31 #include <sqlite3.h>
32 
33 #include <QStringList>
34 
36 
37 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
38  const QgsCoordinateReferenceSystem &destinationCrs,
39  const QgsDatumTransform::GridDetails &grid )> QgsCoordinateTransformPrivate::sMissingRequiredGridHandler = nullptr;
40 
41 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
42  const QgsCoordinateReferenceSystem &destinationCrs,
43  const QgsDatumTransform::TransformDetails &preferredOperation,
44  const QgsDatumTransform::TransformDetails &availableOperation )> QgsCoordinateTransformPrivate::sMissingPreferredGridHandler = nullptr;
45 
46 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
47  const QgsCoordinateReferenceSystem &destinationCrs,
48  const QString &error )> QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler = nullptr;
49 
50 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
51  const QgsCoordinateReferenceSystem &destinationCrs,
52  const QgsDatumTransform::TransformDetails &desiredOperation )> QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler = nullptr;
53 
54 #if PROJ_VERSION_MAJOR<6
55 #ifdef USE_THREAD_LOCAL
56 thread_local QgsProjContextStore QgsCoordinateTransformPrivate::mProjContext;
57 #else
58 QThreadStorage< QgsProjContextStore * > QgsCoordinateTransformPrivate::mProjContext;
59 #endif
60 
61 QgsProjContextStore::QgsProjContextStore()
62 {
63  context = pj_ctx_alloc();
64 }
65 
66 QgsProjContextStore::~QgsProjContextStore()
67 {
68  pj_ctx_free( context );
69 }
70 
71 #endif
72 
73 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
74 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
75 {
76 }
78 
79 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
80 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source,
81  const QgsCoordinateReferenceSystem &destination,
82  const QgsCoordinateTransformContext &context )
83  : mSourceCRS( source )
84  , mDestCRS( destination )
85 {
86  calculateTransforms( context );
87 }
89 
90 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
91 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destDatumTransform )
92  : mSourceCRS( source )
93  , mDestCRS( destination )
94  , mSourceDatumTransform( sourceDatumTransform )
95  , mDestinationDatumTransform( destDatumTransform )
96 {
97 }
98 
99 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateTransformPrivate &other )
100  : QSharedData( other )
101  , mIsValid( other.mIsValid )
102  , mShortCircuit( other.mShortCircuit )
103  , mSourceCRS( other.mSourceCRS )
104  , mDestCRS( other.mDestCRS )
105  , mSourceDatumTransform( other.mSourceDatumTransform )
106  , mDestinationDatumTransform( other.mDestinationDatumTransform )
107  , mProjCoordinateOperation( other.mProjCoordinateOperation )
108 {
109 #if PROJ_VERSION_MAJOR < 6
110  //must reinitialize to setup mSourceProjection and mDestinationProjection
111  initialize();
112 #endif
113 }
115 
117 QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
118 {
119  // free the proj objects
120  freeProj();
121 }
123 
124 bool QgsCoordinateTransformPrivate::checkValidity()
125 {
126  if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
127  {
128  invalidate();
129  return false;
130  }
131  return true;
132 }
133 
134 void QgsCoordinateTransformPrivate::invalidate()
135 {
136  mShortCircuit = true;
137  mIsValid = false;
138 }
139 
140 bool QgsCoordinateTransformPrivate::initialize()
141 {
142  invalidate();
143  if ( !mSourceCRS.isValid() )
144  {
145  // Pass through with no projection since we have no idea what the layer
146  // coordinates are and projecting them may not be appropriate
147  QgsDebugMsgLevel( QStringLiteral( "Source CRS is invalid!" ), 4 );
148  return false;
149  }
150 
151  if ( !mDestCRS.isValid() )
152  {
153  //No destination projection is set so we set the default output projection to
154  //be the same as input proj.
155  mDestCRS = mSourceCRS;
156  QgsDebugMsgLevel( QStringLiteral( "Destination CRS is invalid!" ), 4 );
157  return false;
158  }
159 
160  mIsValid = true;
161 
162  // init the projections (destination and source)
163  freeProj();
164 
165 #if PROJ_VERSION_MAJOR < 6
167  int sourceDatumTransform = mSourceDatumTransform;
168  int destDatumTransform = mDestinationDatumTransform;
169  bool useDefaultDatumTransform = ( sourceDatumTransform == - 1 && destDatumTransform == -1 );
170 
171  mSourceProjString = mSourceCRS.toProj4();
172  if ( !useDefaultDatumTransform )
173  {
174  mSourceProjString = stripDatumTransform( mSourceProjString );
175  }
176  if ( sourceDatumTransform != -1 )
177  {
178  mSourceProjString += ( ' ' + QgsDatumTransform::datumTransformToProj( sourceDatumTransform ) );
179  }
180 
181  mDestProjString = mDestCRS.toProj4();
182  if ( !useDefaultDatumTransform )
183  {
184  mDestProjString = stripDatumTransform( mDestProjString );
185  }
186  if ( destDatumTransform != -1 )
187  {
188  mDestProjString += ( ' ' + QgsDatumTransform::datumTransformToProj( destDatumTransform ) );
189  }
190 
191  if ( !useDefaultDatumTransform )
192  {
193  addNullGridShifts( mSourceProjString, mDestProjString, sourceDatumTransform, destDatumTransform );
194  }
196 #endif
197 
198  // create proj projections for current thread
199  ProjData res = threadLocalProjData();
200 
201 #ifdef COORDINATE_TRANSFORM_VERBOSE
202  QgsDebugMsg( "From proj : " + mSourceCRS.toProj4() );
203  QgsDebugMsg( "To proj : " + mDestCRS.toProj4() );
204 #endif
205 
206 #if PROJ_VERSION_MAJOR>=6
207  if ( !res )
208  mIsValid = false;
209 #else
210  if ( !res.first || !res.second )
211  {
212  mIsValid = false;
213  }
214 #endif
215 
216 #ifdef COORDINATE_TRANSFORM_VERBOSE
217  if ( mIsValid )
218  {
219  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
220  QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation for this layer was set to" ) );
221  QgsLogger::debug<QgsCoordinateReferenceSystem>( "Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
222  QgsLogger::debug<QgsCoordinateReferenceSystem>( "Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
223  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
224  }
225  else
226  {
227  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
228  QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation FAILED TO INITIALIZE!" ) );
229  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
230  }
231 #else
232  if ( !mIsValid )
233  {
234  QgsDebugMsg( QStringLiteral( "Coordinate transformation failed to initialize!" ) );
235  }
236 #endif
237 
238  //XXX todo overload == operator for QgsCoordinateReferenceSystem
239  //at the moment srs.parameters contains the whole proj def...soon it won't...
240  //if (mSourceCRS->toProj4() == mDestCRS->toProj4())
241  if ( mSourceCRS == mDestCRS )
242  {
243  // If the source and destination projection are the same, set the short
244  // circuit flag (no transform takes place)
245  mShortCircuit = true;
246  QgsDebugMsgLevel( QStringLiteral( "Source/Dest CRS equal, shortcircuit is set." ), 3 );
247  }
248  else
249  {
250  // Transform must take place
251  mShortCircuit = false;
252  QgsDebugMsgLevel( QStringLiteral( "Source/Dest CRS not equal, shortcircuit is not set." ), 3 );
253  }
254  return mIsValid;
255 }
256 
257 void QgsCoordinateTransformPrivate::calculateTransforms( const QgsCoordinateTransformContext &context )
258 {
259  // recalculate datum transforms from context
260 #if PROJ_VERSION_MAJOR >= 6
261  mProjCoordinateOperation = context.calculateCoordinateOperation( mSourceCRS, mDestCRS );
262 #else
264  QgsDatumTransform::TransformPair transforms = context.calculateDatumTransforms( mSourceCRS, mDestCRS );
265  mSourceDatumTransform = transforms.sourceTransformId;
266  mDestinationDatumTransform = transforms.destinationTransformId;
268 #endif
269 }
270 
271 #if PROJ_VERSION_MAJOR>=6
272 static void proj_collecting_logger( void *user_data, int /*level*/, const char *message )
273 {
274  QStringList *dest = reinterpret_cast< QStringList * >( user_data );
275  dest->append( QString( message ) );
276 }
277 
278 static void proj_logger( void *, int level, const char *message )
279 {
280  if ( level == PJ_LOG_ERROR )
281  {
282  QgsDebugMsg( QString( message ) );
283  }
284  else if ( level == PJ_LOG_DEBUG )
285  {
286  QgsDebugMsgLevel( QString( message ), 3 );
287  }
288 }
289 #endif
290 
291 ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
292 {
293  QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
294 
295 #if PROJ_VERSION_MAJOR>=6
296  PJ_CONTEXT *context = QgsProjContext::get();
297  QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
298 #else
299 #ifdef USE_THREAD_LOCAL
300  QMap < uintptr_t, QPair< projPJ, projPJ > >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( mProjContext.get() ) );
301 #else
302  projCtx pContext = nullptr;
303  if ( mProjContext.hasLocalData() )
304  {
305  pContext = mProjContext.localData()->get();
306  }
307  else
308  {
309  mProjContext.setLocalData( new QgsProjContextStore() );
310  pContext = mProjContext.localData()->get();
311  }
312  QMap < uintptr_t, QPair< projPJ, projPJ > >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( pContext ) );
313 #endif
314 #endif
315 
316  if ( it != mProjProjections.constEnd() )
317  {
318  ProjData res = it.value();
319  return res;
320  }
321 
322  // proj projections don't exist yet, so we need to create
323  locker.changeMode( QgsReadWriteLocker::Write );
324 
325 #if PROJ_VERSION_MAJOR>=6
326  // use a temporary proj error collector
327  QStringList projErrors;
328  proj_log_func( context, &projErrors, proj_collecting_logger );
329 
330  QgsProjUtils::proj_pj_unique_ptr transform;
331  if ( !mProjCoordinateOperation.isEmpty() )
332  {
333  transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
334  if ( !transform || !proj_coordoperation_is_instantiable( context, transform.get() ) )
335  {
336  if ( sMissingGridUsedByContextHandler )
337  {
339  desired.proj = mProjCoordinateOperation;
340  desired.accuracy = -1; //unknown, can't retrieve from proj as we can't instantiate the op
341  desired.grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
342  sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
343  }
344  else
345  {
346  const QString err = QObject::tr( "Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(),
347  mDestCRS.authid(),
348  mProjCoordinateOperation );
349  QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
350  }
351 
352  transform.reset();
353  }
354  }
355 
356  QString nonAvailableError;
357  if ( !transform ) // fallback on default proj pathway
358  {
359  if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
360  {
361  proj_log_func( context, nullptr, nullptr );
362  return nullptr;
363  }
364 
365  PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context, nullptr );
366 
367  // We want to check ALL grids, not just those available for use
368  proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
369 
370  // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
371  proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
372 
373  if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
374  {
375  int count = proj_list_get_count( ops );
376  if ( count < 1 )
377  {
378  // huh?
379  int errNo = proj_context_errno( context );
380  if ( errNo && errNo != -61 )
381  {
382  nonAvailableError = QString( proj_errno_string( errNo ) );
383  }
384  else
385  {
386  nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
387  }
388  }
389  else if ( count == 1 )
390  {
391  // only a single operation available. Can we use it?
392  transform.reset( proj_list_get( context, ops, 0 ) );
393  if ( transform )
394  {
395  if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
396  {
397  // uh oh :( something is missing! find what it is
398  for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
399  {
400  const char *shortName = nullptr;
401  const char *fullName = nullptr;
402  const char *packageName = nullptr;
403  const char *url = nullptr;
404  int directDownload = 0;
405  int openLicense = 0;
406  int isAvailable = 0;
407  proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
408  if ( !isAvailable )
409  {
410  // found it!
411  if ( sMissingRequiredGridHandler )
412  {
413  QgsDatumTransform::GridDetails gridDetails;
414  gridDetails.shortName = QString( shortName );
415  gridDetails.fullName = QString( fullName );
416  gridDetails.packageName = QString( packageName );
417  gridDetails.url = QString( url );
418  gridDetails.directDownload = directDownload;
419  gridDetails.openLicense = openLicense;
420  gridDetails.isAvailable = isAvailable;
421  sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
422  }
423  else
424  {
425  const QString err = QObject::tr( "Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
426  mDestCRS.authid(),
427  shortName );
428  QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
429  }
430  break;
431  }
432  }
433  }
434  else
435  {
436 
437  // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
438  transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
439  if ( !transform )
440  {
441  const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
442  mDestCRS.authid() );
443  QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
444  }
445  }
446  }
447  }
448  else
449  {
450  // multiple operations available. Can we use the best one?
452  bool missingPreferred = false;
453  for ( int i = 0; i < count; ++ i )
454  {
455  transform.reset( proj_list_get( context, ops, i ) );
456  const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
457  if ( i == 0 && transform && !isInstantiable )
458  {
459  // uh oh :( something is missing blocking us from the preferred operation!
460  missingPreferred = true;
461  preferred = QgsDatumTransform::transformDetailsFromPj( transform.get() );
462  }
463  if ( transform && isInstantiable )
464  {
465  // found one
466  break;
467  }
468  transform.reset();
469  }
470 
471  if ( transform && missingPreferred )
472  {
473  // found a transform, but it's not the preferred
474  QgsDatumTransform::TransformDetails available = QgsDatumTransform::transformDetailsFromPj( transform.get() );
475  if ( sMissingPreferredGridHandler )
476  {
477  sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
478  }
479  else
480  {
481  const QString err = QObject::tr( "Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
482  mDestCRS.authid(),
483  available.proj,
484  preferred.proj );
485  QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
486  }
487  }
488 
489  // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
490  if ( transform )
491  transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
492  if ( !transform )
493  {
494  const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
495  mDestCRS.authid() );
496  QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
497  }
498  }
499  proj_list_destroy( ops );
500  }
501  proj_operation_factory_context_destroy( operationContext );
502  }
503 
504  if ( !transform && nonAvailableError.isEmpty() )
505  {
506  int errNo = proj_context_errno( context );
507  if ( errNo && errNo != -61 )
508  {
509  nonAvailableError = QString( proj_errno_string( errNo ) );
510  }
511  else if ( !projErrors.empty() )
512  {
513  nonAvailableError = projErrors.constLast();
514  }
515 
516  if ( nonAvailableError.isEmpty() )
517  {
518  nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
519  }
520  else
521  {
522  // strip proj prefixes from error string, so that it's nicer for users
523  nonAvailableError = nonAvailableError.remove( QStringLiteral( "internal_proj_create_operations: " ) );
524  }
525  }
526 
527  if ( !nonAvailableError.isEmpty() )
528  {
529  if ( sCoordinateOperationCreationErrorHandler )
530  {
531  sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
532  }
533  else
534  {
535  const QString err = QObject::tr( "Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
536  mDestCRS.authid(),
537  nonAvailableError );
538  QgsMessageLog::logMessage( err, QString(), Qgis::Critical );
539  }
540  }
541 
542  // reset logger to terminal output
543  proj_log_func( context, nullptr, proj_logger );
544 
545  if ( !transform )
546  {
547  // ouch!
548  return nullptr;
549  }
550 
551  ProjData res = transform.release();
552  mProjProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
553 #else
554 #ifdef USE_THREAD_LOCAL
556  QPair<projPJ, projPJ> res = qMakePair( pj_init_plus_ctx( mProjContext.get(), mSourceProjString.toUtf8() ),
557  pj_init_plus_ctx( mProjContext.get(), mDestProjString.toUtf8() ) );
559  mProjProjections.insert( reinterpret_cast< uintptr_t>( mProjContext.get() ), res );
560 #else
561  QPair<projPJ, projPJ> res = qMakePair( pj_init_plus_ctx( pContext, mSourceProjString.toUtf8() ),
562  pj_init_plus_ctx( pContext, mDestProjString.toUtf8() ) );
563  mProjProjections.insert( reinterpret_cast< uintptr_t>( pContext ), res );
564 #endif
565 #endif
566  return res;
567 }
568 
569 void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::GridDetails & )> &handler )
570 {
571  sMissingRequiredGridHandler = handler;
572 }
573 
574 void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails &, const QgsDatumTransform::TransformDetails & )> &handler )
575 {
576  sMissingPreferredGridHandler = handler;
577 }
578 
579 void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
580 {
581  sCoordinateOperationCreationErrorHandler = handler;
582 }
583 
584 void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
585 {
586  sMissingGridUsedByContextHandler = handler;
587 }
588 
589 #if PROJ_VERSION_MAJOR<6
590 QString QgsCoordinateTransformPrivate::stripDatumTransform( const QString &proj4 ) const
591 {
592  QStringList parameterSplit = proj4.split( '+', QString::SkipEmptyParts );
593  QString currentParameter;
594  QString newProjString;
595 
596  for ( int i = 0; i < parameterSplit.size(); ++i )
597  {
598  currentParameter = parameterSplit.at( i );
599  if ( !currentParameter.startsWith( QLatin1String( "towgs84" ), Qt::CaseInsensitive )
600  && !currentParameter.startsWith( QLatin1String( "nadgrids" ), Qt::CaseInsensitive ) )
601  {
602  newProjString.append( '+' );
603  newProjString.append( currentParameter );
604  newProjString.append( ' ' );
605  }
606  }
607  return newProjString;
608 }
609 
610 void QgsCoordinateTransformPrivate::addNullGridShifts( QString &srcProjString, QString &destProjString,
611  int sourceDatumTransform, int destinationDatumTransform ) const
612 {
613  //if one transformation uses ntv2, the other one needs to be null grid shift
614  if ( destinationDatumTransform == -1 && srcProjString.contains( QLatin1String( "+nadgrids" ) ) ) //add null grid if source transformation is ntv2
615  {
616  destProjString += QLatin1String( " +nadgrids=@null" );
617  return;
618  }
619  if ( sourceDatumTransform == -1 && destProjString.contains( QLatin1String( "+nadgrids" ) ) )
620  {
621  srcProjString += QLatin1String( " +nadgrids=@null" );
622  return;
623  }
624 
625  //add null shift grid for google mercator
626  //(see e.g. http://trac.osgeo.org/proj/wiki/FAQ#ChangingEllipsoidWhycantIconvertfromWGS84toGoogleEarthVirtualGlobeMercator)
627  if ( mSourceCRS.authid().compare( QLatin1String( "EPSG:3857" ), Qt::CaseInsensitive ) == 0 && sourceDatumTransform == -1 )
628  {
629  srcProjString += QLatin1String( " +nadgrids=@null" );
630  }
631  if ( mDestCRS.authid().compare( QLatin1String( "EPSG:3857" ), Qt::CaseInsensitive ) == 0 && destinationDatumTransform == -1 )
632  {
633  destProjString += QLatin1String( " +nadgrids=@null" );
634  }
635 }
636 #endif
637 
638 void QgsCoordinateTransformPrivate::freeProj()
639 {
640  QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
641  QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
642  for ( ; it != mProjProjections.constEnd(); ++it )
643  {
644 #if PROJ_VERSION_MAJOR>=6
645  proj_destroy( it.value() );
646 #else
647  pj_free( it.value().first );
648  pj_free( it.value().second );
649 #endif
650  }
651  mProjProjections.clear();
652 }
653 
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:624
Contains information about a projection transformation grid file.
QString packageName
Name of package the grid is included within.
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
QString fullName
Full name of transform grid.
bool openLicense
true if grid is available under an open license
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QString shortName
Short name of transform grid.
bool isAvailable
true if grid is currently available for use
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
QString url
Url to download grid from.
Contains information about the context in which a coordinate transform is executed.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:625
QString calculateCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns the Proj coordinate operation string to use when transforming from the specified source CRS t...
Q_DECL_DEPRECATED QgsDatumTransform::TransformPair calculateDatumTransforms(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns the pair of source and destination datum transforms to use for a transform from the specified...
Contains datum transform information.
void PJ_CONTEXT
Definition: qgsprojutils.h:135
QList< QgsDatumTransform::GridDetails > grids
Contains a list of transform grids used by the operation.
This class represents a coordinate reference system (CRS).
static Q_DECL_DEPRECATED QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID...
double accuracy
Transformation accuracy (in meters)
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
bool directDownload
true if direct download of grid is possible
Contains information about a coordinate transformation operation.
QString proj
Proj representation of transform operation.
int sourceTransformId
ID for the datum transform to use when projecting from the source CRS.