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