QGIS API Documentation 3.43.0-Master (0cdc48caa8d)
qgscoordinatetransform.cpp
Go to the documentation of this file.
1/***************************************************************************
2 QgsCoordinateTransform.cpp - Coordinate Transforms
3 -------------------
4 begin : Dec 2004
5 copyright : (C) 2004 Tim Sutton
6 email : tim at linfiniti.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 ***************************************************************************/
19#include "qgsapplication.h"
20#include "qgsmessagelog.h"
21#include "qgslogger.h"
22#include "qgspointxy.h"
23#include "qgsrectangle.h"
24#include "qgsexception.h"
25#include "qgsproject.h"
26#include "qgsreadwritelocker.h"
27#include "qgsvector3d.h"
28#include "qgis.h"
29
30//qt includes
31#include <QDomNode>
32#include <QDomElement>
33#include <QApplication>
34#include <QPolygonF>
35#include <QStringList>
36#include <QVector>
37
38#include <proj.h>
39#include "qgsprojutils.h"
40
41#include <sqlite3.h>
42#include <qlogging.h>
43#include <vector>
44#include <algorithm>
45
46// if defined shows all information about transform to stdout
47// #define COORDINATE_TRANSFORM_VERBOSE
48
49QReadWriteLock QgsCoordinateTransform::sCacheLock;
50QMultiHash< QPair< QString, QString >, QgsCoordinateTransform > QgsCoordinateTransform::sTransforms; //same auth_id pairs might have different datum transformations
51bool QgsCoordinateTransform::sDisableCache = false;
52
53std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
54 const QgsCoordinateReferenceSystem &destinationCrs,
55 const QString &desiredOperation )> QgsCoordinateTransform::sFallbackOperationOccurredHandler = nullptr;
56
58{
59 d = new QgsCoordinateTransformPrivate();
60}
61
63{
64 mContext = context;
65 d = new QgsCoordinateTransformPrivate( source, destination, mContext );
66
68 mIgnoreImpossible = true;
69
70#ifdef QGISDEBUG
71 mHasContext = true;
72#endif
73
74 if ( mIgnoreImpossible && !isTransformationPossible( source, destination ) )
75 {
76 d->invalidate();
77 return;
78 }
79
80 if ( !d->checkValidity() )
81 return;
82
84 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
85 {
86 d->initialize();
87 addToCache();
88 }
90
92 mBallparkTransformsAreAppropriate = true;
93}
94
96{
97 mContext = project ? project->transformContext() : QgsCoordinateTransformContext();
98 d = new QgsCoordinateTransformPrivate( source, destination, mContext );
99#ifdef QGISDEBUG
100 if ( project )
101 mHasContext = true;
102#endif
103
105 mIgnoreImpossible = true;
106
107 if ( mIgnoreImpossible && !isTransformationPossible( source, destination ) )
108 {
109 d->invalidate();
110 return;
111 }
112
113 if ( !d->checkValidity() )
114 return;
115
117 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
118 {
119 d->initialize();
120 addToCache();
121 }
123
125 mBallparkTransformsAreAppropriate = true;
126}
127
128QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destinationDatumTransform )
129{
130 d = new QgsCoordinateTransformPrivate( source, destination, sourceDatumTransform, destinationDatumTransform );
131#ifdef QGISDEBUG
132 mHasContext = true; // not strictly true, but we don't need to worry if datums have been explicitly set
133#endif
134
135 if ( !d->checkValidity() )
136 return;
137
139 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
140 {
141 d->initialize();
142 addToCache();
143 }
145}
146
148 : mContext( o.mContext )
149#ifdef QGISDEBUG
150 , mHasContext( o.mHasContext )
151#endif
152 , mLastError()
153 // none of these should be copied -- they must be set manually for every object instead, or
154 // we risk contaminating the cache and copies retrieved from cache with settings which should NOT
155 // be applied to all transforms
156 , mIgnoreImpossible( false )
157 , mBallparkTransformsAreAppropriate( false )
158 , mDisableFallbackHandler( false )
159 , mFallbackOperationOccurred( false )
160{
161 d = o.d;
162}
163
165{
166 d = o.d;
167#ifdef QGISDEBUG
168 mHasContext = o.mHasContext;
169#endif
170 mContext = o.mContext;
171 mLastError = QString();
172 return *this;
173}
174
176
178{
179 return d->mSourceCRS == other.d->mSourceCRS
180 && d->mDestCRS == other.d->mDestCRS
181 && mBallparkTransformsAreAppropriate == other.mBallparkTransformsAreAppropriate
182 && d->mProjCoordinateOperation == other.d->mProjCoordinateOperation
184}
185
187{
188 return !( *this == other );
189}
190
192{
193 if ( !source.isValid() || !destination.isValid() )
194 return false;
195
196 if ( source.celestialBodyName() != destination.celestialBodyName() )
197 return false;
198
199 return true;
200}
201
203{
204 d.detach();
205 d->mSourceCRS = crs;
206
207 if ( mIgnoreImpossible && !isTransformationPossible( d->mSourceCRS, d->mDestCRS ) )
208 {
209 d->invalidate();
210 return;
211 }
212
213 if ( !d->checkValidity() )
214 return;
215
216 d->calculateTransforms( mContext );
218 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
219 {
220 d->initialize();
221 addToCache();
222 }
224}
226{
227 d.detach();
228 d->mDestCRS = crs;
229
230 if ( mIgnoreImpossible && !isTransformationPossible( d->mSourceCRS, d->mDestCRS ) )
231 {
232 d->invalidate();
233 return;
234 }
235
236 if ( !d->checkValidity() )
237 return;
238
239 d->calculateTransforms( mContext );
241 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
242 {
243 d->initialize();
244 addToCache();
245 }
247}
248
250{
251 d.detach();
252 mContext = context;
253#ifdef QGISDEBUG
254 mHasContext = true;
255#endif
256
257 if ( mIgnoreImpossible && !isTransformationPossible( d->mSourceCRS, d->mDestCRS ) )
258 {
259 d->invalidate();
260 return;
261 }
262
263 if ( !d->checkValidity() )
264 return;
265
266 d->calculateTransforms( mContext );
268 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
269 {
270 d->initialize();
271 addToCache();
272 }
274}
275
280
282{
283 return d->mSourceCRS;
284}
285
290
292{
293 if ( !d->mIsValid || d->mShortCircuit )
294 return point;
295
296 // transform x
297 double x = point.x();
298 double y = point.y();
299 double z = 0.0;
300 try
301 {
302 transformCoords( 1, &x, &y, &z, direction );
303 }
304 catch ( const QgsCsException & )
305 {
306 // rethrow the exception
307 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
308 throw;
309 }
310
311 return QgsPointXY( x, y );
312}
313
314
315QgsPointXY QgsCoordinateTransform::transform( const double theX, const double theY = 0.0, Qgis::TransformDirection direction ) const
316{
317 try
318 {
319 return transform( QgsPointXY( theX, theY ), direction );
320 }
321 catch ( const QgsCsException & )
322 {
323 // rethrow the exception
324 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
325 throw;
326 }
327}
328
330{
331 if ( !d->mIsValid || d->mShortCircuit )
332 return rect;
333 // transform x
334 double x1 = rect.xMinimum();
335 double y1 = rect.yMinimum();
336 double x2 = rect.xMaximum();
337 double y2 = rect.yMaximum();
338
339 // Number of points to reproject------+
340 // |
341 // V
342 try
343 {
344 double z = 0.0;
345 transformCoords( 1, &x1, &y1, &z, direction );
346 transformCoords( 1, &x2, &y2, &z, direction );
347 }
348 catch ( const QgsCsException & )
349 {
350 // rethrow the exception
351 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
352 throw;
353 }
354
355#ifdef COORDINATE_TRANSFORM_VERBOSE
356 QgsDebugMsgLevel( QStringLiteral( "Rect projection..." ), 2 );
357 QgsDebugMsgLevel( QStringLiteral( "Xmin : %1 --> %2" ).arg( rect.xMinimum() ).arg( x1 ), 2 );
358 QgsDebugMsgLevel( QStringLiteral( "Ymin : %1 --> %2" ).arg( rect.yMinimum() ).arg( y1 ), 2 );
359 QgsDebugMsgLevel( QStringLiteral( "Xmax : %1 --> %2" ).arg( rect.xMaximum() ).arg( x2 ), 2 );
360 QgsDebugMsgLevel( QStringLiteral( "Ymax : %1 --> %2" ).arg( rect.yMaximum() ).arg( y2 ), 2 );
361#endif
362 return QgsRectangle( x1, y1, x2, y2 );
363}
364
366{
367 double x = point.x();
368 double y = point.y();
369 double z = point.z();
370 try
371 {
372 transformCoords( 1, &x, &y, &z, direction );
373 }
374 catch ( const QgsCsException & )
375 {
376 // rethrow the exception
377 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
378 throw;
379 }
380 return QgsVector3D( x, y, z );
381}
382
383void QgsCoordinateTransform::transformInPlace( double &x, double &y, double &z,
384 Qgis::TransformDirection direction ) const
385{
386 if ( !d->mIsValid || d->mShortCircuit )
387 return;
388#ifdef QGISDEBUG
389// QgsDebugMsgLevel(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__), 2);
390#endif
391 // transform x
392 try
393 {
394 transformCoords( 1, &x, &y, &z, direction );
395 }
396 catch ( const QgsCsException & )
397 {
398 // rethrow the exception
399 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
400 throw;
401 }
402}
403
404void QgsCoordinateTransform::transformInPlace( float &x, float &y, double &z,
405 Qgis::TransformDirection direction ) const
406{
407 double xd = static_cast< double >( x ), yd = static_cast< double >( y );
408 transformInPlace( xd, yd, z, direction );
409 x = xd;
410 y = yd;
411}
412
413void QgsCoordinateTransform::transformInPlace( float &x, float &y, float &z,
414 Qgis::TransformDirection direction ) const
415{
416 if ( !d->mIsValid || d->mShortCircuit )
417 return;
418#ifdef QGISDEBUG
419 // QgsDebugMsgLevel(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__), 2);
420#endif
421 // transform x
422 try
423 {
424 double xd = x;
425 double yd = y;
426 double zd = z;
427 transformCoords( 1, &xd, &yd, &zd, direction );
428 x = xd;
429 y = yd;
430 z = zd;
431 }
432 catch ( QgsCsException & )
433 {
434 // rethrow the exception
435 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
436 throw;
437 }
438}
439
441{
442 if ( !d->mIsValid || d->mShortCircuit )
443 {
444 return;
445 }
446
447 //create x, y arrays
448 const int nVertices = poly.size();
449
450 QVector<double> x( nVertices );
451 QVector<double> y( nVertices );
452 QVector<double> z( nVertices );
453 double *destX = x.data();
454 double *destY = y.data();
455 double *destZ = z.data();
456
457 const QPointF *polyData = poly.constData();
458 for ( int i = 0; i < nVertices; ++i )
459 {
460 *destX++ = polyData->x();
461 *destY++ = polyData->y();
462 *destZ++ = 0;
463 polyData++;
464 }
465
466 QString err;
467 try
468 {
469 transformCoords( nVertices, x.data(), y.data(), z.data(), direction );
470 }
471 catch ( const QgsCsException &e )
472 {
473 // record the exception, but don't rethrow it until we've recorded the coordinates we *could* transform
474 err = e.what();
475 }
476
477 QPointF *destPoint = poly.data();
478 const double *srcX = x.constData();
479 const double *srcY = y.constData();
480 for ( int i = 0; i < nVertices; ++i )
481 {
482 destPoint->rx() = *srcX++;
483 destPoint->ry() = *srcY++;
484 destPoint++;
485 }
486
487 // rethrow the exception
488 if ( !err.isEmpty() )
489 throw QgsCsException( err );
490}
491
492void QgsCoordinateTransform::transformInPlace( QVector<double> &x, QVector<double> &y, QVector<double> &z,
493 Qgis::TransformDirection direction ) const
494{
495
496 if ( !d->mIsValid || d->mShortCircuit )
497 return;
498
499 Q_ASSERT( x.size() == y.size() );
500
501 // Apparently, if one has a std::vector, it is valid to use the
502 // address of the first element in the vector as a pointer to an
503 // array of the vectors data, and hence easily interface with code
504 // that wants C-style arrays.
505
506 try
507 {
508 transformCoords( x.size(), &x[0], &y[0], &z[0], direction );
509 }
510 catch ( const QgsCsException & )
511 {
512 // rethrow the exception
513 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
514 throw;
515 }
516}
517
518
519void QgsCoordinateTransform::transformInPlace( QVector<float> &x, QVector<float> &y, QVector<float> &z,
520 Qgis::TransformDirection direction ) const
521{
522 if ( !d->mIsValid || d->mShortCircuit )
523 return;
524
525 Q_ASSERT( x.size() == y.size() );
526
527 // Apparently, if one has a std::vector, it is valid to use the
528 // address of the first element in the vector as a pointer to an
529 // array of the vectors data, and hence easily interface with code
530 // that wants C-style arrays.
531
532 try
533 {
534 //copy everything to double vectors since proj needs double
535 const int vectorSize = x.size();
536 QVector<double> xd( x.size() );
537 QVector<double> yd( y.size() );
538 QVector<double> zd( z.size() );
539
540 double *destX = xd.data();
541 double *destY = yd.data();
542 double *destZ = zd.data();
543
544 const float *srcX = x.constData();
545 const float *srcY = y.constData();
546 const float *srcZ = z.constData();
547
548 for ( int i = 0; i < vectorSize; ++i )
549 {
550 *destX++ = static_cast< double >( *srcX++ );
551 *destY++ = static_cast< double >( *srcY++ );
552 *destZ++ = static_cast< double >( *srcZ++ );
553 }
554
555 transformCoords( x.size(), &xd[0], &yd[0], &zd[0], direction );
556
557 //copy back
558 float *destFX = x.data();
559 float *destFY = y.data();
560 float *destFZ = z.data();
561 const double *srcXD = xd.constData();
562 const double *srcYD = yd.constData();
563 const double *srcZD = zd.constData();
564 for ( int i = 0; i < vectorSize; ++i )
565 {
566 *destFX++ = static_cast< float >( *srcXD++ );
567 *destFY++ = static_cast< float >( *srcYD++ );
568 *destFZ++ = static_cast< float >( *srcZD++ );
569 }
570 }
571 catch ( QgsCsException & )
572 {
573 // rethrow the exception
574 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
575 throw;
576 }
577}
578
579QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &rect, Qgis::TransformDirection direction, const bool handle180Crossover ) const
580{
581 // Calculate the bounding box of a QgsRectangle in the source CRS
582 // when projected to the destination CRS (or the inverse).
583 // This is done by looking at a number of points spread evenly
584 // across the rectangle
585
586 if ( !d->mIsValid || d->mShortCircuit )
587 return rect;
588
589 if ( rect.isEmpty() )
590 {
591 const QgsPointXY p = transform( rect.xMinimum(), rect.yMinimum(), direction );
592 return QgsRectangle( p, p );
593 }
594
595#ifdef QGISDEBUG
596 if ( !mHasContext )
597 {
598 QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 );
599 }
600#endif
601
602 // we can't calculate if transform involves a geocentric CRS. This is silly anyway,
603 // as transformation of a 2d bounding box makes no sense when a geocentric CRS is involved!
604 if ( d->mSourceCRS.type() == Qgis::CrsType::Geocentric )
605 {
606 throw QgsCsException( QObject::tr( "Could not transform bounding box for geocentric CRS %1" ).arg( d->mSourceCRS.authid() ) );
607 }
608 if ( d->mDestCRS.type() == Qgis::CrsType::Geocentric )
609 {
610 throw QgsCsException( QObject::tr( "Could not transform bounding box for geocentric CRS %1" ).arg( d->mDestCRS.authid() ) );
611 }
612
613 const double xMin = rect.xMinimum();
614 const double xMax = rect.xMaximum();
615 double yMin = rect.yMinimum();
616 double yMax = rect.yMaximum();
617 if ( d->mGeographicToWebMercator &&
618 ( ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) ||
619 ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ) )
620 {
621 // Latitudes close to 90 degree project to infinite northing in theory.
622 // We limit to 90 - 1e-1 which reproject to northing of ~ 44e6 m (about twice
623 // the maximum easting of ~20e6 m).
624 // For reference, GoogleMercator tiles are limited to a northing ~85 deg / ~20e6 m
625 // so limiting to 90 - 1e-1 is reasonable.
626 constexpr double EPS = 1e-1;
627 if ( yMin < -90 + EPS )
628 {
629 if ( yMax < -90 + EPS )
630 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
631 yMin = -90 + EPS;
632 }
633 if ( yMax > 90 - EPS )
634 {
635 if ( yMin > 90 - EPS )
636 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
637 yMax = 90 - EPS;
638 }
639 }
640
641 QgsScopedProjSilentLogger errorLogger;
642
643 QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 );
644
645#if PROJ_VERSION_MAJOR< 9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR<7)
646 const auto legacyImplementation = [this, &rect, xMin, yMin, yMax, direction, handle180Crossover]()
647 {
648 // this logic is buggy! See https://github.com/qgis/QGIS/issues/59821
649
650 // 64 points (<=2.12) is not enough, see #13665, for EPSG:4326 -> EPSG:3574 (say that it is a hard one),
651 // are decent result from about 500 points and more. This method is called quite often, but
652 // even with 1000 points it takes < 1ms.
653 // TODO: how to effectively and precisely reproject bounding box?
654 const int nPoints = 1000;
655 const double dst = std::sqrt( ( rect.width() * ( yMax - yMin ) ) / std::pow( std::sqrt( static_cast< double >( nPoints ) ) - 1, 2.0 ) );
656 const int nXPoints = static_cast<int>( std::clamp( std::ceil( rect.width() / dst ) + 1, 3.0, 1000.0 ) );
657 const int nYPoints = static_cast<int>( std::clamp( std::ceil( ( yMax - yMin ) / dst ) + 1, 3.0, 1000.0 ) );
658
659 QgsRectangle bb_rect;
660 bb_rect.setNull();
661
662 std::vector<double> x( nXPoints * static_cast< std::size_t >( nYPoints ) );
663 std::vector<double> y( nXPoints * static_cast< std::size_t >( nYPoints ) );
664 std::vector<double> z( nXPoints * static_cast< std::size_t >( nYPoints ) );
665
666 // Populate the vectors
667
668 const double dx = rect.width() / static_cast< double >( nXPoints - 1 );
669 const double dy = ( yMax - yMin ) / static_cast< double >( nYPoints - 1 );
670
671 double pointY = yMin;
672
673 for ( int i = 0; i < nYPoints ; i++ )
674 {
675
676 // Start at right edge
677 double pointX = xMin;
678
679 for ( int j = 0; j < nXPoints; j++ )
680 {
681 x[( i * nXPoints ) + j] = pointX;
682 y[( i * nXPoints ) + j] = pointY;
683 // and the height...
684 z[( i * nXPoints ) + j] = 0.0;
685 // QgsDebugMsgLevel(QString("BBox coord: (%1, %2)").arg(x[(i*numP) + j]).arg(y[(i*numP) + j]), 2);
686 pointX += dx;
687 }
688 pointY += dy;
689 }
690
691 // Do transformation. Any exception generated must
692 // be handled in above layers.
693 try
694 {
695 transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
696 }
697 catch ( const QgsCsException & )
698 {
699 // rethrow the exception
700 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
701 throw;
702 }
703
704 // check if result bbox is geographic and is crossing 180/-180 line: ie. min X is before the 180° and max X is after the -180°
705 bool doHandle180Crossover = false;
706 if ( nXPoints > 0 )
707 {
708 const double xMin = std::fmod( x[0], 180.0 );
709 const double xMax = std::fmod( x[nXPoints - 1], 180.0 );
710 if ( handle180Crossover
711 && ( ( direction == Qgis::TransformDirection::Forward && d->mDestCRS.isGeographic() ) ||
712 ( direction == Qgis::TransformDirection::Reverse && d->mSourceCRS.isGeographic() ) )
713 && xMin > 0.0 && xMin <= 180.0 && xMax < 0.0 && xMax >= -180.0 )
714 {
715 doHandle180Crossover = true;
716 }
717 }
718
719 // Calculate the bounding box and use that for the extent
720 for ( int i = 0; i < nXPoints * nYPoints; i++ )
721 {
722 if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
723 {
724 continue;
725 }
726
727 if ( doHandle180Crossover )
728 {
729 //if crossing the date line, temporarily add 360 degrees to -ve longitudes
730 bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] );
731 }
732 else
733 {
734 bb_rect.combineExtentWith( x[i], y[i] );
735 }
736 }
737
738 if ( bb_rect.isNull() )
739 {
740 // something bad happened when reprojecting the filter rect... no finite points were left!
741 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
742 }
743
744 if ( doHandle180Crossover )
745 {
746 //subtract temporary addition of 360 degrees from longitudes
747 if ( bb_rect.xMinimum() > 180.0 )
748 bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 );
749 if ( bb_rect.xMaximum() > 180.0 )
750 bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 );
751 }
752
753 QgsDebugMsgLevel( "Projected extent: " + bb_rect.toString(), 4 );
754
755 if ( bb_rect.isEmpty() )
756 {
757 QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
758 }
759
760 return bb_rect;
761 };
762#endif
763
764 // delegate logic to proj if version >= 8.2 available
765#if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=2)
766
767#if PROJ_VERSION_MAJOR< 9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR<7)
768 if ( !( ( direction == Qgis::TransformDirection::Forward && d->mDestCRS.isGeographic() ) ||
769 ( direction == Qgis::TransformDirection::Reverse && d->mSourceCRS.isGeographic() ) ) )
770 {
771 // PROJ < 9.7 has for example issues from world coverage in EPSG:4326
772 // to Spilhaus. But do not use the legacy implementation when going through
773 // geographic as the proj_trans_bounds() heuristics to detect anti-meridian
774 // crossing is better
775 return legacyImplementation();
776 }
777#endif
778
779 ProjData projData = d->threadLocalProjData();
780 PJ_CONTEXT *projContext = QgsProjContext::get();
781
782#if PROJ_VERSION_MAJOR< 9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR<6)
783 // if source or destination crs include vertical components, we need to demote them to
784 // 2d crs first, otherwise proj_trans_bounds fails on proj < 9.6 (see https://github.com/OSGeo/PROJ/pull/4333)
785
786 QgsProjUtils::proj_pj_unique_ptr srcCrs( proj_get_source_crs( projContext, projData ) );
787 QgsProjUtils::proj_pj_unique_ptr destCrs( proj_get_target_crs( projContext, projData ) );
788
789 QgsProjUtils::proj_pj_unique_ptr srcCrsHorizontal;
790 QgsProjUtils::proj_pj_unique_ptr destCrsHorizontal;
792 if ( QgsProjUtils::hasVerticalAxis( srcCrs.get() ) ||
793 QgsProjUtils::hasVerticalAxis( destCrs.get() ) )
794 {
795 srcCrsHorizontal = QgsProjUtils::crsToHorizontalCrs( srcCrs.get() );
796 destCrsHorizontal = QgsProjUtils::crsToHorizontalCrs( destCrs.get() );
797 transform2D.reset( proj_create_crs_to_crs_from_pj( projContext, srcCrsHorizontal.get(), destCrsHorizontal.get(), nullptr, nullptr ) );
798 if ( !transform2D )
799 {
800 const QString err = QStringLiteral( "proj_create_crs_to_crs_from_pj(horizontalCrs(%1), horizontalCrs(%2)) failed" ).arg( d->mSourceCRS.authid(), d->mSourceCRS.authid() );
801 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS: %1" ).arg( err ) );
802 }
803 transform2D.reset( proj_normalize_for_visualization( projContext, transform2D.get() ) );
804 if ( !transform2D )
805 {
806 const QString err = QStringLiteral( "Cannot normalize transform between horizontalCrs(%1) and horizontalCrs(%2)" ).arg( d->mSourceCRS.authid(), d->mDestCRS.authid() );
807 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS: %1" ).arg( err ) );
808 }
809 projData = transform2D.get();
810 }
811#endif
812
813 double transXMin = 0;
814 double transYMin = 0;
815 double transXMax = 0;
816 double transYMax = 0;
817
818 proj_errno_reset( projData );
819 // proj documentation recommends 21 points for densification, bump to
820 // 30 for higher accuracy in some situations
821 constexpr int DENSIFY_POINTS = 30;
822 int projResult = proj_trans_bounds( projContext, projData, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
823 xMin, yMin, xMax, yMax,
824 &transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
825
826 if ( ( projResult != 1
827 || !std::isfinite( transXMin )
828 || !std::isfinite( transXMax )
829 || !std::isfinite( transYMin )
830 || !std::isfinite( transYMax ) )
831 && ( d->mAvailableOpCount > 1 || d->mAvailableOpCount == -1 ) // only use fallbacks if more than one operation is possible -- otherwise we've already tried it and it failed
832 )
833 {
834 // fail #1 -- try with getting proj to auto-pick an appropriate coordinate operation for the points
835 if ( PJ *transform = d->threadLocalFallbackProjData() )
836 {
837 projResult = proj_trans_bounds( projContext, transform, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
838 xMin, yMin, xMax, yMax,
839 &transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
840 }
841 }
842
843 if ( projResult != 1
844 || !std::isfinite( transXMin )
845 || !std::isfinite( transXMax )
846 || !std::isfinite( transYMin )
847 || !std::isfinite( transYMax ) )
848 {
849#if PROJ_VERSION_MAJOR< 9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR<7)
850 // proj_trans_bounds() of PROJ < 9.7 may fail if the transform passed to it
851 // has been directly instantiated from a PROJ pipeline string.
852 // Cf https://github.com/OSGeo/PROJ/pull/4512
853 return legacyImplementation();
854#else
855 const QString projErr = QString::fromUtf8( proj_context_errno_string( projContext, proj_errno( projData ) ) );
856 const QString dir = ( direction == Qgis::TransformDirection::Forward ) ? QObject::tr( "Forward transform" ) : QObject::tr( "Inverse transform" );
857 const QString msg = QObject::tr( "%1 (%2 to %3) of bounding box failed: %4" )
858 .arg( dir,
859 ( direction == Qgis::TransformDirection::Forward ) ? d->mSourceCRS.authid() : d->mDestCRS.authid(),
860 ( direction == Qgis::TransformDirection::Forward ) ? d->mDestCRS.authid() : d->mSourceCRS.authid(),
861 projErr );
862 QgsDebugError( msg );
863
864 throw QgsCsException( msg );
865#endif
866 }
867
868 bool doHandle180Crossover = false;
869 // check if result bbox is geographic and is crossing 180/-180 line: ie. min X is before the 180° and max X is after the -180°
870 if ( handle180Crossover
871 && ( ( direction == Qgis::TransformDirection::Forward && d->mDestCRS.isGeographic() ) ||
872 ( direction == Qgis::TransformDirection::Reverse && d->mSourceCRS.isGeographic() ) )
873 && ( transXMax < transXMin ) )
874 {
875 //if crossing the date line, temporarily add 360 degrees to -ve longitudes
876 std::swap( transXMax, transXMin );
877 if ( transXMin < 0 )
878 transXMin += 360;
879 if ( transXMax < 0 )
880 transXMax += 360;
881 doHandle180Crossover = true;
882 }
883
884 QgsRectangle boundingBoxRect{ transXMin, transYMin, transXMax, transYMax };
885 if ( boundingBoxRect.isNull() )
886 {
887 // something bad happened when reprojecting the filter rect... no finite points were left!
888 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
889 }
890
891 if ( doHandle180Crossover )
892 {
893 //subtract temporary addition of 360 degrees from longitudes
894 if ( boundingBoxRect.xMinimum() > 180.0 )
895 boundingBoxRect.setXMinimum( boundingBoxRect.xMinimum() - 360.0 );
896 if ( boundingBoxRect.xMaximum() > 180.0 )
897 boundingBoxRect.setXMaximum( boundingBoxRect.xMaximum() - 360.0 );
898 }
899
900 QgsDebugMsgLevel( "Projected extent: " + boundingBoxRect.toString(), 4 );
901
902 if ( boundingBoxRect.isEmpty() )
903 {
904 QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
905 }
906
907 return boundingBoxRect;
908#endif
909#if PROJ_VERSION_MAJOR< 9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR<7)
910 return legacyImplementation();
911#endif
912}
913
914void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, Qgis::TransformDirection direction ) const
915{
916 if ( !d->mIsValid || d->mShortCircuit )
917 return;
918 // Refuse to transform the points if the srs's are invalid
919 if ( !d->mSourceCRS.isValid() )
920 {
921 QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. "
922 "The coordinates can not be reprojected. The CRS is: %1" )
923 .arg( d->mSourceCRS.toProj() ), QObject::tr( "CRS" ) );
924 return;
925 }
926 if ( !d->mDestCRS.isValid() )
927 {
928 QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. "
929 "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj() ), QObject::tr( "CRS" ) );
930 return;
931 }
932
933 std::vector< int > zNanPositions;
934 for ( int i = 0; i < numPoints; i++ )
935 {
936 if ( std::isnan( z[i] ) )
937 {
938 zNanPositions.push_back( i );
939 z[i] = 0.0;
940 }
941 }
942
943 std::vector< double > xprev( numPoints );
944 memcpy( xprev.data(), x, sizeof( double ) * numPoints );
945 std::vector< double > yprev( numPoints );
946 memcpy( yprev.data(), y, sizeof( double ) * numPoints );
947 std::vector< double > zprev( numPoints );
948 memcpy( zprev.data(), z, sizeof( double ) * numPoints );
949
950 const bool useTime = !std::isnan( d->mDefaultTime );
951 std::vector< double > t( useTime ? numPoints : 0, d->mDefaultTime );
952
953#ifdef COORDINATE_TRANSFORM_VERBOSE
954 double xorg = *x;
955 double yorg = *y;
956 QgsDebugMsgLevel( QStringLiteral( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ), 2 );
957#endif
958
959#ifdef QGISDEBUG
960 if ( !mHasContext )
961 {
962 QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 );
963 }
964#endif
965
966 // use proj4 to do the transform
967 ProjData projData = d->threadLocalProjData();
968
969 int projResult = 0;
970
971 proj_errno_reset( projData );
972 proj_trans_generic( projData, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
973 x, sizeof( double ), numPoints,
974 y, sizeof( double ), numPoints,
975 z, sizeof( double ), numPoints,
976 useTime ? t.data() : nullptr, sizeof( double ), useTime ? numPoints : 0 );
977 // Try to - approximately - emulate the behavior of pj_transform()...
978 // In the case of a single point transform, and a transformation error occurs,
979 // pj_transform() would return the errno. In cases of multiple point transform,
980 // it would continue (for non-transient errors, that is pipeline definition
981 // errors) and just set the resulting x,y to infinity. This is in fact a
982 // bit more subtle than that, and I'm not completely sure the logic in
983 // pj_transform() was really sane & fully bullet proof
984 // So here just check proj_errno() for single point transform
985 int actualRes = 0;
986 if ( numPoints == 1 )
987 {
988 projResult = proj_errno( projData );
989 actualRes = projResult;
990 }
991 else
992 {
993 actualRes = proj_errno( projData );
994 }
995 if ( actualRes == 0 )
996 {
997 // proj_errno is sometimes not an accurate method to test for transform failures - so we need to
998 // manually scan for nan values
999 if ( std::any_of( x, x + numPoints, []( double v ) { return std::isinf( v ); } )
1000 || std::any_of( y, y + numPoints, []( double v ) { return std::isinf( v ); } )
1001 || std::any_of( z, z + numPoints, []( double v ) { return std::isinf( v ); } ) )
1002 {
1003 actualRes = 1;
1004 }
1005 }
1006
1007 mFallbackOperationOccurred = false;
1008 bool errorOccurredDuringFallbackOperation = false;
1009 if ( actualRes != 0
1010 && ( d->mAvailableOpCount > 1 || d->mAvailableOpCount == -1 ) // only use fallbacks if more than one operation is possible -- otherwise we've already tried it and it failed
1011 && ( d->mAllowFallbackTransforms || mBallparkTransformsAreAppropriate ) )
1012 {
1013 // fail #1 -- try with getting proj to auto-pick an appropriate coordinate operation for the points
1014 if ( PJ *transform = d->threadLocalFallbackProjData() )
1015 {
1016 projResult = 0;
1017 proj_errno_reset( transform );
1018 memcpy( x, xprev.data(), sizeof( double ) * numPoints );
1019 memcpy( y, yprev.data(), sizeof( double ) * numPoints );
1020 memcpy( z, zprev.data(), sizeof( double ) * numPoints );
1021 proj_trans_generic( transform, direction == Qgis::TransformDirection::Forward ? PJ_FWD : PJ_INV,
1022 x, sizeof( double ), numPoints,
1023 y, sizeof( double ), numPoints,
1024 z, sizeof( double ), numPoints,
1025 useTime ? t.data() : nullptr, sizeof( double ), useTime ? numPoints : 0 );
1026 // Try to - approximately - emulate the behavior of pj_transform()...
1027 // In the case of a single point transform, and a transformation error occurs,
1028 // pj_transform() would return the errno. In cases of multiple point transform,
1029 // it would continue (for non-transient errors, that is pipeline definition
1030 // errors) and just set the resulting x,y to infinity. This is in fact a
1031 // bit more subtle than that, and I'm not completely sure the logic in
1032 // pj_transform() was really sane & fully bullet proof
1033 // So here just check proj_errno() for single point transform
1034 if ( numPoints == 1 )
1035 {
1036 projResult = proj_errno( transform );
1037 // hmm - something very odd here. We can't trust proj_errno( transform ), as that's giving us incorrect error numbers
1038 // (such as "failed to load datum shift file", which is definitely incorrect for a default proj created operation!)
1039 // so we resort to testing values ourselves...
1040 errorOccurredDuringFallbackOperation = std::isinf( x[0] ) || std::isinf( y[0] ) || std::isinf( z[0] );
1041 }
1042
1043 if ( !errorOccurredDuringFallbackOperation )
1044 {
1045 mFallbackOperationOccurred = true;
1046 }
1047
1048 if ( !mBallparkTransformsAreAppropriate && !mDisableFallbackHandler && sFallbackOperationOccurredHandler )
1049 {
1050 sFallbackOperationOccurredHandler( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation );
1051#if 0
1052 const QString warning = QStringLiteral( "A fallback coordinate operation was used between %1 and %2" ).arg( d->mSourceCRS.authid(),
1053 d->mDestCRS.authid() );
1054 qWarning( "%s", warning.toLatin1().constData() );
1055#endif
1056 }
1057 }
1058 }
1059
1060 for ( const int &pos : zNanPositions )
1061 {
1062 z[pos] = std::numeric_limits<double>::quiet_NaN();
1063 }
1064
1065 if ( projResult != 0 || errorOccurredDuringFallbackOperation )
1066 {
1067 //something bad happened....
1068 QString points;
1069
1070 const QChar delim = numPoints > 1 ? '\n' : ' ';
1071 for ( int i = 0; i < numPoints; ++i )
1072 {
1073 points += QStringLiteral( "(%1, %2)" ).arg( xprev[i], 0, 'f' ).arg( yprev[i], 0, 'f' ) + delim;
1074 }
1075
1076 const QString dir = ( direction == Qgis::TransformDirection::Forward ) ? QObject::tr( "Forward transform" ) : QObject::tr( "Inverse transform" );
1077
1078 PJ_CONTEXT *projContext = QgsProjContext::get();
1079 const QString projError = !errorOccurredDuringFallbackOperation ? QString::fromUtf8( proj_context_errno_string( projContext, projResult ) ) : QObject::tr( "Fallback transform failed" );
1080
1081 const QString msg = QObject::tr( "%1 (%2 to %3) of%4%5Error: %6" )
1082 .arg( dir,
1083 ( direction == Qgis::TransformDirection::Forward ) ? d->mSourceCRS.authid() : d->mDestCRS.authid(),
1084 ( direction == Qgis::TransformDirection::Forward ) ? d->mDestCRS.authid() : d->mSourceCRS.authid(),
1085 QString( delim ),
1086 points,
1087 projError );
1088
1089 // don't flood console with thousands of duplicate transform error messages
1090 if ( msg != mLastError )
1091 {
1092 QgsDebugError( "Projection failed emitting invalid transform signal: " + msg );
1093 mLastError = msg;
1094 }
1095 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
1096
1097 throw QgsCsException( msg );
1098 }
1099
1100#ifdef COORDINATE_TRANSFORM_VERBOSE
1101 QgsDebugMsgLevel( QStringLiteral( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
1102 .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 )
1103 .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ), 2 );
1104#endif
1105}
1106
1108{
1109 return d->mIsValid;
1110}
1111
1113{
1114 return !d->mIsValid || d->mShortCircuit;
1115}
1116
1118{
1119 return d->mIsValid && d->mHasVerticalComponent;
1120}
1121
1123{
1124 return d->mProjCoordinateOperation;
1125}
1126
1128{
1129 ProjData projData = d->threadLocalProjData();
1131}
1132
1133void QgsCoordinateTransform::setCoordinateOperation( const QString &operation ) const
1134{
1135 d.detach();
1136 d->mProjCoordinateOperation = operation;
1137 d->mShouldReverseCoordinateOperation = false;
1138}
1139
1141{
1142 d.detach();
1143 d->mAllowFallbackTransforms = allowed;
1144}
1145
1147{
1148 return d->mAllowFallbackTransforms;
1149}
1150
1152{
1153 mBallparkTransformsAreAppropriate = appropriate;
1154}
1155
1157{
1158 mDisableFallbackHandler = disabled;
1159}
1160
1162{
1163 return mFallbackOperationOccurred;
1164}
1165
1166const char *finder( const char *name )
1167{
1168 QString proj;
1169#ifdef Q_OS_WIN
1170 proj = QApplication::applicationDirPath()
1171 + "/share/proj/" + QString( name );
1172#else
1173 Q_UNUSED( name )
1174#endif
1175 return proj.toUtf8();
1176}
1177
1178bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, const QString &coordinateOperationProj, bool allowFallback )
1179{
1180 if ( !src.isValid() || !dest.isValid() )
1181 return false;
1182
1183 const QString sourceKey = src.authid().isEmpty() ?
1185 const QString destKey = dest.authid().isEmpty() ?
1187
1188 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1189 return false;
1190
1191 QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Read );
1192 if ( sDisableCache )
1193 return false;
1194
1195 const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
1196 for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
1197 {
1198 if ( ( *valIt ).coordinateOperation() == coordinateOperationProj
1199 && ( *valIt ).allowFallbackTransforms() == allowFallback
1200 && qgsNanCompatibleEquals( src.coordinateEpoch(), ( *valIt ).sourceCrs().coordinateEpoch() )
1201 && qgsNanCompatibleEquals( dest.coordinateEpoch(), ( *valIt ).destinationCrs().coordinateEpoch() )
1202 )
1203 {
1204 // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
1205 const QgsCoordinateTransformContext context = mContext;
1206#ifdef QGISDEBUG
1207 const bool hasContext = mHasContext;
1208#endif
1209 *this = *valIt;
1210 locker.unlock();
1211
1212 mContext = context;
1213#ifdef QGISDEBUG
1214 mHasContext = hasContext;
1215#endif
1216
1217 return true;
1218 }
1219 }
1220 return false;
1221}
1222
1223void QgsCoordinateTransform::addToCache()
1224{
1225 if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
1226 return;
1227
1228 const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
1229 d->mSourceCRS.toWkt( Qgis::CrsWktVariant::Preferred ) : d->mSourceCRS.authid();
1230 const QString destKey = d->mDestCRS.authid().isEmpty() ?
1231 d->mDestCRS.toWkt( Qgis::CrsWktVariant::Preferred ) : d->mDestCRS.authid();
1232
1233 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1234 return;
1235
1236 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1237 if ( sDisableCache )
1238 return;
1239
1240 sTransforms.insert( qMakePair( sourceKey, destKey ), *this );
1241}
1242
1244{
1246 return d->mSourceDatumTransform;
1248}
1249
1251{
1252 d.detach();
1254 d->mSourceDatumTransform = dt;
1256}
1257
1259{
1261 return d->mDestinationDatumTransform;
1263}
1264
1266{
1267 d.detach();
1269 d->mDestinationDatumTransform = dt;
1271}
1272
1274{
1275 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1276 if ( sDisableCache )
1277 return;
1278
1279 if ( disableCache )
1280 {
1281 sDisableCache = true;
1282 }
1283
1284 sTransforms.clear();
1285}
1286
1287void QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( void *pj_context )
1288{
1289 // Not completely sure about object order destruction after main() has
1290 // exited. So it is safer to check sDisableCache before using sCacheLock
1291 // in case sCacheLock would have been destroyed before the current TLS
1292 // QgsProjContext object that has called us...
1293 if ( sDisableCache )
1294 return;
1295
1296 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1297 // cppcheck-suppress identicalConditionAfterEarlyExit
1298 if ( sDisableCache )
1299 return;
1300
1301 for ( auto it = sTransforms.begin(); it != sTransforms.end(); )
1302 {
1303 auto &v = it.value();
1304 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
1305 it = sTransforms.erase( it );
1306 else
1307 ++it;
1308 }
1309}
1310
1311double QgsCoordinateTransform::scaleFactor( const QgsRectangle &ReferenceExtent ) const
1312{
1313 const QgsPointXY source1( ReferenceExtent.xMinimum(), ReferenceExtent.yMinimum() );
1314 const QgsPointXY source2( ReferenceExtent.xMaximum(), ReferenceExtent.yMaximum() );
1315 const double distSourceUnits = std::sqrt( source1.sqrDist( source2 ) );
1316 const QgsPointXY dest1 = transform( source1 );
1317 const QgsPointXY dest2 = transform( source2 );
1318 const double distDestUnits = std::sqrt( dest1.sqrDist( dest2 ) );
1319 return distDestUnits / distSourceUnits;
1320}
1321
1323{
1324 QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( handler );
1325}
1326
1328{
1329 QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( handler );
1330}
1331
1333{
1334 QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
1335}
1336
1338{
1339 QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
1340}
1341
1342void QgsCoordinateTransform::setFallbackOperationOccurredHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
1343{
1344 sFallbackOperationOccurredHandler = handler;
1345}
1346
1348{
1349 QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( handler );
1350}
@ Geocentric
Geocentric CRS.
QFlags< CoordinateTransformationFlag > CoordinateTransformationFlags
Coordinate transformation flags.
Definition qgis.h:2642
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
@ BallparkTransformsAreAppropriate
Indicates that approximate "ballpark" results are appropriate for this coordinate transform....
@ IgnoreImpossibleTransformations
Indicates that impossible transformations (such as those which attempt to transform between two diffe...
TransformDirection
Indicates the direction (forward or inverse) of a transform.
Definition qgis.h:2619
@ Forward
Forward transform (from source to destination)
@ Reverse
Reverse/inverse transform (from destination to source)
Represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QString celestialBodyName() const
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
double coordinateEpoch() const
Returns the coordinate epoch, as a decimal year.
Contains information about the context in which a coordinate transform is executed.
Handles coordinate transforms between two coordinate systems.
QgsCoordinateTransformContext context() const
Returns the context in which the coordinate transform will be calculated.
QgsCoordinateTransform()
Default constructor, creates an invalid QgsCoordinateTransform.
static bool isTransformationPossible(const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination)
Returns true if it is theoretically possible to transform between source and destination CRSes.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from.
bool allowFallbackTransforms() const
Returns whether "ballpark" fallback transformations will be used in the case that the specified coord...
static void setCustomMissingRequiredGridHandler(const std::function< void(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsDatumTransform::GridDetails &grid)> &handler)
Sets a custom handler to use when a coordinate transform is created between sourceCrs and destination...
QgsDatumTransform::TransformDetails instantiatedCoordinateOperationDetails() const
Returns the transform details representing the coordinate operation which is being used to transform ...
Q_DECL_DEPRECATED void setDestinationDatumTransformId(int datumId)
Sets the datumId ID of the datum transform to use when projecting to the destination CRS.
void setContext(const QgsCoordinateTransformContext &context)
Sets the context in which the coordinate transform should be calculated.
QString coordinateOperation() const
Returns a Proj string representing the coordinate operation which will be used to transform coordinat...
void setSourceCrs(const QgsCoordinateReferenceSystem &crs)
Sets the source coordinate reference system.
void setBallparkTransformsAreAppropriate(bool appropriate)
Sets whether approximate "ballpark" results are appropriate for this coordinate transform.
double scaleFactor(const QgsRectangle &referenceExtent) const
Computes an estimated conversion factor between source and destination units:
static void setCustomCoordinateOperationCreationErrorHandler(const std::function< void(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &error)> &handler)
Sets a custom handler to use when a coordinate transform was required between sourceCrs and destinati...
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform an array of coordinates to the destination CRS.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transform the point from the source CRS to the destination CRS.
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
Q_DECL_DEPRECATED void setSourceDatumTransformId(int datumId)
Sets the datumId ID of the datum transform to use when projecting from the source CRS.
bool fallbackOperationOccurred() const
Returns true if a fallback operation occurred for the most recent transform.
bool operator==(const QgsCoordinateTransform &other) const
void transformPolygon(QPolygonF &polygon, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const
Transforms a polygon to the destination coordinate system.
QgsCoordinateTransform & operator=(const QgsCoordinateTransform &o)
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const
Transforms a rectangle from the source CRS to the destination CRS.
bool hasVerticalComponent() const
Returns true if the transform includes a vertical component, i.e.
void setAllowFallbackTransforms(bool allowed)
Sets whether "ballpark" fallback transformations can be used in the case that the specified coordinat...
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateTransform objects.
static void setCustomMissingPreferredGridHandler(const std::function< void(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsDatumTransform::TransformDetails &preferredOperation, const QgsDatumTransform::TransformDetails &availableOperation)> &handler)
Sets a custom handler to use when a coordinate transform is created between sourceCrs and destination...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
bool operator!=(const QgsCoordinateTransform &other) const
Q_DECL_DEPRECATED int destinationDatumTransformId() const
Returns the ID of the datum transform to use when projecting to the destination CRS.
void disableFallbackOperationHandler(bool disabled)
Sets whether the default fallback operation handler is disabled for this transform instance.
void setCoordinateOperation(const QString &operation) const
Sets a Proj string representing the coordinate operation which will be used to transform coordinates.
static void setDynamicCrsToDynamicCrsWarningHandler(const std::function< void(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs)> &handler)
Sets a custom handler to use when the desired coordinate operation for use between sourceCrs and dest...
Q_DECL_DEPRECATED int sourceDatumTransformId() const
Returns the ID of the datum transform to use when projecting from the source CRS.
static void setCustomMissingGridUsedByContextHandler(const std::function< void(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QgsDatumTransform::TransformDetails &desiredOperation)> &handler)
Sets a custom handler to use when a coordinate operation was specified for use between sourceCrs and ...
static void setFallbackOperationOccurredHandler(const std::function< void(const QgsCoordinateReferenceSystem &sourceCrs, const QgsCoordinateReferenceSystem &destinationCrs, const QString &desiredOperation)> &handler)
Sets a custom handler to use when the desired coordinate operation for use between sourceCrs and dest...
Custom exception class for Coordinate Reference System related exceptions.
static QgsDatumTransform::TransformDetails transformDetailsFromPj(PJ *op)
Returns the transform details for a Proj coordinate operation op.
QString what() const
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).
Represents a 2D point.
Definition qgspointxy.h:60
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
Definition qgspointxy.h:186
double y
Definition qgspointxy.h:64
double x
Definition qgspointxy.h:63
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
static proj_pj_unique_ptr crsToHorizontalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), extract the horizontal c...
static bool hasVerticalAxis(const PJ *crs)
Returns true if a PROJ crs has a vertical axis.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
QgsCoordinateTransformContext transformContext
Definition qgsproject.h:113
A convenience class that simplifies locking and unlocking QReadWriteLocks.
@ Write
Lock for write.
A rectangle specified with double values.
Q_INVOKABLE QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double xMinimum
double yMinimum
double xMaximum
void setXMinimum(double x)
Set the minimum x value.
void setXMaximum(double x)
Set the maximum x value.
double yMaximum
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
void setNull()
Mark a rectangle as being null (holding no spatial information).
Scoped object for temporary suppression of PROJ logging output.
A 3D vector (similar to QVector3D) with the difference that it uses double precision instead of singl...
Definition qgsvector3d.h:30
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:49
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:51
double x() const
Returns X coordinate.
Definition qgsvector3d.h:47
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6855
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6854
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition qgis.h:6264
struct pj_ctx PJ_CONTEXT
struct PJconsts PJ
const char * finder(const char *name)
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:41
#define QgsDebugError(str)
Definition qgslogger.h:40
const QgsCoordinateReferenceSystem & crs
Contains information about a projection transformation grid file.
Contains information about a coordinate transformation operation.
QString proj
Proj representation of transform operation.