QGIS API Documentation 4.0.0-Norrköping (1ddcee3d0e4)
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
20#include <proj.h>
21#include <proj_experimental.h>
22#include <sqlite3.h>
23
24#include "qgsapplication.h"
25#include "qgslogger.h"
26#include "qgsmessagelog.h"
27#include "qgsprojutils.h"
28#include "qgsreadwritelocker.h"
29
30#include <QString>
31#include <QStringList>
32
33using namespace Qt::StringLiterals;
34
36
37std::function< void( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsDatumTransform::GridDetails &grid )>
38 QgsCoordinateTransformPrivate::sMissingRequiredGridHandler = nullptr;
39
40std::function<
41 void( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsDatumTransform::TransformDetails &preferredOperation, const QgsDatumTransform::TransformDetails &availableOperation )>
42 QgsCoordinateTransformPrivate::sMissingPreferredGridHandler = nullptr;
43
44std::function< void( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &error )>
45 QgsCoordinateTransformPrivate::sCoordinateOperationCreationErrorHandler = nullptr;
46
47std::function< void( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsDatumTransform::TransformDetails &desiredOperation )>
48 QgsCoordinateTransformPrivate::sMissingGridUsedByContextHandler = nullptr;
49
50std::function< void( const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs )> QgsCoordinateTransformPrivate::sDynamicCrsToDynamicCrsWarningHandler = nullptr;
51
52Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
53 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate()
54{}
56
57Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
58 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, const QgsCoordinateTransformContext &context )
59 : mSourceCRS( source )
60 , mDestCRS( destination )
61{
62 if ( mSourceCRS != mDestCRS )
63 calculateTransforms( context );
64}
66
67Q_NOWARN_DEPRECATED_PUSH // because of deprecated members
68 QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destDatumTransform )
69 : mSourceCRS( source )
70 , mDestCRS( destination )
71 , mSourceDatumTransform( sourceDatumTransform )
72 , mDestinationDatumTransform( destDatumTransform )
73{}
74
75QgsCoordinateTransformPrivate::QgsCoordinateTransformPrivate( const QgsCoordinateTransformPrivate &other )
76 : QSharedData( other )
77 , mAvailableOpCount( other.mAvailableOpCount )
78 , mIsValid( other.mIsValid )
79 , mShortCircuit( other.mShortCircuit )
80 , mGeographicToWebMercator( other.mGeographicToWebMercator )
81 , mHasVerticalComponent( other.mHasVerticalComponent )
82 , mSourceCRS( other.mSourceCRS )
83 , mDestCRS( other.mDestCRS )
84 , mSourceDatumTransform( other.mSourceDatumTransform )
85 , mDestinationDatumTransform( other.mDestinationDatumTransform )
86 , mProjCoordinateOperation( other.mProjCoordinateOperation )
87 , mShouldReverseCoordinateOperation( other.mShouldReverseCoordinateOperation )
88 , mAllowFallbackTransforms( other.mAllowFallbackTransforms )
89 , mSourceIsDynamic( other.mSourceIsDynamic )
90 , mDestIsDynamic( other.mDestIsDynamic )
91 , mSourceCoordinateEpoch( other.mSourceCoordinateEpoch )
92 , mDestCoordinateEpoch( other.mDestCoordinateEpoch )
93 , mDefaultTime( other.mDefaultTime )
94 , mIsReversed( other.mIsReversed )
95 , mProjLock()
96 , mProjProjections()
97 , mProjFallbackProjections()
98{}
100
102QgsCoordinateTransformPrivate::~QgsCoordinateTransformPrivate()
103{
104 // free the proj objects
105 freeProj();
106}
108
109bool QgsCoordinateTransformPrivate::checkValidity()
110{
111 if ( !mSourceCRS.isValid() || !mDestCRS.isValid() )
112 {
113 invalidate();
114 return false;
115 }
116 return true;
117}
118
119void QgsCoordinateTransformPrivate::invalidate()
120{
121 mShortCircuit = true;
122 mIsValid = false;
123 mAvailableOpCount = -1;
124}
125
126bool QgsCoordinateTransformPrivate::initialize()
127{
128 invalidate();
129 if ( !mSourceCRS.isValid() )
130 {
131 // Pass through with no projection since we have no idea what the layer
132 // coordinates are and projecting them may not be appropriate
133 QgsDebugMsgLevel( u"Source CRS is invalid!"_s, 4 );
134 return false;
135 }
136
137 if ( !mDestCRS.isValid() )
138 {
139 //No destination projection is set so we set the default output projection to
140 //be the same as input proj.
141 mDestCRS = mSourceCRS;
142 QgsDebugMsgLevel( u"Destination CRS is invalid!"_s, 4 );
143 return false;
144 }
145
146 mIsValid = true;
147
148 if ( mSourceCRS == mDestCRS )
149 {
150 // If the source and destination projection are the same, set the short
151 // circuit flag (no transform takes place)
152 mShortCircuit = true;
153 return true;
154 }
155
156 mGeographicToWebMercator = mSourceCRS.isGeographic() && mDestCRS.authid() == "EPSG:3857"_L1;
157
158 mHasVerticalComponent = mSourceCRS.hasVerticalAxis() && mDestCRS.hasVerticalAxis();
159
160 mSourceIsDynamic = mSourceCRS.isDynamic();
161 mSourceCoordinateEpoch = mSourceCRS.coordinateEpoch();
162 mDestIsDynamic = mDestCRS.isDynamic();
163 mDestCoordinateEpoch = mDestCRS.coordinateEpoch();
164
165 // Determine the default coordinate epoch.
166 // For time-dependent transformations, PROJ can currently only do
167 // staticCRS -> dynamicCRS or dynamicCRS -> staticCRS transformations, and
168 // in either case, the coordinate epoch of the dynamicCRS must be provided
169 // as the input time.
170 mDefaultTime = ( mSourceIsDynamic && !std::isnan( mSourceCoordinateEpoch ) && !mDestIsDynamic ) ? mSourceCoordinateEpoch
171 : ( mDestIsDynamic && !std::isnan( mDestCoordinateEpoch ) && !mSourceIsDynamic ) ? mDestCoordinateEpoch
172 : std::numeric_limits< double >::quiet_NaN();
173
174 if ( mSourceIsDynamic && mDestIsDynamic && !qgsNanCompatibleEquals( mSourceCoordinateEpoch, mDestCoordinateEpoch ) )
175 {
176 // transforms from dynamic crs to dynamic crs with different coordinate epochs are not yet supported by PROJ
177 if ( sDynamicCrsToDynamicCrsWarningHandler )
178 {
179 sDynamicCrsToDynamicCrsWarningHandler( mSourceCRS, mDestCRS );
180 }
181 }
182
183 // init the projections (destination and source)
184 freeProj();
185
186 // create proj projections for current thread
187 ProjData res = threadLocalProjData();
188
189#ifdef COORDINATE_TRANSFORM_VERBOSE
190 QgsDebugMsgLevel( "From proj : " + mSourceCRS.toProj(), 2 );
191 QgsDebugMsgLevel( "To proj : " + mDestCRS.toProj(), 2 );
192#endif
193
194 if ( !res )
195 mIsValid = false;
196
197#ifdef COORDINATE_TRANSFORM_VERBOSE
198 if ( mIsValid )
199 {
200 QgsDebugMsgLevel( u"------------------------------------------------------------"_s, 2 );
201 QgsDebugMsgLevel( u"The OGR Coordinate transformation for this layer was set to"_s, 2 );
202 QgsLogger::debug<QgsCoordinateReferenceSystem>( "Input", mSourceCRS, __FILE__, __FUNCTION__, __LINE__ );
203 QgsLogger::debug<QgsCoordinateReferenceSystem>( "Output", mDestCRS, __FILE__, __FUNCTION__, __LINE__ );
204 QgsDebugMsgLevel( u"------------------------------------------------------------"_s, 2 );
205 }
206 else
207 {
208 QgsDebugError( u"The OGR Coordinate transformation FAILED TO INITIALIZE!"_s );
209 }
210#else
211 if ( !mIsValid )
212 {
213 QgsDebugError( u"Coordinate transformation failed to initialize!"_s );
214 }
215#endif
216
217 // Transform must take place
218 mShortCircuit = false;
219
220 return mIsValid;
221}
222
223void QgsCoordinateTransformPrivate::calculateTransforms( const QgsCoordinateTransformContext &context )
224{
225 // recalculate datum transforms from context
226 if ( mSourceCRS.isValid() && mDestCRS.isValid() )
227 {
228 mProjCoordinateOperation = context.calculateCoordinateOperation( mSourceCRS, mDestCRS );
229 mShouldReverseCoordinateOperation = context.mustReverseCoordinateOperation( mSourceCRS, mDestCRS );
230 mAllowFallbackTransforms = context.allowFallbackTransform( mSourceCRS, mDestCRS );
231 }
232 else
233 {
234 mProjCoordinateOperation.clear();
235 mShouldReverseCoordinateOperation = false;
236 mAllowFallbackTransforms = false;
237 }
238}
239
240ProjData QgsCoordinateTransformPrivate::threadLocalProjData()
241{
242 QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
243
244 PJ_CONTEXT *context = QgsProjContext::get();
245 const QMap< uintptr_t, ProjData >::const_iterator it = mProjProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
246
247 if ( it != mProjProjections.constEnd() )
248 {
249 ProjData res = it.value();
250 return res;
251 }
252
253 // proj projections don't exist yet, so we need to create
254 locker.changeMode( QgsReadWriteLocker::Write );
255
256 // use a temporary proj error collector
258
259 mIsReversed = false;
260
262 if ( !mProjCoordinateOperation.isEmpty() )
263 {
264 transform.reset( proj_create( context, mProjCoordinateOperation.toUtf8().constData() ) );
265 // Only use proj_coordoperation_is_instantiable() if PROJ networking is enabled.
266 // The reason is that proj_coordoperation_is_instantiable() in PROJ < 9.0
267 // does not work properly when a coordinate operation refers to a PROJ < 7 grid name (gtx/gsb)
268 // but the user has installed PROJ >= 7 GeoTIFF grids.
269 // Cf https://github.com/OSGeo/PROJ/pull/3025.
270 // When networking is not enabled, proj_create() will check that all grids are
271 // present, so proj_coordoperation_is_instantiable() is not necessary.
272 if ( !transform || ( proj_context_is_network_enabled( context ) && !proj_coordoperation_is_instantiable( context, transform.get() ) ) )
273 {
274 if ( sMissingGridUsedByContextHandler )
275 {
277 desired.proj = mProjCoordinateOperation;
278 desired.accuracy = -1; //unknown, can't retrieve from proj as we can't instantiate the op
279 desired.grids = QgsProjUtils::gridsUsed( mProjCoordinateOperation );
280 sMissingGridUsedByContextHandler( mSourceCRS, mDestCRS, desired );
281 }
282 else
283 {
284 const QString err = QObject::tr( "Could not use operation specified in project between %1 and %2. (Wanted to use: %3)." ).arg( mSourceCRS.authid(), mDestCRS.authid(), mProjCoordinateOperation );
286 }
287
288 transform.reset();
289 }
290 else
291 {
292 mIsReversed = mShouldReverseCoordinateOperation;
293 }
294 }
295
296 QString nonAvailableError;
297 if ( !transform ) // fallback on default proj pathway
298 {
299 if ( !mSourceCRS.projObject() || !mDestCRS.projObject() )
300 {
301 return nullptr;
302 }
303
304 PJ_OPERATION_FACTORY_CONTEXT *operationContext = proj_create_operation_factory_context( context, nullptr );
305
306 // We want to check ALL grids, not just those available for use
307 proj_operation_factory_context_set_grid_availability_use( context, operationContext, PROJ_GRID_AVAILABILITY_IGNORED );
308
309 // See https://lists.osgeo.org/pipermail/proj/2019-May/008604.html
310 proj_operation_factory_context_set_spatial_criterion( context, operationContext, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION );
311
312 if ( PJ_OBJ_LIST *ops = proj_create_operations( context, mSourceCRS.projObject(), mDestCRS.projObject(), operationContext ) )
313 {
314 mAvailableOpCount = proj_list_get_count( ops );
315 if ( mAvailableOpCount < 1 )
316 {
317 // huh?
318 const int errNo = proj_context_errno( context );
319 if ( errNo )
320 {
321 nonAvailableError = QString( proj_context_errno_string( context, errNo ) );
322 }
323 else
324 {
325 // in theory should never be hit!
326 nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
327 }
328 }
329 else if ( mAvailableOpCount == 1 )
330 {
331 // only a single operation available. Can we use it?
332 transform.reset( proj_list_get( context, ops, 0 ) );
333 if ( transform )
334 {
335 if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
336 {
337 // uh oh :( something is missing! find what it is
338 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
339 {
340 const char *shortName = nullptr;
341 const char *fullName = nullptr;
342 const char *packageName = nullptr;
343 const char *url = nullptr;
344 int directDownload = 0;
345 int openLicense = 0;
346 int isAvailable = 0;
347 proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
348 if ( !isAvailable )
349 {
350 // found it!
351 if ( sMissingRequiredGridHandler )
352 {
354 gridDetails.shortName = QString( shortName );
355 gridDetails.fullName = QString( fullName );
356 gridDetails.packageName = QString( packageName );
357 gridDetails.url = QString( url );
358 gridDetails.directDownload = directDownload;
359 gridDetails.openLicense = openLicense;
360 gridDetails.isAvailable = isAvailable;
361 sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
362 }
363 else
364 {
365 const QString err = QObject::tr( "Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(), mDestCRS.authid(), shortName );
367 }
368 break;
369 }
370 }
371 }
372 else
373 {
374 // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
375 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
376 if ( !transform )
377 {
378 const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(), mDestCRS.authid() );
380 }
381 }
382 }
383 }
384 else
385 {
386 // multiple operations available. Can we use the best one?
388 bool missingPreferred = false;
389 bool stillLookingForPreferred = true;
390 for ( int i = 0; i < mAvailableOpCount; ++i )
391 {
392 transform.reset( proj_list_get( context, ops, i ) );
393 const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
394 if ( stillLookingForPreferred && transform && !isInstantiable )
395 {
396 // uh oh :( something is missing blocking us from the preferred operation!
398 if ( !candidate.proj.isEmpty() )
399 {
400 preferred = candidate;
401 missingPreferred = true;
402 stillLookingForPreferred = false;
403 }
404 }
405 if ( transform && isInstantiable )
406 {
407 // found one
408 break;
409 }
410 transform.reset();
411 }
412
413 if ( transform && missingPreferred )
414 {
415 // found a transform, but it's not the preferred
417 if ( sMissingPreferredGridHandler )
418 {
419 sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
420 }
421 else
422 {
423 const QString err
424 = QObject::tr( "Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(), mDestCRS.authid(), available.proj, preferred.proj );
426 }
427 }
428
429 // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
430 if ( transform )
431 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
432 if ( !transform )
433 {
434 const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(), mDestCRS.authid() );
436 }
437 }
438 proj_list_destroy( ops );
439 }
440 proj_operation_factory_context_destroy( operationContext );
441 }
442
443 if ( !transform && nonAvailableError.isEmpty() )
444 {
445 const int errNo = proj_context_errno( context );
446 const QStringList projErrors = errorLogger.errors();
447 if ( errNo )
448 {
449 nonAvailableError = QString( proj_context_errno_string( context, errNo ) );
450 }
451 else if ( !projErrors.empty() )
452 {
453 nonAvailableError = projErrors.constLast();
454 }
455
456 if ( nonAvailableError.isEmpty() )
457 {
458 nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
459 }
460 else
461 {
462 // strip proj prefixes from error string, so that it's nicer for users
463 nonAvailableError = nonAvailableError.remove( u"internal_proj_create_operations: "_s );
464 }
465 }
466
467 if ( !nonAvailableError.isEmpty() )
468 {
469 if ( sCoordinateOperationCreationErrorHandler )
470 {
471 sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
472 }
473 else
474 {
475 const QString err = QObject::tr( "Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(), mDestCRS.authid(), nonAvailableError );
477 }
478 }
479
480 if ( !transform )
481 {
482 // ouch!
483 return nullptr;
484 }
485
486 ProjData res = transform.release();
487 mProjProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
488 return res;
489}
490
491ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
492{
493 QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
494
495 PJ_CONTEXT *context = QgsProjContext::get();
496 const QMap< uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
497
498 if ( it != mProjFallbackProjections.constEnd() )
499 {
500 ProjData res = it.value();
501 return res;
502 }
503
504 // proj projections don't exist yet, so we need to create
505 locker.changeMode( QgsReadWriteLocker::Write );
506
507 QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs_from_pj( context, mSourceCRS.projObject(), mDestCRS.projObject(), nullptr, nullptr ) );
508 if ( transform )
509 transform.reset( proj_normalize_for_visualization( QgsProjContext::get(), transform.get() ) );
510
511 ProjData res = transform.release();
512 mProjFallbackProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
513 return res;
514}
515
516void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler(
517 const std::function<void( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::GridDetails & )> &handler
518)
519{
520 sMissingRequiredGridHandler = handler;
521}
522
523void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler(
525)
526{
527 sMissingPreferredGridHandler = handler;
528}
529
530void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler(
531 const std::function<void( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler
532)
533{
534 sCoordinateOperationCreationErrorHandler = handler;
535}
536
537void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler(
538 const std::function<void( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler
539)
540{
541 sMissingGridUsedByContextHandler = handler;
542}
543
544void QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( const std::function<void( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem & )> &handler )
545{
546 sDynamicCrsToDynamicCrsWarningHandler = handler;
547}
548
549void QgsCoordinateTransformPrivate::freeProj()
550{
551 const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
552 if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
553 return;
554 QMap< uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
555
556 // During destruction of PJ* objects, the errno is set in the underlying
557 // context. Consequently the context attached to the PJ* must still exist !
558 // Which is not necessarily the case currently unfortunately. So
559 // create a temporary dummy context, and attach it to the PJ* before destroying
560 // it
561 PJ_CONTEXT *tmpContext = proj_context_create();
562 for ( ; it != mProjProjections.constEnd(); ++it )
563 {
564 proj_assign_context( it.value(), tmpContext );
565 proj_destroy( it.value() );
566 }
567
568 it = mProjFallbackProjections.constBegin();
569 for ( ; it != mProjFallbackProjections.constEnd(); ++it )
570 {
571 proj_assign_context( it.value(), tmpContext );
572 proj_destroy( it.value() );
573 }
574
575 proj_context_destroy( tmpContext );
576 mProjProjections.clear();
577 mProjFallbackProjections.clear();
578}
579
580bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread( void *pj_context )
581{
582 const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
583
584 QMap< uintptr_t, ProjData >::iterator it = mProjProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
585 if ( it != mProjProjections.end() )
586 {
587 proj_destroy( it.value() );
588 mProjProjections.erase( it );
589 }
590
591 it = mProjFallbackProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
592 if ( it != mProjFallbackProjections.end() )
593 {
594 proj_destroy( it.value() );
595 mProjFallbackProjections.erase( it );
596 }
597
598 return mProjProjections.isEmpty();
599}
600
@ Critical
Critical/error message.
Definition qgis.h:163
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 debug(const QString &msg, int debuglevel=1, const char *file=nullptr, const char *function=nullptr, int line=-1)
Goes to qDebug.
Definition qgslogger.cpp:62
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE(), Qgis::StringFormat format=Qgis::StringFormat::PlainText)
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.
A convenience class that simplifies locking and unlocking QReadWriteLocks.
@ 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:7504
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7503
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition qgis.h:6938
struct pj_ctx PJ_CONTEXT
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
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.