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