QGIS API Documentation 3.32.0-Lima (311a8cb8a6)
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
62QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, const QgsCoordinateTransformContext &context, Qgis::CoordinateTransformationFlags flags )
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
95QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, const QgsProject *project, Qgis::CoordinateTransformationFlags flags )
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 if ( !source.isValid() || !destination.isValid() )
180 return false;
181
182#if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=1)
183 if ( source.celestialBodyName() != destination.celestialBodyName() )
184 return false;
185#endif
186
187 return true;
188}
189
191{
192 d.detach();
193 d->mSourceCRS = crs;
194
195 if ( mIgnoreImpossible && !isTransformationPossible( d->mSourceCRS, d->mDestCRS ) )
196 {
197 d->invalidate();
198 return;
199 }
200
201 if ( !d->checkValidity() )
202 return;
203
204 d->calculateTransforms( mContext );
206 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
207 {
208 d->initialize();
209 addToCache();
210 }
212}
214{
215 d.detach();
216 d->mDestCRS = crs;
217
218 if ( mIgnoreImpossible && !isTransformationPossible( d->mSourceCRS, d->mDestCRS ) )
219 {
220 d->invalidate();
221 return;
222 }
223
224 if ( !d->checkValidity() )
225 return;
226
227 d->calculateTransforms( mContext );
229 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
230 {
231 d->initialize();
232 addToCache();
233 }
235}
236
238{
239 d.detach();
240 mContext = context;
241#ifdef QGISDEBUG
242 mHasContext = true;
243#endif
244
245 if ( mIgnoreImpossible && !isTransformationPossible( d->mSourceCRS, d->mDestCRS ) )
246 {
247 d->invalidate();
248 return;
249 }
250
251 if ( !d->checkValidity() )
252 return;
253
254 d->calculateTransforms( mContext );
256 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
257 {
258 d->initialize();
259 addToCache();
260 }
262}
263
265{
266 return mContext;
267}
268
270{
271 return d->mSourceCRS;
272}
273
275{
276 return d->mDestCRS;
277}
278
280{
281 if ( !d->mIsValid || d->mShortCircuit )
282 return point;
283
284 // transform x
285 double x = point.x();
286 double y = point.y();
287 double z = 0.0;
288 try
289 {
290 transformCoords( 1, &x, &y, &z, direction );
291 }
292 catch ( const QgsCsException & )
293 {
294 // rethrow the exception
295 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
296 throw;
297 }
298
299 return QgsPointXY( x, y );
300}
301
302
303QgsPointXY QgsCoordinateTransform::transform( const double theX, const double theY = 0.0, Qgis::TransformDirection direction ) const
304{
305 try
306 {
307 return transform( QgsPointXY( theX, theY ), direction );
308 }
309 catch ( const QgsCsException & )
310 {
311 // rethrow the exception
312 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
313 throw;
314 }
315}
316
318{
319 if ( !d->mIsValid || d->mShortCircuit )
320 return rect;
321 // transform x
322 double x1 = rect.xMinimum();
323 double y1 = rect.yMinimum();
324 double x2 = rect.xMaximum();
325 double y2 = rect.yMaximum();
326
327 // Number of points to reproject------+
328 // |
329 // V
330 try
331 {
332 double z = 0.0;
333 transformCoords( 1, &x1, &y1, &z, direction );
334 transformCoords( 1, &x2, &y2, &z, direction );
335 }
336 catch ( const QgsCsException & )
337 {
338 // rethrow the exception
339 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
340 throw;
341 }
342
343#ifdef COORDINATE_TRANSFORM_VERBOSE
344 QgsDebugMsgLevel( QStringLiteral( "Rect projection..." ), 2 );
345 QgsDebugMsgLevel( QStringLiteral( "Xmin : %1 --> %2" ).arg( rect.xMinimum() ).arg( x1 ), 2 );
346 QgsDebugMsgLevel( QStringLiteral( "Ymin : %1 --> %2" ).arg( rect.yMinimum() ).arg( y1 ), 2 );
347 QgsDebugMsgLevel( QStringLiteral( "Xmax : %1 --> %2" ).arg( rect.xMaximum() ).arg( x2 ), 2 );
348 QgsDebugMsgLevel( QStringLiteral( "Ymax : %1 --> %2" ).arg( rect.yMaximum() ).arg( y2 ), 2 );
349#endif
350 return QgsRectangle( x1, y1, x2, y2 );
351}
352
354{
355 double x = point.x();
356 double y = point.y();
357 double z = point.z();
358 try
359 {
360 transformCoords( 1, &x, &y, &z, direction );
361 }
362 catch ( const QgsCsException & )
363 {
364 // rethrow the exception
365 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
366 throw;
367 }
368 return QgsVector3D( x, y, z );
369}
370
371void QgsCoordinateTransform::transformInPlace( double &x, double &y, double &z,
372 Qgis::TransformDirection direction ) const
373{
374 if ( !d->mIsValid || d->mShortCircuit )
375 return;
376#ifdef QGISDEBUG
377// QgsDebugMsgLevel(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__), 2);
378#endif
379 // transform x
380 try
381 {
382 transformCoords( 1, &x, &y, &z, direction );
383 }
384 catch ( const QgsCsException & )
385 {
386 // rethrow the exception
387 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
388 throw;
389 }
390}
391
392void QgsCoordinateTransform::transformInPlace( float &x, float &y, double &z,
393 Qgis::TransformDirection direction ) const
394{
395 double xd = static_cast< double >( x ), yd = static_cast< double >( y );
396 transformInPlace( xd, yd, z, direction );
397 x = xd;
398 y = yd;
399}
400
401void QgsCoordinateTransform::transformInPlace( float &x, float &y, float &z,
402 Qgis::TransformDirection direction ) const
403{
404 if ( !d->mIsValid || d->mShortCircuit )
405 return;
406#ifdef QGISDEBUG
407 // QgsDebugMsgLevel(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__), 2);
408#endif
409 // transform x
410 try
411 {
412 double xd = x;
413 double yd = y;
414 double zd = z;
415 transformCoords( 1, &xd, &yd, &zd, direction );
416 x = xd;
417 y = yd;
418 z = zd;
419 }
420 catch ( QgsCsException & )
421 {
422 // rethrow the exception
423 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
424 throw;
425 }
426}
427
429{
430 if ( !d->mIsValid || d->mShortCircuit )
431 {
432 return;
433 }
434
435 //create x, y arrays
436 const int nVertices = poly.size();
437
438 QVector<double> x( nVertices );
439 QVector<double> y( nVertices );
440 QVector<double> z( nVertices );
441 double *destX = x.data();
442 double *destY = y.data();
443 double *destZ = z.data();
444
445 const QPointF *polyData = poly.constData();
446 for ( int i = 0; i < nVertices; ++i )
447 {
448 *destX++ = polyData->x();
449 *destY++ = polyData->y();
450 *destZ++ = 0;
451 polyData++;
452 }
453
454 QString err;
455 try
456 {
457 transformCoords( nVertices, x.data(), y.data(), z.data(), direction );
458 }
459 catch ( const QgsCsException &e )
460 {
461 // record the exception, but don't rethrow it until we've recorded the coordinates we *could* transform
462 err = e.what();
463 }
464
465 QPointF *destPoint = poly.data();
466 const double *srcX = x.constData();
467 const double *srcY = y.constData();
468 for ( int i = 0; i < nVertices; ++i )
469 {
470 destPoint->rx() = *srcX++;
471 destPoint->ry() = *srcY++;
472 destPoint++;
473 }
474
475 // rethrow the exception
476 if ( !err.isEmpty() )
477 throw QgsCsException( err );
478}
479
480void QgsCoordinateTransform::transformInPlace( QVector<double> &x, QVector<double> &y, QVector<double> &z,
481 Qgis::TransformDirection direction ) const
482{
483
484 if ( !d->mIsValid || d->mShortCircuit )
485 return;
486
487 Q_ASSERT( x.size() == y.size() );
488
489 // Apparently, if one has a std::vector, it is valid to use the
490 // address of the first element in the vector as a pointer to an
491 // array of the vectors data, and hence easily interface with code
492 // that wants C-style arrays.
493
494 try
495 {
496 transformCoords( x.size(), &x[0], &y[0], &z[0], direction );
497 }
498 catch ( const QgsCsException & )
499 {
500 // rethrow the exception
501 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
502 throw;
503 }
504}
505
506
507void QgsCoordinateTransform::transformInPlace( QVector<float> &x, QVector<float> &y, QVector<float> &z,
508 Qgis::TransformDirection direction ) const
509{
510 if ( !d->mIsValid || d->mShortCircuit )
511 return;
512
513 Q_ASSERT( x.size() == y.size() );
514
515 // Apparently, if one has a std::vector, it is valid to use the
516 // address of the first element in the vector as a pointer to an
517 // array of the vectors data, and hence easily interface with code
518 // that wants C-style arrays.
519
520 try
521 {
522 //copy everything to double vectors since proj needs double
523 const int vectorSize = x.size();
524 QVector<double> xd( x.size() );
525 QVector<double> yd( y.size() );
526 QVector<double> zd( z.size() );
527
528 double *destX = xd.data();
529 double *destY = yd.data();
530 double *destZ = zd.data();
531
532 const float *srcX = x.constData();
533 const float *srcY = y.constData();
534 const float *srcZ = z.constData();
535
536 for ( int i = 0; i < vectorSize; ++i )
537 {
538 *destX++ = static_cast< double >( *srcX++ );
539 *destY++ = static_cast< double >( *srcY++ );
540 *destZ++ = static_cast< double >( *srcZ++ );
541 }
542
543 transformCoords( x.size(), &xd[0], &yd[0], &zd[0], direction );
544
545 //copy back
546 float *destFX = x.data();
547 float *destFY = y.data();
548 float *destFZ = z.data();
549 const double *srcXD = xd.constData();
550 const double *srcYD = yd.constData();
551 const double *srcZD = zd.constData();
552 for ( int i = 0; i < vectorSize; ++i )
553 {
554 *destFX++ = static_cast< float >( *srcXD++ );
555 *destFY++ = static_cast< float >( *srcYD++ );
556 *destFZ++ = static_cast< float >( *srcZD++ );
557 }
558 }
559 catch ( QgsCsException & )
560 {
561 // rethrow the exception
562 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
563 throw;
564 }
565}
566
567QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &rect, Qgis::TransformDirection direction, const bool handle180Crossover ) const
568{
569 // Calculate the bounding box of a QgsRectangle in the source CRS
570 // when projected to the destination CRS (or the inverse).
571 // This is done by looking at a number of points spread evenly
572 // across the rectangle
573
574 if ( !d->mIsValid || d->mShortCircuit )
575 return rect;
576
577 if ( rect.isEmpty() )
578 {
579 const QgsPointXY p = transform( rect.xMinimum(), rect.yMinimum(), direction );
580 return QgsRectangle( p, p );
581 }
582
583 double yMin = rect.yMinimum();
584 double yMax = rect.yMaximum();
585 if ( d->mGeographicToWebMercator &&
586 ( ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) ||
587 ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ) )
588 {
589 // Latitudes close to 90 degree project to infinite northing in theory.
590 // We limit to 90 - 1e-1 which reproject to northing of ~ 44e6 m (about twice
591 // the maximum easting of ~20e6 m).
592 // For reference, GoogleMercator tiles are limited to a northing ~85 deg / ~20e6 m
593 // so limiting to 90 - 1e-1 is reasonable.
594 constexpr double EPS = 1e-1;
595 if ( yMin < -90 + EPS )
596 {
597 if ( yMax < -90 + EPS )
598 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
599 yMin = -90 + EPS;
600 }
601 if ( yMax > 90 - EPS )
602 {
603 if ( yMin > 90 - EPS )
604 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
605 yMax = 90 - EPS;
606 }
607 }
608
609 // 64 points (<=2.12) is not enough, see #13665, for EPSG:4326 -> EPSG:3574 (say that it is a hard one),
610 // are decent result from about 500 points and more. This method is called quite often, but
611 // even with 1000 points it takes < 1ms.
612 // TODO: how to effectively and precisely reproject bounding box?
613 const int nPoints = 1000;
614 const double d = std::sqrt( ( rect.width() * ( yMax - yMin ) ) / std::pow( std::sqrt( static_cast< double >( nPoints ) ) - 1, 2.0 ) );
615 const int nXPoints = std::min( static_cast< int >( std::ceil( rect.width() / d ) ) + 1, 1000 );
616 const int nYPoints = std::min( static_cast< int >( std::ceil( ( yMax - yMin ) / d ) ) + 1, 1000 );
617
618 QgsRectangle bb_rect;
619 bb_rect.setMinimal();
620
621 // We're interfacing with C-style vectors in the
622 // end, so let's do C-style vectors here too.
623 QVector<double> x( nXPoints * nYPoints );
624 QVector<double> y( nXPoints * nYPoints );
625 QVector<double> z( nXPoints * nYPoints );
626
627 QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 );
628
629 // Populate the vectors
630
631 const double dx = rect.width() / static_cast< double >( nXPoints - 1 );
632 const double dy = ( yMax - yMin ) / static_cast< double >( nYPoints - 1 );
633
634 double pointY = yMin;
635
636 for ( int i = 0; i < nYPoints ; i++ )
637 {
638
639 // Start at right edge
640 double pointX = rect.xMinimum();
641
642 for ( int j = 0; j < nXPoints; j++ )
643 {
644 x[( i * nXPoints ) + j] = pointX;
645 y[( i * nXPoints ) + j] = pointY;
646 // and the height...
647 z[( i * nXPoints ) + j] = 0.0;
648 // QgsDebugMsgLevel(QString("BBox coord: (%1, %2)").arg(x[(i*numP) + j]).arg(y[(i*numP) + j]), 2);
649 pointX += dx;
650 }
651 pointY += dy;
652 }
653
654 // Do transformation. Any exception generated must
655 // be handled in above layers.
656 try
657 {
658 transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
659 }
660 catch ( const QgsCsException & )
661 {
662 // rethrow the exception
663 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
664 throw;
665 }
666
667 // Calculate the bounding box and use that for the extent
668
669 for ( int i = 0; i < nXPoints * nYPoints; i++ )
670 {
671 if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
672 {
673 continue;
674 }
675
676 if ( handle180Crossover )
677 {
678 //if crossing the date line, temporarily add 360 degrees to -ve longitudes
679 bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] );
680 }
681 else
682 {
683 bb_rect.combineExtentWith( x[i], y[i] );
684 }
685 }
686
687 if ( bb_rect.isNull() )
688 {
689 // something bad happened when reprojecting the filter rect... no finite points were left!
690 throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
691 }
692
693 if ( handle180Crossover )
694 {
695 //subtract temporary addition of 360 degrees from longitudes
696 if ( bb_rect.xMinimum() > 180.0 )
697 bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 );
698 if ( bb_rect.xMaximum() > 180.0 )
699 bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 );
700 }
701
702 QgsDebugMsgLevel( "Projected extent: " + bb_rect.toString(), 4 );
703
704 if ( bb_rect.isEmpty() )
705 {
706 QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
707 }
708
709 return bb_rect;
710}
711
712void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, Qgis::TransformDirection direction ) const
713{
714 if ( !d->mIsValid || d->mShortCircuit )
715 return;
716 // Refuse to transform the points if the srs's are invalid
717 if ( !d->mSourceCRS.isValid() )
718 {
719 QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. "
720 "The coordinates can not be reprojected. The CRS is: %1" )
721 .arg( d->mSourceCRS.toProj() ), QObject::tr( "CRS" ) );
722 return;
723 }
724 if ( !d->mDestCRS.isValid() )
725 {
726 QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. "
727 "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj() ), QObject::tr( "CRS" ) );
728 return;
729 }
730
731 std::vector< int > zNanPositions;
732 for ( int i = 0; i < numPoints; i++ )
733 {
734 if ( std::isnan( z[i] ) )
735 {
736 zNanPositions.push_back( i );
737 z[i] = 0.0;
738 }
739 }
740
741 std::vector< double > xprev( numPoints );
742 memcpy( xprev.data(), x, sizeof( double ) * numPoints );
743 std::vector< double > yprev( numPoints );
744 memcpy( yprev.data(), y, sizeof( double ) * numPoints );
745 std::vector< double > zprev( numPoints );
746 memcpy( zprev.data(), z, sizeof( double ) * numPoints );
747
748 const bool useTime = !std::isnan( d->mDefaultTime );
749 std::vector< double > t( useTime ? numPoints : 0, d->mDefaultTime );
750
751#ifdef COORDINATE_TRANSFORM_VERBOSE
752 double xorg = *x;
753 double yorg = *y;
754 QgsDebugMsgLevel( QStringLiteral( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ), 2 );
755#endif
756
757#ifdef QGISDEBUG
758 if ( !mHasContext )
759 QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 );
760#endif
761
762 // use proj4 to do the transform
763
764 // if the source/destination projection is lat/long, convert the points to radians
765 // prior to transforming
766 ProjData projData = d->threadLocalProjData();
767
768 int projResult = 0;
769
770 proj_errno_reset( projData );
771 proj_trans_generic( projData, ( direction == Qgis::TransformDirection::Forward && !d->mIsReversed ) || ( direction == Qgis::TransformDirection::Reverse && d->mIsReversed ) ? PJ_FWD : PJ_INV,
772 x, sizeof( double ), numPoints,
773 y, sizeof( double ), numPoints,
774 z, sizeof( double ), numPoints,
775 useTime ? t.data() : nullptr, sizeof( double ), useTime ? numPoints : 0 );
776 // Try to - approximatively - emulate the behavior of pj_transform()...
777 // In the case of a single point transform, and a transformation error occurs,
778 // pj_transform() would return the errno. In cases of multiple point transform,
779 // it would continue (for non-transient errors, that is pipeline definition
780 // errors) and just set the resulting x,y to infinity. This is in fact a
781 // bit more subtle than that, and I'm not completely sure the logic in
782 // pj_transform() was really sane & fully bullet proof
783 // So here just check proj_errno() for single point transform
784 int actualRes = 0;
785 if ( numPoints == 1 )
786 {
787 projResult = proj_errno( projData );
788 actualRes = projResult;
789 }
790 else
791 {
792 actualRes = proj_errno( projData );
793 }
794 if ( actualRes == 0 )
795 {
796 // proj_errno is sometimes not an accurate method to test for transform failures - so we need to
797 // manually scan for nan values
798 if ( std::any_of( x, x + numPoints, []( double v ) { return std::isinf( v ); } )
799 || std::any_of( y, y + numPoints, []( double v ) { return std::isinf( v ); } )
800 || std::any_of( z, z + numPoints, []( double v ) { return std::isinf( v ); } ) )
801 {
802 actualRes = 1;
803 }
804 }
805
806 mFallbackOperationOccurred = false;
807 if ( actualRes != 0
808 && ( 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
809 && ( d->mAllowFallbackTransforms || mBallparkTransformsAreAppropriate ) )
810 {
811 // fail #1 -- try with getting proj to auto-pick an appropriate coordinate operation for the points
812 if ( PJ *transform = d->threadLocalFallbackProjData() )
813 {
814 projResult = 0;
815 proj_errno_reset( transform );
816 proj_trans_generic( transform, direction == Qgis::TransformDirection::Forward ? PJ_FWD : PJ_INV,
817 xprev.data(), sizeof( double ), numPoints,
818 yprev.data(), sizeof( double ), numPoints,
819 zprev.data(), sizeof( double ), numPoints,
820 useTime ? t.data() : nullptr, sizeof( double ), useTime ? numPoints : 0 );
821 // Try to - approximatively - emulate the behavior of pj_transform()...
822 // In the case of a single point transform, and a transformation error occurs,
823 // pj_transform() would return the errno. In cases of multiple point transform,
824 // it would continue (for non-transient errors, that is pipeline definition
825 // errors) and just set the resulting x,y to infinity. This is in fact a
826 // bit more subtle than that, and I'm not completely sure the logic in
827 // pj_transform() was really sane & fully bullet proof
828 // So here just check proj_errno() for single point transform
829 if ( numPoints == 1 )
830 {
831 // hmm - something very odd here. We can't trust proj_errno( transform ), as that's giving us incorrect error numbers
832 // (such as "failed to load datum shift file", which is definitely incorrect for a default proj created operation!)
833 // so we resort to testing values ourselves...
834 projResult = std::isinf( xprev[0] ) || std::isinf( yprev[0] ) || std::isinf( zprev[0] ) ? 1 : 0;
835 }
836
837 if ( projResult == 0 )
838 {
839 memcpy( x, xprev.data(), sizeof( double ) * numPoints );
840 memcpy( y, yprev.data(), sizeof( double ) * numPoints );
841 memcpy( z, zprev.data(), sizeof( double ) * numPoints );
842 mFallbackOperationOccurred = true;
843 }
844
845 if ( !mBallparkTransformsAreAppropriate && !mDisableFallbackHandler && sFallbackOperationOccurredHandler )
846 {
847 sFallbackOperationOccurredHandler( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation );
848#if 0
849 const QString warning = QStringLiteral( "A fallback coordinate operation was used between %1 and %2" ).arg( d->mSourceCRS.authid(),
850 d->mDestCRS.authid() );
851 qWarning( "%s", warning.toLatin1().constData() );
852#endif
853 }
854 }
855 }
856
857 for ( const int &pos : zNanPositions )
858 {
859 z[pos] = std::numeric_limits<double>::quiet_NaN();
860 }
861
862 if ( projResult != 0 )
863 {
864 //something bad happened....
865 QString points;
866
867 for ( int i = 0; i < numPoints; ++i )
868 {
869 points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' );
870 }
871
872 const QString dir = ( direction == Qgis::TransformDirection::Forward ) ? QObject::tr( "forward transform" ) : QObject::tr( "inverse transform" );
873
874 const QString msg = QObject::tr( "%1 of\n"
875 "%2"
876 "Error: %3" )
877 .arg( dir,
878 points,
879 projResult < 0 ? QString::fromUtf8( proj_errno_string( projResult ) ) : QObject::tr( "Fallback transform failed" ) );
880
881
882 // don't flood console with thousands of duplicate transform error messages
883 if ( msg != mLastError )
884 {
885 QgsDebugError( "Projection failed emitting invalid transform signal: " + msg );
886 mLastError = msg;
887 }
888 QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
889
890 throw QgsCsException( msg );
891 }
892
893#ifdef COORDINATE_TRANSFORM_VERBOSE
894 QgsDebugMsgLevel( QStringLiteral( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
895 .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 )
896 .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ), 2 );
897#endif
898}
899
901{
902 return d->mIsValid;
903}
904
906{
907 return !d->mIsValid || d->mShortCircuit;
908}
909
911{
912 return d->mProjCoordinateOperation;
913}
914
916{
917 ProjData projData = d->threadLocalProjData();
919}
920
921void QgsCoordinateTransform::setCoordinateOperation( const QString &operation ) const
922{
923 d.detach();
924 d->mProjCoordinateOperation = operation;
925 d->mShouldReverseCoordinateOperation = false;
926}
927
929{
930 d.detach();
931 d->mAllowFallbackTransforms = allowed;
932}
933
935{
936 return d->mAllowFallbackTransforms;
937}
938
940{
941 mBallparkTransformsAreAppropriate = appropriate;
942}
943
945{
946 mDisableFallbackHandler = disabled;
947}
948
950{
951 return mFallbackOperationOccurred;
952}
953
954const char *finder( const char *name )
955{
956 QString proj;
957#ifdef Q_OS_WIN
958 proj = QApplication::applicationDirPath()
959 + "/share/proj/" + QString( name );
960#else
961 Q_UNUSED( name )
962#endif
963 return proj.toUtf8();
964}
965
966bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, const QString &coordinateOperationProj, bool allowFallback )
967{
968 if ( !src.isValid() || !dest.isValid() )
969 return false;
970
971 const QString sourceKey = src.authid().isEmpty() ?
973 const QString destKey = dest.authid().isEmpty() ?
975
976 if ( sourceKey.isEmpty() || destKey.isEmpty() )
977 return false;
978
979 QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Read );
980 if ( sDisableCache )
981 return false;
982
983 const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
984 for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
985 {
986 if ( ( *valIt ).coordinateOperation() == coordinateOperationProj
987 && ( *valIt ).allowFallbackTransforms() == allowFallback
988 && qgsNanCompatibleEquals( src.coordinateEpoch(), ( *valIt ).sourceCrs().coordinateEpoch() )
989 && qgsNanCompatibleEquals( dest.coordinateEpoch(), ( *valIt ).destinationCrs().coordinateEpoch() )
990 )
991 {
992 // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
993 const QgsCoordinateTransformContext context = mContext;
994#ifdef QGISDEBUG
995 const bool hasContext = mHasContext;
996#endif
997 *this = *valIt;
998 locker.unlock();
999
1000 mContext = context;
1001#ifdef QGISDEBUG
1002 mHasContext = hasContext;
1003#endif
1004
1005 return true;
1006 }
1007 }
1008 return false;
1009}
1010
1011void QgsCoordinateTransform::addToCache()
1012{
1013 if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
1014 return;
1015
1016 const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
1017 d->mSourceCRS.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : d->mSourceCRS.authid();
1018 const QString destKey = d->mDestCRS.authid().isEmpty() ?
1019 d->mDestCRS.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : d->mDestCRS.authid();
1020
1021 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1022 return;
1023
1024 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1025 if ( sDisableCache )
1026 return;
1027
1028 sTransforms.insert( qMakePair( sourceKey, destKey ), *this );
1029}
1030
1032{
1034 return d->mSourceDatumTransform;
1036}
1037
1039{
1040 d.detach();
1042 d->mSourceDatumTransform = dt;
1044}
1045
1047{
1049 return d->mDestinationDatumTransform;
1051}
1052
1054{
1055 d.detach();
1057 d->mDestinationDatumTransform = dt;
1059}
1060
1062{
1063 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1064 if ( sDisableCache )
1065 return;
1066
1067 if ( disableCache )
1068 {
1069 sDisableCache = true;
1070 }
1071
1072 sTransforms.clear();
1073}
1074
1075void QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( void *pj_context )
1076{
1077 // Not completely sure about object order destruction after main() has
1078 // exited. So it is safer to check sDisableCache before using sCacheLock
1079 // in case sCacheLock would have been destroyed before the current TLS
1080 // QgsProjContext object that has called us...
1081 if ( sDisableCache )
1082 return;
1083
1084 const QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1085 // cppcheck-suppress identicalConditionAfterEarlyExit
1086 if ( sDisableCache )
1087 return;
1088
1089 for ( auto it = sTransforms.begin(); it != sTransforms.end(); )
1090 {
1091 auto &v = it.value();
1092 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
1093 it = sTransforms.erase( it );
1094 else
1095 ++it;
1096 }
1097}
1098
1099double QgsCoordinateTransform::scaleFactor( const QgsRectangle &ReferenceExtent ) const
1100{
1101 const QgsPointXY source1( ReferenceExtent.xMinimum(), ReferenceExtent.yMinimum() );
1102 const QgsPointXY source2( ReferenceExtent.xMaximum(), ReferenceExtent.yMaximum() );
1103 const double distSourceUnits = std::sqrt( source1.sqrDist( source2 ) );
1104 const QgsPointXY dest1 = transform( source1 );
1105 const QgsPointXY dest2 = transform( source2 );
1106 const double distDestUnits = std::sqrt( dest1.sqrDist( dest2 ) );
1107 return distDestUnits / distSourceUnits;
1108}
1109
1111{
1112 QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( handler );
1113}
1114
1116{
1117 QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( handler );
1118}
1119
1121{
1122 QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
1123}
1124
1126{
1127 QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
1128}
1129
1130void QgsCoordinateTransform::setFallbackOperationOccurredHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
1131{
1132 sFallbackOperationOccurredHandler = handler;
1133}
1134
1136{
1137 QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( handler );
1138}
@ 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
Flags for raster layer temporal capabilities.
Definition: qgis.h:1789
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
double coordinateEpoch() const
Returns the coordinate epoch, as a decimal year.
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QString celestialBodyName() const SIP_THROW(QgsNotSupportedException)
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
Contains information about the context in which a coordinate transform is executed.
Class for doing transforms between two map coordinate systems.
void transformPolygon(QPolygonF &polygon, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transforms a polygon to the destination coordinate system.
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 setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system.
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent.
void transformCoords(int numPoint, double *x, double *y, double *z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform an array of coordinates to the destination CRS.
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.
QgsCoordinateTransform & operator=(const QgsCoordinateTransform &o)
Assignment operator.
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...
void transformInPlace(double &x, double &y, double &z, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
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.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
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.
QgsPointXY transform(const QgsPointXY &point, Qgis::TransformDirection direction=Qgis::TransformDirection::Forward) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination 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.
Definition: qgsexception.h:67
static QgsDatumTransform::TransformDetails transformDetailsFromPj(PJ *op)
Returns the transform details for a Proj coordinate operation op.
QString what() const
Definition: qgsexception.h:49
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true)
Adds a message to the log instance (and creates it if necessary).
A class to represent a 2D point.
Definition: qgspointxy.h:59
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:190
double y
Definition: qgspointxy.h:63
Q_GADGET double x
Definition: qgspointxy.h:62
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.
@ Read
Lock for read.
A rectangle specified with double values.
Definition: qgsrectangle.h:42
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
double yMaximum() const SIP_HOLDGIL
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:193
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:183
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:188
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:198
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:479
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:156
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:151
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:172
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:223
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:391
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:469
double y() const
Returns Y coordinate.
Definition: qgsvector3d.h:51
double z() const
Returns Z coordinate.
Definition: qgsvector3d.h:53
double x() const
Returns X coordinate.
Definition: qgsvector3d.h:49
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:4572
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:4571
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
Definition: qgis.h:3972
struct PJconsts PJ
const char * finder(const char *name)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Contains information about a projection transformation grid file.
Contains information about a coordinate transformation operation.