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