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