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