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