QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 #include "qgsprojutils.h"
25 #include <proj.h>
26 #include <proj_experimental.h>
27 
28 #include <sqlite3.h>
29 
30 #include <QStringList>
31 
33 
34 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
35  const QgsCoordinateReferenceSystem &destinationCrs,
36  const QgsDatumTransform::GridDetails &grid )> QgsCoordinateTransformPrivate::sMissingRequiredGridHandler = nullptr;
37 
38 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
39  const QgsCoordinateReferenceSystem &destinationCrs,
40  const QgsDatumTransform::TransformDetails &preferredOperation,
41  const QgsDatumTransform::TransformDetails &availableOperation )> QgsCoordinateTransformPrivate::sMissingPreferredGridHandler = nullptr;
42 
43 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
44  const QgsCoordinateReferenceSystem &destinationCrs,
45  const QString &error )> QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler = nullptr;
46 
47 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
48  const QgsCoordinateReferenceSystem &destinationCrs,
49  const QgsDatumTransform::TransformDetails &desiredOperation )> QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler = nullptr;
50 
51 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
52  const QgsCoordinateReferenceSystem &destinationCrs )> QgsCoordinateTransformPrivate::sDynamicCrsToDynamicCrsWarningHandler = nullptr;
53 
54 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
55 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
56 {
57 }
59 
60 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
61 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source,
62  const QgsCoordinateReferenceSystem &destination,
63  const QgsCoordinateTransformContext &context )
64  : mSourceCRS( source )
65  , mDestCRS( destination )
66 {
67  if ( mSourceCRS != mDestCRS )
68  calculateTransforms( context );
69 }
71 
72 Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
73 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destDatumTransform )
74  : mSourceCRS( source )
75  , mDestCRS( destination )
76  , mSourceDatumTransform( sourceDatumTransform )
77  , mDestinationDatumTransform( destDatumTransform )
78 {
79 }
80 
81 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateTransformPrivate &other )
82  : QSharedData( other )
83  , mAvailableOpCount( other.mAvailableOpCount )
84  , mIsValid( other.mIsValid )
85  , mShortCircuit( other.mShortCircuit )
86  , mGeographicToWebMercator( other.mGeographicToWebMercator )
87  , mSourceCRS( other.mSourceCRS )
88  , mDestCRS( other.mDestCRS )
89  , mSourceDatumTransform( other.mSourceDatumTransform )
90  , mDestinationDatumTransform( other.mDestinationDatumTransform )
91  , mProjCoordinateOperation( other.mProjCoordinateOperation )
92  , mShouldReverseCoordinateOperation( other.mShouldReverseCoordinateOperation )
93  , mAllowFallbackTransforms( other.mAllowFallbackTransforms )
94  , mSourceIsDynamic( other.mSourceIsDynamic )
95  , mDestIsDynamic( other.mDestIsDynamic )
96  , mSourceCoordinateEpoch( other.mSourceCoordinateEpoch )
97  , mDestCoordinateEpoch( other.mDestCoordinateEpoch )
98  , mDefaultTime( other.mDefaultTime )
99  , mIsReversed( other.mIsReversed )
100  , mProjLock()
101  , mProjProjections()
102  , mProjFallbackProjections()
103 {
104 }
106 
108 QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
109 {
110  // free the proj objects
111  freeProj();
112 }
114 
115 bool QgsCoordinateTransformPrivate::checkValidity()
116 {
117  if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
118  {
119  invalidate();
120  return false;
121  }
122  return true;
123 }
124 
125 void QgsCoordinateTransformPrivate::invalidate()
126 {
127  mShortCircuit = true;
128  mIsValid = false;
129  mAvailableOpCount = -1;
130 }
131 
132 bool QgsCoordinateTransformPrivate::initialize()
133 {
134  invalidate();
135  if ( !mSourceCRS.isValid() )
136  {
137  // Pass through with no projection since we have no idea what the layer
138  // coordinates are and projecting them may not be appropriate
139  QgsDebugMsgLevel( QStringLiteral( "Source CRS is invalid!" ), 4 );
140  return false;
141  }
142 
143  if ( !mDestCRS.isValid() )
144  {
145  //No destination projection is set so we set the default output projection to
146  //be the same as input proj.
147  mDestCRS = mSourceCRS;
148  QgsDebugMsgLevel( QStringLiteral( "Destination CRS is invalid!" ), 4 );
149  return false;
150  }
151 
152  mIsValid = true;
153 
154  if ( mSourceCRS == mDestCRS )
155  {
156  // If the source and destination projection are the same, set the short
157  // circuit flag (no transform takes place)
158  mShortCircuit = true;
159  return true;
160  }
161 
162  mGeographicToWebMercator =
163  mSourceCRS.isGeographic() &&
164  mDestCRS.authid() == QLatin1String( "EPSG:3857" );
165 
166  mSourceIsDynamic = mSourceCRS.isDynamic();
167  mSourceCoordinateEpoch = mSourceCRS.coordinateEpoch();
168  mDestIsDynamic = mDestCRS.isDynamic();
169  mDestCoordinateEpoch = mDestCRS.coordinateEpoch();
170 
171  // Determine the default coordinate epoch.
172  // For time-dependent transformations, PROJ can currently only do
173  // staticCRS -> dynamicCRS or dynamicCRS -> staticCRS transformations, and
174  // in either case, the coordinate epoch of the dynamicCRS must be provided
175  // as the input time.
176  mDefaultTime = ( mSourceIsDynamic && !std::isnan( mSourceCoordinateEpoch ) && !mDestIsDynamic )
177  ? mSourceCoordinateEpoch
178  : ( mDestIsDynamic && !std::isnan( mDestCoordinateEpoch ) && !mSourceIsDynamic )
179  ? mDestCoordinateEpoch : std::numeric_limits< double >::quiet_NaN();
180 
181  if ( mSourceIsDynamic && mDestIsDynamic && !qgsNanCompatibleEquals( mSourceCoordinateEpoch, mDestCoordinateEpoch ) )
182  {
183  // transforms from dynamic crs to dynamic crs with different coordinate epochs are not yet supported by PROJ
184  if ( sDynamicCrsToDynamicCrsWarningHandler )
185  {
186  sDynamicCrsToDynamicCrsWarningHandler( mSourceCRS, mDestCRS );
187  }
188  }
189 
190  // init the projections (destination and source)
191  freeProj();
192 
193  // create proj projections for current thread
194  ProjData res = threadLocalProjData();
195 
196 #ifdef COORDINATE_TRANSFORM_VERBOSE
197  QgsDebugMsg( "From proj : " + mSourceCRS.toProj() );
198  QgsDebugMsg( "To proj : " + mDestCRS.toProj() );
199 #endif
200 
201  if ( !res )
202  mIsValid = false;
203 
204 #ifdef COORDINATE_TRANSFORM_VERBOSE
205  if ( mIsValid )
206  {
207  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
208  QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation for this layer was set to" ) );
209  QgsLogger::debug<QgsCoordinateReferenceSystem>( "Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
210  QgsLogger::debug<QgsCoordinateReferenceSystem>( "Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
211  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
212  }
213  else
214  {
215  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
216  QgsDebugMsg( QStringLiteral( "The OGR Coordinate transformation FAILED TO INITIALIZE!" ) );
217  QgsDebugMsg( QStringLiteral( "------------------------------------------------------------" ) );
218  }
219 #else
220  if ( !mIsValid )
221  {
222  QgsDebugMsg( QStringLiteral( "Coordinate transformation failed to initialize!" ) );
223  }
224 #endif
225 
226  // Transform must take place
227  mShortCircuit = false;
228 
229  return mIsValid;
230 }
231 
232 void QgsCoordinateTransformPrivate::calculateTransforms( const QgsCoordinateTransformContext &context )
233 {
234  // recalculate datum transforms from context
235  if ( mSourceCRS.isValid() && mDestCRS.isValid() )
236  {
237  mProjCoordinateOperation = context.calculateCoordinateOperation( mSourceCRS, mDestCRS );
238  mShouldReverseCoordinateOperation = context.mustReverseCoordinateOperation( mSourceCRS, mDestCRS );
239  mAllowFallbackTransforms = context.allowFallbackTransform( mSourceCRS, mDestCRS );
240  }
241  else
242  {
243  mProjCoordinateOperation.clear();
244  mShouldReverseCoordinateOperation = false;
245  mAllowFallbackTransforms = false;
246  }
247 }
248 
249 static void proj_collecting_logger( void *user_data, int /*level*/, const char *message )
250 {
251  QStringList *dest = reinterpret_cast< QStringList * >( user_data );
252  dest->append( QString( message ) );
253 }
254 
255 static void proj_logger( void *, int level, const char *message )
256 {
257 #ifndef QGISDEBUG
258  Q_UNUSED( message )
259 #endif
260  if ( level == PJ_LOG_ERROR )
261  {
262  QgsDebugMsg( QString( message ) );
263  }
264  else if ( level == PJ_LOG_DEBUG )
265  {
266  QgsDebugMsgLevel( QString( message ), 3 );
267  }
268 }
269 
270 ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
271 {
272  QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
273 
274  PJ_CONTEXT *context = QgsProjContext::get();
275  const QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
276 
277  if ( it != mProjProjections.constEnd() )
278  {
279  ProjData res = it.value();
280  return res;
281  }
282 
283  // proj projections don't exist yet, so we need to create
284  locker.changeMode( QgsReadWriteLocker::Write );
285 
286  // use a temporary proj error collector
287  QStringList projErrors;
288  proj_log_func( context, &projErrors, proj_collecting_logger );
289 
290  mIsReversed = false;
291 
293  if ( !mProjCoordinateOperation.isEmpty() )
294  {
295  transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
296  // Only use proj_coordoperation_is_instantiable() if PROJ networking is enabled.
297  // The reason is that proj_coordoperation_is_instantiable() in PROJ < 9.0
298  // does not work properly when a coordinate operation refers to a PROJ < 7 grid name (gtx/gsb)
299  // but the user has installed PROJ >= 7 GeoTIFF grids.
300  // Cf https://github.com/OSGeo/PROJ/pull/3025.
301  // When networking is not enabled, proj_create() will check that all grids are
302  // present, so proj_coordoperation_is_instantiable() is not necessary.
303  if ( !transform
304 #if PROJ_VERSION_MAJOR >= 7
305  || (
306  proj_context_is_network_enabled( context ) &&
307  !proj_coordoperation_is_instantiable( context, transform.get() ) )
308 #endif
309  )
310  {
311  if ( sMissingGridUsedByContextHandler )
312  {
314  desired.proj = mProjCoordinateOperation;
315  desired.accuracy = -1; //unknown, can't retrieve from proj as we can't instantiate the op
316  desired.grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
317  sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
318  }
319  else
320  {
321  const QString err = QObject::tr( "Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(),
322  mDestCRS.authid(),
323  mProjCoordinateOperation );
324  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
325  }
326 
327  transform.reset();
328  }
329  else
330  {
331  mIsReversed = mShouldReverseCoordinateOperation;
332  }
333  }
334 
335  QString nonAvailableError;
336  if ( !transform ) // fallback on default proj pathway
337  {
338  if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
339  {
340  proj_log_func( context, nullptr, nullptr );
341  return nullptr;
342  }
343 
344  PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context, nullptr );
345 
346  // We want to check ALL grids, not just those available for use
347  proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
348 
349  // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
350  proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
351 
352  if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
353  {
354  mAvailableOpCount = proj_list_get_count( ops );
355  if ( mAvailableOpCount < 1 )
356  {
357  // huh?
358  const int errNo = proj_context_errno( context );
359  if ( errNo && errNo != -61 )
360  {
361  nonAvailableError = QString( proj_errno_string( errNo ) );
362  }
363  else
364  {
365  nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
366  }
367  }
368  else if ( mAvailableOpCount == 1 )
369  {
370  // only a single operation available. Can we use it?
371  transform.reset( proj_list_get( context, ops, 0 ) );
372  if ( transform )
373  {
374  if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
375  {
376  // uh oh :( something is missing! find what it is
377  for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
378  {
379  const char *shortName = nullptr;
380  const char *fullName = nullptr;
381  const char *packageName = nullptr;
382  const char *url = nullptr;
383  int directDownload = 0;
384  int openLicense = 0;
385  int isAvailable = 0;
386  proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
387  if ( !isAvailable )
388  {
389  // found it!
390  if ( sMissingRequiredGridHandler )
391  {
392  QgsDatumTransform::GridDetails gridDetails;
393  gridDetails.shortName = QString( shortName );
394  gridDetails.fullName = QString( fullName );
395  gridDetails.packageName = QString( packageName );
396  gridDetails.url = QString( url );
397  gridDetails.directDownload = directDownload;
398  gridDetails.openLicense = openLicense;
399  gridDetails.isAvailable = isAvailable;
400  sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
401  }
402  else
403  {
404  const QString err = QObject::tr( "Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
405  mDestCRS.authid(),
406  shortName );
407  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
408  }
409  break;
410  }
411  }
412  }
413  else
414  {
415 
416  // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
417  transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
418  if ( !transform )
419  {
420  const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
421  mDestCRS.authid() );
422  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
423  }
424  }
425  }
426  }
427  else
428  {
429  // multiple operations available. Can we use the best one?
431  bool missingPreferred = false;
432  bool stillLookingForPreferred = true;
433  for ( int i = 0; i < mAvailableOpCount; ++ i )
434  {
435  transform.reset( proj_list_get( context, ops, i ) );
436  const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
437  if ( stillLookingForPreferred && transform && !isInstantiable )
438  {
439  // uh oh :( something is missing blocking us from the preferred operation!
441  if ( !candidate.proj.isEmpty() )
442  {
443  preferred = candidate;
444  missingPreferred = true;
445  stillLookingForPreferred = false;
446  }
447  }
448  if ( transform && isInstantiable )
449  {
450  // found one
451  break;
452  }
453  transform.reset();
454  }
455 
456  if ( transform && missingPreferred )
457  {
458  // found a transform, but it's not the preferred
460  if ( sMissingPreferredGridHandler )
461  {
462  sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
463  }
464  else
465  {
466  const QString err = QObject::tr( "Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
467  mDestCRS.authid(),
468  available.proj,
469  preferred.proj );
470  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
471  }
472  }
473 
474  // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
475  if ( transform )
476  transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
477  if ( !transform )
478  {
479  const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
480  mDestCRS.authid() );
481  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
482  }
483  }
484  proj_list_destroy( ops );
485  }
486  proj_operation_factory_context_destroy( operationContext );
487  }
488 
489  if ( !transform && nonAvailableError.isEmpty() )
490  {
491  const int errNo = proj_context_errno( context );
492  if ( errNo && errNo != -61 )
493  {
494  nonAvailableError = QString( proj_errno_string( errNo ) );
495  }
496  else if ( !projErrors.empty() )
497  {
498  nonAvailableError = projErrors.constLast();
499  }
500 
501  if ( nonAvailableError.isEmpty() )
502  {
503  nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
504  }
505  else
506  {
507  // strip proj prefixes from error string, so that it's nicer for users
508  nonAvailableError = nonAvailableError.remove( QStringLiteral( "internal_proj_create_operations: " ) );
509  }
510  }
511 
512  if ( !nonAvailableError.isEmpty() )
513  {
514  if ( sCoordinateOperationCreationErrorHandler )
515  {
516  sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
517  }
518  else
519  {
520  const QString err = QObject::tr( "Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
521  mDestCRS.authid(),
522  nonAvailableError );
523  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
524  }
525  }
526 
527  // reset logger to terminal output
528  proj_log_func( context, nullptr, proj_logger );
529 
530  if ( !transform )
531  {
532  // ouch!
533  return nullptr;
534  }
535 
536  ProjData res = transform.release();
537  mProjProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
538  return res;
539 }
540 
541 ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
542 {
543  QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
544 
545  PJ_CONTEXT *context = QgsProjContext::get();
546  const QMap < uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
547 
548  if ( it != mProjFallbackProjections.constEnd() )
549  {
550  ProjData res = it.value();
551  return res;
552  }
553 
554  // proj projections don't exist yet, so we need to create
555  locker.changeMode( QgsReadWriteLocker::Write );
556 
557  QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs_from_pj( context, mSourceCRS.projObject(), mDestCRS.projObject(), nullptr, nullptr ) );
558  if ( transform )
559  transform.reset( proj_normalize_for_visualization( QgsProjContext::get(), transform.get() ) );
560 
561  ProjData res = transform.release();
562  mProjFallbackProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
563  return res;
564 }
565 
566 void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::GridDetails & )> &handler )
567 {
568  sMissingRequiredGridHandler = handler;
569 }
570 
571 void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails &, const QgsDatumTransform::TransformDetails & )> &handler )
572 {
573  sMissingPreferredGridHandler = handler;
574 }
575 
576 void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
577 {
578  sCoordinateOperationCreationErrorHandler = handler;
579 }
580 
581 void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
582 {
583  sMissingGridUsedByContextHandler = handler;
584 }
585 
586 void QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem & )> &handler )
587 {
588  sDynamicCrsToDynamicCrsWarningHandler = handler;
589 }
590 
591 void QgsCoordinateTransformPrivate::freeProj()
592 {
593  const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
594  if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
595  return;
596  QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
597 
598  // During destruction of PJ* objects, the errno is set in the underlying
599  // context. Consequently the context attached to the PJ* must still exist !
600  // Which is not necessarily the case currently unfortunately. So
601  // create a temporary dummy context, and attach it to the PJ* before destroying
602  // it
603  PJ_CONTEXT *tmpContext = proj_context_create();
604  for ( ; it != mProjProjections.constEnd(); ++it )
605  {
606  proj_assign_context( it.value(), tmpContext );
607  proj_destroy( it.value() );
608  }
609 
610  it = mProjFallbackProjections.constBegin();
611  for ( ; it != mProjFallbackProjections.constEnd(); ++it )
612  {
613  proj_assign_context( it.value(), tmpContext );
614  proj_destroy( it.value() );
615  }
616 
617  proj_context_destroy( tmpContext );
618  mProjProjections.clear();
619  mProjFallbackProjections.clear();
620 }
621 
622 bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread( void *pj_context )
623 {
624  const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
625 
626  QMap < uintptr_t, ProjData >::iterator it = mProjProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
627  if ( it != mProjProjections.end() )
628  {
629  proj_destroy( it.value() );
630  mProjProjections.erase( it );
631  }
632 
633  it = mProjFallbackProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
634  if ( it != mProjFallbackProjections.end() )
635  {
636  proj_destroy( it.value() );
637  mProjFallbackProjections.erase( it );
638  }
639 
640  return mProjProjections.isEmpty();
641 }
642 
QgsProjContext::get
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
Definition: qgsprojutils.cpp:48
qgsreadwritelocker.h
QgsReadWriteLocker::Read
@ Read
Lock for read.
Definition: qgsreadwritelocker.h:75
QgsDatumTransform::GridDetails::directDownload
bool directDownload
true if direct download of grid is possible
Definition: qgsdatumtransform.h:143
QgsCoordinateTransformContext
Contains information about the context in which a coordinate transform is executed.
Definition: qgscoordinatetransformcontext.h:57
QgsDatumTransform::GridDetails::openLicense
bool openLicense
true if grid is available under an open license
Definition: qgsdatumtransform.h:145
QgsDebugMsgLevel
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
QgsDatumTransform::transformDetailsFromPj
static QgsDatumTransform::TransformDetails transformDetailsFromPj(PJ *op)
Returns the transform details for a Proj coordinate operation op.
Definition: qgsdatumtransform.cpp:307
qgsNanCompatibleEquals
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:2249
PJ_CONTEXT
struct projCtx_t PJ_CONTEXT
Definition: qgscoordinatereferencesystem.h:56
QgsProjUtils::proj_pj_unique_ptr
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
Definition: qgsprojutils.h:141
QgsProjUtils::gridsUsed
static QList< QgsDatumTransform::GridDetails > gridsUsed(const QString &proj)
Returns a list of grids used by the given proj string.
Definition: qgsprojutils.cpp:303
QgsDebugMsg
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsDatumTransform::GridDetails
Contains information about a projection transformation grid file.
Definition: qgsdatumtransform.h:132
QgsReadWriteLocker
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
Definition: qgsreadwritelocker.h:40
qgsapplication.h
Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2820
QgsCoordinateTransformContext::allowFallbackTransform
bool allowFallbackTransform(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if approximate "ballpark" transforms may be used when transforming between a source and ...
Definition: qgscoordinatetransformcontext.cpp:158
QgsDatumTransform::TransformDetails::grids
QList< QgsDatumTransform::GridDetails > grids
Contains a list of transform grids used by the operation.
Definition: qgsdatumtransform.h:248
QgsDatumTransform::GridDetails::url
QString url
Url to download grid from.
Definition: qgsdatumtransform.h:141
QgsDatumTransform::TransformDetails::accuracy
double accuracy
Transformation accuracy (in meters)
Definition: qgsdatumtransform.h:187
qgscoordinatetransform_p.h
QgsDatumTransform::GridDetails::fullName
QString fullName
Full name of transform grid.
Definition: qgsdatumtransform.h:137
QgsReadWriteLocker::Write
@ Write
Lock for write.
Definition: qgsreadwritelocker.h:76
QgsMessageLog::logMessage
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
Definition: qgsmessagelog.cpp:27
QgsDatumTransform::GridDetails::shortName
QString shortName
Short name of transform grid.
Definition: qgsdatumtransform.h:135
QgsDatumTransform::TransformDetails
Contains information about a coordinate transformation operation.
Definition: qgsdatumtransform.h:180
QgsCoordinateReferenceSystem
This class represents a coordinate reference system (CRS).
Definition: qgscoordinatereferencesystem.h:211
QgsCoordinateTransformContext::calculateCoordinateOperation
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...
Definition: qgscoordinatetransformcontext.cpp:142
qgsprojutils.h
QgsDatumTransform::GridDetails::isAvailable
bool isAvailable
true if grid is currently available for use
Definition: qgsdatumtransform.h:147
QgsDatumTransform::GridDetails::packageName
QString packageName
Name of package the grid is included within.
Definition: qgsdatumtransform.h:139
qgslogger.h
QgsCoordinateTransformContext::mustReverseCoordinateOperation
bool mustReverseCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if the coordinate operation returned by calculateCoordinateOperation() for the source to...
Definition: qgscoordinatetransformcontext.cpp:174
QgsDatumTransform::TransformDetails::proj
QString proj
Proj representation of transform operation.
Definition: qgsdatumtransform.h:183
Q_NOWARN_DEPRECATED_PUSH
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2819
qgsmessagelog.h