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 if ( ( projResult != 1
684 || !std::isfinite( transXMin )
685 || !std::isfinite( transXMax )
686 || !std::isfinite( transYMin )
687 || !std::isfinite( transYMax ) )
688 && ( d->mAvailableOpCount > 1 || d->mAvailableOpCount == -1 )
692 if (
PJ *
transform = d->threadLocalFallbackProjData() )
695 xMin, yMin, xMax, yMax,
696 &transXMin, &transYMin, &transXMax, &transYMax, DENSIFY_POINTS );
701 || !std::isfinite( transXMin )
702 || !std::isfinite( transXMax )
703 || !std::isfinite( transYMin )
704 || !std::isfinite( transYMax ) )
706 const QString projErr = QString::fromUtf8( proj_context_errno_string( projContext, proj_errno( projData ) ) );
708 const QString msg = QObject::tr(
"%1 (%2 to %3) of bounding box failed: %4" )
719 bool doHandle180Crossover =
false;
721 if ( handle180Crossover
724 && ( transXMax < transXMin ) )
727 std::swap( transXMax, transXMin );
732 doHandle180Crossover =
true;
735 QgsRectangle boundingBoxRect{ transXMin, transYMin, transXMax, transYMax };
736 if ( boundingBoxRect.isNull() )
739 throw QgsCsException( QObject::tr(
"Could not transform bounding box to target CRS" ) );
742 if ( doHandle180Crossover )
745 if ( boundingBoxRect.xMinimum() > 180.0 )
746 boundingBoxRect.setXMinimum( boundingBoxRect.xMinimum() - 360.0 );
747 if ( boundingBoxRect.xMaximum() > 180.0 )
748 boundingBoxRect.setXMaximum( boundingBoxRect.xMaximum() - 360.0 );
753 if ( boundingBoxRect.isEmpty() )
758 return boundingBoxRect;
766 const int nPoints = 1000;
767 const double dst = std::sqrt( ( rect.
width() * ( yMax - yMin ) ) / std::pow( std::sqrt(
static_cast< double >( nPoints ) ) - 1, 2.0 ) );
768 const int nXPoints = std::min(
static_cast< int >( std::ceil( rect.
width() / dst ) ) + 1, 1000 );
769 const int nYPoints = std::min(
static_cast< int >( std::ceil( ( yMax - yMin ) / dst ) ) + 1, 1000 );
774 std::vector<double> x( nXPoints *
static_cast< std::size_t
>( nYPoints ) );
775 std::vector<double> y( nXPoints *
static_cast< std::size_t
>( nYPoints ) );
776 std::vector<double> z( nXPoints *
static_cast< std::size_t
>( nYPoints ) );
778 QgsDebugMsgLevel( QStringLiteral(
"Entering transformBoundingBox..." ), 4 );
782 const double dx = rect.
width() /
static_cast< double >( nXPoints - 1 );
783 const double dy = ( yMax - yMin ) /
static_cast< double >( nYPoints - 1 );
785 double pointY = yMin;
787 for (
int i = 0; i < nYPoints ; i++ )
791 double pointX = xMin;
793 for (
int j = 0; j < nXPoints; j++ )
795 x[( i * nXPoints ) + j] = pointX;
796 y[( i * nXPoints ) + j] = pointY;
798 z[( i * nXPoints ) + j] = 0.0;
809 transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction );
819 bool doHandle180Crossover =
false;
822 const double xMin = std::fmod( x[0], 180.0 );
823 const double xMax = std::fmod( x[nXPoints - 1], 180.0 );
824 if ( handle180Crossover
827 && xMin > 0.0 && xMin <= 180.0 && xMax < 0.0 && xMax >= -180.0 )
829 doHandle180Crossover =
true;
834 for (
int i = 0; i < nXPoints * nYPoints; i++ )
836 if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) )
841 if ( doHandle180Crossover )
855 throw QgsCsException( QObject::tr(
"Could not transform bounding box to target CRS" ) );
858 if ( doHandle180Crossover )
880 if ( !d->mIsValid || d->mShortCircuit )
883 if ( !d->mSourceCRS.isValid() )
886 "The coordinates can not be reprojected. The CRS is: %1" )
887 .arg( d->mSourceCRS.toProj() ), QObject::tr(
"CRS" ) );
890 if ( !d->mDestCRS.isValid() )
893 "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj() ), QObject::tr(
"CRS" ) );
897 std::vector< int > zNanPositions;
898 for (
int i = 0; i < numPoints; i++ )
900 if ( std::isnan( z[i] ) )
902 zNanPositions.push_back( i );
907 std::vector< double > xprev( numPoints );
908 memcpy( xprev.data(), x,
sizeof(
double ) * numPoints );
909 std::vector< double > yprev( numPoints );
910 memcpy( yprev.data(), y,
sizeof(
double ) * numPoints );
911 std::vector< double > zprev( numPoints );
912 memcpy( zprev.data(), z,
sizeof(
double ) * numPoints );
914 const bool useTime = !std::isnan( d->mDefaultTime );
915 std::vector< double > t( useTime ? numPoints : 0, d->mDefaultTime );
917#ifdef COORDINATE_TRANSFORM_VERBOSE
920 QgsDebugMsgLevel( QStringLiteral(
"[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ), 2 );
926 QgsDebugMsgLevel( QStringLiteral(
"No QgsCoordinateTransformContext context set for transform" ), 4 );
931 ProjData projData = d->threadLocalProjData();
935 proj_errno_reset( projData );
937 x,
sizeof( double ), numPoints,
938 y,
sizeof(
double ), numPoints,
939 z,
sizeof( double ), numPoints,
940 useTime ? t.data() :
nullptr,
sizeof( double ), useTime ? numPoints : 0 );
950 if ( numPoints == 1 )
952 projResult = proj_errno( projData );
953 actualRes = projResult;
957 actualRes = proj_errno( projData );
959 if ( actualRes == 0 )
963 if ( std::any_of( x, x + numPoints, [](
double v ) {
return std::isinf( v ); } )
964 || std::any_of( y, y + numPoints, [](
double v ) {
return std::isinf( v ); } )
965 || std::any_of( z, z + numPoints, [](
double v ) {
return std::isinf( v ); } ) )
971 mFallbackOperationOccurred =
false;
972 bool errorOccurredDuringFallbackOperation =
false;
974 && ( d->mAvailableOpCount > 1 || d->mAvailableOpCount == -1 )
975 && ( d->mAllowFallbackTransforms || mBallparkTransformsAreAppropriate ) )
978 if (
PJ *
transform = d->threadLocalFallbackProjData() )
982 memcpy( x, xprev.data(),
sizeof(
double ) * numPoints );
983 memcpy( y, yprev.data(),
sizeof(
double ) * numPoints );
984 memcpy( z, zprev.data(),
sizeof(
double ) * numPoints );
986 x,
sizeof( double ), numPoints,
987 y,
sizeof(
double ), numPoints,
988 z,
sizeof( double ), numPoints,
989 useTime ? t.data() :
nullptr,
sizeof( double ), useTime ? numPoints : 0 );
998 if ( numPoints == 1 )
1004 errorOccurredDuringFallbackOperation = std::isinf( x[0] ) || std::isinf( y[0] ) || std::isinf( z[0] );
1007 if ( !errorOccurredDuringFallbackOperation )
1009 mFallbackOperationOccurred =
true;
1012 if ( !mBallparkTransformsAreAppropriate && !mDisableFallbackHandler && sFallbackOperationOccurredHandler )
1014 sFallbackOperationOccurredHandler( d->mSourceCRS, d->mDestCRS, d->mProjCoordinateOperation );
1016 const QString warning = QStringLiteral(
"A fallback coordinate operation was used between %1 and %2" ).arg( d->mSourceCRS.authid(),
1017 d->mDestCRS.authid() );
1018 qWarning(
"%s", warning.toLatin1().constData() );
1024 for (
const int &pos : zNanPositions )
1026 z[pos] = std::numeric_limits<double>::quiet_NaN();
1029 if ( projResult != 0 || errorOccurredDuringFallbackOperation )
1034 const QChar delim = numPoints > 1 ?
'\n' :
' ';
1035 for (
int i = 0; i < numPoints; ++i )
1037 points += QStringLiteral(
"(%1, %2)" ).arg( xprev[i], 0,
'f' ).arg( yprev[i], 0,
'f' ) + delim;
1043 const QString projError = !errorOccurredDuringFallbackOperation ? QString::fromUtf8( proj_context_errno_string( projContext, projResult ) ) : QObject::tr(
"Fallback transform failed" );
1045 const QString msg = QObject::tr(
"%1 (%2 to %3) of%4%5Error: %6" )
1054 if ( msg != mLastError )
1056 QgsDebugError(
"Projection failed emitting invalid transform signal: " + msg );
1064#ifdef COORDINATE_TRANSFORM_VERBOSE
1065 QgsDebugMsgLevel( QStringLiteral(
"[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" )
1066 .arg( xorg, 0,
'g', 15 ).arg( yorg, 0,
'g', 15 )
1067 .arg( *x, 0,
'g', 15 ).arg( *y, 0,
'g', 15 ), 2 );
1078 return !d->mIsValid || d->mShortCircuit;
1083 return d->mIsValid && d->mHasVerticalComponent;
1088 return d->mProjCoordinateOperation;
1093 ProjData projData = d->threadLocalProjData();
1100 d->mProjCoordinateOperation = operation;
1101 d->mShouldReverseCoordinateOperation =
false;
1107 d->mAllowFallbackTransforms = allowed;
1112 return d->mAllowFallbackTransforms;
1117 mBallparkTransformsAreAppropriate = appropriate;
1122 mDisableFallbackHandler = disabled;
1127 return mFallbackOperationOccurred;
1134 proj = QApplication::applicationDirPath()
1135 +
"/share/proj/" + QString( name );
1139 return proj.toUtf8();
1147 const QString sourceKey = src.
authid().isEmpty() ?
1149 const QString destKey = dest.
authid().isEmpty() ?
1152 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1156 if ( sDisableCache )
1159 const QList< QgsCoordinateTransform > values = sTransforms.values( qMakePair( sourceKey, destKey ) );
1160 for (
auto valIt = values.constBegin(); valIt != values.constEnd(); ++valIt )
1162 if ( ( *valIt ).coordinateOperation() == coordinateOperationProj
1163 && ( *valIt ).allowFallbackTransforms() == allowFallback
1171 const bool hasContext = mHasContext;
1178 mHasContext = hasContext;
1187void QgsCoordinateTransform::addToCache()
1189 if ( !d->mSourceCRS.isValid() || !d->mDestCRS.isValid() )
1192 const QString sourceKey = d->mSourceCRS.authid().isEmpty() ?
1194 const QString destKey = d->mDestCRS.authid().isEmpty() ?
1197 if ( sourceKey.isEmpty() || destKey.isEmpty() )
1201 if ( sDisableCache )
1204 sTransforms.insert( qMakePair( sourceKey, destKey ), *
this );
1210 return d->mSourceDatumTransform;
1218 d->mSourceDatumTransform = dt;
1225 return d->mDestinationDatumTransform;
1233 d->mDestinationDatumTransform = dt;
1240 if ( sDisableCache )
1245 sDisableCache =
true;
1248 sTransforms.clear();
1251void QgsCoordinateTransform::removeFromCacheObjectsBelongingToCurrentThread(
void *pj_context )
1257 if ( sDisableCache )
1262 if ( sDisableCache )
1265 for (
auto it = sTransforms.begin(); it != sTransforms.end(); )
1267 auto &v = it.value();
1268 if ( v.d->removeObjectsBelongingToCurrentThread( pj_context ) )
1269 it = sTransforms.erase( it );
1279 const double distSourceUnits = std::sqrt( source1.
sqrDist( source2 ) );
1282 const double distDestUnits = std::sqrt( dest1.
sqrDist( dest2 ) );
1283 return distDestUnits / distSourceUnits;
1288 QgsCoordinateTransformPrivate::setCustomMissingRequiredGridHandler( handler );
1293 QgsCoordinateTransformPrivate::setCustomMissingPreferredGridHandler( handler );
1298 QgsCoordinateTransformPrivate::setCustomCoordinateOperationCreationErrorHandler( handler );
1303 QgsCoordinateTransformPrivate::setCustomMissingGridUsedByContextHandler( handler );
1308 sFallbackOperationOccurredHandler = handler;
1313 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