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