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