QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 
This class represents a coordinate reference system (CRS).
Contains information about the context in which a coordinate transform is executed.
bool allowFallbackTransform(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if approximate "ballpark" transforms may be used when transforming between a source and ...
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...
bool mustReverseCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if the coordinate operation returned by calculateCoordinateOperation() for the source to...
static QgsDatumTransform::TransformDetails transformDetailsFromPj(PJ *op)
Returns the transform details for a Proj coordinate operation op.
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).
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
static QList< QgsDatumTransform::GridDetails > gridsUsed(const QString &proj)
Returns a list of grids used by the given proj string.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
Definition: qgsprojutils.h:141
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
@ Write
Lock for write.
@ Read
Lock for read.
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:2065
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:2064
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:1562
struct projCtx_t PJ_CONTEXT
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Contains information about a projection transformation grid file.
QString shortName
Short name of transform grid.
bool isAvailable
true if grid is currently available for use
QString fullName
Full name of transform grid.
bool directDownload
true if direct download of grid is possible
QString packageName
Name of package the grid is included within.
QString url
Url to download grid from.
bool openLicense
true if grid is available under an open license
Contains information about a coordinate transformation operation.
double accuracy
Transformation accuracy (in meters)
QString proj
Proj representation of transform operation.
QList< QgsDatumTransform::GridDetails > grids
Contains a list of transform grids used by the operation.