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