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