QGIS API Documentation  3.21.0-Master (5b68dc587e)
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  if ( !transform || !proj_coordoperation_is_instantiable( context, transform.get() ) )
292  {
293  if ( sMissingGridUsedByContextHandler )
294  {
296  desired.proj = mProjCoordinateOperation;
297  desired.accuracy = -1; //unknown, can't retrieve from proj as we can't instantiate the op
298  desired.grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
299  sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
300  }
301  else
302  {
303  const QString err = QObject::tr( "Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(),
304  mDestCRS.authid(),
305  mProjCoordinateOperation );
306  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
307  }
308 
309  transform.reset();
310  }
311  else
312  {
313  mIsReversed = mShouldReverseCoordinateOperation;
314  }
315  }
316 
317  QString nonAvailableError;
318  if ( !transform ) // fallback on default proj pathway
319  {
320  if ( !mSourceCRS.projObject() || ! mDestCRS.projObject() )
321  {
322  proj_log_func( context, nullptr, nullptr );
323  return nullptr;
324  }
325 
326  PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context, nullptr );
327 
328  // We want to check ALL grids, not just those available for use
329  proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
330 
331  // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
332  proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
333 
334  if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
335  {
336  mAvailableOpCount = proj_list_get_count( ops );
337  if ( mAvailableOpCount < 1 )
338  {
339  // huh?
340  const int errNo = proj_context_errno( context );
341  if ( errNo && errNo != -61 )
342  {
343  nonAvailableError = QString( proj_errno_string( errNo ) );
344  }
345  else
346  {
347  nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
348  }
349  }
350  else if ( mAvailableOpCount == 1 )
351  {
352  // only a single operation available. Can we use it?
353  transform.reset( proj_list_get( context, ops, 0 ) );
354  if ( transform )
355  {
356  if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
357  {
358  // uh oh :( something is missing! find what it is
359  for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
360  {
361  const char *shortName = nullptr;
362  const char *fullName = nullptr;
363  const char *packageName = nullptr;
364  const char *url = nullptr;
365  int directDownload = 0;
366  int openLicense = 0;
367  int isAvailable = 0;
368  proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
369  if ( !isAvailable )
370  {
371  // found it!
372  if ( sMissingRequiredGridHandler )
373  {
374  QgsDatumTransform::GridDetails gridDetails;
375  gridDetails.shortName = QString( shortName );
376  gridDetails.fullName = QString( fullName );
377  gridDetails.packageName = QString( packageName );
378  gridDetails.url = QString( url );
379  gridDetails.directDownload = directDownload;
380  gridDetails.openLicense = openLicense;
381  gridDetails.isAvailable = isAvailable;
382  sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
383  }
384  else
385  {
386  const QString err = QObject::tr( "Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
387  mDestCRS.authid(),
388  shortName );
389  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
390  }
391  break;
392  }
393  }
394  }
395  else
396  {
397 
398  // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
399  transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
400  if ( !transform )
401  {
402  const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
403  mDestCRS.authid() );
404  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
405  }
406  }
407  }
408  }
409  else
410  {
411  // multiple operations available. Can we use the best one?
413  bool missingPreferred = false;
414  bool stillLookingForPreferred = true;
415  for ( int i = 0; i < mAvailableOpCount; ++ i )
416  {
417  transform.reset( proj_list_get( context, ops, i ) );
418  const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
419  if ( stillLookingForPreferred && transform && !isInstantiable )
420  {
421  // uh oh :( something is missing blocking us from the preferred operation!
423  if ( !candidate.proj.isEmpty() )
424  {
425  preferred = candidate;
426  missingPreferred = true;
427  stillLookingForPreferred = false;
428  }
429  }
430  if ( transform && isInstantiable )
431  {
432  // found one
433  break;
434  }
435  transform.reset();
436  }
437 
438  if ( transform && missingPreferred )
439  {
440  // found a transform, but it's not the preferred
442  if ( sMissingPreferredGridHandler )
443  {
444  sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
445  }
446  else
447  {
448  const QString err = QObject::tr( "Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
449  mDestCRS.authid(),
450  available.proj,
451  preferred.proj );
452  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
453  }
454  }
455 
456  // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
457  if ( transform )
458  transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
459  if ( !transform )
460  {
461  const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
462  mDestCRS.authid() );
463  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
464  }
465  }
466  proj_list_destroy( ops );
467  }
468  proj_operation_factory_context_destroy( operationContext );
469  }
470 
471  if ( !transform && nonAvailableError.isEmpty() )
472  {
473  const int errNo = proj_context_errno( context );
474  if ( errNo && errNo != -61 )
475  {
476  nonAvailableError = QString( proj_errno_string( errNo ) );
477  }
478  else if ( !projErrors.empty() )
479  {
480  nonAvailableError = projErrors.constLast();
481  }
482 
483  if ( nonAvailableError.isEmpty() )
484  {
485  nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
486  }
487  else
488  {
489  // strip proj prefixes from error string, so that it's nicer for users
490  nonAvailableError = nonAvailableError.remove( QStringLiteral( "internal_proj_create_operations: " ) );
491  }
492  }
493 
494  if ( !nonAvailableError.isEmpty() )
495  {
496  if ( sCoordinateOperationCreationErrorHandler )
497  {
498  sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
499  }
500  else
501  {
502  const QString err = QObject::tr( "Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
503  mDestCRS.authid(),
504  nonAvailableError );
505  QgsMessageLog::logMessage( err, QString(), Qgis::MessageLevel::Critical );
506  }
507  }
508 
509  // reset logger to terminal output
510  proj_log_func( context, nullptr, proj_logger );
511 
512  if ( !transform )
513  {
514  // ouch!
515  return nullptr;
516  }
517 
518  ProjData res = transform.release();
519  mProjProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
520  return res;
521 }
522 
523 ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
524 {
525  QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
526 
527  PJ_CONTEXT *context = QgsProjContext::get();
528  const QMap < uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
529 
530  if ( it != mProjFallbackProjections.constEnd() )
531  {
532  ProjData res = it.value();
533  return res;
534  }
535 
536  // proj projections don't exist yet, so we need to create
537  locker.changeMode( QgsReadWriteLocker::Write );
538 
539  QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs_from_pj( context, mSourceCRS.projObject(), mDestCRS.projObject(), nullptr, nullptr ) );
540  if ( transform )
541  transform.reset( proj_normalize_for_visualization( QgsProjContext::get(), transform.get() ) );
542 
543  ProjData res = transform.release();
544  mProjFallbackProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
545  return res;
546 }
547 
548 void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::GridDetails & )> &handler )
549 {
550  sMissingRequiredGridHandler = handler;
551 }
552 
553 void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails &, const QgsDatumTransform::TransformDetails & )> &handler )
554 {
555  sMissingPreferredGridHandler = handler;
556 }
557 
558 void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
559 {
560  sCoordinateOperationCreationErrorHandler = handler;
561 }
562 
563 void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
564 {
565  sMissingGridUsedByContextHandler = handler;
566 }
567 
568 void QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem & )> &handler )
569 {
570  sDynamicCrsToDynamicCrsWarningHandler = handler;
571 }
572 
573 void QgsCoordinateTransformPrivate::freeProj()
574 {
575  const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
576  if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
577  return;
578  QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
579 
580  // During destruction of PJ* objects, the errno is set in the underlying
581  // context. Consequently the context attached to the PJ* must still exist !
582  // Which is not necessarily the case currently unfortunately. So
583  // create a temporary dummy context, and attach it to the PJ* before destroying
584  // it
585  PJ_CONTEXT *tmpContext = proj_context_create();
586  for ( ; it != mProjProjections.constEnd(); ++it )
587  {
588  proj_assign_context( it.value(), tmpContext );
589  proj_destroy( it.value() );
590  }
591 
592  it = mProjFallbackProjections.constBegin();
593  for ( ; it != mProjFallbackProjections.constEnd(); ++it )
594  {
595  proj_assign_context( it.value(), tmpContext );
596  proj_destroy( it.value() );
597  }
598 
599  proj_context_destroy( tmpContext );
600  mProjProjections.clear();
601  mProjFallbackProjections.clear();
602 }
603 
604 bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread( void *pj_context )
605 {
606  const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
607 
608  QMap < uintptr_t, ProjData >::iterator it = mProjProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
609  if ( it != mProjProjections.end() )
610  {
611  proj_destroy( it.value() );
612  mProjProjections.erase( it );
613  }
614 
615  it = mProjFallbackProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
616  if ( it != mProjFallbackProjections.end() )
617  {
618  proj_destroy( it.value() );
619  mProjFallbackProjections.erase( it );
620  }
621 
622  return mProjProjections.isEmpty();
623 }
624 
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:1730
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:1729
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:1218
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.