QGIS API Documentation 3.41.0-Master (45a0abf3bec)
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 && errNo != -61 )
336 {
337 nonAvailableError = QString( proj_errno_string( errNo ) );
338 }
339 else
340 {
341 nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
342 }
343 }
344 else if ( mAvailableOpCount == 1 )
345 {
346 // only a single operation available. Can we use it?
347 transform.reset( proj_list_get( context, ops, 0 ) );
348 if ( transform )
349 {
350 if ( !proj_coordoperation_is_instantiable( context, transform.get() ) )
351 {
352 // uh oh :( something is missing! find what it is
353 for ( int j = 0; j < proj_coordoperation_get_grid_used_count( context, transform.get() ); ++j )
354 {
355 const char *shortName = nullptr;
356 const char *fullName = nullptr;
357 const char *packageName = nullptr;
358 const char *url = nullptr;
359 int directDownload = 0;
360 int openLicense = 0;
361 int isAvailable = 0;
362 proj_coordoperation_get_grid_used( context, transform.get(), j, &shortName, &fullName, &packageName, &url, &directDownload, &openLicense, &isAvailable );
363 if ( !isAvailable )
364 {
365 // found it!
366 if ( sMissingRequiredGridHandler )
367 {
369 gridDetails.shortName = QString( shortName );
370 gridDetails.fullName = QString( fullName );
371 gridDetails.packageName = QString( packageName );
372 gridDetails.url = QString( url );
373 gridDetails.directDownload = directDownload;
374 gridDetails.openLicense = openLicense;
375 gridDetails.isAvailable = isAvailable;
376 sMissingRequiredGridHandler( mSourceCRS, mDestCRS, gridDetails );
377 }
378 else
379 {
380 const QString err = QObject::tr( "Cannot create transform between %1 and %2, missing required grid %3" ).arg( mSourceCRS.authid(),
381 mDestCRS.authid(),
382 shortName );
384 }
385 break;
386 }
387 }
388 }
389 else
390 {
391
392 // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
393 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
394 if ( !transform )
395 {
396 const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
397 mDestCRS.authid() );
399 }
400 }
401 }
402 }
403 else
404 {
405 // multiple operations available. Can we use the best one?
407 bool missingPreferred = false;
408 bool stillLookingForPreferred = true;
409 for ( int i = 0; i < mAvailableOpCount; ++ i )
410 {
411 transform.reset( proj_list_get( context, ops, i ) );
412 const bool isInstantiable = transform && proj_coordoperation_is_instantiable( context, transform.get() );
413 if ( stillLookingForPreferred && transform && !isInstantiable )
414 {
415 // uh oh :( something is missing blocking us from the preferred operation!
417 if ( !candidate.proj.isEmpty() )
418 {
419 preferred = candidate;
420 missingPreferred = true;
421 stillLookingForPreferred = false;
422 }
423 }
424 if ( transform && isInstantiable )
425 {
426 // found one
427 break;
428 }
429 transform.reset();
430 }
431
432 if ( transform && missingPreferred )
433 {
434 // found a transform, but it's not the preferred
436 if ( sMissingPreferredGridHandler )
437 {
438 sMissingPreferredGridHandler( mSourceCRS, mDestCRS, preferred, available );
439 }
440 else
441 {
442 const QString err = QObject::tr( "Using non-preferred coordinate operation between %1 and %2. Using %3, preferred %4." ).arg( mSourceCRS.authid(),
443 mDestCRS.authid(),
444 available.proj,
445 preferred.proj );
447 }
448 }
449
450 // transform may have either the source or destination CRS using swapped axis order. For QGIS, we ALWAYS need regular x/y axis order
451 if ( transform )
452 transform.reset( proj_normalize_for_visualization( context, transform.get() ) );
453 if ( !transform )
454 {
455 const QString err = QObject::tr( "Cannot normalize transform between %1 and %2" ).arg( mSourceCRS.authid(),
456 mDestCRS.authid() );
458 }
459 }
460 proj_list_destroy( ops );
461 }
462 proj_operation_factory_context_destroy( operationContext );
463 }
464
465 if ( !transform && nonAvailableError.isEmpty() )
466 {
467 const int errNo = proj_context_errno( context );
468 const QStringList projErrors = errorLogger.errors();
469 if ( errNo && errNo != -61 )
470 {
471 nonAvailableError = QString( proj_errno_string( errNo ) );
472 }
473 else if ( !projErrors.empty() )
474 {
475 nonAvailableError = projErrors.constLast();
476 }
477
478 if ( nonAvailableError.isEmpty() )
479 {
480 nonAvailableError = QObject::tr( "No coordinate operations are available between these two reference systems" );
481 }
482 else
483 {
484 // strip proj prefixes from error string, so that it's nicer for users
485 nonAvailableError = nonAvailableError.remove( QStringLiteral( "internal_proj_create_operations: " ) );
486 }
487 }
488
489 if ( !nonAvailableError.isEmpty() )
490 {
491 if ( sCoordinateOperationCreationErrorHandler )
492 {
493 sCoordinateOperationCreationErrorHandler( mSourceCRS, mDestCRS, nonAvailableError );
494 }
495 else
496 {
497 const QString err = QObject::tr( "Cannot create transform between %1 and %2: %3" ).arg( mSourceCRS.authid(),
498 mDestCRS.authid(),
499 nonAvailableError );
501 }
502 }
503
504 if ( !transform )
505 {
506 // ouch!
507 return nullptr;
508 }
509
510 ProjData res = transform.release();
511 mProjProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
512 return res;
513}
514
515ProjData QgsCoordinateTransformPrivate::threadLocalFallbackProjData()
516{
517 QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Read );
518
519 PJ_CONTEXT *context = QgsProjContext::get();
520 const QMap < uintptr_t, ProjData >::const_iterator it = mProjFallbackProjections.constFind( reinterpret_cast< uintptr_t>( context ) );
521
522 if ( it != mProjFallbackProjections.constEnd() )
523 {
524 ProjData res = it.value();
525 return res;
526 }
527
528 // proj projections don't exist yet, so we need to create
529 locker.changeMode( QgsReadWriteLocker::Write );
530
531 QgsProjUtils::proj_pj_unique_ptr transform( proj_create_crs_to_crs_from_pj( context, mSourceCRS.projObject(), mDestCRS.projObject(), nullptr, nullptr ) );
532 if ( transform )
533 transform.reset( proj_normalize_for_visualization( QgsProjContext::get(), transform.get() ) );
534
535 ProjData res = transform.release();
536 mProjFallbackProjections.insert( reinterpret_cast< uintptr_t>( context ), res );
537 return res;
538}
539
540void QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::GridDetails & )> &handler )
541{
542 sMissingRequiredGridHandler = handler;
543}
544
545void QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails &, const QgsDatumTransform::TransformDetails & )> &handler )
546{
547 sMissingPreferredGridHandler = handler;
548}
549
550void QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
551{
552 sCoordinateOperationCreationErrorHandler = handler;
553}
554
555void QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QgsDatumTransform::TransformDetails & )> &handler )
556{
557 sMissingGridUsedByContextHandler = handler;
558}
559
560void QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem & )> &handler )
561{
562 sDynamicCrsToDynamicCrsWarningHandler = handler;
563}
564
565void QgsCoordinateTransformPrivate::freeProj()
566{
567 const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
568 if ( mProjProjections.isEmpty() && mProjFallbackProjections.isEmpty() )
569 return;
570 QMap < uintptr_t, ProjData >::const_iterator it = mProjProjections.constBegin();
571
572 // During destruction of PJ* objects, the errno is set in the underlying
573 // context. Consequently the context attached to the PJ* must still exist !
574 // Which is not necessarily the case currently unfortunately. So
575 // create a temporary dummy context, and attach it to the PJ* before destroying
576 // it
577 PJ_CONTEXT *tmpContext = proj_context_create();
578 for ( ; it != mProjProjections.constEnd(); ++it )
579 {
580 proj_assign_context( it.value(), tmpContext );
581 proj_destroy( it.value() );
582 }
583
584 it = mProjFallbackProjections.constBegin();
585 for ( ; it != mProjFallbackProjections.constEnd(); ++it )
586 {
587 proj_assign_context( it.value(), tmpContext );
588 proj_destroy( it.value() );
589 }
590
591 proj_context_destroy( tmpContext );
592 mProjProjections.clear();
593 mProjFallbackProjections.clear();
594}
595
596bool QgsCoordinateTransformPrivate::removeObjectsBelongingToCurrentThread( void *pj_context )
597{
598 const QgsReadWriteLocker locker( mProjLock, QgsReadWriteLocker::Write );
599
600 QMap < uintptr_t, ProjData >::iterator it = mProjProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
601 if ( it != mProjProjections.end() )
602 {
603 proj_destroy( it.value() );
604 mProjProjections.erase( it );
605 }
606
607 it = mProjFallbackProjections.find( reinterpret_cast< uintptr_t>( pj_context ) );
608 if ( it != mProjFallbackProjections.end() )
609 {
610 proj_destroy( it.value() );
611 mProjFallbackProjections.erase( it );
612 }
613
614 return mProjProjections.isEmpty();
615}
616
@ 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.