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