QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 extern "C"
37 {
38 #ifndef ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
39 #define ACCEPT_USE_OF_DEPRECATED_PROJ_API_H
40 #endif
41 #include <proj_api.h>
42 }
43 #include <sqlite3.h>
44 
45 // if defined shows all information about transform to stdout
46 // #define COORDINATE_TRANSFORM_VERBOSE
47 
48 QReadWriteLock QgsCoordinateTransform::sCacheLock;
49 QMultiHash< QPair< QString, QString >, QgsCoordinateTransform > QgsCoordinateTransform::sTransforms; //same auth_id pairs might have different datum transformations
50 bool QgsCoordinateTransform::sDisableCache = false;
51 
53 {
54  d = new QgsCoordinateTransformPrivate();
55 }
56 
58 {
59  d = new QgsCoordinateTransformPrivate( source, destination, QgsCoordinateTransformContext() );
60 
61  if ( !d->checkValidity() )
62  return;
63 
64  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
65  {
66  d->initialize();
67  addToCache();
68  }
69 }
70 
72 {
73  mContext = context;
74  d = new QgsCoordinateTransformPrivate( source, destination, mContext );
75 #ifdef QGISDEBUG
76  mHasContext = true;
77 #endif
78 
79  if ( !d->checkValidity() )
80  return;
81 
82  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
83  {
84  d->initialize();
85  addToCache();
86  }
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 
101  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
102  {
103  d->initialize();
104  addToCache();
105  }
106 }
107 
108 QgsCoordinateTransform::QgsCoordinateTransform( const QgsCoordinateReferenceSystem &source, const QgsCoordinateReferenceSystem &destination, int sourceDatumTransform, int destinationDatumTransform )
109 {
110  d = new QgsCoordinateTransformPrivate( source, destination, sourceDatumTransform, destinationDatumTransform );
111 #ifdef QGISDEBUG
112  mHasContext = true; // not strictly true, but we don't need to worry if datums have been explicitly set
113 #endif
114 
115  if ( !d->checkValidity() )
116  return;
117 
118  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
119  {
120  d->initialize();
121  addToCache();
122  }
123 }
124 
126  : mContext( o.mContext )
127 #ifdef QGISDEBUG
128  , mHasContext( o.mHasContext )
129 #endif
130 {
131  d = o.d;
132 }
133 
135 {
136  d = o.d;
137 #ifdef QGISDEBUG
138  mHasContext = o.mHasContext;
139 #endif
140  mContext = o.mContext;
141  return *this;
142 }
143 
145 
147 {
148  d.detach();
149  d->mSourceCRS = crs;
150  if ( !d->checkValidity() )
151  return;
152 
153  d->calculateTransforms( mContext );
154  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
155  {
156  d->initialize();
157  addToCache();
158  }
159 }
161 {
162  d.detach();
163  d->mDestCRS = crs;
164  if ( !d->checkValidity() )
165  return;
166 
167  d->calculateTransforms( mContext );
168  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
169  {
170  d->initialize();
171  addToCache();
172  }
173 }
174 
176 {
177  d.detach();
178  mContext = context;
179 #ifdef QGISDEBUG
180  mHasContext = true;
181 #endif
182  if ( !d->checkValidity() )
183  return;
184 
185  d->calculateTransforms( mContext );
186  if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mSourceDatumTransform, d->mDestinationDatumTransform ) )
187  {
188  d->initialize();
189  addToCache();
190  }
191 }
192 
194 {
195  return mContext;
196 }
197 
199 {
200  return d->mSourceCRS;
201 }
202 
204 {
205  return d->mDestCRS;
206 }
207 
209 {
210  if ( !d->mIsValid || d->mShortCircuit )
211  return point;
212 
213  // transform x
214  double x = point.x();
215  double y = point.y();
216  double z = 0.0;
217  try
218  {
219  transformCoords( 1, &x, &y, &z, direction );
220  }
221  catch ( const QgsCsException & )
222  {
223  // rethrow the exception
224  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
225  throw;
226  }
227 
228  return QgsPointXY( x, y );
229 }
230 
231 
232 QgsPointXY QgsCoordinateTransform::transform( const double theX, const double theY = 0.0, TransformDirection direction ) const
233 {
234  try
235  {
236  return transform( QgsPointXY( theX, theY ), direction );
237  }
238  catch ( const QgsCsException & )
239  {
240  // rethrow the exception
241  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
242  throw;
243  }
244 }
245 
247 {
248  if ( !d->mIsValid || d->mShortCircuit )
249  return rect;
250  // transform x
251  double x1 = rect.xMinimum();
252  double y1 = rect.yMinimum();
253  double x2 = rect.xMaximum();
254  double y2 = rect.yMaximum();
255 
256  // Number of points to reproject------+
257  // |
258  // V
259  try
260  {
261  double z = 0.0;
262  transformCoords( 1, &x1, &y1, &z, direction );
263  transformCoords( 1, &x2, &y2, &z, direction );
264  }
265  catch ( const QgsCsException & )
266  {
267  // rethrow the exception
268  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
269  throw;
270  }
271 
272 #ifdef COORDINATE_TRANSFORM_VERBOSE
273  QgsDebugMsg( QStringLiteral( "Rect projection..." ) );
274  QgsDebugMsg( QStringLiteral( "Xmin : %1 --> %2" ).arg( rect.xMinimum() ).arg( x1 ) );
275  QgsDebugMsg( QStringLiteral( "Ymin : %1 --> %2" ).arg( rect.yMinimum() ).arg( y1 ) );
276  QgsDebugMsg( QStringLiteral( "Xmax : %1 --> %2" ).arg( rect.xMaximum() ).arg( x2 ) );
277  QgsDebugMsg( QStringLiteral( "Ymax : %1 --> %2" ).arg( rect.yMaximum() ).arg( y2 ) );
278 #endif
279  return QgsRectangle( x1, y1, x2, y2 );
280 }
281 
282 void QgsCoordinateTransform::transformInPlace( double &x, double &y, double &z,
283  TransformDirection direction ) const
284 {
285  if ( !d->mIsValid || d->mShortCircuit )
286  return;
287 #ifdef QGISDEBUG
288 // QgsDebugMsg(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__));
289 #endif
290  // transform x
291  try
292  {
293  transformCoords( 1, &x, &y, &z, direction );
294  }
295  catch ( const QgsCsException & )
296  {
297  // rethrow the exception
298  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
299  throw;
300  }
301 }
302 
303 void QgsCoordinateTransform::transformInPlace( float &x, float &y, double &z,
304  TransformDirection direction ) const
305 {
306  double xd = static_cast< double >( x ), yd = static_cast< double >( y );
307  transformInPlace( xd, yd, z, direction );
308  x = xd;
309  y = yd;
310 }
311 
312 void QgsCoordinateTransform::transformInPlace( float &x, float &y, float &z,
313  TransformDirection direction ) const
314 {
315  if ( !d->mIsValid || d->mShortCircuit )
316  return;
317 #ifdef QGISDEBUG
318  // QgsDebugMsg(QString("Using transform in place %1 %2").arg(__FILE__).arg(__LINE__));
319 #endif
320  // transform x
321  try
322  {
323  double xd = x;
324  double yd = y;
325  double zd = z;
326  transformCoords( 1, &xd, &yd, &zd, direction );
327  x = xd;
328  y = yd;
329  z = zd;
330  }
331  catch ( QgsCsException & )
332  {
333  // rethrow the exception
334  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
335  throw;
336  }
337 }
338 
339 void QgsCoordinateTransform::transformPolygon( QPolygonF &poly, TransformDirection direction ) const
340 {
341  if ( !d->mIsValid || d->mShortCircuit )
342  {
343  return;
344  }
345 
346  //create x, y arrays
347  int nVertices = poly.size();
348 
349  QVector<double> x( nVertices );
350  QVector<double> y( nVertices );
351  QVector<double> z( nVertices );
352  double *destX = x.data();
353  double *destY = y.data();
354  double *destZ = z.data();
355 
356  const QPointF *polyData = poly.constData();
357  for ( int i = 0; i < nVertices; ++i )
358  {
359  *destX++ = polyData->x();
360  *destY++ = polyData->y();
361  *destZ++ = 0;
362  polyData++;
363  }
364 
365  try
366  {
367  transformCoords( nVertices, x.data(), y.data(), z.data(), direction );
368  }
369  catch ( const QgsCsException & )
370  {
371  // rethrow the exception
372  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
373  throw;
374  }
375 
376  QPointF *destPoint = poly.data();
377  const double *srcX = x.constData();
378  const double *srcY = y.constData();
379  for ( int i = 0; i < nVertices; ++i )
380  {
381  destPoint->rx() = *srcX++;
382  destPoint->ry() = *srcY++;
383  destPoint++;
384  }
385 }
386 
388  QVector<double> &x, QVector<double> &y, QVector<double> &z,
389  TransformDirection direction ) const
390 {
391 
392  if ( !d->mIsValid || d->mShortCircuit )
393  return;
394 
395  Q_ASSERT( x.size() == y.size() );
396 
397  // Apparently, if one has a std::vector, it is valid to use the
398  // address of the first element in the vector as a pointer to an
399  // array of the vectors data, and hence easily interface with code
400  // that wants C-style arrays.
401 
402  try
403  {
404  transformCoords( x.size(), &x[0], &y[0], &z[0], direction );
405  }
406  catch ( const QgsCsException & )
407  {
408  // rethrow the exception
409  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
410  throw;
411  }
412 }
413 
414 
416  QVector<float> &x, QVector<float> &y, QVector<float> &z,
417  TransformDirection direction ) const
418 {
419  if ( !d->mIsValid || d->mShortCircuit )
420  return;
421 
422  Q_ASSERT( x.size() == y.size() );
423 
424  // Apparently, if one has a std::vector, it is valid to use the
425  // address of the first element in the vector as a pointer to an
426  // array of the vectors data, and hence easily interface with code
427  // that wants C-style arrays.
428 
429  try
430  {
431  //copy everything to double vectors since proj needs double
432  int vectorSize = x.size();
433  QVector<double> xd( x.size() );
434  QVector<double> yd( y.size() );
435  QVector<double> zd( z.size() );
436 
437  double *destX = xd.data();
438  double *destY = yd.data();
439  double *destZ = zd.data();
440 
441  const float *srcX = x.constData();
442  const float *srcY = y.constData();
443  const float *srcZ = z.constData();
444 
445  for ( int i = 0; i < vectorSize; ++i )
446  {
447  *destX++ = static_cast< double >( *srcX++ );
448  *destY++ = static_cast< double >( *srcY++ );
449  *destZ++ = static_cast< double >( *srcZ++ );
450  }
451 
452  transformCoords( x.size(), &xd[0], &yd[0], &zd[0], direction );
453 
454  //copy back
455  float *destFX = x.data();
456  float *destFY = y.data();
457  float *destFZ = z.data();
458  const double *srcXD = xd.constData();
459  const double *srcYD = yd.constData();
460  const double *srcZD = zd.constData();
461  for ( int i = 0; i < vectorSize; ++i )
462  {
463  *destFX++ = static_cast< float >( *srcXD++ );
464  *destFY++ = static_cast< float >( *srcYD++ );
465  *destFZ++ = static_cast< float >( *srcZD++ );
466  }
467  }
468  catch ( QgsCsException & )
469  {
470  // rethrow the exception
471  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
472  throw;
473  }
474 }
475 
476 QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &rect, TransformDirection direction, const bool handle180Crossover ) const
477 {
478  // Calculate the bounding box of a QgsRectangle in the source CRS
479  // when projected to the destination CRS (or the inverse).
480  // This is done by looking at a number of points spread evenly
481  // across the rectangle
482 
483  if ( !d->mIsValid || d->mShortCircuit )
484  return rect;
485 
486  if ( rect.isEmpty() )
487  {
488  QgsPointXY p = transform( rect.xMinimum(), rect.yMinimum(), direction );
489  return QgsRectangle( p, p );
490  }
491 
492  // 64 points (<=2.12) is not enough, see #13665, for EPSG:4326 -> EPSG:3574 (say that it is a hard one),
493  // are decent result from about 500 points and more. This method is called quite often, but
494  // even with 1000 points it takes < 1ms.
495  // TODO: how to effectively and precisely reproject bounding box?
496  const int nPoints = 1000;
497  double d = std::sqrt( ( rect.width() * rect.height() ) / std::pow( std::sqrt( static_cast< double >( nPoints ) ) - 1, 2.0 ) );
498  int nXPoints = std::min( static_cast< int >( std::ceil( rect.width() / d ) ) + 1, 1000 );
499  int nYPoints = std::min( static_cast< int >( std::ceil( rect.height() / d ) ) + 1, 1000 );
500 
501  QgsRectangle bb_rect;
502  bb_rect.setMinimal();
503 
504  // We're interfacing with C-style vectors in the
505  // end, so let's do C-style vectors here too.
506  QVector<double> x( nXPoints * nYPoints );
507  QVector<double> y( nXPoints * nYPoints );
508  QVector<double> z( nXPoints * nYPoints );
509 
510  QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 );
511 
512  // Populate the vectors
513 
514  double dx = rect.width() / static_cast< double >( nXPoints - 1 );
515  double dy = rect.height() / static_cast< double >( nYPoints - 1 );
516 
517  double pointY = rect.yMinimum();
518 
519  for ( int i = 0; i < nYPoints ; i++ )
520  {
521 
522  // Start at right edge
523  double pointX = rect.xMinimum();
524 
525  for ( int j = 0; j < nXPoints; j++ )
526  {
527  x[( i * nXPoints ) + j] = pointX;
528  y[( i * nXPoints ) + j] = pointY;
529  // and the height...
530  z[( i * nXPoints ) + j] = 0.0;
531  // QgsDebugMsg(QString("BBox coord: (%1, %2)").arg(x[(i*numP) + j]).arg(y[(i*numP) + j]));
532  pointX += dx;
533  }
534  pointY += dy;
535  }
536 
537  // Do transformation. Any exception generated must
538  // be handled in above layers.
539  try
540  {
541  transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
542  }
543  catch ( const QgsCsException & )
544  {
545  // rethrow the exception
546  QgsDebugMsg( QStringLiteral( "rethrowing exception" ) );
547  throw;
548  }
549 
550  // Calculate the bounding box and use that for the extent
551 
552  for ( int i = 0; i < nXPoints * nYPoints; i++ )
553  {
554  if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
555  {
556  continue;
557  }
558 
559  if ( handle180Crossover )
560  {
561  //if crossing the date line, temporarily add 360 degrees to -ve longitudes
562  bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] );
563  }
564  else
565  {
566  bb_rect.combineExtentWith( x[i], y[i] );
567  }
568  }
569 
570  if ( bb_rect.isNull() )
571  {
572  // something bad happened when reprojecting the filter rect... no finite points were left!
573  throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) );
574  }
575 
576  if ( handle180Crossover )
577  {
578  //subtract temporary addition of 360 degrees from longitudes
579  if ( bb_rect.xMinimum() > 180.0 )
580  bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 );
581  if ( bb_rect.xMaximum() > 180.0 )
582  bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 );
583  }
584 
585  QgsDebugMsgLevel( "Projected extent: " + bb_rect.toString(), 4 );
586 
587  if ( bb_rect.isEmpty() )
588  {
589  QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 );
590  }
591 
592  return bb_rect;
593 }
594 
595 void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, TransformDirection direction ) const
596 {
597  if ( !d->mIsValid || d->mShortCircuit )
598  return;
599  // Refuse to transform the points if the srs's are invalid
600  if ( !d->mSourceCRS.isValid() )
601  {
602  QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. "
603  "The coordinates can not be reprojected. The CRS is: %1" )
604  .arg( d->mSourceCRS.toProj4() ), QObject::tr( "CRS" ) );
605  return;
606  }
607  if ( !d->mDestCRS.isValid() )
608  {
609  QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. "
610  "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj4() ), QObject::tr( "CRS" ) );
611  return;
612  }
613 
614 #ifdef COORDINATE_TRANSFORM_VERBOSE
615  double xorg = *x;
616  double yorg = *y;
617  QgsDebugMsg( QStringLiteral( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ) );
618 #endif
619 
620 #ifdef QGISDEBUG
621  if ( !mHasContext )
622  QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 );
623 #endif
624 
625  // use proj4 to do the transform
626 
627  // if the source/destination projection is lat/long, convert the points to radians
628  // prior to transforming
629  QPair<projPJ, projPJ> projData = d->threadLocalProjData();
630  projPJ sourceProj = projData.first;
631  projPJ destProj = projData.second;
632 
633  if ( ( pj_is_latlong( destProj ) && ( direction == ReverseTransform ) )
634  || ( pj_is_latlong( sourceProj ) && ( direction == ForwardTransform ) ) )
635  {
636  for ( int i = 0; i < numPoints; ++i )
637  {
638  x[i] *= DEG_TO_RAD;
639  y[i] *= DEG_TO_RAD;
640  }
641 
642  }
643  int projResult;
644  if ( direction == ReverseTransform )
645  {
646  projResult = pj_transform( destProj, sourceProj, numPoints, 0, x, y, z );
647  }
648  else
649  {
650  Q_ASSERT( sourceProj );
651  Q_ASSERT( destProj );
652  projResult = pj_transform( sourceProj, destProj, numPoints, 0, x, y, z );
653  }
654 
655  if ( projResult != 0 )
656  {
657  //something bad happened....
658  QString points;
659 
660  for ( int i = 0; i < numPoints; ++i )
661  {
662  if ( direction == ForwardTransform )
663  {
664  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' );
665  }
666  else
667  {
668  points += QStringLiteral( "(%1, %2)\n" ).arg( x[i] * RAD_TO_DEG, 0, 'f' ).arg( y[i] * RAD_TO_DEG, 0, 'f' );
669  }
670  }
671 
672  QString dir = ( direction == ForwardTransform ) ? QObject::tr( "forward transform" ) : QObject::tr( "inverse transform" );
673 
674  char *srcdef = pj_get_def( sourceProj, 0 );
675  char *dstdef = pj_get_def( destProj, 0 );
676 
677  QString msg = QObject::tr( "%1 of\n"
678  "%2"
679  "PROJ: %3 +to %4\n"
680  "Error: %5" )
681  .arg( dir,
682  points,
683  srcdef, dstdef,
684  QString::fromUtf8( pj_strerrno( projResult ) ) );
685 
686  pj_dalloc( srcdef );
687  pj_dalloc( dstdef );
688 
689  QgsDebugMsg( "Projection failed emitting invalid transform signal: " + msg );
690  QgsDebugMsg( QStringLiteral( "throwing exception" ) );
691 
692  throw QgsCsException( msg );
693  }
694 
695  // if the result is lat/long, convert the results from radians back
696  // to degrees
697  if ( ( pj_is_latlong( destProj ) && ( direction == ForwardTransform ) )
698  || ( pj_is_latlong( sourceProj ) && ( direction == ReverseTransform ) ) )
699  {
700  for ( int i = 0; i < numPoints; ++i )
701  {
702  x[i] *= RAD_TO_DEG;
703  y[i] *= RAD_TO_DEG;
704  }
705  }
706 #ifdef COORDINATE_TRANSFORM_VERBOSE
707  QgsDebugMsg( QStringLiteral( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
708  .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 )
709  .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ) );
710 #endif
711 }
712 
714 {
715  return d->mIsValid;
716 }
717 
719 {
720  return !d->mIsValid || d->mShortCircuit;
721 }
722 
723 const char *finder( const char *name )
724 {
725  QString proj;
726 #ifdef Q_OS_WIN
727  proj = QApplication::applicationDirPath()
728  + "/share/proj/" + QString( name );
729 #else
730  Q_UNUSED( name );
731 #endif
732  return proj.toUtf8();
733 }
734 
735 bool QgsCoordinateTransform::setFromCache( const QgsCoordinateReferenceSystem &src, const QgsCoordinateReferenceSystem &dest, int srcDatumTransform, int destDatumTransform )
736 {
737  if ( !src.isValid() || !dest.isValid() )
738  return false;
739 
740  const QString sourceKey = src.authid().isEmpty() ?
741  src.toWkt() : src.authid();
742  const QString destKey = dest.authid().isEmpty() ?
743  dest.toWkt() : dest.authid();
744 
745  if ( sourceKey.isEmpty() || destKey.isEmpty() )
746  return false;
747 
748  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Read );
749  if ( sDisableCache )
750  return false;
751 
752  const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
753  for ( auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
754  {
755  if ( ( *valIt ).sourceDatumTransformId() == srcDatumTransform &&
756  ( *valIt ).destinationDatumTransformId() == destDatumTransform )
757  {
758  // need to save, and then restore the context... we don't want this to be cached or to use the values from the cache
760 #ifdef QGISDEBUG
761  bool hasContext = mHasContext;
762 #endif
763  *this = *valIt;
764  locker.unlock();
765 
766  mContext = context;
767 #ifdef QGISDEBUG
768  mHasContext = hasContext;
769 #endif
770 
771  return true;
772  }
773  }
774  return false;
775 }
776 
777 void QgsCoordinateTransform::addToCache()
778 {
779  if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
780  return;
781 
782  const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
783  d->mSourceCRS.toWkt() : d->mSourceCRS.authid();
784  const QString destKey = d->mDestCRS.authid().isEmpty() ?
785  d->mDestCRS.toWkt() : d->mDestCRS.authid();
786 
787  if ( sourceKey.isEmpty() || destKey.isEmpty() )
788  return;
789 
790  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
791  if ( sDisableCache )
792  return;
793 
794  sTransforms.insertMulti( qMakePair( sourceKey, destKey ), *this );
795 }
796 
798 {
799  return d->mSourceDatumTransform;
800 }
801 
803 {
804  d.detach();
805  d->mSourceDatumTransform = dt;
806 }
807 
809 {
810  return d->mDestinationDatumTransform;
811 }
812 
814 {
815  d.detach();
816  d->mDestinationDatumTransform = dt;
817 }
818 
820 {
821  QgsReadWriteLocker locker( sCacheLock, QgsReadWriteLocker::Write );
822  if ( sDisableCache )
823  return;
824 
825  if ( disableCache )
826  {
827  sDisableCache = true;
828  }
829 
830  sTransforms.clear();
831 }
832 
833 double QgsCoordinateTransform::scaleFactor( const QgsRectangle &ReferenceExtent ) const
834 {
835  QgsPointXY source1( ReferenceExtent.xMinimum(), ReferenceExtent.yMinimum() );
836  QgsPointXY source2( ReferenceExtent.xMaximum(), ReferenceExtent.yMaximum() );
837  double distSourceUnits = std::sqrt( source1.sqrDist( source2 ) );
838  QgsPointXY dest1 = transform( source1 );
839  QgsPointXY dest2 = transform( source2 );
840  double distDestUnits = std::sqrt( dest1.sqrDist( dest2 ) );
841  return distDestUnits / distSourceUnits;
842 }
A rectangle specified with double values.
Definition: qgsrectangle.h:40
void setDestinationDatumTransformId(int datumId)
Sets the datumId ID of the datum transform to use when projecting to the destination CRS...
bool isEmpty() const
Returns true if the rectangle is empty.
Definition: qgsrectangle.h:425
void setMinimal()
Set a rectangle so that min corner is at max and max corner is at min.
Definition: qgsrectangle.h:150
void setContext(const QgsCoordinateTransformContext &context)
Sets the context in which the coordinate transform should be calculated.
double yMaximum() const
Returns the y maximum value (top side of rectangle).
Definition: qgsrectangle.h:171
#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.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the source coordinate reference system, which the transform will transform coordinates from...
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.
QString toWkt() const
Returns a WKT representation of this CRS.
static void invalidateCache(bool disableCache=false)
Clears the internal cache used to initialize QgsCoordinateTransform objects.
QgsCoordinateTransformContext context() const
Returns the context in which the coordinate transform will be calculated.
QgsCoordinateTransform & operator=(const QgsCoordinateTransform &o)
Assignment operator.
const QgsCoordinateReferenceSystem & crs
bool isShortCircuited() const
Returns true if the transform short circuits because the source and destination are equivalent...
void transformPolygon(QPolygonF &polygon, TransformDirection direction=ForwardTransform) const SIP_THROW(QgsCsException)
Transforms a polygon to the destination coordinate system.
QgsCoordinateReferenceSystem destinationCrs() const
Returns the destination coordinate reference system, which the transform will transform coordinates t...
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
QgsCoordinateTransform()
Default constructor, creates an invalid QgsCoordinateTransform.
void setDestinationCrs(const QgsCoordinateReferenceSystem &crs)
Sets the destination coordinate reference system.
double yMinimum() const
Returns the y minimum value (bottom side of rectangle).
Definition: qgsrectangle.h:176
double xMaximum() const
Returns the x maximum value (right side of rectangle).
Definition: qgsrectangle.h:161
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
int sourceDatumTransformId() const
Returns the ID of the datum transform to use when projecting from the source CRS. ...
int destinationDatumTransformId() const
Returns the ID of the datum transform to use when projecting to the destination CRS.
QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
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).
Reads and writes project states.
Definition: qgsproject.h:89
Contains information about the context in which a coordinate transform is executed.
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 scaleFactor(const QgsRectangle &referenceExtent) const
Computes an estimated conversion factor between source and destination units:
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 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 unlock()
Unlocks the lock.
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Transform from destination to source CRS.
This class represents a coordinate reference system (CRS).
QString authid() const
Returns the authority identifier for the CRS.
Class for doing transforms between two map coordinate systems.
Transform from source to destination CRS.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:65
double width() const
Returns the width of the rectangle.
Definition: qgsrectangle.h:201
double xMinimum() const
Returns the x minimum value (left side of rectangle).
Definition: qgsrectangle.h:166
void setSourceCrs(const QgsCoordinateReferenceSystem &crs)
Sets the source coordinate reference system.
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.
double height() const
Returns the height of the rectangle.
Definition: qgsrectangle.h:208
const char * finder(const char *name)
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...