QGIS API Documentation  3.18.1-Zürich (202f1bf7e5)
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  ***************************************************************************/
17 #include "qgscoordinatetransform.h"
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 
29 //qt includes
30 #include <QDomNode>
31 #include <QDomElement>
32 #include <QApplication>
33 #include <QPolygonF>
34 #include <QStringList>
35 #include <QVector>
36 
37 #if PROJ_VERSION_MAJOR>=6
38 #include <proj.h>
39 #include "qgsprojutils.h"
40 #else
41 #include <proj_api.h>
42 #endif
43 
44 #include <sqlite3.h>
45 #include <qlogging.h>
46 #include <vector>
47 #include <algorithm>
48 
49 // if defined shows all information about transform to stdout
50 // #define COORDINATE_TRANSFORM_VERBOSE
51 
52 QReadWriteLock QgsCoordinateTransform::sCacheLock;
53 QMultiHash< QPair< QString, QString >, QgsCoordinateTransform > QgsCoordinateTransform::sTransforms; //same auth_id pairs might have different datum transformations
54 bool QgsCoordinateTransform::sDisableCache = false;
55 
56 std::function< void( const QgsCoordinateReferenceSystem &sourceCrs,
57  const QgsCoordinateReferenceSystem &destinationCrs,
58  const QString &desiredOperation )> QgsCoordinateTransform::sFallbackOperationOccurredHandler = nullptr;
59 
61 {
62  d = new QgsCoordinateTransformPrivate();
63 }
64 
66 {
67  mContext = context;
68  d = new QgsCoordinateTransformPrivate( source, destination, mContext );
69 #ifdef QGISDEBUG
70  mHasContext = true;
71 #endif
72 
73  if ( !d->checkValidity() )
74  return;
75 
77 #if PROJ_VERSION_MAJOR>=6
78  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
79 #else
80  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
81 #endif
82  {
83  d->initialize();
84  addToCache();
85  }
87 }
88 
90 {
91  mContext = project ? project->transformContext() : QgsCoordinateTransformContext();
92  d = new QgsCoordinateTransformPrivate( source, destination, mContext );
93 #ifdef QGISDEBUG
94  if ( project )
95  mHasContext = true;
96 #endif
97 
98  if ( !d->checkValidity() )
99  return;
100 
102 #if PROJ_VERSION_MAJOR>=6
103  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
104 #else
105  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
106 #endif
107  {
108  d->initialize();
109  addToCache();
110  }
112 }
113 
114 QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destinationDatumTransform )
115 {
116  d = new QgsCoordinateTransformPrivate( source, destination, sourceDatumTransform, destinationDatumTransform );
117 #ifdef QGISDEBUG
118  mHasContext = true; // not strictly true, but we don't need to worry if datums have been explicitly set
119 #endif
120 
121  if ( !d->checkValidity() )
122  return;
123 
125 #if PROJ_VERSION_MAJOR>=6
126  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
127 #else
128  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
129 #endif
130  {
131  d->initialize();
132  addToCache();
133  }
135 }
136 
138  : mContext( o.mContext )
139 #ifdef QGISDEBUG
140  , mHasContext( o.mHasContext )
141 #endif
142  , mLastError()
143 {
144  d = o.d;
145 }
146 
148 {
149  d = o.d;
150 #ifdef QGISDEBUG
151  mHasContext = o.mHasContext;
152 #endif
153  mContext = o.mContext;
154  mLastError = QString();
155  return *this;
156 }
157 
159 
161 {
162  d.detach();
163  d->mSourceCRS = crs;
164  if ( !d->checkValidity() )
165  return;
166 
167  d->calculateTransforms( mContext );
169 #if PROJ_VERSION_MAJOR>=6
170  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
171 #else
172  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
173 #endif
174  {
175  d->initialize();
176  addToCache();
177  }
179 }
181 {
182  d.detach();
183  d->mDestCRS = crs;
184  if ( !d->checkValidity() )
185  return;
186 
187  d->calculateTransforms( mContext );
189 #if PROJ_VERSION_MAJOR>=6
190  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
191 #else
192  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
193 #endif
194  {
195  d->initialize();
196  addToCache();
197  }
199 }
200 
202 {
203  d.detach();
204  mContext = context;
205 #ifdef QGISDEBUG
206  mHasContext = true;
207 #endif
208  if ( !d->checkValidity() )
209  return;
210 
211  d->calculateTransforms( mContext );
213 #if PROJ_VERSION_MAJOR>=6
214  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
215 #else
216  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
217 #endif
218  {
219  d->initialize();
220  addToCache();
221  }
223 }
224 
226 {
227  return mContext;
228 }
229 
231 {
232  return d->mSourceCRS;
233 }
234 
236 {
237  return d->mDestCRS;
238 }
239 
241 {
242  if ( !d->mIsValid || d->mShortCircuit )
243  return point;
244 
245  // transform x
246  double x = point.x();
247  double y = point.y();
248  double z = 0.0;
249  try
250  {
251  transformCoords( 1, &x, &y, &z, direction );
252  }
253  catch ( const QgsCsException & )
254  {
255  // rethrow the exception
256  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
257  throw;
258  }
259 
260  return QgsPointXY( x, y );
261 }
262 
263 
264 QgsPointXY QgsCoordinateTransform::transform( const double theX, const double theY = 0.0, TransformDirection direction ) const
265 {
266  try
267  {
268  return transform( QgsPointXY( theX, theY ), direction );
269  }
270  catch ( const QgsCsException & )
271  {
272  // rethrow the exception
273  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
274  throw;
275  }
276 }
277 
279 {
280  if ( !d->mIsValid || d->mShortCircuit )
281  return rect;
282  // transform x
283  double x1 = rect.xMinimum();
284  double y1 = rect.yMinimum();
285  double x2 = rect.xMaximum();
286  double y2 = rect.yMaximum();
287 
288  // Number of points to reproject------+
289  // |
290  // V
291  try
292  {
293  double z = 0.0;
294  transformCoords( 1, &x1, &y1, &z, direction );
295  transformCoords( 1, &x2, &y2, &z, direction );
296  }
297  catch ( const QgsCsException & )
298  {
299  // rethrow the exception
300  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
301  throw;
302  }
303 
304 #ifdef COORDINATE_TRANSFORM_VERBOSE
305  QgsDebugMsg( QStringLiteral( "Rect projection..." ) );
306  QgsDebugMsg( QStringLiteral( "Xmin : %1 --> %2" ).arg( rect.xMinimum() ).arg( x1 ) );
307  QgsDebugMsg( QStringLiteral( "Ymin : %1 --> %2" ).arg( rect.yMinimum() ).arg( y1 ) );
308  QgsDebugMsg( QStringLiteral( "Xmax : %1 --> %2" ).arg( rect.xMaximum() ).arg( x2 ) );
309  QgsDebugMsg( QStringLiteral( "Ymax : %1 --> %2" ).arg( rect.yMaximum() ).arg( y2 ) );
310 #endif
311  return QgsRectangle( x1, y1, x2, y2 );
312 }
313 
315 {
316  double x = point.x();
317  double y = point.y();
318  double z = point.z();
319  try
320  {
321  transformCoords( 1, &x, &y, &z, direction );
322  }
323  catch ( const QgsCsException & )
324  {
325  // rethrow the exception
326  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
327  throw;
328  }
329  return QgsVector3D( x, y, z );
330 }
331 
332 void QgsCoordinateTransform::transformInPlace( double &x, double &y, double &z,
333  TransformDirection direction ) const
334 {
335  if ( !d->mIsValid || d->mShortCircuit )
336  return;
337 #ifdef QGISDEBUG
338 // QgsDebugMsg(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__));
339 #endif
340  // transform x
341  try
342  {
343  transformCoords( 1, &x, &y, &z, direction );
344  }
345  catch ( const QgsCsException & )
346  {
347  // rethrow the exception
348  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
349  throw;
350  }
351 }
352 
353 void QgsCoordinateTransform::transformInPlace( float &x, float &y, double &z,
354  TransformDirection direction ) const
355 {
356  double xd = static_cast< double >( x ), yd = static_cast< double >( y );
357  transformInPlace( xd, yd, z, direction );
358  x = xd;
359  y = yd;
360 }
361 
362 void QgsCoordinateTransform::transformInPlace( float &x, float &y, float &z,
363  TransformDirection direction ) const
364 {
365  if ( !d->mIsValid || d->mShortCircuit )
366  return;
367 #ifdef QGISDEBUG
368  // QgsDebugMsg(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__));
369 #endif
370  // transform x
371  try
372  {
373  double xd = x;
374  double yd = y;
375  double zd = z;
376  transformCoords( 1, &xd, &yd, &zd, direction );
377  x = xd;
378  y = yd;
379  z = zd;
380  }
381  catch ( QgsCsException & )
382  {
383  // rethrow the exception
384  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
385  throw;
386  }
387 }
388 
389 void QgsCoordinateTransform::transformPolygon( QPolygonF &poly, TransformDirection direction ) const
390 {
391  if ( !d->mIsValid || d->mShortCircuit )
392  {
393  return;
394  }
395 
396  //create x, y arrays
397  int nVertices = poly.size();
398 
399  QVector<double> x( nVertices );
400  QVector<double> y( nVertices );
401  QVector<double> z( nVertices );
402  double *destX = x.data();
403  double *destY = y.data();
404  double *destZ = z.data();
405 
406  const QPointF *polyData = poly.constData();
407  for ( int i = 0; i < nVertices; ++i )
408  {
409  *destX++ = polyData->x();
410  *destY++ = polyData->y();
411  *destZ++ = 0;
412  polyData++;
413  }
414 
415  QString err;
416  try
417  {
418  transformCoords( nVertices, x.data(), y.data(), z.data(), direction );
419  }
420  catch ( const QgsCsException &e )
421  {
422  // record the exception, but don't rethrow it until we've recorded the coordinates we *could* transform
423  err = e.what();
424  }
425 
426  QPointF *destPoint = poly.data();
427  const double *srcX = x.constData();
428  const double *srcY = y.constData();
429  for ( int i = 0; i < nVertices; ++i )
430  {
431  destPoint->rx() = *srcX++;
432  destPoint->ry() = *srcY++;
433  destPoint++;
434  }
435 
436  // rethrow the exception
437  if ( !err.isEmpty() )
438  throw QgsCsException( err );
439 }
440 
442  QVector<double> &x, QVector<double> &y, QVector<double> &z,
443  TransformDirection direction ) const
444 {
445 
446  if ( !d->mIsValid || d->mShortCircuit )
447  return;
448 
449  Q_ASSERT( x.size() == y.size() );
450 
451  // Apparently, if one has a std::vector, it is valid to use the
452  // address of the first element in the vector as a pointer to an
453  // array of the vectors data, and hence easily interface with code
454  // that wants C-style arrays.
455 
456  try
457  {
458  transformCoords( x.size(), &x[0], &y[0], &z[0], direction );
459  }
460  catch ( const QgsCsException & )
461  {
462  // rethrow the exception
463  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
464  throw;
465  }
466 }
467 
468 
470  QVector<float> &x, QVector<float> &y, QVector<float> &z,
471  TransformDirection direction ) const
472 {
473  if ( !d->mIsValid || d->mShortCircuit )
474  return;
475 
476  Q_ASSERT( x.size() == y.size() );
477 
478  // Apparently, if one has a std::vector, it is valid to use the
479  // address of the first element in the vector as a pointer to an
480  // array of the vectors data, and hence easily interface with code
481  // that wants C-style arrays.
482 
483  try
484  {
485  //copy everything to double vectors since proj needs double
486  int vectorSize = x.size();
487  QVector<double> xd( x.size() );
488  QVector<double> yd( y.size() );
489  QVector<double> zd( z.size() );
490 
491  double *destX = xd.data();
492  double *destY = yd.data();
493  double *destZ = zd.data();
494 
495  const float *srcX = x.constData();
496  const float *srcY = y.constData();
497  const float *srcZ = z.constData();
498 
499  for ( int i = 0; i < vectorSize; ++i )
500  {
501  *destX++ = static_cast< double >( *srcX++ );
502  *destY++ = static_cast< double >( *srcY++ );
503  *destZ++ = static_cast< double >( *srcZ++ );
504  }
505 
506  transformCoords( x.size(), &xd[0], &yd[0], &zd[0], direction );
507 
508  //copy back
509  float *destFX = x.data();
510  float *destFY = y.data();
511  float *destFZ = z.data();
512  const double *srcXD = xd.constData();
513  const double *srcYD = yd.constData();
514  const double *srcZD = zd.constData();
515  for ( int i = 0; i < vectorSize; ++i )
516  {
517  *destFX++ = static_cast< float >( *srcXD++ );
518  *destFY++ = static_cast< float >( *srcYD++ );
519  *destFZ++ = static_cast< float >( *srcZD++ );
520  }
521  }
522  catch ( QgsCsException & )
523  {
524  // rethrow the exception
525  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
526  throw;
527  }
528 }
529 
530 QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &rect, TransformDirection direction, const bool handle180Crossover ) const
531 {
532  // Calculate the bounding box of a QgsRectangle in the source CRS
533  // when projected to the destination CRS (or the inverse).
534  // This is done by looking at a number of points spread evenly
535  // across the rectangle
536 
537  if ( !d->mIsValid || d->mShortCircuit )
538  return rect;
539 
540  if ( rect.isEmpty() )
541  {
542  QgsPointXY p = transform( rect.xMinimum(), rect.yMinimum(), direction );
543  return QgsRectangle( p, p );
544  }
545 
546  // 64 points (<=2.12) is not enough, see #13665, for EPSG:4326 -> EPSG:3574 (say that it is a hard one),
547  // are decent result from about 500 points and more. This method is called quite often, but
548  // even with 1000 points it takes < 1ms.
549  // TODO: how to effectively and precisely reproject bounding box?
550  const int nPoints = 1000;
551  double d = std::sqrt( ( rect.width() * rect.height() ) / std::pow( std::sqrt( static_cast< double >( nPoints ) ) - 1, 2.0 ) );
552  int nXPoints = std::min( static_cast< int >( std::ceil( rect.width() / d ) ) + 1, 1000 );
553  int nYPoints = std::min( static_cast< int >( std::ceil( rect.height() / d ) ) + 1, 1000 );
554 
555  QgsRectangle bb_rect;
556  bb_rect.setMinimal();
557 
558  // We're interfacing with C-style vectors in the
559  // end, so let's do C-style vectors here too.
560  QVector<double> x( nXPoints * nYPoints );
561  QVector<double> y( nXPoints * nYPoints );
562  QVector<double> z( nXPoints * nYPoints );
563 
564  QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 );
565 
566  // Populate the vectors
567 
568  double dx = rect.width() / static_cast< double >( nXPoints - 1 );
569  double dy = rect.height() / static_cast< double >( nYPoints - 1 );
570 
571  double pointY = rect.yMinimum();
572 
573  for ( int i = 0; i < nYPoints ; i++ )
574  {
575 
576  // Start at right edge
577  double pointX = rect.xMinimum();
578 
579  for ( int j = 0; j < nXPoints; j++ )
580  {
581  x[( i * nXPoints ) + j] = pointX;
582  y[( i * nXPoints ) + j] = pointY;
583  // and the height...
584  z[( i * nXPoints ) + j] = 0.0;
585  // QgsDebugMsg(QString("BBox coord: (%1, %2)").arg(x[(i*numP) + j]).arg(y[(i*numP) + j]));
586  pointX += dx;
587  }
588  pointY += dy;
589  }
590 
591  // Do transformation. Any exception generated must
592  // be handled in above layers.
593  try
594  {
595  transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
596  }
597  catch ( const QgsCsException & )
598  {
599  // rethrow the exception
600  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
601  throw;
602  }
603 
604  // Calculate the bounding box and use that for the extent
605 
606  for ( int i = 0; i < nXPoints * nYPoints; i++ )
607  {
608  if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
609  {
610  continue;
611  }
612 
613  if ( handle180Crossover )
614  {
615  //if crossing the date line, temporarily add 360 degrees to -ve longitudes
616  bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] );
617  }
618  else
619  {
620  bb_rect.combineExtentWith( x[i], y[i] );
621  }
622  }
623 
624  if ( bb_rect.isNull() )
625  {
626  // something bad happened when reprojecting the filter rect... no finite points were left!
627  throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
628  }
629 
630  if ( handle180Crossover )
631  {
632  //subtract temporary addition of 360 degrees from longitudes
633  if ( bb_rect.xMinimum() > 180.0 )
634  bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 );
635  if ( bb_rect.xMaximum() > 180.0 )
636  bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 );
637  }
638 
639  QgsDebugMsgLevel( "Projected extent: " + bb_rect.toString(), 4 );
640 
641  if ( bb_rect.isEmpty() )
642  {
643  QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
644  }
645 
646  return bb_rect;
647 }
648 
649 void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, TransformDirection direction ) const
650 {
651  if ( !d->mIsValid || d->mShortCircuit )
652  return;
653  // Refuse to transform the points if the srs's are invalid
654  if ( !d->mSourceCRS.isValid() )
655  {
656  QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. "
657  "The coordinates can not be reprojected. The CRS is: %1" )
658  .arg( d->mSourceCRS.toProj() ), QObject::tr( "CRS" ) );
659  return;
660  }
661  if ( !d->mDestCRS.isValid() )
662  {
663  QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. "
664  "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj() ), QObject::tr( "CRS" ) );
665  return;
666  }
667 
668  std::vector< int > zNanPositions;
669  for ( int i = 0; i < numPoints; i++ )
670  {
671  if ( std::isnan( z[i] ) )
672  {
673  zNanPositions.push_back( i );
674  z[i] = 0.0;
675  }
676  }
677 
678 #if PROJ_VERSION_MAJOR>=6
679  std::vector< double > xprev( numPoints );
680  memcpy( xprev.data(), x, sizeof( double ) * numPoints );
681  std::vector< double > yprev( numPoints );
682  memcpy( yprev.data(), y, sizeof( double ) * numPoints );
683  std::vector< double > zprev( numPoints );
684  memcpy( zprev.data(), z, sizeof( double ) * numPoints );
685 #endif
686 
687 #ifdef COORDINATE_TRANSFORM_VERBOSE
688  double xorg = *x;
689  double yorg = *y;
690  QgsDebugMsg( QStringLiteral( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ) );
691 #endif
692 
693 #ifdef QGISDEBUG
694  if ( !mHasContext )
695  QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 );
696 #endif
697 
698  // use proj4 to do the transform
699 
700  // if the source/destination projection is lat/long, convert the points to radians
701  // prior to transforming
702  ProjData projData = d->threadLocalProjData();
703 
704  int projResult = 0;
705 #if PROJ_VERSION_MAJOR>=6
706  proj_errno_reset( projData );
707  proj_trans_generic( projData, ( direction == ForwardTransform && !d->mIsReversed ) || ( direction == ReverseTransform && d->mIsReversed ) ? PJ_FWD : PJ_INV,
708  x, sizeof( double ), numPoints,
709  y, sizeof( double ), numPoints,
710  z, sizeof( double ), numPoints,
711  nullptr, sizeof( double ), 0 );
712  // Try to - approximatively - emulate the behavior of pj_transform()...
713  // In the case of a single point transform, and a transformation error occurs,
714  // pj_transform() would return the errno. In cases of multiple point transform,
715  // it would continue (for non-transient errors, that is pipeline definition
716  // errors) and just set the resulting x,y to infinity. This is in fact a
717  // bit more subtle than that, and I'm not completely sure the logic in
718  // pj_transform() was really sane & fully bullet proof
719  // So here just check proj_errno() for single point transform
720  int actualRes = 0;
721  if ( numPoints == 1 )
722  {
723  projResult = proj_errno( projData );
724  actualRes = projResult;
725  }
726  else
727  {
728  actualRes = proj_errno( projData );
729  }
730  if ( actualRes == 0 )
731  {
732  // proj_errno is sometimes not an accurate method to test for transform failures - so we need to
733  // manually scan for nan values
734  if ( std::any_of( x, x + numPoints, []( double v ) { return std::isinf( v ); } )
735  || std::any_of( y, y + numPoints, []( double v ) { return std::isinf( v ); } )
736  || std::any_of( z, z + numPoints, []( double v ) { return std::isinf( v ); } ) )
737  {
738  actualRes = 1;
739  }
740  }
741 #else
742  bool sourceIsLatLong = false;
743  bool destIsLatLong = false;
744 
745  projPJ sourceProj = projData.first;
746  projPJ destProj = projData.second;
747  sourceIsLatLong = pj_is_latlong( sourceProj );
748  destIsLatLong = pj_is_latlong( destProj );
749 
750  if ( ( destIsLatLong && ( direction == ReverseTransform ) )
751  || ( sourceIsLatLong && ( direction == ForwardTransform ) ) )
752  {
753  for ( int i = 0; i < numPoints; ++i )
754  {
755  x[i] *= DEG_TO_RAD;
756  y[i] *= DEG_TO_RAD;
757  }
758  }
759 #endif
760 
761 #if PROJ_VERSION_MAJOR<6
762  if ( direction == ReverseTransform )
763  {
764  projResult = pj_transform( destProj, sourceProj, numPoints, 0, x, y, z );
765  }
766  else
767  {
768  Q_ASSERT( sourceProj );
769  Q_ASSERT( destProj );
770  projResult = pj_transform( sourceProj, destProj, numPoints, 0, x, y, z );
771  }
772 #endif
773 
774 #if PROJ_VERSION_MAJOR>=6
775 
776  mFallbackOperationOccurred = false;
777  if ( actualRes != 0
778  && ( 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
779  && ( d->mAllowFallbackTransforms || mBallparkTransformsAreAppropriate ) )
780  {
781  // fail #1 -- try with getting proj to auto-pick an appropriate coordinate operation for the points
782  if ( PJ *transform = d->threadLocalFallbackProjData() )
783  {
784  projResult = 0;
785  proj_errno_reset( transform );
786  proj_trans_generic( transform, direction == ForwardTransform ? PJ_FWD : PJ_INV,
787  xprev.data(), sizeof( double ), numPoints,
788  yprev.data(), sizeof( double ), numPoints,
789  zprev.data(), sizeof( double ), numPoints,
790  nullptr, sizeof( double ), 0 );
791  // Try to - approximatively - emulate the behavior of pj_transform()...
792  // In the case of a single point transform, and a transformation error occurs,
793  // pj_transform() would return the errno. In cases of multiple point transform,
794  // it would continue (for non-transient errors, that is pipeline definition
795  // errors) and just set the resulting x,y to infinity. This is in fact a
796  // bit more subtle than that, and I'm not completely sure the logic in
797  // pj_transform() was really sane & fully bullet proof
798  // So here just check proj_errno() for single point transform
799  if ( numPoints == 1 )
800  {
801  // hmm - something very odd here. We can't trust proj_errno( transform ), as that's giving us incorrect error numbers
802  // (such as "failed to load datum shift file", which is definitely incorrect for a default proj created operation!)
803  // so we resort to testing values ourselves...
804  projResult = std::isinf( xprev[0] ) || std::isinf( yprev[0] ) || std::isinf( zprev[0] ) ? 1 : 0;
805  }
806 
807  if ( projResult == 0 )
808  {
809  memcpy( x, xprev.data(), sizeof( double ) * numPoints );
810  memcpy( y, yprev.data(), sizeof( double ) * numPoints );
811  memcpy( z, zprev.data(), sizeof( double ) * numPoints );
812  mFallbackOperationOccurred = true;
813  }
814 
815  if ( !mBallparkTransformsAreAppropriate && !mDisableFallbackHandler && sFallbackOperationOccurredHandler )
816  {
817  sFallbackOperationOccurredHandler( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation );
818 #if 0
819  const QString warning = QStringLiteral( "A fallback coordinate operation was used between %1 and %2" ).arg( d->mSourceCRS.authid(),
820  d->mDestCRS.authid() );
821  qWarning( "%s", warning.toLatin1().constData() );
822 #endif
823  }
824  }
825  }
826 #endif
827 
828  for ( const int &pos : zNanPositions )
829  {
830  z[pos] = std::numeric_limits<double>::quiet_NaN();
831  }
832 
833  if ( projResult != 0 )
834  {
835  //something bad happened....
836  QString points;
837 
838  for ( int i = 0; i < numPoints; ++i )
839  {
840  if ( direction == ForwardTransform )
841  {
842  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' );
843  }
844  else
845  {
846 #if PROJ_VERSION_MAJOR>=6
847  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' );
848 #else
849  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i] * RAD_TO_DEG, 0, 'f' ).arg( y[i] * RAD_TO_DEG, 0, 'f' );
850 #endif
851  }
852  }
853 
854  QString dir = ( direction == ForwardTransform ) ? QObject::tr( "forward transform" ) : QObject::tr( "inverse transform" );
855 
856 #if PROJ_VERSION_MAJOR>=6
857  QString msg = QObject::tr( "%1 of\n"
858  "%2"
859  "Error: %3" )
860  .arg( dir,
861  points,
862  projResult < 0 ? QString::fromUtf8( proj_errno_string( projResult ) ) : QObject::tr( "Fallback transform failed" ) );
863 #else
864  char *srcdef = pj_get_def( sourceProj, 0 );
865  char *dstdef = pj_get_def( destProj, 0 );
866 
867  QString msg = QObject::tr( "%1 of\n"
868  "%2"
869  "PROJ: %3 +to %4\n"
870  "Error: %5" )
871  .arg( dir,
872  points,
873  srcdef, dstdef,
874  QString::fromUtf8( pj_strerrno( projResult ) ) );
875 
876  pj_dalloc( srcdef );
877  pj_dalloc( dstdef );
878 #endif
879 
880  // don't flood console with thousands of duplicate transform error messages
881  if ( msg != mLastError )
882  {
883  QgsDebugMsg( "Projection failed emitting invalid transform signal: " + msg );
884  mLastError = msg;
885  }
886  QgsDebugMsgLevel( QStringLiteral( "rethrowing exception" ), 2 );
887 
888  throw QgsCsException( msg );
889  }
890 
891 #if PROJ_VERSION_MAJOR<6
892  // if the result is lat/long, convert the results from radians back
893  // to degrees
894  if ( ( destIsLatLong && ( direction == ForwardTransform ) )
895  || ( sourceIsLatLong && ( direction == ReverseTransform ) ) )
896  {
897  for ( int i = 0; i < numPoints; ++i )
898  {
899  x[i] *= RAD_TO_DEG;
900  y[i] *= RAD_TO_DEG;
901  }
902  }
903 #endif
904 #ifdef COORDINATE_TRANSFORM_VERBOSE
905  QgsDebugMsg( QStringLiteral( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
906  .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 )
907  .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ) );
908 #endif
909 }
910 
912 {
913  return d->mIsValid;
914 }
915 
917 {
918  return !d->mIsValid || d->mShortCircuit;
919 }
920 
922 {
923  return d->mProjCoordinateOperation;
924 }
925 
927 {
928 #if PROJ_VERSION_MAJOR>=6
929  ProjData projData = d->threadLocalProjData();
930  return QgsDatumTransform::transformDetailsFromPj( projData );
931 #else
933 #endif
934 }
935 
936 void QgsCoordinateTransform::setCoordinateOperation( const QString &operation ) const
937 {
938  d.detach();
939  d->mProjCoordinateOperation = operation;
940  d->mShouldReverseCoordinateOperation = false;
941 }
942 
944 {
945  d.detach();
946  d->mAllowFallbackTransforms = allowed;
947 }
948 
950 {
951  return d->mAllowFallbackTransforms;
952 }
953 
955 {
956  mBallparkTransformsAreAppropriate = appropriate;
957 }
958 
960 {
961  mDisableFallbackHandler = disabled;
962 }
963 
965 {
966  return mFallbackOperationOccurred;
967 }
968 
969 const char *finder( const char *name )
970 {
971  QString proj;
972 #ifdef Q_OS_WIN
973  proj = QApplication::applicationDirPath()
974  + "/share/proj/" + QString( name );
975 #else
976  Q_UNUSED( name )
977 #endif
978  return proj.toUtf8();
979 }
980 
981 #if PROJ_VERSION_MAJOR>=6
982 bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, const QString &coordinateOperationProj, bool allowFallback )
983 {
984  if ( !src.isValid() || !dest.isValid() )
985  return false;
986 
987  const QString sourceKey = src.authid().isEmpty() ?
989  const QString destKey = dest.authid().isEmpty() ?
991 
992  if ( sourceKey.isEmpty() || destKey.isEmpty() )
993  return false;
994 
995  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Read );
996  if ( sDisableCache )
997  return false;
998 
999  const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
1000  for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
1001  {
1002  if ( ( *valIt ).coordinateOperation() == coordinateOperationProj && ( *valIt ).allowFallbackTransforms() == allowFallback )
1003  {
1004  // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
1006 #ifdef QGISDEBUG
1007  bool hasContext = mHasContext;
1008 #endif
1009  *this = *valIt;
1010  locker.unlock();
1011 
1012  mContext = context;
1013 #ifdef QGISDEBUG
1014  mHasContext = hasContext;
1015 #endif
1016 
1017  return true;
1018  }
1019  }
1020  return false;
1021 }
1022 #else
1023 bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, int srcDatumTransform, int destDatumTransform )
1024 {
1025  if ( !src.isValid() || !dest.isValid() )
1026  return false;
1027 
1028  const QString sourceKey = src.authid().isEmpty() ?
1029  src.toWkt() : src.authid();
1030  const QString destKey = dest.authid().isEmpty() ?
1031  dest.toWkt() : dest.authid();
1032 
1033  if ( sourceKey.isEmpty() || destKey.isEmpty() )
1034  return false;
1035 
1036  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Read );
1037  if ( sDisableCache )
1038  return false;
1039 
1040  const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( src.authid(), dest.authid() ) );
1041  for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
1042  {
1044  if ( ( *valIt ).sourceDatumTransformId() == srcDatumTransform &&
1045  ( *valIt ).destinationDatumTransformId() == destDatumTransform )
1046  {
1047  // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
1049 #ifdef QGISDEBUG
1050  bool hasContext = mHasContext;
1051 #endif
1052  *this = *valIt;
1053  locker.unlock();
1054 
1055  mContext = context;
1056 #ifdef QGISDEBUG
1057  mHasContext = hasContext;
1058 #endif
1059 
1060  return true;
1061  }
1063  }
1064  return false;
1065 }
1066 #endif
1067 
1068 void QgsCoordinateTransform::addToCache()
1069 {
1070  if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
1071  return;
1072 
1073  const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
1074  d->mSourceCRS.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : d->mSourceCRS.authid();
1075  const QString destKey = d->mDestCRS.authid().isEmpty() ?
1076  d->mDestCRS.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) : d->mDestCRS.authid();
1077 
1078  if ( sourceKey.isEmpty() || destKey.isEmpty() )
1079  return;
1080 
1081  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1082  if ( sDisableCache )
1083  return;
1084 
1085  sTransforms.insert( qMakePair( sourceKey, destKey ), *this );
1086 }
1087 
1089 {
1091  return d->mSourceDatumTransform;
1093 }
1094 
1096 {
1097  d.detach();
1099  d->mSourceDatumTransform = dt;
1101 }
1102 
1104 {
1106  return d->mDestinationDatumTransform;
1108 }
1109 
1111 {
1112  d.detach();
1114  d->mDestinationDatumTransform = dt;
1116 }
1117 
1119 {
1120  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1121  if ( sDisableCache )
1122  return;
1123 
1124  if ( disableCache )
1125  {
1126  sDisableCache = true;
1127  }
1128 
1129  sTransforms.clear();
1130 }
1131 
1132 #if PROJ_VERSION_MAJOR>=6
1133 void QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread( void *pj_context )
1134 {
1135  // Not completely sure about object order destruction after main() has
1136  // exited. So it is safer to check sDisableCache before using sCacheLock
1137  // in case sCacheLock would have been destroyed before the current TLS
1138  // QgsProjContext object that has called us...
1139  if ( sDisableCache )
1140  return;
1141 
1142  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
1143  // cppcheck-suppress identicalConditionAfterEarlyExit
1144  if ( sDisableCache )
1145  return;
1146 
1147  for ( auto it = sTransforms.begin(); it != sTransforms.end(); )
1148  {
1149  auto &v = it.value();
1150  if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
1151  it = sTransforms.erase( it );
1152  else
1153  ++it;
1154  }
1155 }
1156 #endif
1157 
1158 double QgsCoordinateTransform::scaleFactor( const QgsRectangle &ReferenceExtent ) const
1159 {
1160  QgsPointXY source1( ReferenceExtent.xMinimum(), ReferenceExtent.yMinimum() );
1161  QgsPointXY source2( ReferenceExtent.xMaximum(), ReferenceExtent.yMaximum() );
1162  double distSourceUnits = std::sqrt( source1.sqrDist( source2 ) );
1163  QgsPointXY dest1 = transform( source1 );
1164  QgsPointXY dest2 = transform( source2 );
1165  double distDestUnits = std::sqrt( dest1.sqrDist( dest2 ) );
1166  return distDestUnits / distSourceUnits;
1167 }
1168 
1170 {
1171  QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( handler );
1172 }
1173 
1175 {
1176  QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( handler );
1177 }
1178 
1180 {
1181  QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
1182 }
1183 
1185 {
1186  QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
1187 }
1188 
1189 void QgsCoordinateTransform::setFallbackOperationOccurredHandler( const std::function<void ( const QgsCoordinateReferenceSystem &, const QgsCoordinateReferenceSystem &, const QString & )> &handler )
1190 {
1191  sFallbackOperationOccurredHandler = handler;
1192 }
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString authid() const
Returns the authority identifier for the CRS.
@ WKT_PREFERRED
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
QString toWkt(WktVariant variant=WKT1_GDAL, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
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.
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...
QgsPointXY transform(const QgsPointXY &point, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform the point from the source CRS to the destination CRS.
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.
void transformPolygon(QPolygonF &polygon, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transforms a polygon to the destination coordinate system.
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.
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.
TransformDirection
Enum used to indicate the direction (forward or inverse) of the transform.
@ ForwardTransform
Transform from source to destination CRS.
@ ReverseTransform
Transform from destination to source CRS.
QgsRectangle transformBoundingBox(const QgsRectangle &rectangle, TransformDirection direction=ForwardTransform, bool handle180Crossover=false) const SIP_THROW(QgsCsException)
Transforms a rectangle from the source CRS to the destination CRS.
void transformCoords(int numPoint, double *x, double *y, double *z, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transform an array of coordinates to the destination CRS.
QgsCoordinateTransform & operator=(const QgsCoordinateTransform &o)
Assignment operator.
void transformInPlace(double &x, double &y, double &z, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transforms an array of x, y and z double coordinates in place, from the source CRS to the destination...
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...
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.
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.
Definition: qgsexception.h:66
QString what() const
Definition: qgsexception.h:48
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::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:44
double sqrDist(double x, double y) const SIP_HOLDGIL
Returns the squared distance between this point a specified x, y coordinate.
Definition: qgspointxy.h:175
double y
Definition: qgspointxy.h:48
Q_GADGET double x
Definition: qgspointxy.h:47
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition: qgsproject.h:99
QgsCoordinateTransformContext transformContext
Definition: qgsproject.h:105
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:172
double xMaximum() const SIP_HOLDGIL
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:162
double xMinimum() const SIP_HOLDGIL
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:167
double yMinimum() const SIP_HOLDGIL
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:177
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:447
void setXMaximum(double x) SIP_HOLDGIL
Set the maximum x value.
Definition: qgsrectangle.h:135
void setXMinimum(double x) SIP_HOLDGIL
Set the minimum x value.
Definition: qgsrectangle.h:130
double height() const SIP_HOLDGIL
Returns the height of the rectangle.
Definition: qgsrectangle.h:209
void setMinimal() SIP_HOLDGIL
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:151
double width() const SIP_HOLDGIL
Returns the width of the rectangle.
Definition: qgsrectangle.h:202
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
Definition: qgsrectangle.h:359
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:437
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:798
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:797
const char * finder(const char *name)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs
Contains information about a projection transformation grid file.
Contains information about a coordinate transformation operation.