QGIS API Documentation 3.43.0-Master (c6edab485a4)
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 // delegate logic to proj if version >= 8.2 available
642#if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=2)
643
644 QgsScopedProjSilentLogger errorLogger;
645
646 QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 );
647
648 ProjData projData = d->threadLocalProjData();
649 PJ_CONTEXT *projContext = QgsProjContext::get();
650
651#if PROJ_VERSION_MAJOR< 9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR<6)
652 // if source or destination crs include vertical components, we need to demote them to
653 // 2d crs first, otherwise proj_trans_bounds fails on proj < 9.6 (see https://github.com/OSGeo/PROJ/pull/4333)
654
655 QgsProjUtils::proj_pj_unique_ptr srcCrs( proj_get_source_crs( projContext, projData ) );
656 QgsProjUtils::proj_pj_unique_ptr destCrs( proj_get_target_crs( projContext, projData ) );
657
658 QgsProjUtils::proj_pj_unique_ptr srcCrsHorizontal;
659 QgsProjUtils::proj_pj_unique_ptr destCrsHorizontal;
661 if ( QgsProjUtils::hasVerticalAxis( srcCrs.get() ) ||
662 QgsProjUtils::hasVerticalAxis( destCrs.get() ) )
663 {
664 srcCrsHorizontal = QgsProjUtils::crsToHorizontalCrs( srcCrs.get() );
665 destCrsHorizontal = QgsProjUtils::crsToHorizontalCrs( destCrs.get() );
666 transform2D.reset( proj_create_crs_to_crs_from_pj( projContext, srcCrsHorizontal.get(), destCrsHorizontal.get(), nullptr, nullptr ) );
667 projData = transform2D.get();
668 }
669#endif
670
671 double transXMin = 0;
672 double transYMin = 0;
673 double transXMax = 0;
674 double transYMax = 0;
675
676 proj_errno_reset( projData );
677 // proj documentation recommends 21 points for densification
678 constexpr int DENSIFY_POINTS = 21;
679 int projResult = proj_trans_bounds( projContext, projData, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
680 xMin, yMin, xMax, yMax,
681 &transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
682
683 if ( ( projResult != 1
684 || !std::isfinite( transXMin )
685 || !std::isfinite( transXMax )
686 || !std::isfinite( transYMin )
687 || !std::isfinite( transYMax ) )
688 && ( 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
689 )
690 {
691 // fail #1 -- try with getting proj to auto-pick an appropriate coordinate operation for the points
692 if ( PJ *transform = d->threadLocalFallbackProjData() )
693 {
694 projResult = proj_trans_bounds( projContext, transform, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
695 xMin, yMin, xMax, yMax,
696 &transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
697 }
698 }
699
700 if ( projResult != 1
701 || !std::isfinite( transXMin )
702 || !std::isfinite( transXMax )
703 || !std::isfinite( transYMin )
704 || !std::isfinite( transYMax ) )
705 {
706 const QString projErr = QString::fromUtf8( proj_context_errno_string( projContext, proj_errno( projData ) ) );
707 const QString dir = ( direction == Qgis::TransformDirection::Forward ) ? QObject::tr( "Forward transform" ) : QObject::tr( "Inverse transform" );
708 const QString msg = QObject::tr( "%1 (%2 to %3) of bounding box failed: %4" )
709 .arg( dir,
710 ( direction == Qgis::TransformDirection::Forward ) ? d->mSourceCRS.authid() : d->mDestCRS.authid(),
711 ( direction == Qgis::TransformDirection::Forward ) ? d->mDestCRS.authid() : d->mSourceCRS.authid(),
712 projErr );
713 QgsDebugError( msg );
714
715 throw QgsCsException( msg );
716 }
717
718 // 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°
719 bool doHandle180Crossover = false;
720
721 if ( handle180Crossover
722 && ( ( direction == Qgis::TransformDirection::Forward && d->mDestCRS.isGeographic() ) ||
723 ( direction == Qgis::TransformDirection::Reverse && d->mSourceCRS.isGeographic() ) )
724 && ( transXMax < transXMin ) )
725 {
726 //if crossing the date line, temporarily add 360 degrees to -ve longitudes
727 std::swap( transXMax, transXMin );
728 if ( transXMin < 0 )
729 transXMin += 360;
730 if ( transXMax < 0 )
731 transXMax += 360;
732 doHandle180Crossover = true;
733 }
734
735 QgsRectangle boundingBoxRect{ transXMin, transYMin, transXMax, transYMax };
736 if ( boundingBoxRect.isNull() )
737 {
738 // something bad happened when reprojecting the filter rect... no finite points were left!
739 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
740 }
741
742 if ( doHandle180Crossover )
743 {
744 //subtract temporary addition of 360 degrees from longitudes
745 if ( boundingBoxRect.xMinimum() > 180.0 )
746 boundingBoxRect.setXMinimum( boundingBoxRect.xMinimum() - 360.0 );
747 if ( boundingBoxRect.xMaximum() > 180.0 )
748 boundingBoxRect.setXMaximum( boundingBoxRect.xMaximum() - 360.0 );
749 }
750
751 QgsDebugMsgLevel( "Projected extent: " + boundingBoxRect.toString(), 4 );
752
753 if ( boundingBoxRect.isEmpty() )
754 {
755 QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
756 }
757
758 return boundingBoxRect;
759#else
760 // this logic is buggy! See https://github.com/qgis/QGIS/issues/59821
761
762 // 64 points (<=2.12) is not enough, see #13665, for EPSG:4326 -> EPSG:3574 (say that it is a hard one),
763 // are decent result from about 500 points and more. This method is called quite often, but
764 // even with 1000 points it takes < 1ms.
765 // TODO: how to effectively and precisely reproject bounding box?
766 const int nPoints = 1000;
767 const double dst = std::sqrt( ( rect.width() * ( yMax - yMin ) ) / std::pow( std::sqrt( static_cast< double >( nPoints ) ) - 1, 2.0 ) );
768 const int nXPoints = std::min( static_cast< int >( std::ceil( rect.width() / dst ) ) + 1, 1000 );
769 const int nYPoints = std::min( static_cast< int >( std::ceil( ( yMax - yMin ) / dst ) ) + 1, 1000 );
770
771 QgsRectangle bb_rect;
772 bb_rect.setNull();
773
774 std::vector<double> x( nXPoints * static_cast< std::size_t >( nYPoints ) );
775 std::vector<double> y( nXPoints * static_cast< std::size_t >( nYPoints ) );
776 std::vector<double> z( nXPoints * static_cast< std::size_t >( nYPoints ) );
777
778 QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 );
779
780 // Populate the vectors
781
782 const double dx = rect.width() / static_cast< double >( nXPoints - 1 );
783 const double dy = ( yMax - yMin ) / static_cast< double >( nYPoints - 1 );
784
785 double pointY = yMin;
786
787 for ( int i = 0; i < nYPoints ; i++ )
788 {
789
790 // Start at right edge
791 double pointX = xMin;
792
793 for ( int j = 0; j < nXPoints; j++ )
794 {
795 x[( i * nXPoints ) + j] = pointX;
796 y[( i * nXPoints ) + j] = pointY;
797 // and the height...
798 z[( i * nXPoints ) + j] = 0.0;
799 // QgsDebugMsgLevel(QString("BBox coord: (%1, %2)").arg(x[(i*numP) + j]).arg(y[(i*numP) + j]), 2);
800 pointX += dx;
801 }
802 pointY += dy;
803 }
804
805 // Do transformation. Any exception generated must
806 // be handled in above layers.
807 try
808 {
809 transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
810 }
811 catch ( const QgsCsException & )
812 {
813 // rethrow the exception
814 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
815 throw;
816 }
817
818 // 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°
819 bool doHandle180Crossover = false;
820 if ( nXPoints > 0 )
821 {
822 const double xMin = std::fmod( x[0], 180.0 );
823 const double xMax = std::fmod( x[nXPoints - 1], 180.0 );
824 if ( handle180Crossover
825 && ( ( direction == Qgis::TransformDirection::Forward && d->mDestCRS.isGeographic() ) ||
826 ( direction == Qgis::TransformDirection::Reverse && d->mSourceCRS.isGeographic() ) )
827 && xMin > 0.0 && xMin <= 180.0 && xMax < 0.0 && xMax >= -180.0 )
828 {
829 doHandle180Crossover = true;
830 }
831 }
832
833 // Calculate the bounding box and use that for the extent
834 for ( int i = 0; i < nXPoints * nYPoints; i++ )
835 {
836 if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
837 {
838 continue;
839 }
840
841 if ( doHandle180Crossover )
842 {
843 //if crossing the date line, temporarily add 360 degrees to -ve longitudes
844 bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] );
845 }
846 else
847 {
848 bb_rect.combineExtentWith( x[i], y[i] );
849 }
850 }
851
852 if ( bb_rect.isNull() )
853 {
854 // something bad happened when reprojecting the filter rect... no finite points were left!
855 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
856 }
857
858 if ( doHandle180Crossover )
859 {
860 //subtract temporary addition of 360 degrees from longitudes
861 if ( bb_rect.xMinimum() > 180.0 )
862 bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 );
863 if ( bb_rect.xMaximum() > 180.0 )
864 bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 );
865 }
866
867 QgsDebugMsgLevel( "Projected extent: " + bb_rect.toString(), 4 );
868
869 if ( bb_rect.isEmpty() )
870 {
871 QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
872 }
873
874 return bb_rect;
875#endif
876}
877
878void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, Qgis::TransformDirection direction ) const
879{
880 if ( !d->mIsValid || d->mShortCircuit )
881 return;
882 // Refuse to transform the points if the srs's are invalid
883 if ( !d->mSourceCRS.isValid() )
884 {
885 QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. "
886 "The coordinates can not be reprojected. The CRS is: %1" )
887 .arg( d->mSourceCRS.toProj() ), QObject::tr( "CRS" ) );
888 return;
889 }
890 if ( !d->mDestCRS.isValid() )
891 {
892 QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. "
893 "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj() ), QObject::tr( "CRS" ) );
894 return;
895 }
896
897 std::vector< int > zNanPositions;
898 for ( int i = 0; i < numPoints; i++ )
899 {
900 if ( std::isnan( z[i] ) )
901 {
902 zNanPositions.push_back( i );
903 z[i] = 0.0;
904 }
905 }
906
907 std::vector< double > xprev( numPoints );
908 memcpy( xprev.data(), x, sizeof( double ) * numPoints );
909 std::vector< double > yprev( numPoints );
910 memcpy( yprev.data(), y, sizeof( double ) * numPoints );
911 std::vector< double > zprev( numPoints );
912 memcpy( zprev.data(), z, sizeof( double ) * numPoints );
913
914 const bool useTime = !std::isnan( d->mDefaultTime );
915 std::vector< double > t( useTime ? numPoints : 0, d->mDefaultTime );
916
917#ifdef COORDINATE_TRANSFORM_VERBOSE
918 double xorg = *x;
919 double yorg = *y;
920 QgsDebugMsgLevel( QStringLiteral( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ), 2 );
921#endif
922
923#ifdef QGISDEBUG
924 if ( !mHasContext )
925 {
926 QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 );
927 }
928#endif
929
930 // use proj4 to do the transform
931 ProjData projData = d->threadLocalProjData();
932
933 int projResult = 0;
934
935 proj_errno_reset( projData );
936 proj_trans_generic( projData, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
937 x, sizeof( double ), numPoints,
938 y, sizeof( double ), numPoints,
939 z, sizeof( double ), numPoints,
940 useTime ? t.data() : nullptr, sizeof( double ), useTime ? numPoints : 0 );
941 // Try to - approximately - emulate the behavior of pj_transform()...
942 // In the case of a single point transform, and a transformation error occurs,
943 // pj_transform() would return the errno. In cases of multiple point transform,
944 // it would continue (for non-transient errors, that is pipeline definition
945 // errors) and just set the resulting x,y to infinity. This is in fact a
946 // bit more subtle than that, and I'm not completely sure the logic in
947 // pj_transform() was really sane & fully bullet proof
948 // So here just check proj_errno() for single point transform
949 int actualRes = 0;
950 if ( numPoints == 1 )
951 {
952 projResult = proj_errno( projData );
953 actualRes = projResult;
954 }
955 else
956 {
957 actualRes = proj_errno( projData );
958 }
959 if ( actualRes == 0 )
960 {
961 // proj_errno is sometimes not an accurate method to test for transform failures - so we need to
962 // manually scan for nan values
963 if ( std::any_of( x, x + numPoints, []( double v ) { return std::isinf( v ); } )
964 || std::any_of( y, y + numPoints, []( double v ) { return std::isinf( v ); } )
965 || std::any_of( z, z + numPoints, []( double v ) { return std::isinf( v ); } ) )
966 {
967 actualRes = 1;
968 }
969 }
970
971 mFallbackOperationOccurred = false;
972 bool errorOccurredDuringFallbackOperation = false;
973 if ( actualRes != 0
974 && ( 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
975 && ( d->mAllowFallbackTransforms || mBallparkTransformsAreAppropriate ) )
976 {
977 // fail #1 -- try with getting proj to auto-pick an appropriate coordinate operation for the points
978 if ( PJ *transform = d->threadLocalFallbackProjData() )
979 {
980 projResult = 0;
981 proj_errno_reset( transform );
982 memcpy( x, xprev.data(), sizeof( double ) * numPoints );
983 memcpy( y, yprev.data(), sizeof( double ) * numPoints );
984 memcpy( z, zprev.data(), sizeof( double ) * numPoints );
985 proj_trans_generic( transform, direction == Qgis::TransformDirection::Forward ? PJ_FWD : PJ_INV,
986 x, sizeof( double ), numPoints,
987 y, sizeof( double ), numPoints,
988 z, sizeof( double ), numPoints,
989 useTime ? t.data() : nullptr, sizeof( double ), useTime ? numPoints : 0 );
990 // Try to - approximately - emulate the behavior of pj_transform()...
991 // In the case of a single point transform, and a transformation error occurs,
992 // pj_transform() would return the errno. In cases of multiple point transform,
993 // it would continue (for non-transient errors, that is pipeline definition
994 // errors) and just set the resulting x,y to infinity. This is in fact a
995 // bit more subtle than that, and I'm not completely sure the logic in
996 // pj_transform() was really sane & fully bullet proof
997 // So here just check proj_errno() for single point transform
998 if ( numPoints == 1 )
999 {
1000 projResult = proj_errno( transform );
1001 // hmm - something very odd here. We can't trust proj_errno( transform ), as that's giving us incorrect error numbers
1002 // (such as "failed to load datum shift file", which is definitely incorrect for a default proj created operation!)
1003 // so we resort to testing values ourselves...
1004 errorOccurredDuringFallbackOperation = std::isinf( x[0] ) || std::isinf( y[0] ) || std::isinf( z[0] );
1005 }
1006
1007 if ( !errorOccurredDuringFallbackOperation )
1008 {
1009 mFallbackOperationOccurred = true;
1010 }
1011
1012 if ( !mBallparkTransformsAreAppropriate && !mDisableFallbackHandler && sFallbackOperationOccurredHandler )
1013 {
1014 sFallbackOperationOccurredHandler( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation );
1015#if 0
1016 const QString warning = QStringLiteral( "A fallback coordinate operation was used between %1 and %2" ).arg( d->mSourceCRS.authid(),
1017 d->mDestCRS.authid() );
1018 qWarning( "%s", warning.toLatin1().constData() );
1019#endif
1020 }
1021 }
1022 }
1023
1024 for ( const int &pos : zNanPositions )
1025 {
1026 z[pos] = std::numeric_limits<double>::quiet_NaN();
1027 }
1028
1029 if ( projResult != 0 || errorOccurredDuringFallbackOperation )
1030 {
1031 //something bad happened....
1032 QString points;
1033
1034 const QChar delim = numPoints > 1 ? '\n' : ' ';
1035 for ( int i = 0; i < numPoints; ++i )
1036 {
1037 points += QStringLiteral( "(%1, %2)" ).arg( xprev[i], 0, 'f' ).arg( yprev[i], 0, 'f' ) + delim;
1038 }
1039
1040 const QString dir = ( direction == Qgis::TransformDirection::Forward ) ? QObject::tr( "Forward transform" ) : QObject::tr( "Inverse transform" );
1041
1042 PJ_CONTEXT *projContext = QgsProjContext::get();
1043 const QString projError = !errorOccurredDuringFallbackOperation ? QString::fromUtf8( proj_context_errno_string( projContext, projResult ) ) : QObject::tr( "Fallback transform failed" );
1044
1045 const QString msg = QObject::tr( "%1 (%2 to %3) of%4%5Error: %6" )
1046 .arg( dir,
1047 ( direction == Qgis::TransformDirection::Forward ) ? d->mSourceCRS.authid() : d->mDestCRS.authid(),
1048 ( direction == Qgis::TransformDirection::Forward ) ? d->mDestCRS.authid() : d->mSourceCRS.authid(),
1049 QString( delim ),
1050 points,
1051 projError );
1052
1053 // don't flood console with thousands of duplicate transform error messages
1054 if ( msg != mLastError )
1055 {
1056 QgsDebugError( "Projection failed emitting invalid transform signal: " + msg );
1057 mLastError = msg;
1058 }
1059 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
1060
1061 throw QgsCsException( msg );
1062 }
1063
1064#ifdef COORDINATE_TRANSFORM_VERBOSE
1065 QgsDebugMsgLevel( QStringLiteral( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
1066 .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 )
1067 .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ), 2 );
1068#endif
1069}
1070
1072{
1073 return d->mIsValid;
1074}
1075
1077{
1078 return !d->mIsValid || d->mShortCircuit;
1079}
1080
1082{
1083 return d->mIsValid && d->mHasVerticalComponent;
1084}
1085
1087{
1088 return d->mProjCoordinateOperation;
1089}
1090
1092{
1093 ProjData projData = d->threadLocalProjData();
1095}
1096
1097void QgsCoordinateTransform::setCoordinateOperation( const QString &operation ) const
1098{
1099 d.detach();
1100 d->mProjCoordinateOperation = operation;
1101 d->mShouldReverseCoordinateOperation = false;
1102}
1103
1105{
1106 d.detach();
1107 d->mAllowFallbackTransforms = allowed;
1108}
1109
1111{
1112 return d->mAllowFallbackTransforms;
1113}
1114
1116{
1117 mBallparkTransformsAreAppropriate = appropriate;
1118}
1119
1121{
1122 mDisableFallbackHandler = disabled;
1123}
1124
1126{
1127 return mFallbackOperationOccurred;
1128}
1129
1130const char *finder( const char *name )
1131{
1132 QString proj;
1133#ifdef Q_OS_WIN
1134 proj = QApplication::applicationDirPath()
1135 + "/share/proj/" + QString( name );
1136#else
1137 Q_UNUSED( name )
1138#endif
1139 return proj.toUtf8();
1140}
1141
1142bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, const QString &coordinateOperationProj, bool allowFallback )
1143{
1144 if ( !src.isValid() || !dest.isValid() )
1145 return false;
1146
1147 const QString sourceKey = src.authid().isEmpty() ?
1149 const QString destKey = dest.authid().isEmpty() ?
1151
1152 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1153 return false;
1154
1155 QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Read );
1156 if ( sDisableCache )
1157 return false;
1158
1159 const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
1160 for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
1161 {
1162 if ( ( *valIt ).coordinateOperation() == coordinateOperationProj
1163 && ( *valIt ).allowFallbackTransforms() == allowFallback
1164 && qgsNanCompatibleEquals( src.coordinateEpoch(), ( *valIt ).sourceCrs().coordinateEpoch() )
1165 && qgsNanCompatibleEquals( dest.coordinateEpoch(), ( *valIt ).destinationCrs().coordinateEpoch() )
1166 )
1167 {
1168 // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
1169 const QgsCoordinateTransformContext context = mContext;
1170#ifdef QGISDEBUG
1171 const bool hasContext = mHasContext;
1172#endif
1173 *this = *valIt;
1174 locker.unlock();
1175
1176 mContext = context;
1177#ifdef QGISDEBUG
1178 mHasContext = hasContext;
1179#endif
1180
1181 return true;
1182 }
1183 }
1184 return false;
1185}
1186
1187void QgsCoordinateTransform::addToCache()
1188{
1189 if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
1190 return;
1191
1192 const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
1193 d->mSourceCRS.toWkt( Qgis::CrsWktVariant::Preferred ) : d->mSourceCRS.authid();
1194 const QString destKey = d->mDestCRS.authid().isEmpty() ?
1195 d->mDestCRS.toWkt( Qgis::CrsWktVariant::Preferred ) : d->mDestCRS.authid();
1196
1197 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1198 return;
1199
1200 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1201 if ( sDisableCache )
1202 return;
1203
1204 sTransforms.insert( qMakePair( sourceKey, destKey ), *this );
1205}
1206
1208{
1210 return d->mSourceDatumTransform;
1212}
1213
1215{
1216 d.detach();
1218 d->mSourceDatumTransform = dt;
1220}
1221
1223{
1225 return d->mDestinationDatumTransform;
1227}
1228
1230{
1231 d.detach();
1233 d->mDestinationDatumTransform = dt;
1235}
1236
1238{
1239 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1240 if ( sDisableCache )
1241 return;
1242
1243 if ( disableCache )
1244 {
1245 sDisableCache = true;
1246 }
1247
1248 sTransforms.clear();
1249}
1250
1251void QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( void *pj_context )
1252{
1253 // Not completely sure about object order destruction after main() has
1254 // exited. So it is safer to check sDisableCache before using sCacheLock
1255 // in case sCacheLock would have been destroyed before the current TLS
1256 // QgsProjContext object that has called us...
1257 if ( sDisableCache )
1258 return;
1259
1260 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1261 // cppcheck-suppress identicalConditionAfterEarlyExit
1262 if ( sDisableCache )
1263 return;
1264
1265 for ( auto it = sTransforms.begin(); it != sTransforms.end(); )
1266 {
1267 auto &v = it.value();
1268 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
1269 it = sTransforms.erase( it );
1270 else
1271 ++it;
1272 }
1273}
1274
1275double QgsCoordinateTransform::scaleFactor( const QgsRectangle &ReferenceExtent ) const
1276{
1277 const QgsPointXY source1( ReferenceExtent.xMinimum(), ReferenceExtent.yMinimum() );
1278 const QgsPointXY source2( ReferenceExtent.xMaximum(), ReferenceExtent.yMaximum() );
1279 const double distSourceUnits = std::sqrt( source1.sqrDist( source2 ) );
1280 const QgsPointXY dest1 = transform( source1 );
1281 const QgsPointXY dest2 = transform( source2 );
1282 const double distDestUnits = std::sqrt( dest1.sqrDist( dest2 ) );
1283 return distDestUnits / distSourceUnits;
1284}
1285
1287{
1288 QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( handler );
1289}
1290
1292{
1293 QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( handler );
1294}
1295
1297{
1298 QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
1299}
1300
1302{
1303 QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
1304}
1305
1306void QgsCoordinateTransform::setFallbackOperationOccurredHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
1307{
1308 sFallbackOperationOccurredHandler = handler;
1309}
1310
1312{
1313 QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( handler );
1314}
@ 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)
This class 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.
Class for doing transforms between two map 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).
A class to represent 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
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
@ 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.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
Definition qgsvector3d.h:31
double y() const
Returns Y coordinate.
Definition qgsvector3d.h:50
double z() const
Returns Z coordinate.
Definition qgsvector3d.h:52
double x() const
Returns X coordinate.
Definition qgsvector3d.h:48
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6783
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6782
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition qgis.h:6168
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.