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