QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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 
@ Critical
Definition: qgis.h:92
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...
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...
bool mustReverseCoordinateOperation(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination) const
Returns true if the coordinate operation returned by calculateCoordinateOperation() for the source to...
static Q_DECL_DEPRECATED QString datumTransformToProj(int datumTransformId)
Returns a proj string representing the specified datumTransformId datum transform ID.
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).
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
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:798
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:797
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
void PJ_CONTEXT
Definition: qgsprojutils.h:151
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.
Contains datum transform information.
int sourceTransformId
ID for the datum transform to use when projecting from the source CRS.