33#include <QApplication>
49QReadWriteLock QgsCoordinateTransform::sCacheLock;
51bool QgsCoordinateTransform::sDisableCache =
false;
55 const QString &desiredOperation )> QgsCoordinateTransform::sFallbackOperationOccurredHandler =
nullptr;
59 d =
new QgsCoordinateTransformPrivate();
65 d =
new QgsCoordinateTransformPrivate( source, destination, mContext );
68 mIgnoreImpossible =
true;
80 if ( !d->checkValidity() )
84 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
92 mBallparkTransformsAreAppropriate =
true;
98 d =
new QgsCoordinateTransformPrivate( source, destination, mContext );
105 mIgnoreImpossible =
true;
113 if ( !d->checkValidity() )
117 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
125 mBallparkTransformsAreAppropriate =
true;
130 d =
new QgsCoordinateTransformPrivate( source, destination, sourceDatumTransform, destinationDatumTransform );
135 if ( !d->checkValidity() )
139 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
148 : mContext( o.mContext )
150 , mHasContext( o.mHasContext )
156 , mIgnoreImpossible( false )
157 , mBallparkTransformsAreAppropriate( false )
158 , mDisableFallbackHandler( false )
159 , mFallbackOperationOccurred( false )
168 mHasContext = o.mHasContext;
170 mContext = o.mContext;
171 mLastError = QString();
179 return d->mSourceCRS == other.d->mSourceCRS
180 && d->mDestCRS == other.d->mDestCRS
181 && mBallparkTransformsAreAppropriate == other.mBallparkTransformsAreAppropriate
182 && d->mProjCoordinateOperation == other.d->mProjCoordinateOperation
188 return !( *
this == other );
213 if ( !d->checkValidity() )
216 d->calculateTransforms( mContext );
218 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
236 if ( !d->checkValidity() )
239 d->calculateTransforms( mContext );
241 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
263 if ( !d->checkValidity() )
266 d->calculateTransforms( mContext );
268 if ( !setFromCache( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation, d->mAllowFallbackTransforms ) )
283 return d->mSourceCRS;
293 if ( !d->mIsValid || d->mShortCircuit )
297 double x = point.
x();
298 double y = point.
y();
331 if ( !d->mIsValid || d->mShortCircuit )
355#ifdef COORDINATE_TRANSFORM_VERBOSE
367 double x = point.
x();
368 double y = point.
y();
369 double z = point.
z();
386 if ( !d->mIsValid || d->mShortCircuit )
407 double xd =
static_cast< double >( x ), yd =
static_cast< double >( y );
416 if ( !d->mIsValid || d->mShortCircuit )
442 if ( !d->mIsValid || d->mShortCircuit )
448 const int nVertices = poly.size();
450 QVector<double> x( nVertices );
451 QVector<double> y( nVertices );
452 QVector<double> z( nVertices );
453 double *destX = x.data();
454 double *destY = y.data();
455 double *destZ = z.data();
457 const QPointF *polyData = poly.constData();
458 for (
int i = 0; i < nVertices; ++i )
460 *destX++ = polyData->x();
461 *destY++ = polyData->y();
477 QPointF *destPoint = poly.data();
478 const double *srcX = x.constData();
479 const double *srcY = y.constData();
480 for (
int i = 0; i < nVertices; ++i )
482 destPoint->rx() = *srcX++;
483 destPoint->ry() = *srcY++;
488 if ( !err.isEmpty() )
496 if ( !d->mIsValid || d->mShortCircuit )
499 Q_ASSERT( x.size() == y.size() );
522 if ( !d->mIsValid || d->mShortCircuit )
525 Q_ASSERT( x.size() == y.size() );
535 const int vectorSize = x.size();
536 QVector<double> xd( x.size() );
537 QVector<double> yd( y.size() );
538 QVector<double> zd( z.size() );
540 double *destX = xd.data();
541 double *destY = yd.data();
542 double *destZ = zd.data();
544 const float *srcX = x.constData();
545 const float *srcY = y.constData();
546 const float *srcZ = z.constData();
548 for (
int i = 0; i < vectorSize; ++i )
550 *destX++ =
static_cast< double >( *srcX++ );
551 *destY++ =
static_cast< double >( *srcY++ );
552 *destZ++ =
static_cast< double >( *srcZ++ );
558 float *destFX = x.data();
559 float *destFY = y.data();
560 float *destFZ = z.data();
561 const double *srcXD = xd.constData();
562 const double *srcYD = yd.constData();
563 const double *srcZD = zd.constData();
564 for (
int i = 0; i < vectorSize; ++i )
566 *destFX++ =
static_cast< float >( *srcXD++ );
567 *destFY++ =
static_cast< float >( *srcYD++ );
568 *destFZ++ =
static_cast< float >( *srcZD++ );
586 if ( !d->mIsValid || d->mShortCircuit )
598 QgsDebugMsgLevel( QStringLiteral(
"No QgsCoordinateTransformContext context set for transform" ), 4 );
606 throw QgsCsException( QObject::tr(
"Could not transform bounding box for geocentric CRS %1" ).arg( d->mSourceCRS.authid() ) );
610 throw QgsCsException( QObject::tr(
"Could not transform bounding box for geocentric CRS %1" ).arg( d->mDestCRS.authid() ) );
613 const double xMin = rect.
xMinimum();
614 const double xMax = rect.
xMaximum();
617 if ( d->mGeographicToWebMercator &&
626 constexpr double EPS = 1e-1;
627 if ( yMin < -90 + EPS )
629 if ( yMax < -90 + EPS )
630 throw QgsCsException( QObject::tr(
"Could not transform bounding box to target CRS" ) );
633 if ( yMax > 90 - EPS )
635 if ( yMin > 90 - EPS )
636 throw QgsCsException( QObject::tr(
"Could not transform bounding box to target CRS" ) );
642#if PROJ_VERSION_MAJOR>8 || (PROJ_VERSION_MAJOR==8 && PROJ_VERSION_MINOR>=2)
646 QgsDebugMsgLevel( QStringLiteral(
"Entering transformBoundingBox..." ), 4 );
648 ProjData projData = d->threadLocalProjData();
651#if PROJ_VERSION_MAJOR< 9 || (PROJ_VERSION_MAJOR==9 && PROJ_VERSION_MINOR<6)
666 transform2D.reset( proj_create_crs_to_crs_from_pj( projContext, srcCrsHorizontal.get(), destCrsHorizontal.get(),
nullptr,
nullptr ) );
667 projData = transform2D.get();
671 double transXMin = 0;
672 double transYMin = 0;
673 double transXMax = 0;
674 double transYMax = 0;
676 proj_errno_reset( projData );
678 constexpr int DENSIFY_POINTS = 21;
680 xMin, yMin, xMax, yMax,
681 &transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
683 || !std::isfinite( transXMin )
684 || !std::isfinite( transXMax )
685 || !std::isfinite( transYMin )
686 || !std::isfinite( transYMax ) )
688 const QString projErr = QString::fromUtf8( proj_context_errno_string( projContext, proj_errno( projData ) ) );
690 const QString msg = QObject::tr(
"%1 (%2 to %3) of bounding box failed: %4" )
701 bool doHandle180Crossover =
false;
703 if ( handle180Crossover
706 && ( transXMax < transXMin ) )
709 std::swap( transXMax, transXMin );
714 doHandle180Crossover =
true;
717 QgsRectangle boundingBoxRect{ transXMin, transYMin, transXMax, transYMax };
718 if ( boundingBoxRect.isNull() )
721 throw QgsCsException( QObject::tr(
"Could not transform bounding box to target CRS" ) );
724 if ( doHandle180Crossover )
727 if ( boundingBoxRect.xMinimum() > 180.0 )
728 boundingBoxRect.setXMinimum( boundingBoxRect.xMinimum() - 360.0 );
729 if ( boundingBoxRect.xMaximum() > 180.0 )
730 boundingBoxRect.setXMaximum( boundingBoxRect.xMaximum() - 360.0 );
735 if ( boundingBoxRect.isEmpty() )
740 return boundingBoxRect;
748 const int nPoints = 1000;
749 const double dst = std::sqrt( ( rect.
width() * ( yMax - yMin ) ) / std::pow( std::sqrt(
static_cast< double >( nPoints ) ) - 1, 2.0 ) );
750 const int nXPoints = std::min(
static_cast< int >( std::ceil( rect.
width() / dst ) ) + 1, 1000 );
751 const int nYPoints = std::min(
static_cast< int >( std::ceil( ( yMax - yMin ) / dst ) ) + 1, 1000 );
756 std::vector<double> x( nXPoints *
static_cast< std::size_t
>( nYPoints ) );
757 std::vector<double> y( nXPoints *
static_cast< std::size_t
>( nYPoints ) );
758 std::vector<double> z( nXPoints *
static_cast< std::size_t
>( nYPoints ) );
760 QgsDebugMsgLevel( QStringLiteral(
"Entering transformBoundingBox..." ), 4 );
764 const double dx = rect.
width() /
static_cast< double >( nXPoints - 1 );
765 const double dy = ( yMax - yMin ) /
static_cast< double >( nYPoints - 1 );
767 double pointY = yMin;
769 for (
int i = 0; i < nYPoints ; i++ )
773 double pointX = xMin;
775 for (
int j = 0; j < nXPoints; j++ )
777 x[( i * nXPoints ) + j] = pointX;
778 y[( i * nXPoints ) + j] = pointY;
780 z[( i * nXPoints ) + j] = 0.0;
791 transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
801 bool doHandle180Crossover =
false;
804 const double xMin = std::fmod( x[0], 180.0 );
805 const double xMax = std::fmod( x[nXPoints - 1], 180.0 );
806 if ( handle180Crossover
809 && xMin > 0.0 && xMin <= 180.0 && xMax < 0.0 && xMax >= -180.0 )
811 doHandle180Crossover =
true;
816 for (
int i = 0; i < nXPoints * nYPoints; i++ )
818 if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
823 if ( doHandle180Crossover )
837 throw QgsCsException( QObject::tr(
"Could not transform bounding box to target CRS" ) );
840 if ( doHandle180Crossover )
862 if ( !d->mIsValid || d->mShortCircuit )
865 if ( !d->mSourceCRS.isValid() )
868 "The coordinates can not be reprojected. The CRS is: %1" )
869 .arg( d->mSourceCRS.toProj() ), QObject::tr(
"CRS" ) );
872 if ( !d->mDestCRS.isValid() )
875 "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj() ), QObject::tr(
"CRS" ) );
879 std::vector< int > zNanPositions;
880 for (
int i = 0; i < numPoints; i++ )
882 if ( std::isnan( z[i] ) )
884 zNanPositions.push_back( i );
889 std::vector< double > xprev( numPoints );
890 memcpy( xprev.data(), x,
sizeof(
double ) * numPoints );
891 std::vector< double > yprev( numPoints );
892 memcpy( yprev.data(), y,
sizeof(
double ) * numPoints );
893 std::vector< double > zprev( numPoints );
894 memcpy( zprev.data(), z,
sizeof(
double ) * numPoints );
896 const bool useTime = !std::isnan( d->mDefaultTime );
897 std::vector< double > t( useTime ? numPoints : 0, d->mDefaultTime );
899#ifdef COORDINATE_TRANSFORM_VERBOSE
902 QgsDebugMsgLevel( QStringLiteral(
"[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ), 2 );
908 QgsDebugMsgLevel( QStringLiteral(
"No QgsCoordinateTransformContext context set for transform" ), 4 );
913 ProjData projData = d->threadLocalProjData();
917 proj_errno_reset( projData );
919 x,
sizeof( double ), numPoints,
920 y,
sizeof(
double ), numPoints,
921 z,
sizeof( double ), numPoints,
922 useTime ? t.data() :
nullptr,
sizeof( double ), useTime ? numPoints : 0 );
932 if ( numPoints == 1 )
934 projResult = proj_errno( projData );
935 actualRes = projResult;
939 actualRes = proj_errno( projData );
941 if ( actualRes == 0 )
945 if ( std::any_of( x, x + numPoints, [](
double v ) {
return std::isinf( v ); } )
946 || std::any_of( y, y + numPoints, [](
double v ) {
return std::isinf( v ); } )
947 || std::any_of( z, z + numPoints, [](
double v ) {
return std::isinf( v ); } ) )
953 mFallbackOperationOccurred =
false;
954 bool errorOccurredDuringFallbackOperation =
false;
956 && ( d->mAvailableOpCount > 1 || d->mAvailableOpCount == -1 )
957 && ( d->mAllowFallbackTransforms || mBallparkTransformsAreAppropriate ) )
960 if (
PJ *
transform = d->threadLocalFallbackProjData() )
964 memcpy( x, xprev.data(),
sizeof(
double ) * numPoints );
965 memcpy( y, yprev.data(),
sizeof(
double ) * numPoints );
966 memcpy( z, zprev.data(),
sizeof(
double ) * numPoints );
968 x,
sizeof( double ), numPoints,
969 y,
sizeof(
double ), numPoints,
970 z,
sizeof( double ), numPoints,
971 useTime ? t.data() :
nullptr,
sizeof( double ), useTime ? numPoints : 0 );
980 if ( numPoints == 1 )
986 errorOccurredDuringFallbackOperation = std::isinf( x[0] ) || std::isinf( y[0] ) || std::isinf( z[0] );
989 if ( !errorOccurredDuringFallbackOperation )
991 mFallbackOperationOccurred =
true;
994 if ( !mBallparkTransformsAreAppropriate && !mDisableFallbackHandler && sFallbackOperationOccurredHandler )
996 sFallbackOperationOccurredHandler( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation );
998 const QString warning = QStringLiteral(
"A fallback coordinate operation was used between %1 and %2" ).arg( d->mSourceCRS.authid(),
999 d->mDestCRS.authid() );
1000 qWarning(
"%s", warning.toLatin1().constData() );
1006 for (
const int &pos : zNanPositions )
1008 z[pos] = std::numeric_limits<double>::quiet_NaN();
1011 if ( projResult != 0 || errorOccurredDuringFallbackOperation )
1016 const QChar delim = numPoints > 1 ?
'\n' :
' ';
1017 for (
int i = 0; i < numPoints; ++i )
1019 points += QStringLiteral(
"(%1, %2)" ).arg( xprev[i], 0,
'f' ).arg( yprev[i], 0,
'f' ) + delim;
1025 const QString projError = !errorOccurredDuringFallbackOperation ? QString::fromUtf8( proj_context_errno_string( projContext, projResult ) ) : QObject::tr(
"Fallback transform failed" );
1027 const QString msg = QObject::tr(
"%1 (%2 to %3) of%4%5Error: %6" )
1036 if ( msg != mLastError )
1038 QgsDebugError(
"Projection failed emitting invalid transform signal: " + msg );
1046#ifdef COORDINATE_TRANSFORM_VERBOSE
1047 QgsDebugMsgLevel( QStringLiteral(
"[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
1048 .arg( xorg, 0,
'g', 15 ).arg( yorg, 0,
'g', 15 )
1049 .arg( *x, 0,
'g', 15 ).arg( *y, 0,
'g', 15 ), 2 );
1060 return !d->mIsValid || d->mShortCircuit;
1065 return d->mIsValid && d->mHasVerticalComponent;
1070 return d->mProjCoordinateOperation;
1075 ProjData projData = d->threadLocalProjData();
1082 d->mProjCoordinateOperation = operation;
1083 d->mShouldReverseCoordinateOperation =
false;
1089 d->mAllowFallbackTransforms = allowed;
1094 return d->mAllowFallbackTransforms;
1099 mBallparkTransformsAreAppropriate = appropriate;
1104 mDisableFallbackHandler = disabled;
1109 return mFallbackOperationOccurred;
1116 proj = QApplication::applicationDirPath()
1117 +
"/share/proj/" + QString( name );
1121 return proj.toUtf8();
1129 const QString sourceKey = src.
authid().isEmpty() ?
1131 const QString destKey = dest.
authid().isEmpty() ?
1134 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1138 if ( sDisableCache )
1141 const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
1142 for (
auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
1144 if ( ( *valIt ).coordinateOperation() == coordinateOperationProj
1145 && ( *valIt ).allowFallbackTransforms() == allowFallback
1153 const bool hasContext = mHasContext;
1160 mHasContext = hasContext;
1169void QgsCoordinateTransform::addToCache()
1171 if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
1174 const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
1176 const QString destKey = d->mDestCRS.authid().isEmpty() ?
1179 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1183 if ( sDisableCache )
1186 sTransforms.insert( qMakePair( sourceKey, destKey ), *
this );
1192 return d->mSourceDatumTransform;
1200 d->mSourceDatumTransform = dt;
1207 return d->mDestinationDatumTransform;
1215 d->mDestinationDatumTransform = dt;
1222 if ( sDisableCache )
1227 sDisableCache =
true;
1230 sTransforms.clear();
1233void QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread(
void *pj_context )
1239 if ( sDisableCache )
1244 if ( sDisableCache )
1247 for (
auto it = sTransforms.begin(); it != sTransforms.end(); )
1249 auto &v = it.value();
1250 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
1251 it = sTransforms.erase( it );
1261 const double distSourceUnits = std::sqrt( source1.
sqrDist( source2 ) );
1264 const double distDestUnits = std::sqrt( dest1.
sqrDist( dest2 ) );
1265 return distDestUnits / distSourceUnits;
1270 QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( handler );
1275 QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( handler );
1280 QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
1285 QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
1290 sFallbackOperationOccurredHandler = handler;
1295 QgsCoordinateTransformPrivate::setDynamicCrsToDynamicCrsWarningHandler( handler );
@ Geocentric
Geocentric CRS.
QFlags< CoordinateTransformationFlag > CoordinateTransformationFlags
Coordinate transformation flags.
@ Preferred
Preferred format, matching the most recent WKT ISO standard. Currently an alias to WKT2_2019,...
@ BallparkTransformsAreAppropriate
Indicates that approximate "ballpark" results are appropriate for this coordinate transform....
@ IgnoreImpossibleTransformations
Indicates that impossible transformations (such as those which attempt to transform between two diffe...
TransformDirection
Indicates the direction (forward or inverse) of a transform.
@ Forward
Forward transform (from source to destination)
@ Reverse
Reverse/inverse transform (from destination to source)
This class represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
QString toWkt(Qgis::CrsWktVariant variant=Qgis::CrsWktVariant::Wkt1Gdal, bool multiline=false, int indentationWidth=4) const
Returns a WKT representation of this CRS.
QString celestialBodyName() const
Attempts to retrieve the name of the celestial body associated with the CRS (e.g.
double coordinateEpoch() const
Returns the coordinate epoch, as a decimal year.
Contains information about the context in which a coordinate transform is executed.
Custom exception class for Coordinate Reference System related exceptions.
static void logMessage(const QString &message, const QString &tag=QString(), Qgis::MessageLevel level=Qgis::MessageLevel::Warning, bool notifyUser=true, const char *file=__builtin_FILE(), const char *function=__builtin_FUNCTION(), int line=__builtin_LINE())
Adds a message to the log instance (and creates it if necessary).
A class to represent a 2D point.
double sqrDist(double x, double y) const
Returns the squared distance between this point a specified x, y coordinate.
static PJ_CONTEXT * get()
Returns a thread local instance of a proj context, safe for use in the current thread.
static proj_pj_unique_ptr crsToHorizontalCrs(const PJ *crs)
Given a PROJ crs (which may be a compound or bound crs, or some other type), extract the horizontal c...
static bool hasVerticalAxis(const PJ *crs)
Returns true if a PROJ crs has a vertical axis.
std::unique_ptr< PJ, ProjPJDeleter > proj_pj_unique_ptr
Scoped Proj PJ object.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
QgsCoordinateTransformContext transformContext
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteL...
A rectangle specified with double values.
Q_INVOKABLE QString toString(int precision=16) const
Returns a string representation of form xmin,ymin : xmax,ymax Coordinates will be truncated to the sp...
void setXMinimum(double x)
Set the minimum x value.
void setXMaximum(double x)
Set the maximum x value.
void combineExtentWith(const QgsRectangle &rect)
Expands the rectangle so that it covers both the original rectangle and the given rectangle.
void setNull()
Mark a rectangle as being null (holding no spatial information).
Scoped object for temporary suppression of PROJ logging output.
Class for storage of 3D vectors similar to QVector3D, with the difference that it uses double precisi...
double y() const
Returns Y coordinate.
double z() const
Returns Z coordinate.
double x() const
Returns X coordinate.
#define Q_NOWARN_DEPRECATED_POP
#define Q_NOWARN_DEPRECATED_PUSH
bool qgsNanCompatibleEquals(double a, double b)
Compare two doubles, treating nan values as equal.
#define QgsDebugMsgLevel(str, level)
#define QgsDebugError(str)
const QgsCoordinateReferenceSystem & crs