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