void QgsCoordinateTransform::transformCoords( const int& numPoints, double *x, double *y, double *z, TransformDirection direction ) const { // Refuse to transform the points if the srs's are invalid if ( !mSourceCRS.isValid() ) { QgsMessageLog::logMessage( tr( "The source spatial reference system (CRS) is not valid. " "The coordinates can not be reprojected. The CRS is: %1" ) .arg( mSourceCRS.toProj4() ), tr( "CRS" ) ); return; } if ( !mDestCRS.isValid() ) { QgsMessageLog::logMessage( tr( "The destination spatial reference system (CRS) is not valid. " "The coordinates can not be reprojected. The CRS is: %1" ).arg( mDestCRS.toProj4() ), tr( "CRS" ) ); return; } #ifdef COORDINATE_TRANSFORM_VERBOSE double xorg = *x; double yorg = *y; QgsDebugMsg( QString( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ) ); #endif // use proj4 to do the transform QString dir; // if the source/destination projection is lat/long, convert the points to radians // prior to transforming if (( pj_is_latlong( mDestinationProjection ) && ( direction == ReverseTransform ) ) || ( pj_is_latlong( mSourceProjection ) && ( direction == ForwardTransform ) ) ) { for ( int i = 0; i < numPoints; ++i ) { x[i] *= DEG_TO_RAD; y[i] *= DEG_TO_RAD; z[i] *= DEG_TO_RAD; } } int projResult; if ( direction == ReverseTransform ) { projResult = pj_transform( mDestinationProjection, mSourceProjection, numPoints, 0, x, y, z ); } else { Q_ASSERT( mSourceProjection != 0 ); Q_ASSERT( mDestinationProjection != 0 ); projResult = pj_transform( mSourceProjection, mDestinationProjection, numPoints, 0, x, y, z ); } if ( projResult != 0 ) { //something bad happened.... QString points; for ( int i = 0; i < numPoints; ++i ) { if ( direction == ForwardTransform ) { points += QString( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' ); } else { points += QString( "(%1, %2)\n" ).arg( x[i] * RAD_TO_DEG, 0, 'f' ).arg( y[i] * RAD_TO_DEG, 0, 'f' ); } } dir = ( direction == ForwardTransform ) ? tr( "forward transform" ) : tr( "inverse transform" ); QString msg = tr( "%1 of\n" "%2" "PROJ.4: %3 +to %4\n" "Error: %5" ) .arg( dir ) .arg( points ) .arg( mSourceCRS.toProj4() ).arg( mDestCRS.toProj4() ) .arg( QString::fromUtf8( pj_strerrno( projResult ) ) ); QgsDebugMsg( "Projection failed emitting invalid transform signal: " + msg ); emit invalidTransformInput(); QgsDebugMsg( "throwing exception" ); throw QgsCsException( msg ); } // if the result is lat/long, convert the results from radians back // to degrees if (( pj_is_latlong( mDestinationProjection ) && ( direction == ForwardTransform ) ) || ( pj_is_latlong( mSourceProjection ) && ( direction == ReverseTransform ) ) ) { for ( int i = 0; i < numPoints; ++i ) { x[i] *= RAD_TO_DEG; y[i] *= RAD_TO_DEG; z[i] *= RAD_TO_DEG; } } #ifdef COORDINATE_TRANSFORM_VERBOSE QgsDebugMsg( QString( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" ) .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 ) .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ) ); #endif }
QgsRectangle QgsCoordinateTransform::transformBoundingBox( const QgsRectangle &rect, TransformDirection direction, const bool handle180Crossover ) const { // Calculate the bounding box of a QgsRectangle in the source CRS // when projected to the destination CRS (or the inverse). // This is done by looking at a number of points spread evenly // across the rectangle if ( !d->mIsValid || d->mShortCircuit ) return rect; if ( rect.isEmpty() ) { QgsPointXY p = transform( rect.xMinimum(), rect.yMinimum(), direction ); return QgsRectangle( p, p ); } // 64 points (<=2.12) is not enough, see #13665, for EPSG:4326 -> EPSG:3574 (say that it is a hard one), // are decent result from about 500 points and more. This method is called quite often, but // even with 1000 points it takes < 1ms // TODO: how to effectively and precisely reproject bounding box? const int nPoints = 1000; double d = std::sqrt( ( rect.width() * rect.height() ) / std::pow( std::sqrt( static_cast< double >( nPoints ) ) - 1, 2.0 ) ); int nXPoints = static_cast< int >( std::ceil( rect.width() / d ) ) + 1; int nYPoints = static_cast< int >( std::ceil( rect.height() / d ) ) + 1; QgsRectangle bb_rect; bb_rect.setMinimal(); // We're interfacing with C-style vectors in the // end, so let's do C-style vectors here too. QVector<double> x( nXPoints * nYPoints ); QVector<double> y( nXPoints * nYPoints ); QVector<double> z( nXPoints * nYPoints ); QgsDebugMsgLevel( QStringLiteral( "Entering transformBoundingBox..." ), 4 ); // Populate the vectors double dx = rect.width() / static_cast< double >( nXPoints - 1 ); double dy = rect.height() / static_cast< double >( nYPoints - 1 ); double pointY = rect.yMinimum(); for ( int i = 0; i < nYPoints ; i++ ) { // Start at right edge double pointX = rect.xMinimum(); for ( int j = 0; j < nXPoints; j++ ) { x[( i * nXPoints ) + j] = pointX; y[( i * nXPoints ) + j] = pointY; // and the height... z[( i * nXPoints ) + j] = 0.0; // QgsDebugMsg(QString("BBox coord: (%1, %2)").arg(x[(i*numP) + j]).arg(y[(i*numP) + j])); pointX += dx; } pointY += dy; } // Do transformation. Any exception generated must // be handled in above layers. try { transformCoords( nXPoints * nYPoints, x.data(), y.data(), z.data(), direction ); } catch ( const QgsCsException & ) { // rethrow the exception QgsDebugMsg( QStringLiteral( "rethrowing exception" ) ); throw; } // Calculate the bounding box and use that for the extent for ( int i = 0; i < nXPoints * nYPoints; i++ ) { if ( !std::isfinite( x[i] ) || !std::isfinite( y[i] ) ) { continue; } if ( handle180Crossover ) { //if crossing the date line, temporarily add 360 degrees to -ve longitudes bb_rect.combineExtentWith( x[i] >= 0.0 ? x[i] : x[i] + 360.0, y[i] ); } else { bb_rect.combineExtentWith( x[i], y[i] ); } } if ( bb_rect.isNull() ) { // something bad happened when reprojecting the filter rect... no finite points were left! throw QgsCsException( QObject::tr( "Could not transform bounding box to target CRS" ) ); } if ( handle180Crossover ) { //subtract temporary addition of 360 degrees from longitudes if ( bb_rect.xMinimum() > 180.0 ) bb_rect.setXMinimum( bb_rect.xMinimum() - 360.0 ); if ( bb_rect.xMaximum() > 180.0 ) bb_rect.setXMaximum( bb_rect.xMaximum() - 360.0 ); } QgsDebugMsgLevel( "Projected extent: " + bb_rect.toString(), 4 ); if ( bb_rect.isEmpty() ) { QgsDebugMsgLevel( "Original extent: " + rect.toString(), 4 ); } return bb_rect; }
void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, TransformDirection direction ) const { if ( !d->mIsValid || d->mShortCircuit ) return; // Refuse to transform the points if the srs's are invalid if ( !d->mSourceCRS.isValid() ) { QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. " "The coordinates can not be reprojected. The CRS is: %1" ) .arg( d->mSourceCRS.toProj4() ), QObject::tr( "CRS" ) ); return; } if ( !d->mDestCRS.isValid() ) { QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. " "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj4() ), QObject::tr( "CRS" ) ); return; } #ifdef COORDINATE_TRANSFORM_VERBOSE double xorg = *x; double yorg = *y; QgsDebugMsg( QStringLiteral( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ) ); #endif #ifdef QGISDEBUG if ( !mHasContext ) QgsDebugMsgLevel( QStringLiteral( "No QgsCoordinateTransformContext context set for transform" ), 4 ); #endif // use proj4 to do the transform // if the source/destination projection is lat/long, convert the points to radians // prior to transforming ProjData projData = d->threadLocalProjData(); #if PROJ_VERSION_MAJOR<6 bool sourceIsLatLong = false; bool destIsLatLong = false; projPJ sourceProj = projData.first; projPJ destProj = projData.second; sourceIsLatLong = pj_is_latlong( sourceProj ); destIsLatLong = pj_is_latlong( destProj ); if ( ( destIsLatLong && ( direction == ReverseTransform ) ) || ( sourceIsLatLong && ( direction == ForwardTransform ) ) ) { for ( int i = 0; i < numPoints; ++i ) { x[i] *= DEG_TO_RAD; y[i] *= DEG_TO_RAD; } } #endif int projResult = 0; #if PROJ_VERSION_MAJOR>=6 const bool sourceAxisOrderSwapped = direction == ForwardTransform ? d->mSourceAxisOrderSwapped : d->mDestAxisOrderSwapped; proj_trans_generic( projData, direction == ForwardTransform ? PJ_FWD : PJ_INV, !sourceAxisOrderSwapped ? x : y, sizeof( double ), numPoints, !sourceAxisOrderSwapped ? y : x, sizeof( double ), numPoints, z, sizeof( double ), numPoints, nullptr, sizeof( double ), 0 ); projResult = proj_errno( projData ); // ewww - this logic is gross. We should drop support for PROJ 6.0 as quickly as possible and dump this code (in favour of built in methods used for >=6.1 builds) if ( projResult == 0 && ( d->mSourceAxisOrderSwapped != d->mDestAxisOrderSwapped ) ) { size_t size = sizeof( double ) * numPoints; void *tmp = malloc( size ); memcpy( tmp, x, size ); memcpy( x, y, size ); memcpy( y, tmp, size ); free( tmp ); } #else if ( direction == ReverseTransform ) { projResult = pj_transform( destProj, sourceProj, numPoints, 0, x, y, z ); } else { Q_ASSERT( sourceProj ); Q_ASSERT( destProj ); projResult = pj_transform( sourceProj, destProj, numPoints, 0, x, y, z ); } #endif if ( projResult != 0 ) { //something bad happened.... QString points; for ( int i = 0; i < numPoints; ++i ) { if ( direction == ForwardTransform ) { points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' ); } else { #if PROJ_VERSION_MAJOR>=6 points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' ); #else points += QStringLiteral( "(%1, %2)\n" ).arg( x[i] * RAD_TO_DEG, 0, 'f' ).arg( y[i] * RAD_TO_DEG, 0, 'f' ); #endif } } QString dir = ( direction == ForwardTransform ) ? QObject::tr( "forward transform" ) : QObject::tr( "inverse transform" ); #if PROJ_VERSION_MAJOR>=6 QgsProjUtils::proj_pj_unique_ptr src( proj_get_source_crs( QgsProjContext::get(), projData ) ); QgsProjUtils::proj_pj_unique_ptr dest( proj_get_source_crs( QgsProjContext::get(), projData ) ); QString msg = QObject::tr( "%1 of\n" "%2" "PROJ: %3\n" "Error: %4" ) .arg( dir, points, proj_as_proj_string( QgsProjContext::get(), projData, PJ_PROJ_5, nullptr ), QString::fromUtf8( proj_errno_string( projResult ) ) ); #else char *srcdef = pj_get_def( sourceProj, 0 ); char *dstdef = pj_get_def( destProj, 0 ); QString msg = QObject::tr( "%1 of\n" "%2" "PROJ: %3 +to %4\n" "Error: %5" ) .arg( dir, points, srcdef, dstdef, QString::fromUtf8( pj_strerrno( projResult ) ) ); pj_dalloc( srcdef ); pj_dalloc( dstdef ); #endif QgsDebugMsg( "Projection failed emitting invalid transform signal: " + msg ); QgsDebugMsg( QStringLiteral( "throwing exception" ) ); throw QgsCsException( msg ); } #if PROJ_VERSION_MAJOR<6 // if the result is lat/long, convert the results from radians back // to degrees if ( ( destIsLatLong && ( direction == ForwardTransform ) ) || ( sourceIsLatLong && ( direction == ReverseTransform ) ) ) { for ( int i = 0; i < numPoints; ++i ) { x[i] *= RAD_TO_DEG; y[i] *= RAD_TO_DEG; } } #endif #ifdef COORDINATE_TRANSFORM_VERBOSE QgsDebugMsg( QStringLiteral( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" ) .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 ) .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ) ); #endif }
void QgsCoordinateTransform::transformCoords( int numPoints, double *x, double *y, double *z, TransformDirection direction ) const { if ( !d->mIsValid || d->mShortCircuit ) return; // Refuse to transform the points if the srs's are invalid if ( !d->mSourceCRS.isValid() ) { QgsMessageLog::logMessage( QObject::tr( "The source spatial reference system (CRS) is not valid. " "The coordinates can not be reprojected. The CRS is: %1" ) .arg( d->mSourceCRS.toProj4() ), QObject::tr( "CRS" ) ); return; } if ( !d->mDestCRS.isValid() ) { QgsMessageLog::logMessage( QObject::tr( "The destination spatial reference system (CRS) is not valid. " "The coordinates can not be reprojected. The CRS is: %1" ).arg( d->mDestCRS.toProj4() ), QObject::tr( "CRS" ) ); return; } #ifdef COORDINATE_TRANSFORM_VERBOSE double xorg = *x; double yorg = *y; QgsDebugMsg( QString( "[[[[[[ Number of points to transform: %1 ]]]]]]" ).arg( numPoints ) ); #endif // use proj4 to do the transform // if the source/destination projection is lat/long, convert the points to radians // prior to transforming QPair<projPJ, projPJ> projData = d->threadLocalProjData(); projPJ sourceProj = projData.first; projPJ destProj = projData.second; if ( ( pj_is_latlong( destProj ) && ( direction == ReverseTransform ) ) || ( pj_is_latlong( sourceProj ) && ( direction == ForwardTransform ) ) ) { for ( int i = 0; i < numPoints; ++i ) { x[i] *= DEG_TO_RAD; y[i] *= DEG_TO_RAD; } } int projResult; if ( direction == ReverseTransform ) { projResult = pj_transform( destProj, sourceProj, numPoints, 0, x, y, z ); } else { Q_ASSERT( sourceProj ); Q_ASSERT( destProj ); projResult = pj_transform( sourceProj, destProj, numPoints, 0, x, y, z ); } if ( projResult != 0 ) { //something bad happened.... QString points; for ( int i = 0; i < numPoints; ++i ) { if ( direction == ForwardTransform ) { points += QStringLiteral( "(%1, %2)\n" ).arg( x[i], 0, 'f' ).arg( y[i], 0, 'f' ); } else { points += QStringLiteral( "(%1, %2)\n" ).arg( x[i] * RAD_TO_DEG, 0, 'f' ).arg( y[i] * RAD_TO_DEG, 0, 'f' ); } } QString dir = ( direction == ForwardTransform ) ? QObject::tr( "forward transform" ) : QObject::tr( "inverse transform" ); char *srcdef = pj_get_def( sourceProj, 0 ); char *dstdef = pj_get_def( destProj, 0 ); QString msg = QObject::tr( "%1 of\n" "%2" "PROJ.4: %3 +to %4\n" "Error: %5" ) .arg( dir, points, srcdef, dstdef, QString::fromUtf8( pj_strerrno( projResult ) ) ); pj_dalloc( srcdef ); pj_dalloc( dstdef ); QgsDebugMsg( "Projection failed emitting invalid transform signal: " + msg ); QgsDebugMsg( "throwing exception" ); throw QgsCsException( msg ); } // if the result is lat/long, convert the results from radians back // to degrees if ( ( pj_is_latlong( destProj ) && ( direction == ForwardTransform ) ) || ( pj_is_latlong( sourceProj ) && ( direction == ReverseTransform ) ) ) { for ( int i = 0; i < numPoints; ++i ) { x[i] *= RAD_TO_DEG; y[i] *= RAD_TO_DEG; } } #ifdef COORDINATE_TRANSFORM_VERBOSE QgsDebugMsg( QString( "[[[[[[ Projected %1, %2 to %3, %4 ]]]]]]" ) .arg( xorg, 0, 'g', 15 ).arg( yorg, 0, 'g', 15 ) .arg( *x, 0, 'g', 15 ).arg( *y, 0, 'g', 15 ) ); #endif }