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