QGIS API Documentation  3.8.0-Zanzibar (11aff65)
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 
27 //qt includes
28 #include <QDomNode>
29 #include <QDomElement>
30 #include <QApplication>
31 #include <QPolygonF>
32 #include <QStringList>
33 #include <QVector>
34 
35 #if PROJ_VERSION_MAJOR>=6
36 #include <proj.h>
37 #include "qgsprojutils.h"
38 #else
39 #include <proj_api.h>
40 #endif
41 
42 #include <sqlite3.h>
43 
44 // if defined shows all information about transform to stdout
45 // #define COORDINATE_TRANSFORM_VERBOSE
46 
47 QReadWriteLock QgsCoordinateTransform::sCacheLock;
48 QMultiHash< QPair< QString, QString >, QgsCoordinateTransform > QgsCoordinateTransform::sTransforms; //same auth_id pairs might have different datum transformations
49 
51 {
52  d = new QgsCoordinateTransformPrivate();
53 }
54 
56 {
57  mContext = context;
58  d = new QgsCoordinateTransformPrivate( source, destination, mContext );
59 #ifdef QGISDEBUG
60  mHasContext = true;
61 #endif
62 
63  if ( !d->checkValidity() )
64  return;
65 
67 #if PROJ_VERSION_MAJOR>=6
68  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
69 #else
70  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
71 #endif
72  {
73  d->initialize();
74  addToCache();
75  }
77 }
78 
80 {
81  mContext = project ? project->transformContext() : QgsCoordinateTransformContext();
82  d = new QgsCoordinateTransformPrivate( source, destination, mContext );
83 #ifdef QGISDEBUG
84  if ( project )
85  mHasContext = true;
86 #endif
87 
88  if ( !d->checkValidity() )
89  return;
90 
92 #if PROJ_VERSION_MAJOR>=6
93  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
94 #else
95  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
96 #endif
97  {
98  d->initialize();
99  addToCache();
100  }
102 }
103 
104 QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destinationDatumTransform )
105 {
106  d = new QgsCoordinateTransformPrivate( source, destination, sourceDatumTransform, destinationDatumTransform );
107 #ifdef QGISDEBUG
108  mHasContext = true; // not strictly true, but we don't need to worry if datums have been explicitly set
109 #endif
110 
111  if ( !d->checkValidity() )
112  return;
113 
115 #if PROJ_VERSION_MAJOR>=6
116  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
117 #else
118  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
119 #endif
120  {
121  d->initialize();
122  addToCache();
123  }
125 }
126 
128  : mContext( o.mContext )
129 #ifdef QGISDEBUG
130  , mHasContext( o.mHasContext )
131 #endif
132 {
133  d = o.d;
134 }
135 
137 {
138  d = o.d;
139 #ifdef QGISDEBUG
140  mHasContext = o.mHasContext;
141 #endif
142  mContext = o.mContext;
143  return *this;
144 }
145 
147 
149 {
150  d.detach();
151  d->mSourceCRS = crs;
152  if ( !d->checkValidity() )
153  return;
154 
155  d->calculateTransforms( mContext );
157 #if PROJ_VERSION_MAJOR>=6
158  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
159 #else
160  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
161 #endif
162  {
163  d->initialize();
164  addToCache();
165  }
167 }
169 {
170  d.detach();
171  d->mDestCRS = crs;
172  if ( !d->checkValidity() )
173  return;
174 
175  d->calculateTransforms( mContext );
177 #if PROJ_VERSION_MAJOR>=6
178  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
179 #else
180  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
181 #endif
182  {
183  d->initialize();
184  addToCache();
185  }
187 }
188 
190 {
191  d.detach();
192  mContext = context;
193 #ifdef QGISDEBUG
194  mHasContext = true;
195 #endif
196  if ( !d->checkValidity() )
197  return;
198 
199  d->calculateTransforms( mContext );
201 #if PROJ_VERSION_MAJOR>=6
202  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation ) )
203 #else
204  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
205 #endif
206  {
207  d->initialize();
208  addToCache();
209  }
211 }
212 
214 {
215  return mContext;
216 }
217 
219 {
220  return d->mSourceCRS;
221 }
222 
224 {
225  return d->mDestCRS;
226 }
227 
229 {
230  if ( !d->mIsValid || d->mShortCircuit )
231  return point;
232 
233  // transform x
234  double x = point.x();
235  double y = point.y();
236  double z = 0.0;
237  try
238  {
239  transformCoords( 1, &x, &y, &z, direction );
240  }
241  catch ( const QgsCsException & )
242  {
243  // rethrow the exception
244  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
245  throw;
246  }
247 
248  return QgsPointXY( x, y );
249 }
250 
251 
252 QgsPointXY QgsCoordinateTransform::transform( const double theX, const double theY = 0.0, TransformDirection direction ) const
253 {
254  try
255  {
256  return transform( QgsPointXY( theX, theY ), direction );
257  }
258  catch ( const QgsCsException & )
259  {
260  // rethrow the exception
261  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
262  throw;
263  }
264 }
265 
267 {
268  if ( !d->mIsValid || d->mShortCircuit )
269  return rect;
270  // transform x
271  double x1 = rect.xMinimum();
272  double y1 = rect.yMinimum();
273  double x2 = rect.xMaximum();
274  double y2 = rect.yMaximum();
275 
276  // Number of points to reproject------+
277  // |
278  // V
279  try
280  {
281  double z = 0.0;
282  transformCoords( 1, &x1, &y1, &z, direction );
283  transformCoords( 1, &x2, &y2, &z, direction );
284  }
285  catch ( const QgsCsException & )
286  {
287  // rethrow the exception
288  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
289  throw;
290  }
291 
292 #ifdef COORDINATE_TRANSFORM_VERBOSE
293  QgsDebugMsg( QStringLiteral( "Rect projection..." ) );
294  QgsDebugMsg( QStringLiteral( "Xmin : %1 --> %2" ).arg( rect.xMinimum() ).arg( x1 ) );
295  QgsDebugMsg( QStringLiteral( "Ymin : %1 --> %2" ).arg( rect.yMinimum() ).arg( y1 ) );
296  QgsDebugMsg( QStringLiteral( "Xmax : %1 --> %2" ).arg( rect.xMaximum() ).arg( x2 ) );
297  QgsDebugMsg( QStringLiteral( "Ymax : %1 --> %2" ).arg( rect.yMaximum() ).arg( y2 ) );
298 #endif
299  return QgsRectangle( x1, y1, x2, y2 );
300 }
301 
302 void QgsCoordinateTransform::transformInPlace( double &x, double &y, double &z,
303  TransformDirection direction ) const
304 {
305  if ( !d->mIsValid || d->mShortCircuit )
306  return;
307 #ifdef QGISDEBUG
308 // QgsDebugMsg(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__));
309 #endif
310  // transform x
311  try
312  {
313  transformCoords( 1, &x, &y, &z, direction );
314  }
315  catch ( const QgsCsException & )
316  {
317  // rethrow the exception
318  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
319  throw;
320  }
321 }
322 
323 void QgsCoordinateTransform::transformInPlace( float &x, float &y, double &z,
324  TransformDirection direction ) const
325 {
326  double xd = static_cast< double >( x ), yd = static_cast< double >( y );
327  transformInPlace( xd, yd, z, direction );
328  x = xd;
329  y = yd;
330 }
331 
332 void QgsCoordinateTransform::transformInPlace( float &x, float &y, float &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  double xd = x;
344  double yd = y;
345  double zd = z;
346  transformCoords( 1, &xd, &yd, &zd, direction );
347  x = xd;
348  y = yd;
349  z = zd;
350  }
351  catch ( QgsCsException & )
352  {
353  // rethrow the exception
354  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
355  throw;
356  }
357 }
358 
359 void QgsCoordinateTransform::transformPolygon( QPolygonF &poly, TransformDirection direction ) const
360 {
361  if ( !d->mIsValid || d->mShortCircuit )
362  {
363  return;
364  }
365 
366  //create x, y arrays
367  int nVertices = poly.size();
368 
369  QVector<double> x( nVertices );
370  QVector<double> y( nVertices );
371  QVector<double> z( nVertices );
372  double *destX = x.data();
373  double *destY = y.data();
374  double *destZ = z.data();
375 
376  const QPointF *polyData = poly.constData();
377  for ( int i = 0; i < nVertices; ++i )
378  {
379  *destX++ = polyData->x();
380  *destY++ = polyData->y();
381  *destZ++ = 0;
382  polyData++;
383  }
384 
385  QString err;
386  try
387  {
388  transformCoords( nVertices, x.data(), y.data(), z.data(), direction );
389  }
390  catch ( const QgsCsException &e )
391  {
392  // record the exception, but don't rethrow it until we've recorded the coordinates we *could* transform
393  err = e.what();
394  }
395 
396  QPointF *destPoint = poly.data();
397  const double *srcX = x.constData();
398  const double *srcY = y.constData();
399  for ( int i = 0; i < nVertices; ++i )
400  {
401  destPoint->rx() = *srcX++;
402  destPoint->ry() = *srcY++;
403  destPoint++;
404  }
405 
406  // rethrow the exception
407  if ( !err.isEmpty() )
408  throw QgsCsException( err );
409 }
410 
412  QVector<double> &x, QVector<double> &y, QVector<double> &z,
413  TransformDirection direction ) const
414 {
415 
416  if ( !d->mIsValid || d->mShortCircuit )
417  return;
418 
419  Q_ASSERT( x.size() == y.size() );
420 
421  // Apparently, if one has a std::vector, it is valid to use the
422  // address of the first element in the vector as a pointer to an
423  // array of the vectors data, and hence easily interface with code
424  // that wants C-style arrays.
425 
426  try
427  {
428  transformCoords( x.size(), &x[0], &y[0], &z[0], direction );
429  }
430  catch ( const QgsCsException & )
431  {
432  // rethrow the exception
433  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
434  throw;
435  }
436 }
437 
438 
440  QVector<float> &x, QVector<float> &y, QVector<float> &z,
441  TransformDirection direction ) const
442 {
443  if ( !d->mIsValid || d->mShortCircuit )
444  return;
445 
446  Q_ASSERT( x.size() == y.size() );
447 
448  // Apparently, if one has a std::vector, it is valid to use the
449  // address of the first element in the vector as a pointer to an
450  // array of the vectors data, and hence easily interface with code
451  // that wants C-style arrays.
452 
453  try
454  {
455  //copy everything to double vectors since proj needs double
456  int vectorSize = x.size();
457  QVector<double> xd( x.size() );
458  QVector<double> yd( y.size() );
459  QVector<double> zd( z.size() );
460 
461  double *destX = xd.data();
462  double *destY = yd.data();
463  double *destZ = zd.data();
464 
465  const float *srcX = x.constData();
466  const float *srcY = y.constData();
467  const float *srcZ = z.constData();
468 
469  for ( int i = 0; i < vectorSize; ++i )
470  {
471  *destX++ = static_cast< double >( *srcX++ );
472  *destY++ = static_cast< double >( *srcY++ );
473  *destZ++ = static_cast< double >( *srcZ++ );
474  }
475 
476  transformCoords( x.size(), &xd[0], &yd[0], &zd[0], direction );
477 
478  //copy back
479  float *destFX = x.data();
480  float *destFY = y.data();
481  float *destFZ = z.data();
482  const double *srcXD = xd.constData();
483  const double *srcYD = yd.constData();
484  const double *srcZD = zd.constData();
485  for ( int i = 0; i < vectorSize; ++i )
486  {
487  *destFX++ = static_cast< float >( *srcXD++ );
488  *destFY++ = static_cast< float >( *srcYD++ );
489  *destFZ++ = static_cast< float >( *srcZD++ );
490  }
491  }
492  catch ( QgsCsException & )
493  {
494  // rethrow the exception
495  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
496  throw;
497  }
498 }
499 
500 QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &rect, TransformDirection direction, const bool handle180Crossover ) const
501 {
502  // Calculate the bounding box of a QgsRectangle in the source CRS
503  // when projected to the destination CRS (or the inverse).
504  // This is done by looking at a number of points spread evenly
505  // across the rectangle
506 
507  if ( !d->mIsValid || d->mShortCircuit )
508  return rect;
509 
510  if ( rect.isEmpty() )
511  {
512  QgsPointXY p = transform( rect.xMinimum(), rect.yMinimum(), direction );
513  return QgsRectangle( p, p );
514  }
515 
516  // 64 points (<=2.12) is not enough, see #13665, for EPSG:4326 -> EPSG:3574 (say that it is a hard one),
517  // are decent result from about 500 points and more. This method is called quite often, but
518  // even with 1000 points it takes < 1ms
519  // TODO: how to effectively and precisely reproject bounding box?
520  const int nPoints = 1000;
521  double d = std::sqrt( ( rect.width() * rect.height() ) / std::pow( std::sqrt( static_cast< double >( nPoints ) ) - 1, 2.0 ) );
522  int nXPoints = static_cast< int >( std::ceil( rect.width() / d ) ) + 1;
523  int nYPoints = static_cast< int >( std::ceil( rect.height() / d ) ) + 1;
524 
525  QgsRectangle bb_rect;
526  bb_rect.setMinimal();
527 
528  // We're interfacing with C-style vectors in the
529  // end, so let's do C-style vectors here too.
530 
531  QVector<double> x( nXPoints * nYPoints );
532  QVector<double> y( nXPoints * nYPoints );
533  QVector<double> z( nXPoints * nYPoints );
534 
535  QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 );
536 
537  // Populate the vectors
538 
539  double dx = rect.width() / static_cast< double >( nXPoints - 1 );
540  double dy = rect.height() / static_cast< double >( nYPoints - 1 );
541 
542  double pointY = rect.yMinimum();
543 
544  for ( int i = 0; i < nYPoints ; i++ )
545  {
546 
547  // Start at right edge
548  double pointX = rect.xMinimum();
549 
550  for ( int j = 0; j < nXPoints; j++ )
551  {
552  x[( i * nXPoints ) + j] = pointX;
553  y[( i * nXPoints ) + j] = pointY;
554  // and the height...
555  z[( i * nXPoints ) + j] = 0.0;
556  // QgsDebugMsg(QString("BBox coord: (%1, %2)").arg(x[(i*numP) + j]).arg(y[(i*numP) + j]));
557  pointX += dx;
558  }
559  pointY += dy;
560  }
561 
562  // Do transformation. Any exception generated must
563  // be handled in above layers.
564  try
565  {
566  transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
567  }
568  catch ( const QgsCsException & )
569  {
570  // rethrow the exception
571  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
572  throw;
573  }
574 
575  // Calculate the bounding box and use that for the extent
576 
577  for ( int i = 0; i < nXPoints * nYPoints; i++ )
578  {
579  if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
580  {
581  continue;
582  }
583 
584  if ( handle180Crossover )
585  {
586  //if crossing the date line, temporarily add 360 degrees to -ve longitudes
587  bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] );
588  }
589  else
590  {
591  bb_rect.combineExtentWith( x[i], y[i] );
592  }
593  }
594 
595  if ( bb_rect.isNull() )
596  {
597  // something bad happened when reprojecting the filter rect... no finite points were left!
598  throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
599  }
600 
601  if ( handle180Crossover )
602  {
603  //subtract temporary addition of 360 degrees from longitudes
604  if ( bb_rect.xMinimum() > 180.0 )
605  bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 );
606  if ( bb_rect.xMaximum() > 180.0 )
607  bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 );
608  }
609 
610  QgsDebugMsgLevel( "Projected extent: " + bb_rect.toString(), 4 );
611 
612  if ( bb_rect.isEmpty() )
613  {
614  QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
615  }
616 
617  return bb_rect;
618 }
619 
620 void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, TransformDirection direction ) const
621 {
622  if ( !d->mIsValid || d->mShortCircuit )
623  return;
624  // Refuse to transform the points if the srs's are invalid
625  if ( !d->mSourceCRS.isValid() )
626  {
627  QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. "
628  "The coordinates can not be reprojected. The CRS is: %1" )
629  .arg( d->mSourceCRS.toProj4() ), QObject::tr( "CRS" ) );
630  return;
631  }
632  if ( !d->mDestCRS.isValid() )
633  {
634  QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. "
635  "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj4() ), QObject::tr( "CRS" ) );
636  return;
637  }
638 
639 #ifdef COORDINATE_TRANSFORM_VERBOSE
640  double xorg = *x;
641  double yorg = *y;
642  QgsDebugMsg( QStringLiteral( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ) );
643 #endif
644 
645 #ifdef QGISDEBUG
646  if ( !mHasContext )
647  QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 );
648 #endif
649 
650  // use proj4 to do the transform
651 
652  // if the source/destination projection is lat/long, convert the points to radians
653  // prior to transforming
654  ProjData projData = d->threadLocalProjData();
655 
656  int projResult = 0;
657 #if PROJ_VERSION_MAJOR>=6
658  proj_errno_reset( projData );
659  proj_trans_generic( projData, direction == ForwardTransform ? PJ_FWD : PJ_INV,
660  x, sizeof( double ), numPoints,
661  y, sizeof( double ), numPoints,
662  z, sizeof( double ), numPoints,
663  nullptr, sizeof( double ), 0 );
664  projResult = proj_errno( projData );
665 #else
666  bool sourceIsLatLong = false;
667  bool destIsLatLong = false;
668 
669  projPJ sourceProj = projData.first;
670  projPJ destProj = projData.second;
671  sourceIsLatLong = pj_is_latlong( sourceProj );
672  destIsLatLong = pj_is_latlong( destProj );
673 
674  if ( ( destIsLatLong && ( direction == ReverseTransform ) )
675  || ( sourceIsLatLong && ( direction == ForwardTransform ) ) )
676  {
677  for ( int i = 0; i < numPoints; ++i )
678  {
679  x[i] *= DEG_TO_RAD;
680  y[i] *= DEG_TO_RAD;
681  }
682  }
683 #endif
684 
685 #if PROJ_VERSION_MAJOR<6
686  if ( direction == ReverseTransform )
687  {
688  projResult = pj_transform( destProj, sourceProj, numPoints, 0, x, y, z );
689  }
690  else
691  {
692  Q_ASSERT( sourceProj );
693  Q_ASSERT( destProj );
694  projResult = pj_transform( sourceProj, destProj, numPoints, 0, x, y, z );
695  }
696 #endif
697 
698  if ( projResult != 0 )
699  {
700  //something bad happened....
701  QString points;
702 
703  for ( int i = 0; i < numPoints; ++i )
704  {
705  if ( direction == ForwardTransform )
706  {
707  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' );
708  }
709  else
710  {
711 #if PROJ_VERSION_MAJOR>=6
712  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' );
713 #else
714  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i] * RAD_TO_DEG, 0, 'f' ).arg( y[i] * RAD_TO_DEG, 0, 'f' );
715 #endif
716  }
717  }
718 
719  QString dir = ( direction == ForwardTransform ) ? QObject::tr( "forward transform" ) : QObject::tr( "inverse transform" );
720 
721 #if PROJ_VERSION_MAJOR>=6
722  QgsProjUtils::proj_pj_unique_ptr src( proj_get_source_crs( QgsProjContext::get(), projData ) );
723  QgsProjUtils::proj_pj_unique_ptr dest( proj_get_source_crs( QgsProjContext::get(), projData ) );
724  QString msg = QObject::tr( "%1 of\n"
725  "%2"
726  "PROJ: %3\n"
727  "Error: %4" )
728  .arg( dir,
729  points,
730  proj_as_proj_string( QgsProjContext::get(), projData, PJ_PROJ_5, nullptr ),
731  QString::fromUtf8( proj_errno_string( projResult ) ) );
732 #else
733  char *srcdef = pj_get_def( sourceProj, 0 );
734  char *dstdef = pj_get_def( destProj, 0 );
735 
736  QString msg = QObject::tr( "%1 of\n"
737  "%2"
738  "PROJ: %3 +to %4\n"
739  "Error: %5" )
740  .arg( dir,
741  points,
742  srcdef, dstdef,
743  QString::fromUtf8( pj_strerrno( projResult ) ) );
744 
745  pj_dalloc( srcdef );
746  pj_dalloc( dstdef );
747 #endif
748 
749  QgsDebugMsg( "Projection failed emitting invalid transform signal: " + msg );
750  QgsDebugMsg( QStringLiteral( "throwing exception" ) );
751 
752  throw QgsCsException( msg );
753  }
754 
755 #if PROJ_VERSION_MAJOR<6
756  // if the result is lat/long, convert the results from radians back
757  // to degrees
758  if ( ( destIsLatLong && ( direction == ForwardTransform ) )
759  || ( sourceIsLatLong && ( direction == ReverseTransform ) ) )
760  {
761  for ( int i = 0; i < numPoints; ++i )
762  {
763  x[i] *= RAD_TO_DEG;
764  y[i] *= RAD_TO_DEG;
765  }
766  }
767 #endif
768 #ifdef COORDINATE_TRANSFORM_VERBOSE
769  QgsDebugMsg( QStringLiteral( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
770  .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 )
771  .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ) );
772 #endif
773 }
774 
776 {
777  return d->mIsValid;
778 }
779 
781 {
782  return !d->mIsValid || d->mShortCircuit;
783 }
784 
786 {
787  return d->mProjCoordinateOperation;
788 }
789 
790 void QgsCoordinateTransform::setCoordinateOperation( const QString &operation ) const
791 {
792  d.detach();
793  d->mProjCoordinateOperation = operation;
794 }
795 
796 const char *finder( const char *name )
797 {
798  QString proj;
799 #ifdef Q_OS_WIN
800  proj = QApplication::applicationDirPath()
801  + "/share/proj/" + QString( name );
802 #else
803  Q_UNUSED( name )
804 #endif
805  return proj.toUtf8();
806 }
807 
808 #if PROJ_VERSION_MAJOR>=6
809 bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, const QString &coordinateOperationProj )
810 {
811  if ( !src.isValid() || !dest.isValid() )
812  return false;
813 
814  const QString sourceKey = src.authid().isEmpty() ?
815  src.toWkt() : src.authid();
816  const QString destKey = dest.authid().isEmpty() ?
817  dest.toWkt() : dest.authid();
818 
819  if ( sourceKey.isEmpty() || destKey.isEmpty() )
820  return false;
821 
822  sCacheLock.lockForRead();
823  const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
824  for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
825  {
826  if ( ( *valIt ).coordinateOperation() == coordinateOperationProj )
827  {
828  // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
830 #ifdef QGISDEBUG
831  bool hasContext = mHasContext;
832 #endif
833  *this = *valIt;
834  sCacheLock.unlock();
835 
836  mContext = context;
837 #ifdef QGISDEBUG
838  mHasContext = hasContext;
839 #endif
840 
841  return true;
842  }
843  }
844  sCacheLock.unlock();
845  return false;
846 }
847 #else
848 bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, int srcDatumTransform, int destDatumTransform )
849 {
850  if ( !src.isValid() || !dest.isValid() )
851  return false;
852 
853  const QString sourceKey = src.authid().isEmpty() ?
854  src.toWkt() : src.authid();
855  const QString destKey = dest.authid().isEmpty() ?
856  dest.toWkt() : dest.authid();
857 
858  if ( sourceKey.isEmpty() || destKey.isEmpty() )
859  return false;
860 
861  sCacheLock.lockForRead();
862  const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( src.authid(), dest.authid() ) );
863  for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
864  {
866  if ( ( *valIt ).sourceDatumTransformId() == srcDatumTransform &&
867  ( *valIt ).destinationDatumTransformId() == destDatumTransform )
868  {
869  // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
871 #ifdef QGISDEBUG
872  bool hasContext = mHasContext;
873 #endif
874  *this = *valIt;
875  sCacheLock.unlock();
876 
877  mContext = context;
878 #ifdef QGISDEBUG
879  mHasContext = hasContext;
880 #endif
881 
882  return true;
883  }
885  }
886  sCacheLock.unlock();
887  return false;
888 }
889 #endif
890 
891 void QgsCoordinateTransform::addToCache()
892 {
893  if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
894  return;
895 
896  const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
897  d->mSourceCRS.toWkt() : d->mSourceCRS.authid();
898  const QString destKey = d->mDestCRS.authid().isEmpty() ?
899  d->mDestCRS.toWkt() : d->mDestCRS.authid();
900 
901  if ( sourceKey.isEmpty() || destKey.isEmpty() )
902  return;
903 
904  sCacheLock.lockForWrite();
905  sTransforms.insertMulti( qMakePair( sourceKey, destKey ), *this );
906  sCacheLock.unlock();
907 }
908 
910 {
912  return d->mSourceDatumTransform;
914 }
915 
917 {
918  d.detach();
920  d->mSourceDatumTransform = dt;
922 }
923 
925 {
927  return d->mDestinationDatumTransform;
929 }
930 
932 {
933  d.detach();
935  d->mDestinationDatumTransform = dt;
937 }
938 
940 {
941  sCacheLock.lockForWrite();
942  sTransforms.clear();
943  sCacheLock.unlock();
944 }
945 
946 double QgsCoordinateTransform::scaleFactor( const QgsRectangle &ReferenceExtent ) const
947 {
948  QgsPointXY source1( ReferenceExtent.xMinimum(), ReferenceExtent.yMinimum() );
949  QgsPointXY source2( ReferenceExtent.xMaximum(), ReferenceExtent.yMaximum() );
950  double distSourceUnits = std::sqrt( source1.sqrDist( source2 ) );
951  QgsPointXY dest1 = transform( source1 );
952  QgsPointXY dest2 = transform( source2 );
953  double distDestUnits = std::sqrt( dest1.sqrDist( dest2 ) );
954  return distDestUnits / distSourceUnits;
955 }
956 
958 {
959  QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( handler );
960 }
961 
963 {
964  QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( handler );
965 }
966 
968 {
969  QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
970 }
971 
973 {
974  QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
975 }
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 setXMaximum(double x)
Set the maximum x value.
Definition: qgsrectangle.h:135
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.
#define Q_NOWARN_DEPRECATED_PUSH
Definition: qgis.h:624
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...
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
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...
Reads and writes project states.
Definition: qgsproject.h:89
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
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:96
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle...
Definition: qgsrectangle.h:359
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...
#define Q_NOWARN_DEPRECATED_POP
Definition: qgis.h:625
Transform from destination to source CRS.
static void invalidateCache()
Clears the internal cache used to initialize QgsCoordinateTransform objects.
This class represents a coordinate reference system (CRS).
QString toWkt() const
Returns a WKT representation of this CRS.
double scaleFactor(const QgsRectangle &referenceExtent) const
Computes an estimated conversion factor between source and destination units:
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:436
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.
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
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.
void setXMinimum(double x)
Set the minimum x value.
Definition: qgsrectangle.h:130
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.