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