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