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