diff --git a/src/app/georeferencer/qgsgcpcanvasitem.cpp b/src/app/georeferencer/qgsgcpcanvasitem.cpp index 846fe942550a..2175d0f97454 100644 --- a/src/app/georeferencer/qgsgcpcanvasitem.cpp +++ b/src/app/georeferencer/qgsgcpcanvasitem.cpp @@ -54,7 +54,7 @@ void QgsGCPCanvasItem::paint( QPainter *p ) if ( mDataPoint ) { enabled = mDataPoint->isEnabled(); - worldCoords = mDataPoint->canvasCoords(); + worldCoords = mDataPoint->destinationPoint(); id = mDataPoint->id(); } p->setOpacity( enabled ? 1.0 : 0.3 ); @@ -163,17 +163,21 @@ void QgsGCPCanvasItem::updatePosition() if ( mIsGCPSource ) { - setPos( toCanvasCoordinates( mDataPoint->pixelCoords() ) ); - return; + setPos( toCanvasCoordinates( mDataPoint->sourcePoint() ) ); } - if ( mDataPoint->canvasCoords().isEmpty() ) + else { - const QgsCoordinateReferenceSystem mapCrs = mMapCanvas->mapSettings().destinationCrs(); - const QgsCoordinateTransform transf( mDataPoint->crs(), mapCrs, QgsProject::instance() ); - const QgsPointXY mapCoords = transf.transform( mDataPoint->mapCoords() ); - mDataPoint->setCanvasCoords( mapCoords ); + const QgsCoordinateTransform pointToCanvasTransform( mDataPoint->destinationPointCrs(), mMapCanvas->mapSettings().destinationCrs(), QgsProject::instance() ); + try + { + const QgsPointXY canvasMapCoords = pointToCanvasTransform.transform( mDataPoint->destinationPoint() ); + const QPointF canvasCoordinatesInPixels = toCanvasCoordinates( canvasMapCoords ); + + setPos( canvasCoordinatesInPixels ); + } + catch ( QgsCsException & ) + {} } - setPos( toCanvasCoordinates( mDataPoint->canvasCoords() ) ); } void QgsGCPCanvasItem::drawResidualArrow( QPainter *p, const QgsRenderContext &context ) @@ -216,7 +220,7 @@ double QgsGCPCanvasItem::residualToScreenFactor() const } } - return 1.0 / ( mapUnitsPerScreenPixel * mapUnitsPerRasterPixel ); + return mapUnitsPerRasterPixel / mapUnitsPerScreenPixel; } void QgsGCPCanvasItem::checkBoundingRectChange() diff --git a/src/app/georeferencer/qgsgcplist.cpp b/src/app/georeferencer/qgsgcplist.cpp index c607fd85d078..9897e482e9fb 100644 --- a/src/app/georeferencer/qgsgcplist.cpp +++ b/src/app/georeferencer/qgsgcplist.cpp @@ -18,83 +18,232 @@ #include "qgscoordinatereferencesystem.h" #include "qgscoordinatetransform.h" #include "qgsproject.h" +#include "qgsgeoreftransform.h" #include "qgsgcplist.h" +#include +#include -QgsGCPList::QgsGCPList( const QgsGCPList &list ) - : QList() +void QgsGCPList::createGCPVectors( QVector &sourcePoints, QVector &destinationPoints, const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) const { - clear(); - QgsGCPList::const_iterator it = list.constBegin(); - for ( ; it != list.constEnd(); ++it ) + const int targetSize = countEnabledPoints(); + sourcePoints.clear(); + sourcePoints.reserve( targetSize ); + destinationPoints.clear(); + destinationPoints.reserve( targetSize ); + + for ( const QgsGeorefDataPoint *pt : std::as_const( *this ) ) { - QgsGeorefDataPoint *pt = new QgsGeorefDataPoint( **it ); - append( pt ); + if ( !pt->isEnabled() ) + continue; + + sourcePoints.push_back( pt->sourcePoint() ); + if ( targetCrs.isValid() ) + { + destinationPoints.push_back( pt->transformedDestinationPoint( targetCrs, context ) ); + } + else + { + destinationPoints.push_back( pt->destinationPoint() ); + } + } +} + +int QgsGCPList::countEnabledPoints() const +{ + if ( isEmpty() ) + return 0; + + int s = 0; + const_iterator it = begin(); + while ( it != end() ) + { + if ( ( *it )->isEnabled() ) + s++; + ++it; } + return s; } -void QgsGCPList::createGCPVectors( QVector &mapCoords, QVector &pixelCoords, const QgsCoordinateReferenceSystem targetCrs ) +void QgsGCPList::updateResiduals( QgsGeorefTransform *georefTransform, const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context, QgsUnitTypes::RenderUnit residualUnit ) { - mapCoords = QVector( size() ); - pixelCoords = QVector( size() ); - QgsPointXY transCoords; - for ( int i = 0, j = 0; i < sizeAll(); i++ ) + bool bTransformUpdated = false; + QVector sourceCoordinates; + QVector destinationCoordinates; + createGCPVectors( sourceCoordinates, destinationCoordinates, targetCrs, context ); + + if ( georefTransform ) { - QgsGeorefDataPoint *pt = at( i ); - if ( pt->isEnabled() ) + bTransformUpdated = georefTransform->updateParametersFromGcps( sourceCoordinates, destinationCoordinates, true ); + } + + // update residuals + for ( int i = 0; i < size(); ++i ) + { + QgsGeorefDataPoint *p = at( i ); + + if ( !p ) + continue; + + p->setId( i ); + + const QgsPointXY transformedDestinationPoint = p->transformedDestinationPoint( targetCrs, QgsProject::instance()->transformContext() ); + + double dX = 0; + double dY = 0; + // Calculate residual if transform is available and up-to-date + if ( georefTransform && bTransformUpdated && georefTransform->parametersInitialized() ) { - if ( targetCrs.isValid() ) + QgsPointXY dst; + const QgsPointXY pixel = georefTransform->toSourcePixel( p->sourcePoint() ); + if ( residualUnit == QgsUnitTypes::RenderPixels ) { - try + // Transform from world to raster coordinate: + // This is the transform direction used by the warp operation. + // As transforms of order >=2 are not invertible, we are only + // interested in the residual in this direction + if ( georefTransform->transformWorldToRaster( transformedDestinationPoint, dst ) ) { - transCoords = QgsCoordinateTransform( pt->crs(), targetCrs, - QgsProject::instance() ).transform( pt->mapCoords() ); - mapCoords[j] = transCoords; - pt->setTransCoords( transCoords ); + dX = ( dst.x() - pixel.x() ); + dY = -( dst.y() - pixel.y() ); } - catch ( const QgsException &e ) + } + else if ( residualUnit == QgsUnitTypes::RenderMapUnits ) + { + if ( georefTransform->transformRasterToWorld( pixel, dst ) ) { - Q_UNUSED( e ); - mapCoords[j] = pt->mapCoords(); + dX = ( dst.x() - transformedDestinationPoint.x() ); + dY = ( dst.y() - transformedDestinationPoint.y() ); } } - else - mapCoords[j] = pt->mapCoords(); - pixelCoords[j] = pt->pixelCoords(); - j++; } + p->setResidual( QPointF( dX, dY ) ); } } -int QgsGCPList::size() const +QList QgsGCPList::asPoints() const { - if ( QList::isEmpty() ) - return 0; - - int s = 0; - const_iterator it = begin(); - while ( it != end() ) + QList res; + res.reserve( size() ); + for ( QgsGeorefDataPoint *pt : *this ) { - if ( ( *it )->isEnabled() ) - s++; - ++it; + res.append( QgsGcpPoint( pt->point() ) ); } - return s; + return res; } -int QgsGCPList::sizeAll() const +bool QgsGCPList::saveGcps( const QString &filePath, const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context, QString &error ) const { - return QList::size(); + error.clear(); + + QFile pointFile( filePath ); + if ( pointFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) + { + QTextStream points( &pointFile ); + if ( targetCrs.isValid() ) + { + points << QStringLiteral( "#CRS: %1" ).arg( targetCrs.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) ); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + points << endl; +#else + points << Qt::endl; +#endif + } + + points << "mapX,mapY,sourceX,sourceY,enable,dX,dY,residual"; +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + points << endl; +#else + points << Qt::endl; +#endif + + for ( QgsGeorefDataPoint *pt : *this ) + { + const QgsPointXY transformedDestinationPoint = pt->transformedDestinationPoint( targetCrs, context ); + points << QStringLiteral( "%1,%2,%3,%4,%5,%6,%7,%8" ) + .arg( qgsDoubleToString( transformedDestinationPoint.x() ), + qgsDoubleToString( transformedDestinationPoint.y() ), + qgsDoubleToString( pt->sourcePoint().x() ), + qgsDoubleToString( pt->sourcePoint().y() ) ) + .arg( pt->isEnabled() ) + .arg( qgsDoubleToString( pt->residual().x() ), + qgsDoubleToString( pt->residual().y() ), + qgsDoubleToString( std::sqrt( pt->residual().x() * pt->residual().x() + pt->residual().y() * pt->residual().y() ) ) ); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + points << endl; +#else + points << Qt::endl; +#endif + } + return true; + } + else + { + error = QObject::tr( "Could not write to GCP points file %1." ).arg( QDir::toNativeSeparators( filePath ) ); + return false; + } } -QgsGCPList &QgsGCPList::operator =( const QgsGCPList &list ) +QList QgsGCPList::loadGcps( const QString &filePath, const QgsCoordinateReferenceSystem &defaultDestinationCrs, QgsCoordinateReferenceSystem &actualDestinationCrs, QString &error ) { - clear(); - QgsGCPList::const_iterator it = list.constBegin(); - for ( ; it != list.constEnd(); ++it ) + error.clear(); + QFile pointFile( filePath ); + if ( !pointFile.open( QIODevice::ReadOnly ) ) + { + error = QObject::tr( "Could not open GCP points file %1." ).arg( QDir::toNativeSeparators( filePath ) ); + return {}; + } + + QTextStream points( &pointFile ); + int lineNumber = 0; + QString line = points.readLine(); + lineNumber++; + + int i = 0; + if ( line.contains( QLatin1String( "#CRS: " ) ) ) { - QgsGeorefDataPoint *pt = new QgsGeorefDataPoint( **it ); - append( pt ); + const QString crsDef = line.remove( QStringLiteral( "#CRS: " ) ); + if ( !crsDef.trimmed().isEmpty() ) + { + actualDestinationCrs = QgsCoordinateReferenceSystem( crsDef ); + } + else + { + actualDestinationCrs = defaultDestinationCrs; + } + line = points.readLine(); + lineNumber++; + } + else + actualDestinationCrs = defaultDestinationCrs; + + QList res; + while ( !points.atEnd() ) + { + line = points.readLine(); + lineNumber++; + QStringList ls; + if ( line.contains( ',' ) ) // in previous format "\t" is delimiter of points in new - "," + ls = line.split( ',' ); // points from new georeferencer + else + ls = line.split( '\t' ); // points from prev georeferencer + + if ( ls.count() < 4 ) + { + error = QObject::tr( "Malformed content at line %1" ).arg( lineNumber ); + return {}; + } + + const QgsPointXY destinationPoint( ls.at( 0 ).toDouble(), ls.at( 1 ).toDouble() ); // map x,y + const QgsPointXY sourcePoint( ls.at( 2 ).toDouble(), ls.at( 3 ).toDouble() ); // source x,y + bool enable = true; + if ( ls.count() >= 5 ) + { + enable = ls.at( 4 ).toInt(); + } + res.append( QgsGcpPoint( sourcePoint, destinationPoint, actualDestinationCrs, enable ) ); + + ++i; } - return *this; + return res; } diff --git a/src/app/georeferencer/qgsgcplist.h b/src/app/georeferencer/qgsgcplist.h index 8b913993d272..6662a95e97e2 100644 --- a/src/app/georeferencer/qgsgcplist.h +++ b/src/app/georeferencer/qgsgcplist.h @@ -18,23 +18,86 @@ #include #include +#include "qgis_app.h" +#include "qgsunittypes.h" class QgsGeorefDataPoint; +class QgsGcpPoint; class QgsPointXY; class QgsCoordinateReferenceSystem; +class QgsCoordinateTransformContext; +class QgsGeorefTransform; -// what is better use inherid or agrigate QList? -class QgsGCPList : public QList +/** + * A container for GCP data points. + * + * The container does NOT own the points -- they have to be manually deleted elsewhere!! + */ +class APP_EXPORT QgsGCPList : public QList { public: QgsGCPList() = default; - QgsGCPList( const QgsGCPList &list ); + QgsGCPList( const QgsGCPList &list ) = delete; + QgsGCPList &operator =( const QgsGCPList &list ) = delete; - void createGCPVectors( QVector &mapCoords, QVector &pixelCoords, const QgsCoordinateReferenceSystem targetCrs ); - int size() const; - int sizeAll() const; + /** + * Creates vectors of source and destination points, where the destination points are all transformed to the + * specified \a targetCrs. + */ + void createGCPVectors( QVector &sourcePoints, QVector &destinationPoints, + const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) const; + + /** + * Returns the count of currently enabled data points. + */ + int countEnabledPoints() const; + + /** + * Updates the stored residual sizes for points in the list. + * + * \param georefTransform transformation to use for residual calculation + * \param targetCrs georeference output CRS + * \param context transform context + * \param residualUnit units for residual calculation. Supported values are QgsUnitTypes::RenderPixels or QgsUnitTypes::RenderMapUnits + */ + void updateResiduals( QgsGeorefTransform *georefTransform, + const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context, + QgsUnitTypes::RenderUnit residualUnit ); + + /** + * Returns the container as a list of GCP points. + */ + QList< QgsGcpPoint > asPoints() const; + + /** + * Saves the GCPs to a text file. + * + * \param filePath destination file path + * \param targetCrs target CRS for destination points + * \param context transform context + * \param error will be set to a descriptive error message if saving fails + * + * \returns TRUE on success + */ + bool saveGcps( const QString &filePath, + const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context, + QString &error ) const; + + /** + * Loads GCPs from a text file. + * + * \param filePath source file path + * \param defaultDestinationCrs default CRS to use for destination points if no destination CRS information is present in text file. + * \param actualDestinationCrs will be set to actual destination CRS for points, which is either the CRS information from the text file OR the defaultDestinationCrs + * \param error will be set to a descriptive error message if loading fails + * + * \returns TRUE on success + */ + static QList< QgsGcpPoint > loadGcps( const QString &filePath, + const QgsCoordinateReferenceSystem &defaultDestinationCrs, + QgsCoordinateReferenceSystem &actualDestinationCrs, + QString &error ); - QgsGCPList &operator =( const QgsGCPList &list ); }; #endif diff --git a/src/app/georeferencer/qgsgcplistmodel.cpp b/src/app/georeferencer/qgsgcplistmodel.cpp index ce457d6445bb..3b991742544f 100644 --- a/src/app/georeferencer/qgsgcplistmodel.cpp +++ b/src/app/georeferencer/qgsgcplistmodel.cpp @@ -19,185 +19,428 @@ #include "qgsgeorefdatapoint.h" #include "qgsgeoreftransform.h" #include "qgssettings.h" +#include "qgsproject.h" #include -class QgsStandardItem : public QStandardItem -{ - public: - explicit QgsStandardItem( const QString &text ) : QStandardItem( text ) - { - // In addition to the DisplayRole, also set the user role, which is used for sorting. - // This is needed for numerical sorting to work correctly (otherwise sorting is lexicographic). - setData( QVariant( text ), Qt::UserRole ); - setTextAlignment( Qt::AlignRight ); - } - - explicit QgsStandardItem( int value ) : QStandardItem( QString::number( value ) ) - { - setData( QVariant( value ), Qt::UserRole ); - setTextAlignment( Qt::AlignCenter ); - } - - explicit QgsStandardItem( double value ) : QStandardItem( QString::number( value, 'f', 4 ) ) - { - setData( QVariant( value ), Qt::UserRole ); - //show the full precision when editing points - setData( QVariant( value ), Qt::EditRole ); - setData( QVariant( value ), Qt::ToolTipRole ); - setTextAlignment( Qt::AlignRight ); - } -}; - QgsGCPListModel::QgsGCPListModel( QObject *parent ) - : QStandardItemModel( parent ) + : QAbstractTableModel( parent ) { - // Use data provided by Qt::UserRole as sorting key (needed for numerical sorting). - setSortRole( Qt::UserRole ); } void QgsGCPListModel::setGCPList( QgsGCPList *theGCPList ) { + beginResetModel(); mGCPList = theGCPList; - updateModel(); + endResetModel(); + updateResiduals(); } // ------------------------------- public ---------------------------------- // void QgsGCPListModel::setGeorefTransform( QgsGeorefTransform *georefTransform ) { mGeorefTransform = georefTransform; - updateModel(); + updateResiduals(); } -void QgsGCPListModel::updateModel() +void QgsGCPListModel::setTargetCrs( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) { + mTargetCrs = targetCrs; + mTransformContext = context; + updateResiduals(); + emit dataChanged( index( 0, static_cast< int >( Column::DestinationX ) ), + index( rowCount() - 1, static_cast< int >( Column::DestinationY ) ) ); +} - //clear(); - if ( !mGCPList ) - return; +int QgsGCPListModel::rowCount( const QModelIndex & ) const +{ + return mGCPList ? mGCPList->size() : 0; +} - bool bTransformUpdated = false; - // // Setup table header - QStringList itemLabels; - QString unitType; - const QgsSettings s; - bool mapUnitsPossible = false; - QVector mapCoords, pixelCoords; +int QgsGCPListModel::columnCount( const QModelIndex & ) const +{ + return static_cast< int >( Column::LastColumn ); +} - mGCPList->createGCPVectors( mapCoords, pixelCoords, - QgsCoordinateReferenceSystem( s.value( QStringLiteral( "/Plugin-GeoReferencer/targetsrs" ) ).toString() ) ); +QVariant QgsGCPListModel::data( const QModelIndex &index, int role ) const +{ + if ( !mGCPList + || index.row() < 0 + || index.row() >= mGCPList->size() + || index.column() < 0 + || index.column() >= columnCount() ) + return QVariant(); - if ( mGeorefTransform ) - { - bTransformUpdated = mGeorefTransform->updateParametersFromGcps( pixelCoords, mapCoords, true ); - mapUnitsPossible = mGeorefTransform->providesAccurateInverseTransformation(); - } + const Column column = static_cast< Column >( index.column() ); - if ( s.value( QStringLiteral( "/Plugin-GeoReferencer/Config/ResidualUnits" ) ) == "mapUnits" && mapUnitsPossible ) - { - unitType = tr( "map units" ); - } - else + const QgsGeorefDataPoint *point = mGCPList->at( index.row() ); + switch ( role ) { - unitType = tr( "pixels" ); - } + case Qt::DisplayRole: + case Qt::EditRole: + case Qt::UserRole: + case Qt::ToolTipRole: + switch ( column ) + { + case QgsGCPListModel::Column::Enabled: + break; + case QgsGCPListModel::Column::ID: + return index.row(); - itemLabels << tr( "Visible" ) - << tr( "ID" ) - << tr( "Source X" ) - << tr( "Source Y" ) - << tr( "Dest. X" ) - << tr( "Dest. Y" ) - << tr( "dX (%1)" ).arg( unitType ) - << tr( "dY (%1)" ).arg( unitType ) - << tr( "Residual (%1)" ).arg( unitType ); + case QgsGCPListModel::Column::SourceX: + case QgsGCPListModel::Column::SourceY: + { + switch ( role ) + { + case Qt::EditRole: + case Qt::UserRole: + // use full precision + return column == QgsGCPListModel::Column::SourceX ? point->sourcePoint().x() : point->sourcePoint().y(); - setHorizontalHeaderLabels( itemLabels ); - setRowCount( mGCPList->size() ); + case Qt::ToolTipRole: + case Qt::DisplayRole: + // truncate decimals for display + return formatNumber( column == QgsGCPListModel::Column::SourceX ? point->sourcePoint().x() : point->sourcePoint().y() ); + default: + break; + } + break; + } - for ( int i = 0; i < mGCPList->sizeAll(); ++i ) - { - int j = 0; - QgsGeorefDataPoint *p = mGCPList->at( i ); - - if ( !p ) - continue; - - p->setId( i ); - - QStandardItem *si = new QStandardItem(); - si->setTextAlignment( Qt::AlignCenter ); - si->setCheckable( true ); - if ( p->isEnabled() ) - si->setCheckState( Qt::Checked ); - else - si->setCheckState( Qt::Unchecked ); - - setItem( i, j++, si ); - setItem( i, j++, new QgsStandardItem( i ) ); - setItem( i, j++, new QgsStandardItem( p->pixelCoords().x() ) ); - setItem( i, j++, new QgsStandardItem( p->pixelCoords().y() ) ); - setItem( i, j++, new QgsStandardItem( p->transCoords().x() ) ); - setItem( i, j++, new QgsStandardItem( p->transCoords().y() ) ); - - double residual; - double dX = 0; - double dY = 0; - // Calculate residual if transform is available and up-to-date - if ( mGeorefTransform && bTransformUpdated && mGeorefTransform->parametersInitialized() ) - { - QgsPointXY dst; - const QgsPointXY pixel = mGeorefTransform->hasCrs() ? mGeorefTransform->toColumnLine( p->pixelCoords() ) : p->pixelCoords(); - if ( unitType == tr( "pixels" ) ) - { - // Transform from world to raster coordinate: - // This is the transform direction used by the warp operation. - // As transforms of order >=2 are not invertible, we are only - // interested in the residual in this direction - if ( mGeorefTransform->transformWorldToRaster( p->transCoords(), dst ) ) + case QgsGCPListModel::Column::DestinationX: + case QgsGCPListModel::Column::DestinationY: { - dX = ( dst.x() - pixel.x() ); - dY = -( dst.y() - pixel.y() ); + const QgsPointXY transformedDestinationPoint = point->transformedDestinationPoint( mTargetCrs, mTransformContext ); + + switch ( role ) + { + case Qt::ToolTipRole: + { + const QString crsString = mTargetCrs.userFriendlyIdentifier(); + const double value = column == QgsGCPListModel::Column::DestinationX ? transformedDestinationPoint.x() : transformedDestinationPoint.y(); + return QStringLiteral( "%1
%2" ).arg( formatNumber( value ), crsString ); + } + + case Qt::EditRole: + case Qt::UserRole: + // use full precision + return column == QgsGCPListModel::Column::DestinationX ? transformedDestinationPoint.x() : transformedDestinationPoint.y(); + + case Qt::DisplayRole: + // truncate decimals for display + return formatNumber( column == QgsGCPListModel::Column::DestinationX ? transformedDestinationPoint.x() : transformedDestinationPoint.y() ); + default: + break; + } + break; } - } - else if ( unitType == tr( "map units" ) ) - { - if ( mGeorefTransform->transformRasterToWorld( pixel, dst ) ) + + case QgsGCPListModel::Column::ResidualDx: + case QgsGCPListModel::Column::ResidualDy: + case QgsGCPListModel::Column::TotalResidual: { - dX = ( dst.x() - p->transCoords().x() ); - dY = ( dst.y() - p->transCoords().y() ); + const double dX = point->residual().x(); + const double dY = point->residual().y(); + const double residual = std::sqrt( dX * dX + dY * dY ); + if ( !qgsDoubleNear( residual, 0 ) ) + { + double value = 0; + switch ( column ) + { + case QgsGCPListModel::Column::ResidualDx: + value = dX; + break; + case QgsGCPListModel::Column::ResidualDy: + value = dY; + break; + case QgsGCPListModel::Column::TotalResidual: + value = residual; + break; + default: + break; + } + + switch ( role ) + { + case Qt::EditRole: + case Qt::UserRole: + // use full precision + return value; + + case Qt::ToolTipRole: + case Qt::DisplayRole: + // truncate decimals for display + return formatNumber( value ); + default: + break; + } + } + else + { + return tr( "n/a" ); + } + BUILTIN_UNREACHABLE + break; } + case QgsGCPListModel::Column::LastColumn: + break; + } + break; + + case Qt::CheckStateRole: + if ( column == Column::Enabled ) + { + return point->isEnabled() ? Qt::Checked : Qt::Unchecked; + } + break; + + case Qt::TextAlignmentRole: + { + switch ( column ) + { + case QgsGCPListModel::Column::Enabled: + return Qt::AlignHCenter; + + case QgsGCPListModel::Column::LastColumn: + break; + + case QgsGCPListModel::Column::ID: + case QgsGCPListModel::Column::SourceX: + case QgsGCPListModel::Column::SourceY: + case QgsGCPListModel::Column::DestinationX: + case QgsGCPListModel::Column::DestinationY: + case QgsGCPListModel::Column::ResidualDx: + case QgsGCPListModel::Column::ResidualDy: + case QgsGCPListModel::Column::TotalResidual: + return Qt::AlignRight; } + break; } - residual = std::sqrt( dX * dX + dY * dY ); - p->setResidual( QPointF( dX, dY ) ); + case static_cast< int >( Role::SourcePointRole ): + return point->sourcePoint(); - if ( residual >= 0.f ) + } + return QVariant(); +} + +bool QgsGCPListModel::setData( const QModelIndex &index, const QVariant &value, int role ) +{ + if ( !mGCPList + || index.row() < 0 + || index.row() >= mGCPList->size() + || index.column() < 0 + || index.column() >= columnCount() ) + return false; + + QgsGeorefDataPoint *point = mGCPList->at( index.row() ); + const Column column = static_cast< Column >( index.column() ); + switch ( column ) + { + case QgsGCPListModel::Column::Enabled: + if ( role == Qt::CheckStateRole ) + { + const bool checked = static_cast< Qt::CheckState >( value.toInt() ) == Qt::Checked; + point->setEnabled( checked ); + emit dataChanged( index, index ); + updateResiduals(); + emit pointEnabled( point, index.row() ); + return true; + } + break; + + case QgsGCPListModel::Column::SourceX: + case QgsGCPListModel::Column::SourceY: { - setItem( i, j++, new QgsStandardItem( dX ) ); - setItem( i, j++, new QgsStandardItem( dY ) ); - setItem( i, j++, new QgsStandardItem( residual ) ); + QgsPointXY sourcePoint = point->sourcePoint(); + if ( column == QgsGCPListModel::Column::SourceX ) + sourcePoint.setX( value.toDouble() ); + else + sourcePoint.setY( value.toDouble() ); + point->setSourcePoint( sourcePoint ); + emit dataChanged( index, index ); + updateResiduals(); + return true; } - else + + case QgsGCPListModel::Column::DestinationX: + case QgsGCPListModel::Column::DestinationY: { - setItem( i, j++, new QgsStandardItem( QStringLiteral( "n/a" ) ) ); - setItem( i, j++, new QgsStandardItem( QStringLiteral( "n/a" ) ) ); - setItem( i, j++, new QgsStandardItem( QStringLiteral( "n/a" ) ) ); + // when setting a destination point x/y, we need to use the transformed destination point + // as this is what we were showing to users + QgsPointXY destinationPoint = point->transformedDestinationPoint( mTargetCrs, mTransformContext ); + if ( column == QgsGCPListModel::Column::DestinationX ) + destinationPoint.setX( value.toDouble() ); + else + destinationPoint.setY( value.toDouble() ); + point->setDestinationPoint( destinationPoint ); + // we also have to update the destination point crs to the target crs, as the point is now in a different CRS + point->setDestinationPointCrs( mTargetCrs ); + emit dataChanged( index, index ); + updateResiduals(); + return true; } + + case QgsGCPListModel::Column::ID: + case QgsGCPListModel::Column::ResidualDx: + case QgsGCPListModel::Column::ResidualDy: + case QgsGCPListModel::Column::TotalResidual: + case QgsGCPListModel::Column::LastColumn: + return false; + } + + return false; +} + +Qt::ItemFlags QgsGCPListModel::flags( const QModelIndex &index ) const +{ + if ( !mGCPList + || index.row() < 0 + || index.row() >= mGCPList->size() + || index.column() < 0 + || index.column() >= columnCount() ) + return QAbstractTableModel::flags( index ); + + const Column column = static_cast< Column >( index.column() ); + switch ( column ) + { + case QgsGCPListModel::Column::Enabled: + return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsUserCheckable | Qt::ItemFlag::ItemIsSelectable; + + case QgsGCPListModel::Column::SourceX: + case QgsGCPListModel::Column::SourceY: + case QgsGCPListModel::Column::DestinationX: + case QgsGCPListModel::Column::DestinationY: + return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable | Qt::ItemFlag::ItemIsEditable; + + case QgsGCPListModel::Column::ID: + case QgsGCPListModel::Column::ResidualDx: + case QgsGCPListModel::Column::ResidualDy: + case QgsGCPListModel::Column::TotalResidual: + case QgsGCPListModel::Column::LastColumn: + return Qt::ItemFlag::ItemIsEnabled | Qt::ItemFlag::ItemIsSelectable; + } + return QAbstractTableModel::flags( index ); +} + +QVariant QgsGCPListModel::headerData( int section, Qt::Orientation orientation, int role ) const +{ + switch ( orientation ) + { + case Qt::Horizontal: + switch ( role ) + { + case Qt::DisplayRole: + case Qt::ToolTipRole: + { + QString residualUnitType; + switch ( residualUnit() ) + { + case QgsUnitTypes::RenderMapUnits: + residualUnitType = tr( "map units" ); + break; + case QgsUnitTypes::RenderPixels: + residualUnitType = tr( "pixels" ); + break; + + case QgsUnitTypes::RenderMillimeters: + case QgsUnitTypes::RenderPercentage: + case QgsUnitTypes::RenderPoints: + case QgsUnitTypes::RenderInches: + case QgsUnitTypes::RenderUnknownUnit: + case QgsUnitTypes::RenderMetersInMapUnits: + break; + } + + switch ( static_cast< Column >( section ) ) + { + case QgsGCPListModel::Column::Enabled: + return tr( "Enabled" ); + case QgsGCPListModel::Column::ID: + return tr( "ID" ); + case QgsGCPListModel::Column::SourceX: + return tr( "Source X" ); + case QgsGCPListModel::Column::SourceY: + return tr( "Source Y" ); + case QgsGCPListModel::Column::DestinationX: + case QgsGCPListModel::Column::DestinationY: + { + const QString heading = static_cast< Column >( section ) == QgsGCPListModel::Column::DestinationX ? tr( "Dest. X" ) : tr( "Dest. Y" ); + switch ( role ) + { + case Qt::DisplayRole: + return heading; + + case Qt::ToolTipRole: + { + const QString crsString = mTargetCrs.userFriendlyIdentifier(); + return QStringLiteral( "%1
%2" ).arg( heading, crsString ); + } + + default: + break; + } + break; + } + case QgsGCPListModel::Column::ResidualDx: + return tr( "dX (%1)" ).arg( residualUnitType ); + case QgsGCPListModel::Column::ResidualDy: + return tr( "dY (%1)" ).arg( residualUnitType ); + case QgsGCPListModel::Column::TotalResidual: + return tr( "Residual (%1)" ).arg( residualUnitType ); + case QgsGCPListModel::Column::LastColumn: + break; + } + } + break; + default: + break; + } + + break; + case Qt::Vertical: + break; } + return QVariant(); } -// --------------------------- public slots -------------------------------- // -void QgsGCPListModel::replaceDataPoint( QgsGeorefDataPoint *newDataPoint, int i ) +QgsUnitTypes::RenderUnit QgsGCPListModel::residualUnit() const { - mGCPList->replace( i, newDataPoint ); + bool mapUnitsPossible = false; + if ( mGeorefTransform ) + { + mapUnitsPossible = mGeorefTransform->providesAccurateInverseTransformation(); + } + + if ( mapUnitsPossible && QgsSettings().value( QStringLiteral( "/Plugin-GeoReferencer/Config/ResidualUnits" ) ) == "mapUnits" ) + { + return QgsUnitTypes::RenderUnit::RenderMapUnits; + } + else + { + return QgsUnitTypes::RenderUnit::RenderPixels; + } } -void QgsGCPListModel::onGCPListModified() +void QgsGCPListModel::updateResiduals() { + if ( !mGCPList ) + return; + + mGCPList->updateResiduals( mGeorefTransform, mTargetCrs, mTransformContext, residualUnit() ); + emit dataChanged( index( 0, static_cast< int >( Column::ResidualDx ) ), + index( rowCount() - 1, static_cast< int >( Column::TotalResidual ) ) ); } -void QgsGCPListModel::onTransformationModified() +QString QgsGCPListModel::formatNumber( double number ) { + int decimalPlaces = 4; + if ( std::fabs( number ) > 100000 ) + decimalPlaces = 2; + else if ( std::fabs( number ) < 1000 ) + decimalPlaces = 6; + + return QString::number( number, 'f', decimalPlaces ); } + + diff --git a/src/app/georeferencer/qgsgcplistmodel.h b/src/app/georeferencer/qgsgcplistmodel.h index 435527698d7b..7f55d746280d 100644 --- a/src/app/georeferencer/qgsgcplistmodel.h +++ b/src/app/georeferencer/qgsgcplistmodel.h @@ -16,31 +16,77 @@ #ifndef QGSGCP_LIST_TABLE_VIEW_H #define QGSGCP_LIST_TABLE_VIEW_H -#include -#include +#include +#include "qgis_app.h" +#include "qgsunittypes.h" +#include "qgscoordinatereferencesystem.h" +#include "qgscoordinatetransformcontext.h" class QgsGeorefDataPoint; class QgsGeorefTransform; class QgsGCPList; -class QgsGCPListModel : public QStandardItemModel +class APP_EXPORT QgsGCPListModel : public QAbstractTableModel { Q_OBJECT public: + enum class Column : int + { + Enabled, + ID, + SourceX, + SourceY, + DestinationX, + DestinationY, + ResidualDx, + ResidualDy, + TotalResidual, + LastColumn + }; + + enum class Role : int + { + SourcePointRole = Qt::UserRole + 1, + }; + explicit QgsGCPListModel( QObject *parent = nullptr ); void setGCPList( QgsGCPList *theGCPList ); void setGeorefTransform( QgsGeorefTransform *georefTransform ); - void updateModel(); - public slots: - void replaceDataPoint( QgsGeorefDataPoint *newDataPoint, int i ); + /** + * Sets the target (output) CRS for the georeferencing. + */ + void setTargetCrs( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ); + + int rowCount( const QModelIndex &parent = QModelIndex() ) const override; + int columnCount( const QModelIndex &parent = QModelIndex() ) const override; + QVariant data( const QModelIndex &index, int role = Qt::DisplayRole ) const override; + bool setData( const QModelIndex &index, const QVariant &value, int role = Qt::EditRole ) override; + Qt::ItemFlags flags( const QModelIndex &index ) const override; + QVariant headerData( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const override; + + /** + * Recalculates the residual values. + */ + void updateResiduals(); - void onGCPListModified(); - void onTransformationModified(); + /** + * Formats a number for display with an appropriate number of decimal places. + */ + static QString formatNumber( double number ); + + signals: + + void pointEnabled( QgsGeorefDataPoint *pnt, int i ); private: + QgsUnitTypes::RenderUnit residualUnit() const; + + QgsCoordinateReferenceSystem mTargetCrs; + QgsCoordinateTransformContext mTransformContext; + QgsGCPList *mGCPList = nullptr; QgsGeorefTransform *mGeorefTransform = nullptr; }; diff --git a/src/app/georeferencer/qgsgcplistwidget.cpp b/src/app/georeferencer/qgsgcplistwidget.cpp index 23bdcbdf1ee2..0dd0a6fd70d5 100644 --- a/src/app/georeferencer/qgsgcplistwidget.cpp +++ b/src/app/georeferencer/qgsgcplistwidget.cpp @@ -29,7 +29,6 @@ QgsGCPListWidget::QgsGCPListWidget( QWidget *parent ) : QTableView( parent ) , mGCPListModel( new QgsGCPListModel( this ) ) - , mNonEditableDelegate( new QgsNonEditableDelegate( this ) ) , mDmsAndDdDelegate( new QgsDmsAndDdDelegate( this ) ) , mCoordDelegate( new QgsCoordDelegate( this ) ) { @@ -48,14 +47,10 @@ QgsGCPListWidget::QgsGCPListWidget( QWidget *parent ) setAlternatingRowColors( true ); // set delegates for items - setItemDelegateForColumn( 1, mNonEditableDelegate ); // id - setItemDelegateForColumn( 2, mCoordDelegate ); // srcX - setItemDelegateForColumn( 3, mCoordDelegate ); // srcY - setItemDelegateForColumn( 4, mDmsAndDdDelegate ); // dstX - setItemDelegateForColumn( 5, mDmsAndDdDelegate ); // dstY - setItemDelegateForColumn( 6, mNonEditableDelegate ); // dX - setItemDelegateForColumn( 7, mNonEditableDelegate ); // dY - setItemDelegateForColumn( 8, mNonEditableDelegate ); // residual + setItemDelegateForColumn( static_cast< int >( QgsGCPListModel::Column::SourceX ), mCoordDelegate ); + setItemDelegateForColumn( static_cast< int >( QgsGCPListModel::Column::SourceY ), mCoordDelegate ); + setItemDelegateForColumn( static_cast< int >( QgsGCPListModel::Column::DestinationX ), mDmsAndDdDelegate ); + setItemDelegateForColumn( static_cast< int >( QgsGCPListModel::Column::DestinationY ), mDmsAndDdDelegate ); connect( this, &QAbstractItemView::doubleClicked, this, &QgsGCPListWidget::itemDoubleClicked ); @@ -64,10 +59,12 @@ QgsGCPListWidget::QgsGCPListWidget( QWidget *parent ) connect( this, &QWidget::customContextMenuRequested, this, &QgsGCPListWidget::showContextMenu ); - connect( mDmsAndDdDelegate, &QAbstractItemDelegate::closeEditor, - this, &QgsGCPListWidget::updateItemCoords ); - connect( mCoordDelegate, &QAbstractItemDelegate::closeEditor, - this, &QgsGCPListWidget::updateItemCoords ); + connect( mGCPListModel, &QgsGCPListModel::pointEnabled, this, [ = ]( QgsGeorefDataPoint * point, int row ) + { + emit pointEnabled( point, row ); + adjustTableContent(); + return; + } ); } void QgsGCPListWidget::setGCPList( QgsGCPList *theGCPList ) @@ -84,9 +81,15 @@ void QgsGCPListWidget::setGeorefTransform( QgsGeorefTransform *georefTransform ) adjustTableContent(); } -void QgsGCPListWidget::updateGCPList() +void QgsGCPListWidget::setTargetCrs( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) +{ + mGCPListModel->setTargetCrs( targetCrs, context ); + adjustTableContent(); +} + +void QgsGCPListWidget::updateResiduals() { - mGCPListModel->updateModel(); + mGCPListModel->updateResiduals(); adjustTableContent(); } @@ -99,42 +102,18 @@ void QgsGCPListWidget::closeEditors() } } -void QgsGCPListWidget::itemDoubleClicked( QModelIndex index ) +void QgsGCPListWidget::itemDoubleClicked( const QModelIndex &index ) { - index = static_cast( model() )->mapToSource( index ); - QStandardItem *item = mGCPListModel->item( index.row(), 1 ); - bool ok; - const int id = item->text().toInt( &ok ); - - if ( ok ) - { - emit jumpToGCP( id ); - } + const QModelIndex sourceIndex = static_cast( model() )->mapToSource( index ); + jumpToSourcePoint( sourceIndex ); } -void QgsGCPListWidget::itemClicked( QModelIndex index ) +void QgsGCPListWidget::itemClicked( const QModelIndex &index ) { - index = static_cast( model() )->mapToSource( index ); - QStandardItem *item = mGCPListModel->item( index.row(), index.column() ); - if ( item->isCheckable() ) - { - QgsGeorefDataPoint *p = mGCPList->at( index.row() ); - if ( item->checkState() == Qt::Checked ) - { - p->setEnabled( true ); - } - else // Qt::Unchecked - { - p->setEnabled( false ); - } + const QModelIndex sourceIndex = static_cast( model() )->mapToSource( index ); - mGCPListModel->updateModel(); - emit pointEnabled( p, index.row() ); - adjustTableContent(); - } - - mPrevRow = index.row(); - mPrevColumn = index.column(); + mPrevRow = sourceIndex.row(); + mPrevColumn = sourceIndex.column(); } void QgsGCPListWidget::keyPressEvent( QKeyEvent *e ) @@ -152,22 +131,6 @@ void QgsGCPListWidget::keyPressEvent( QKeyEvent *e ) } } } - else if ( e->key() == Qt::Key_Space ) - { - const QModelIndex index = currentIndex(); - if ( index.isValid() ) - { - const QModelIndex sourceIndex = static_cast( model() )->mapToSource( index ); - QgsGeorefDataPoint *p = mGCPList->at( sourceIndex.row() ); - p->setEnabled( !p->isEnabled() ); - - mGCPListModel->updateModel(); - emit pointEnabled( p, sourceIndex.row() ); - adjustTableContent(); - setCurrentIndex( model()->index( index.row(), index.column() ) ); - return; - } - } else if ( e->key() == Qt::Key_Up ) { const QModelIndex index = currentIndex(); @@ -207,44 +170,6 @@ void QgsGCPListWidget::keyPressEvent( QKeyEvent *e ) e->ignore(); } -void QgsGCPListWidget::updateItemCoords( QWidget *editor ) -{ - QLineEdit *lineEdit = qobject_cast( editor ); - QgsGeorefDataPoint *dataPoint = mGCPList->at( mPrevRow ); - if ( lineEdit ) - { - const double value = lineEdit->text().toDouble(); - QgsPointXY newMapCoords( dataPoint->mapCoords() ); - QgsPointXY newPixelCoords( dataPoint->pixelCoords() ); - if ( mPrevColumn == 2 ) // srcX - { - newPixelCoords.setX( value ); - } - else if ( mPrevColumn == 3 ) // srcY - { - newPixelCoords.setY( value ); - } - else if ( mPrevColumn == 4 ) // dstX - { - newMapCoords.setX( value ); - } - else if ( mPrevColumn == 5 ) // dstY - { - newMapCoords.setY( value ); - } - else - { - return; - } - - dataPoint->setPixelCoords( newPixelCoords ); - dataPoint->setMapCoords( newMapCoords ); - } - - dataPoint->updateCoords(); - updateGCPList(); -} - void QgsGCPListWidget::showContextMenu( QPoint p ) { if ( !mGCPList || 0 == mGCPList->count() ) @@ -259,7 +184,13 @@ void QgsGCPListWidget::showContextMenu( QPoint p ) setCurrentIndex( index ); QAction *jumpToPointAction = new QAction( tr( "Recenter" ), this ); - connect( jumpToPointAction, &QAction::triggered, this, &QgsGCPListWidget::jumpToPoint ); + connect( jumpToPointAction, &QAction::triggered, this, [ = ] + { + const QModelIndex sourceIndex = static_cast( model() )->mapToSource( currentIndex() ); + mPrevRow = sourceIndex.row(); + mPrevColumn = sourceIndex.column(); + jumpToSourcePoint( sourceIndex ); + } ); m.addAction( jumpToPointAction ); QAction *removeAction = new QAction( tr( "Remove" ), this ); @@ -274,17 +205,13 @@ void QgsGCPListWidget::removeRow() emit deleteDataPoint( index.row() ); } -void QgsGCPListWidget::editCell() +void QgsGCPListWidget::jumpToSourcePoint( const QModelIndex &modelIndex ) { - edit( currentIndex() ); -} - -void QgsGCPListWidget::jumpToPoint() -{ - const QModelIndex index = static_cast( model() )->mapToSource( currentIndex() ); - mPrevRow = index.row(); - mPrevColumn = index.column(); - emit jumpToGCP( index.row() ); + const QgsPointXY sourcePoint = mGCPListModel->data( modelIndex, static_cast< int >( QgsGCPListModel::Role::SourcePointRole ) ).value< QgsPointXY >(); + if ( !sourcePoint.isEmpty() ) + { + emit jumpToGCP( sourcePoint ); + } } void QgsGCPListWidget::adjustTableContent() diff --git a/src/app/georeferencer/qgsgcplistwidget.h b/src/app/georeferencer/qgsgcplistwidget.h index b3a3acc19389..572658bb9ca3 100644 --- a/src/app/georeferencer/qgsgcplistwidget.h +++ b/src/app/georeferencer/qgsgcplistwidget.h @@ -18,7 +18,6 @@ #include class QgsDoubleSpinBoxDelegate; -class QgsNonEditableDelegate; class QgsDmsAndDdDelegate; class QgsCoordDelegate; @@ -27,6 +26,8 @@ class QgsGCPListModel; class QgsGeorefTransform; class QgsGeorefDataPoint; class QgsPointXY; +class QgsCoordinateReferenceSystem; +class QgsCoordinateTransformContext; class QgsGCPListWidget : public QTableView { @@ -36,28 +37,37 @@ class QgsGCPListWidget : public QTableView void setGCPList( QgsGCPList *theGCPList ); void setGeorefTransform( QgsGeorefTransform *georefTransform ); + + /** + * Sets the target (output) CRS for the georeferencing. + */ + void setTargetCrs( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ); + QgsGCPList *gcpList() { return mGCPList; } - void updateGCPList(); + + /** + * Recalculates the residual values. + */ + void updateResiduals(); + void closeEditors(); void keyPressEvent( QKeyEvent *e ) override; public slots: // This slot is called by the list view if an item is double-clicked - void itemDoubleClicked( QModelIndex index ); - void itemClicked( QModelIndex index ); + void itemDoubleClicked( const QModelIndex &index ); + void itemClicked( const QModelIndex &index ); signals: - void jumpToGCP( uint theGCPIndex ); + void jumpToGCP( const QgsPointXY &point ); void pointEnabled( QgsGeorefDataPoint *pnt, int i ); void deleteDataPoint( int index ); private slots: - void updateItemCoords( QWidget *editor ); void showContextMenu( QPoint ); void removeRow(); - void editCell(); - void jumpToPoint(); + void jumpToSourcePoint( const QModelIndex &modelIndex ); private: void createActions(); @@ -67,7 +77,6 @@ class QgsGCPListWidget : public QTableView QgsGCPList *mGCPList = nullptr; QgsGCPListModel *mGCPListModel = nullptr; - QgsNonEditableDelegate *mNonEditableDelegate = nullptr; QgsDmsAndDdDelegate *mDmsAndDdDelegate = nullptr; QgsCoordDelegate *mCoordDelegate = nullptr; diff --git a/src/app/georeferencer/qgsgeorefdatapoint.cpp b/src/app/georeferencer/qgsgeorefdatapoint.cpp index eeda622827da..727a23f940dc 100644 --- a/src/app/georeferencer/qgsgeorefdatapoint.cpp +++ b/src/app/georeferencer/qgsgeorefdatapoint.cpp @@ -21,40 +21,72 @@ #include "qgsgeorefdatapoint.h" +// +// QgsGcpPoint +// + +QgsGcpPoint::QgsGcpPoint( const QgsPointXY &sourcePoint, const QgsPointXY &destinationPoint, const QgsCoordinateReferenceSystem &destinationPointCrs, bool enabled ) + : mSourcePoint( sourcePoint ) + , mDestinationPoint( destinationPoint ) + , mDestinationCrs( destinationPointCrs ) + , mEnabled( enabled ) +{ + +} + +QgsCoordinateReferenceSystem QgsGcpPoint::destinationPointCrs() const +{ + return mDestinationCrs; +} + +void QgsGcpPoint::setDestinationPointCrs( const QgsCoordinateReferenceSystem &crs ) +{ + mDestinationCrs = crs; +} + +QgsPointXY QgsGcpPoint::transformedDestinationPoint( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) const +{ + const QgsCoordinateTransform transform( mDestinationCrs, targetCrs, context ); + try + { + return transform.transform( mDestinationPoint ); + } + catch ( QgsCsException & ) + { + QgsDebugMsg( QStringLiteral( "Error transforming destination point" ) ); + return mDestinationPoint; + } +} + + +// +// QgsGeorefDataPoint +// + QgsGeorefDataPoint::QgsGeorefDataPoint( QgsMapCanvas *srcCanvas, QgsMapCanvas *dstCanvas, - const QgsPointXY &pixelCoords, const QgsPointXY &mapCoords, - const QgsCoordinateReferenceSystem proj, bool enable ) + const QgsPointXY &sourceCoordinates, const QgsPointXY &destinationPoint, + const QgsCoordinateReferenceSystem &destinationPointCrs, bool enabled ) : mSrcCanvas( srcCanvas ) , mDstCanvas( dstCanvas ) - , mPixelCoords( pixelCoords ) - , mMapCoords( mapCoords ) + , mGcpPoint( sourceCoordinates, destinationPoint, destinationPointCrs, enabled ) , mId( -1 ) - , mCrs( proj ) - , mEnabled( enable ) { - mTransCoords = QgsPointXY( mapCoords ); - mCanvasCoords = QgsPointXY(); mGCPSourceItem = new QgsGCPCanvasItem( srcCanvas, this, true ); mGCPDestinationItem = new QgsGCPCanvasItem( dstCanvas, this, false ); - mGCPSourceItem->setEnabled( enable ); - mGCPDestinationItem->setEnabled( enable ); + mGCPSourceItem->setEnabled( enabled ); + mGCPDestinationItem->setEnabled( enabled ); mGCPSourceItem->show(); mGCPDestinationItem->show(); } QgsGeorefDataPoint::QgsGeorefDataPoint( const QgsGeorefDataPoint &p ) : QObject( nullptr ) + , mGcpPoint( p.mGcpPoint ) { // we share item representation on canvas between all points // mGCPSourceItem = new QgsGCPCanvasItem(p.srcCanvas(), p.pixelCoords(), p.mapCoords(), p.isEnabled()); // mGCPDestinationItem = new QgsGCPCanvasItem(p.dstCanvas(), p.pixelCoords(), p.mapCoords(), p.isEnabled()); - mPixelCoords = p.pixelCoords(); - mMapCoords = p.mapCoords(); - mTransCoords = p.transCoords(); - mEnabled = p.isEnabled(); mResidual = p.residual(); - mCanvasCoords = p.canvasCoords(); - mCrs = p.crs(); mId = p.id(); } @@ -64,58 +96,32 @@ QgsGeorefDataPoint::~QgsGeorefDataPoint() delete mGCPDestinationItem; } -void QgsGeorefDataPoint::setPixelCoords( const QgsPointXY &p ) -{ - mPixelCoords = p; - mGCPSourceItem->update(); - mGCPDestinationItem->update(); -} - -void QgsGeorefDataPoint::setMapCoords( const QgsPointXY &p ) -{ - mMapCoords = p; - if ( mGCPSourceItem ) - { - mGCPSourceItem->update(); - } - if ( mGCPDestinationItem ) - { - mGCPDestinationItem->update(); - } -} - -void QgsGeorefDataPoint::setTransCoords( const QgsPointXY &p ) +void QgsGeorefDataPoint::setSourcePoint( const QgsPointXY &p ) { - mTransCoords = p; - if ( mGCPSourceItem ) - { - mGCPSourceItem->update(); - } - if ( mGCPDestinationItem ) - { - mGCPDestinationItem->update(); - } + mGcpPoint.setSourcePoint( p ); + updateCoords(); } -QgsPointXY QgsGeorefDataPoint::transCoords() const +void QgsGeorefDataPoint::setDestinationPoint( const QgsPointXY &p ) { - return mTransCoords.isEmpty() ? mMapCoords : mTransCoords; + mGcpPoint.setDestinationPoint( p ); + updateCoords(); } - -void QgsGeorefDataPoint::setCanvasCoords( const QgsPointXY &p ) +void QgsGeorefDataPoint::setDestinationPointCrs( const QgsCoordinateReferenceSystem &crs ) { - mCanvasCoords = p; + mGcpPoint.setDestinationPointCrs( crs ); + updateCoords(); } -QgsPointXY QgsGeorefDataPoint::canvasCoords() const +QgsPointXY QgsGeorefDataPoint::transformedDestinationPoint( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) const { - return mCanvasCoords; + return mGcpPoint.transformedDestinationPoint( targetCrs, context ); } void QgsGeorefDataPoint::setEnabled( bool enabled ) { - mEnabled = enabled; + mGcpPoint.setEnabled( enabled ); if ( mGCPSourceItem ) { mGCPSourceItem->update(); @@ -158,40 +164,48 @@ void QgsGeorefDataPoint::updateCoords() } } -bool QgsGeorefDataPoint::contains( QPoint p, bool isMapPlugin ) +bool QgsGeorefDataPoint::contains( QPoint p, QgsGcpPoint::PointType type ) { - if ( isMapPlugin ) - { - const QPointF pnt = mGCPSourceItem->mapFromScene( p ); - return mGCPSourceItem->shape().contains( pnt ); - } - else + switch ( type ) { - const QPointF pnt = mGCPDestinationItem->mapFromScene( p ); - return mGCPDestinationItem->shape().contains( pnt ); + case QgsGcpPoint::PointType::Source: + { + const QPointF pnt = mGCPSourceItem->mapFromScene( p ); + return mGCPSourceItem->shape().contains( pnt ); + } + + case QgsGcpPoint::PointType::Destination: + { + const QPointF pnt = mGCPDestinationItem->mapFromScene( p ); + return mGCPDestinationItem->shape().contains( pnt ); + } } + BUILTIN_UNREACHABLE } -void QgsGeorefDataPoint::moveTo( QPoint p, bool isMapPlugin ) +void QgsGeorefDataPoint::moveTo( QPoint canvasPixels, QgsGcpPoint::PointType type ) { - if ( isMapPlugin ) + switch ( type ) { - const QgsPointXY pnt = mGCPSourceItem->toMapCoordinates( p ); - mPixelCoords = pnt; + case QgsGcpPoint::PointType::Source: + { + const QgsPointXY pnt = mGCPSourceItem->toMapCoordinates( canvasPixels ); + mGcpPoint.setSourcePoint( pnt ); + break; + } + case QgsGcpPoint::PointType::Destination: + { + mGcpPoint.setDestinationPoint( mGCPDestinationItem->toMapCoordinates( canvasPixels ) ); + if ( mSrcCanvas && mSrcCanvas->mapSettings().destinationCrs().isValid() ) + mGcpPoint.setDestinationPointCrs( mSrcCanvas->mapSettings().destinationCrs() ); + else + mGcpPoint.setDestinationPointCrs( mGCPDestinationItem->canvas()->mapSettings().destinationCrs() ); + + if ( !mGcpPoint.destinationPointCrs().isValid() ) + mGcpPoint.setDestinationPointCrs( QgsProject::instance()->crs() ); + break; + } } - else - { - const QgsPointXY pnt = mGCPDestinationItem->toMapCoordinates( p ); - setCanvasCoords( pnt ); - mMapCoords = pnt; - if ( mSrcCanvas && mSrcCanvas->mapSettings().destinationCrs().isValid() ) - mCrs = mSrcCanvas->mapSettings().destinationCrs(); - else - mCrs = mGCPDestinationItem->canvas()->mapSettings().destinationCrs(); - } - if ( !mCrs.isValid() ) - mCrs = QgsProject::instance()->crs(); - mGCPSourceItem->update(); - mGCPDestinationItem->update(); + updateCoords(); } diff --git a/src/app/georeferencer/qgsgeorefdatapoint.h b/src/app/georeferencer/qgsgeorefdatapoint.h index d7ba0d1266cc..b001f8572965 100644 --- a/src/app/georeferencer/qgsgeorefdatapoint.h +++ b/src/app/georeferencer/qgsgeorefdatapoint.h @@ -16,43 +16,213 @@ #ifndef QGSGEOREFDATAPOINT_H #define QGSGEOREFDATAPOINT_H +#include "qgis_app.h" #include "qgsmapcanvasitem.h" #include "qgscoordinatereferencesystem.h" class QgsGCPCanvasItem; +class QgsCoordinateTransformContext; -class QgsGeorefDataPoint : public QObject +/** + * Contains properties of a ground control point (GCP). + */ +class APP_EXPORT QgsGcpPoint +{ + public: + + //! Coordinate point types + enum class PointType + { + Source, //!< Source point + Destination, //!< Destination point + }; + + /** + * Constructor for QgsGcpPoint. + * + * \param sourceCoordinates source coordinates. This may either be in pixels (for completely non-referenced images) OR in the source layer CRS. + * \param destinationPoint destination coordinates + * \param destinationPointCrs CRS of destination point + * \param enabled whether the point is currently enabled + */ + QgsGcpPoint( const QgsPointXY &sourcePoint, const QgsPointXY &destinationPoint, + const QgsCoordinateReferenceSystem &destinationPointCrs, bool enabled ); + + /** + * Returns the source coordinates. + * + * This may either be in pixels (for completely non-referenced images) OR in the source layer CRS. + * + * \see setSourcePoint() + */ + QgsPointXY sourcePoint() const { return mSourcePoint; } + + /** + * Sets the source coordinates. + * + * This may either be in pixels (for completely non-referenced images) OR in the source layer CRS. + * + * \see sourcePoint() + */ + void setSourcePoint( QgsPointXY point ) { mSourcePoint = point; } + + /** + * Returns the destination coordinates. + * + * \see setDestinationPoint() + */ + QgsPointXY destinationPoint() const { return mDestinationPoint; } + + /** + * Sets the destination coordinates. + * + * \see destinationPoint() + */ + void setDestinationPoint( QgsPointXY point ) { mDestinationPoint = point; } + + /** + * Returns the CRS of the destination point. + * + * \see setDestinationCrs() + */ + QgsCoordinateReferenceSystem destinationPointCrs() const; + + /** + * Sets the \a crs of the destination point. + * + * \see destinationCrs() + */ + void setDestinationPointCrs( const QgsCoordinateReferenceSystem &crs ); + + /** + * Returns the destionationPoint() transformed to the given target CRS. + */ + QgsPointXY transformedDestinationPoint( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) const; + + /** + * Returns TRUE if the point is currently enabled. + * + * \see setEnabled() + */ + bool isEnabled() const { return mEnabled; } + + /** + * Sets whether the point is currently enabled. + * + * \see enabled() + */ + void setEnabled( bool enabled ) { mEnabled = enabled; } + + // TODO c++20 - replace with = default + bool operator==( const QgsGcpPoint &other ) const + { + return mEnabled == other.mEnabled + && mSourcePoint == other.mSourcePoint + && mDestinationPoint == other.mDestinationPoint + && mDestinationCrs == other.mDestinationCrs; + } + + bool operator!=( const QgsGcpPoint &other ) const + { + return !( *this == other ); + } + + private: + + QgsPointXY mSourcePoint; + QgsPointXY mDestinationPoint; + QgsCoordinateReferenceSystem mDestinationCrs; + bool mEnabled = true; + +}; + + +/** + * Container for a GCP point and the graphical objects which represent it on the map canvas. + */ +class APP_EXPORT QgsGeorefDataPoint : public QObject { Q_OBJECT public: - //! constructor + + /** + * Constructor for QgsGeorefDataPoint + * \param srcCanvas + * \param dstCanvas + * \param sourceCoordinates source coordinates. This may either be in pixels (for completely non-referenced images) OR in the source layer CRS. + * \param destinationPoint destination coordinates + * \param destinationPointCrs CRS of destination point + * \param enabled whether the point is currently enabled + */ QgsGeorefDataPoint( QgsMapCanvas *srcCanvas, QgsMapCanvas *dstCanvas, - const QgsPointXY &pixelCoords, const QgsPointXY &mapCoords, - const QgsCoordinateReferenceSystem proj, bool enable ); + const QgsPointXY &sourceCoordinates, const QgsPointXY &destinationPoint, + const QgsCoordinateReferenceSystem &destinationPointCrs, bool enabled ); QgsGeorefDataPoint( const QgsGeorefDataPoint &p ); ~QgsGeorefDataPoint() override; - //! returns coordinates of the point - QgsPointXY pixelCoords() const { return mPixelCoords; } - void setPixelCoords( const QgsPointXY &p ); + /** + * Returns the source coordinates. + * + * This may either be in pixels (for completely non-referenced images) OR in the source layer CRS. + * + * \see setSourcePoint() + */ + QgsPointXY sourcePoint() const { return mGcpPoint.sourcePoint(); } - QgsPointXY mapCoords() const { return mMapCoords; } - void setMapCoords( const QgsPointXY &p ); + /** + * Sets the source coordinates. + * + * This may either be in pixels (for completely non-referenced images) OR in the source layer CRS. + * + * \see sourcePoint() + */ + void setSourcePoint( const QgsPointXY &p ); - QgsPointXY transCoords() const; - void setTransCoords( const QgsPointXY &p ); + /** + * Returns the destination coordinates. + * + * \see setDestinationPoint() + */ + QgsPointXY destinationPoint() const { return mGcpPoint.destinationPoint(); } - QgsPointXY canvasCoords() const; - void setCanvasCoords( const QgsPointXY &p ); + /** + * Sets the destination coordinates. + * + * \see destinationPoint() + */ + void setDestinationPoint( const QgsPointXY &p ); - bool isEnabled() const { return mEnabled; } + /** + * Sets the \a crs of the destination point. + * + * \see destinationCrs() + */ + void setDestinationPointCrs( const QgsCoordinateReferenceSystem &crs ); + + /** + * Returns the destionationPoint() transformed to the given target CRS. + */ + QgsPointXY transformedDestinationPoint( const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context ) const; + + /** + * Returns TRUE if the point is currently enabled. + * + * \see setEnabled() + */ + bool isEnabled() const { return mGcpPoint.isEnabled(); } + + /** + * Sets whether the point is currently enabled. + * + * \see enabled() + */ void setEnabled( bool enabled ); int id() const { return mId; } void setId( int id ); - bool contains( QPoint p, bool isMapPlugin ); + bool contains( QPoint p, QgsGcpPoint::PointType type ); QgsMapCanvas *srcCanvas() const { return mSrcCanvas; } QgsMapCanvas *dstCanvas() const { return mDstCanvas; } @@ -60,10 +230,17 @@ class QgsGeorefDataPoint : public QObject QPointF residual() const { return mResidual; } void setResidual( QPointF r ); - QgsCoordinateReferenceSystem crs() const { return mCrs; } + /** + * Returns the CRS of the destination point. + * + * \see setDestinationCrs() + */ + QgsCoordinateReferenceSystem destinationPointCrs() const { return mGcpPoint.destinationPointCrs(); } + + QgsGcpPoint point() const { return mGcpPoint; } public slots: - void moveTo( QPoint, bool isMapPlugin ); + void moveTo( QPoint canvasPixels, QgsGcpPoint::PointType type ); void updateCoords(); private: @@ -71,14 +248,10 @@ class QgsGeorefDataPoint : public QObject QgsMapCanvas *mDstCanvas = nullptr; QgsGCPCanvasItem *mGCPSourceItem = nullptr; QgsGCPCanvasItem *mGCPDestinationItem = nullptr; - QgsPointXY mPixelCoords; - QgsPointXY mMapCoords; - QgsPointXY mTransCoords; - QgsPointXY mCanvasCoords; + + QgsGcpPoint mGcpPoint; int mId; - QgsCoordinateReferenceSystem mCrs; - bool mEnabled; QPointF mResidual; QgsGeorefDataPoint &operator=( const QgsGeorefDataPoint & ) = delete; diff --git a/src/app/georeferencer/qgsgeorefdelegates.cpp b/src/app/georeferencer/qgsgeorefdelegates.cpp index 79d0627658c4..4bf96cb48db9 100644 --- a/src/app/georeferencer/qgsgeorefdelegates.cpp +++ b/src/app/georeferencer/qgsgeorefdelegates.cpp @@ -21,12 +21,6 @@ #include "qgsgeorefdelegates.h" #include -// ------------------------ QgsNonEditableDelegate ------------------------- // -QgsNonEditableDelegate::QgsNonEditableDelegate( QWidget *parent ) - : QStyledItemDelegate( parent ) -{ -} - // ------------------------- QgsDmsAndDdDelegate --------------------------- // QgsDmsAndDdDelegate::QgsDmsAndDdDelegate( QWidget *parent ) : QStyledItemDelegate( parent ) @@ -63,8 +57,6 @@ void QgsDmsAndDdDelegate::setModelData( QWidget *editor, QAbstractItemModel *mod value = stringValue.toDouble(); model->setData( index, value, Qt::EditRole ); - model->setData( index, value, Qt::DisplayRole ); - model->setData( index, value, Qt::ToolTipRole ); } void QgsDmsAndDdDelegate::updateEditorGeometry( QWidget *editor, const QStyleOptionViewItem &option, @@ -121,8 +113,6 @@ void QgsCoordDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QString stringValue = lineEdit->text(); const double value = stringValue.toDouble(); model->setData( index, value, Qt::EditRole ); - model->setData( index, value, Qt::DisplayRole ); - model->setData( index, value, Qt::ToolTipRole ); } void QgsCoordDelegate::updateEditorGeometry( QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index ) const diff --git a/src/app/georeferencer/qgsgeorefdelegates.h b/src/app/georeferencer/qgsgeorefdelegates.h index 87b730e063e7..dd82515dddc3 100644 --- a/src/app/georeferencer/qgsgeorefdelegates.h +++ b/src/app/georeferencer/qgsgeorefdelegates.h @@ -17,23 +17,6 @@ #include -class QgsNonEditableDelegate : public QStyledItemDelegate -{ - Q_OBJECT - - public: - explicit QgsNonEditableDelegate( QWidget *parent = nullptr ); - - QWidget *createEditor( QWidget *parent, const QStyleOptionViewItem &option, - const QModelIndex &index ) const override - { - Q_UNUSED( parent ) - Q_UNUSED( option ) - Q_UNUSED( index ) - return nullptr; - } -}; - class QgsDmsAndDdDelegate : public QStyledItemDelegate { Q_OBJECT diff --git a/src/app/georeferencer/qgsgeorefmainwindow.cpp b/src/app/georeferencer/qgsgeorefmainwindow.cpp index c4285fd0f0a1..7057471aadff 100644 --- a/src/app/georeferencer/qgsgeorefmainwindow.cpp +++ b/src/app/georeferencer/qgsgeorefmainwindow.cpp @@ -87,6 +87,11 @@ QgsGeoreferencerMainWindow::QgsGeoreferencerMainWindow( QWidget *parent, Qt::Win centralWidget->setLayout( mCentralLayout ); mCentralLayout->setContentsMargins( 0, 0, 0, 0 ); + QgsSettings settings; + // default to last used target CRS + QString targetCRSString = settings.value( QStringLiteral( "/Plugin-GeoReferencer/targetsrs" ) ).toString(); + mTargetCrs = QgsCoordinateReferenceSystem( targetCRSString ); + createActions(); createActionGroups(); createMenus(); @@ -108,7 +113,6 @@ QgsGeoreferencerMainWindow::QgsGeoreferencerMainWindow( QWidget *parent, Qt::Win mCanvas->clearExtentHistory(); // reset zoomnext/zoomlast - QgsSettings settings; if ( settings.value( QStringLiteral( "/Plugin-GeoReferencer/Config/ShowDocked" ) ).toBool() ) { dockThisWindow( true ); @@ -151,7 +155,6 @@ QgsGeoreferencerMainWindow::~QgsGeoreferencerMainWindow() delete mToolMovePointQgis; } -// ----------------------------- protected --------------------------------- // void QgsGeoreferencerMainWindow::closeEvent( QCloseEvent *e ) { switch ( checkNeedGCPSave() ) @@ -164,13 +167,6 @@ void QgsGeoreferencerMainWindow::closeEvent( QCloseEvent *e ) mRasterFileName.clear(); e->accept(); return; - case QgsGeoreferencerMainWindow::GCPSILENTSAVE: - if ( !mGCPpointsFileName.isEmpty() ) - saveGCPs(); - clearGCPData(); - removeOldLayer(); - mRasterFileName.clear(); - return; case QgsGeoreferencerMainWindow::GCPDISCARD: writeSettings(); clearGCPData(); @@ -193,6 +189,7 @@ void QgsGeoreferencerMainWindow::reset() { mRasterFileName.clear(); mModifiedRasterFileName.clear(); + mCreateWorldFileOnly = false; setWindowTitle( tr( "Georeferencer" ) ); //delete old points @@ -203,20 +200,13 @@ void QgsGeoreferencerMainWindow::reset() } } -// -------------------------- private slots -------------------------------- // -// File slots void QgsGeoreferencerMainWindow::openRaster( const QString &fileName ) { - // clearLog(); switch ( checkNeedGCPSave() ) { case QgsGeoreferencerMainWindow::GCPSAVE: saveGCPsDialog(); break; - case QgsGeoreferencerMainWindow::GCPSILENTSAVE: - if ( !mGCPpointsFileName.isEmpty() ) - saveGCPs(); - break; case QgsGeoreferencerMainWindow::GCPDISCARD: break; case QgsGeoreferencerMainWindow::GCPCANCEL: @@ -248,6 +238,7 @@ void QgsGeoreferencerMainWindow::openRaster( const QString &fileName ) mRasterFileName = fileName; } mModifiedRasterFileName.clear(); + mCreateWorldFileOnly = false; QString errMsg; if ( !QgsRasterLayer::isValidRasterFileName( mRasterFileName, errMsg ) ) @@ -261,12 +252,10 @@ void QgsGeoreferencerMainWindow::openRaster( const QString &fileName ) s.setValue( QStringLiteral( "/Plugin-GeoReferencer/rasterdirectory" ), fileInfo.path() ); mGeorefTransform.selectTransformParametrisation( mTransformParam ); - mGeorefTransform.setRasterChangeCoords( mRasterFileName ); + mGeorefTransform.loadRaster( mRasterFileName ); statusBar()->showMessage( tr( "Raster loaded: %1" ).arg( mRasterFileName ) ); setWindowTitle( tr( "Georeferencer - %1" ).arg( fileInfo.fileName() ) ); - // showMessageInLog(tr("Input raster"), mRasterFileName); - //delete old points clearGCPData(); @@ -278,7 +267,8 @@ void QgsGeoreferencerMainWindow::openRaster( const QString &fileName ) // load previously added points mGCPpointsFileName = mRasterFileName + ".points"; - ( void )loadGCPs(); + QString error; + ( void )loadGCPs( error ); if ( mLayer ) mCanvas->setExtent( mLayer->extent() ); @@ -286,12 +276,16 @@ void QgsGeoreferencerMainWindow::openRaster( const QString &fileName ) mCanvas->refresh(); QgisApp::instance()->mapCanvas()->refresh(); - mActionLinkGeorefToQgis->setChecked( false ); - mActionLinkQGisToGeoref->setChecked( false ); - mActionLinkGeorefToQgis->setEnabled( false ); - mActionLinkQGisToGeoref->setEnabled( false ); + const bool hasExistingReference = mLayer->crs().isValid(); + mActionLinkGeorefToQgis->setEnabled( hasExistingReference ); + mActionLinkQGisToGeoref->setEnabled( hasExistingReference ); + if ( !hasExistingReference ) + { + mActionLinkGeorefToQgis->setChecked( false ); + mActionLinkQGisToGeoref->setChecked( false ); + } - mCanvas->clearExtentHistory(); // reset zoomnext/zoomlast + mCanvas->clearExtentHistory(); mWorldFileName = guessWorldFileName( mRasterFileName ); } @@ -345,7 +339,7 @@ void QgsGeoreferencerMainWindow::doGeoreference() mMessageBar->pushMessage( tr( "Georeference Successful" ), tr( "Raster was successfully georeferenced." ), Qgis::MessageLevel::Success ); if ( mLoadInQgis ) { - if ( mModifiedRasterFileName.isEmpty() ) + if ( mCreateWorldFileOnly ) { QgisApp::instance()->addRasterLayer( mRasterFileName, QFileInfo( mRasterFileName ).completeBaseName(), QString() ); } @@ -353,50 +347,45 @@ void QgsGeoreferencerMainWindow::doGeoreference() { QgisApp::instance()->addRasterLayer( mModifiedRasterFileName, QFileInfo( mModifiedRasterFileName ).completeBaseName(), QString() ); } - - // showMessageInLog(tr("Modified raster saved in"), mModifiedRasterFileName); - // saveGCPs(); - - // mTransformParam = QgsGeorefTransform::InvalidTransform; - // mGeorefTransform.selectTransformParametrisation(mTransformParam); - // mGCPListWidget->setGeorefTransform(&mGeorefTransform); - // mTransformParamLabel->setText(tr("Transform: ") + convertTransformEnumToString(mTransformParam)); - - mActionLinkGeorefToQgis->setEnabled( false ); - mActionLinkQGisToGeoref->setEnabled( false ); } } } bool QgsGeoreferencerMainWindow::getTransformSettings() { - QgsTransformSettingsDialog d( mRasterFileName, mModifiedRasterFileName, mPoints.size() ); + QgsTransformSettingsDialog d( mRasterFileName, mModifiedRasterFileName ); + d.setTargetCrs( mTargetCrs ); + d.setCreateWorldFileOnly( mCreateWorldFileOnly ); if ( !d.exec() ) { return false; } d.getTransformSettings( mTransformParam, mResamplingMethod, mCompressionMethod, - mModifiedRasterFileName, mProjection, mPdfOutputMapFile, mPdfOutputFile, mSaveGcp, mUseZeroForTrans, mLoadInQgis, mUserResX, mUserResY ); + mModifiedRasterFileName, mPdfOutputMapFile, mPdfOutputFile, mSaveGcp, mUseZeroForTrans, mLoadInQgis, mUserResX, mUserResY ); + mCreateWorldFileOnly = d.createWorldFileOnly(); + mTargetCrs = d.targetCrs(); + mTransformParamLabel->setText( tr( "Transform: " ) + QgsGcpTransformerInterface::methodToString( mTransformParam ) ); mGeorefTransform.selectTransformParametrisation( mTransformParam ); mGCPListWidget->setGeorefTransform( &mGeorefTransform ); mWorldFileName = guessWorldFileName( mRasterFileName ); - // showMessageInLog(tr("Output raster"), mModifiedRasterFileName.isEmpty() ? tr("Non set") : mModifiedRasterFileName); - // showMessageInLog(tr("Target projection"), mProjection.isEmpty() ? tr("Non set") : mProjection); - // logTransformOptions(); - // logRequaredGCPs(); - - if ( QgsGcpTransformerInterface::TransformMethod::InvalidTransform != mTransformParam ) + const bool hasReferencing = QgsGcpTransformerInterface::TransformMethod::InvalidTransform != mTransformParam + || mLayer->crs().isValid(); + mActionLinkGeorefToQgis->setEnabled( hasReferencing ); + mActionLinkQGisToGeoref->setEnabled( hasReferencing ); + if ( !hasReferencing ) { - mActionLinkGeorefToQgis->setEnabled( true ); - mActionLinkQGisToGeoref->setEnabled( true ); + mActionLinkGeorefToQgis->setChecked( false ); + mActionLinkQGisToGeoref->setChecked( false ); } - else + + //update gcp model + if ( mGCPListWidget ) { - mActionLinkGeorefToQgis->setEnabled( false ); - mActionLinkQGisToGeoref->setEnabled( false ); + mGCPListWidget->setTargetCrs( mTargetCrs, QgsProject::instance()->transformContext() ); + mGCPListWidget->updateResiduals(); } updateTransformParamLabel(); @@ -439,7 +428,6 @@ void QgsGeoreferencerMainWindow::generateGDALScript() } } -// Edit slots void QgsGeoreferencerMainWindow::setAddPointTool() { mCanvas->setMapTool( mToolAddPoint ); @@ -466,7 +454,6 @@ void QgsGeoreferencerMainWindow::setMovePointTool() QgisApp::instance()->mapCanvas()->setMapTool( mToolMovePointQgis ); } -// View slots void QgsGeoreferencerMainWindow::setPanTool() { mCanvas->setMapTool( mToolPan ); @@ -505,15 +492,8 @@ void QgsGeoreferencerMainWindow::linkQGisToGeoref( bool link ) { if ( link ) { - if ( QgsGcpTransformerInterface::TransformMethod::InvalidTransform != mTransformParam ) - { - // Indicate that georeferencer canvas extent has changed - extentsChangedGeorefCanvas(); - } - else - { - mActionLinkGeorefToQgis->setEnabled( false ); - } + // Indicate that georeferencer canvas extent has changed + extentsChangedGeorefCanvas(); } } @@ -521,27 +501,21 @@ void QgsGeoreferencerMainWindow::linkGeorefToQgis( bool link ) { if ( link ) { - if ( QgsGcpTransformerInterface::TransformMethod::InvalidTransform != mTransformParam ) - { - // Indicate that qgis main canvas extent has changed - extentsChangedQGisCanvas(); - } - else - { - mActionLinkQGisToGeoref->setEnabled( false ); - } + // Indicate that qgis main canvas extent has changed + extentsChangedQGisCanvas(); } } -// GCPs slots -void QgsGeoreferencerMainWindow::addPoint( const QgsPointXY &pixelCoords, const QgsPointXY &mapCoords, const QgsCoordinateReferenceSystem &crs, +void QgsGeoreferencerMainWindow::addPoint( const QgsPointXY &sourceCoords, const QgsPointXY &destinationMapCoords, const QgsCoordinateReferenceSystem &destinationCrs, bool enable, bool finalize ) { - QgsGeorefDataPoint *pnt = new QgsGeorefDataPoint( mCanvas, QgisApp::instance()->mapCanvas(), pixelCoords, mapCoords, crs, enable ); + QgsGeorefDataPoint *pnt = new QgsGeorefDataPoint( mCanvas, QgisApp::instance()->mapCanvas(), sourceCoords, destinationMapCoords, destinationCrs, enable ); mPoints.append( pnt ); - if ( !mLastGCPProjection.isValid() || mLastGCPProjection != crs ) - mLastGCPProjection = QgsCoordinateReferenceSystem( crs ); + + if ( !mLastGCPProjection.isValid() || mLastGCPProjection != destinationCrs ) + mLastGCPProjection = destinationCrs; mGCPsDirty = true; + if ( finalize ) { mGCPListWidget->setGCPList( &mPoints ); @@ -561,36 +535,35 @@ void QgsGeoreferencerMainWindow::deleteDataPoint( QPoint coords ) for ( QgsGCPList::iterator it = mPoints.begin(); it != mPoints.end(); ++it ) { QgsGeorefDataPoint *pt = *it; - if ( /*pt->pixelCoords() == coords ||*/ pt->contains( coords, true ) ) // first operand for removing from GCP table + if ( pt->contains( coords, QgsGcpPoint::PointType::Source ) ) // first operand for removing from GCP table { delete *it; mPoints.erase( it ); - mGCPListWidget->updateGCPList(); - + mGCPListWidget->setGCPList( &mPoints ); mCanvas->refresh(); + updateGeorefTransform(); break; } } - updateGeorefTransform(); } void QgsGeoreferencerMainWindow::deleteDataPoint( int theGCPIndex ) { Q_ASSERT( theGCPIndex >= 0 ); delete mPoints.takeAt( theGCPIndex ); - mGCPListWidget->updateGCPList(); + // TODO -- would be cleaner to move responsibility for this to the model! + mGCPListWidget->setGCPList( &mPoints ); updateGeorefTransform(); } void QgsGeoreferencerMainWindow::selectPoint( QPoint p ) { - // Get Map Sender - bool isMapPlugin = sender() == mToolMovePoint; - QgsGeorefDataPoint *&mvPoint = isMapPlugin ? mMovingPoint : mMovingPointQgis; + const QgsGcpPoint::PointType pointType = sender() == mToolMovePoint ? QgsGcpPoint::PointType::Source : QgsGcpPoint::PointType::Destination; + QgsGeorefDataPoint *&mvPoint = pointType == QgsGcpPoint::PointType::Source ? mMovingPoint : mMovingPointQgis; for ( QgsGCPList::const_iterator it = mPoints.constBegin(); it != mPoints.constEnd(); ++it ) { - if ( ( *it )->contains( p, isMapPlugin ) ) + if ( ( *it )->contains( p, pointType ) ) { mvPoint = *it; break; @@ -598,23 +571,21 @@ void QgsGeoreferencerMainWindow::selectPoint( QPoint p ) } } -void QgsGeoreferencerMainWindow::movePoint( QPoint p ) +void QgsGeoreferencerMainWindow::movePoint( QPoint canvasPixels ) { - // Get Map Sender - bool isMapPlugin = sender() == mToolMovePoint; - QgsGeorefDataPoint *mvPoint = isMapPlugin ? mMovingPoint : mMovingPointQgis; + const QgsGcpPoint::PointType pointType = sender() == mToolMovePoint ? QgsGcpPoint::PointType::Source : QgsGcpPoint::PointType::Destination; + QgsGeorefDataPoint *&mvPoint = pointType == QgsGcpPoint::PointType::Source ? mMovingPoint : mMovingPointQgis; if ( mvPoint ) { - mvPoint->moveTo( p, isMapPlugin ); + mvPoint->moveTo( canvasPixels, pointType ); } - } void QgsGeoreferencerMainWindow::releasePoint( QPoint p ) { Q_UNUSED( p ) - mGCPListWidget->updateGCPList(); + mGCPListWidget->updateResiduals(); // Get Map Sender if ( sender() == mToolMovePoint ) { @@ -626,7 +597,7 @@ void QgsGeoreferencerMainWindow::releasePoint( QPoint p ) } } -void QgsGeoreferencerMainWindow::showCoordDialog( const QgsPointXY &pixelCoords ) +void QgsGeoreferencerMainWindow::showCoordDialog( const QgsPointXY &sourceCoordinates ) { delete mNewlyAddedPointItem; mNewlyAddedPointItem = nullptr; @@ -634,15 +605,15 @@ void QgsGeoreferencerMainWindow::showCoordDialog( const QgsPointXY &pixelCoords // show a temporary marker at the clicked source point on the raster while we show the coordinate dialog. mNewlyAddedPointItem = new QgsGCPCanvasItem( mCanvas, nullptr, true ); mNewlyAddedPointItem->setPointColor( QColor( 0, 200, 0 ) ); - mNewlyAddedPointItem->setPos( mNewlyAddedPointItem->toCanvasCoordinates( pixelCoords ) ); + mNewlyAddedPointItem->setPos( mNewlyAddedPointItem->toCanvasCoordinates( sourceCoordinates ) ); - QgsCoordinateReferenceSystem lastProjection = mLastGCPProjection.isValid() ? mLastGCPProjection : mProjection; + QgsCoordinateReferenceSystem lastProjection = mLastGCPProjection.isValid() ? mLastGCPProjection : mTargetCrs; if ( mLayer && !mMapCoordsDialog ) { - mMapCoordsDialog = new QgsMapCoordsDialog( QgisApp::instance()->mapCanvas(), pixelCoords, lastProjection, this ); - connect( mMapCoordsDialog, &QgsMapCoordsDialog::pointAdded, this, [ = ]( const QgsPointXY & a, const QgsPointXY & b, const QgsCoordinateReferenceSystem & crs ) + mMapCoordsDialog = new QgsMapCoordsDialog( QgisApp::instance()->mapCanvas(), sourceCoordinates, lastProjection, this ); + connect( mMapCoordsDialog, &QgsMapCoordsDialog::pointAdded, this, [ = ]( const QgsPointXY & sourceLayerCoordinate, const QgsPointXY & destinationCoordinate, const QgsCoordinateReferenceSystem & destinationCrs ) { - addPoint( a, b, crs ); + addPoint( sourceLayerCoordinate, destinationCoordinate, destinationCrs ); } ); connect( mMapCoordsDialog, &QObject::destroyed, this, [ = ] { @@ -661,9 +632,10 @@ void QgsGeoreferencerMainWindow::loadGCPsDialog() if ( mGCPpointsFileName.isEmpty() ) return; - if ( !loadGCPs() ) + QString error; + if ( !loadGCPs( error ) ) { - mMessageBar->pushMessage( tr( "Load GCP Points" ), tr( "Invalid GCP file. File could not be read." ), Qgis::MessageLevel::Critical ); + mMessageBar->pushMessage( tr( "Load GCP Points" ), error, Qgis::MessageLevel::Critical ); } else { @@ -693,7 +665,6 @@ void QgsGeoreferencerMainWindow::saveGCPsDialog() saveGCPs(); } -// Settings slots void QgsGeoreferencerMainWindow::showRasterPropertiesDialog() { if ( mLayer ) @@ -727,14 +698,13 @@ void QgsGeoreferencerMainWindow::showGeorefConfigDialog() //update gcp model if ( mGCPListWidget ) { - mGCPListWidget->updateGCPList(); + mGCPListWidget->updateResiduals(); } //and status bar updateTransformParamLabel(); } } -// Histogram stretch slots void QgsGeoreferencerMainWindow::fullHistogramStretch() { mLayer->setContrastEnhancement( QgsContrastEnhancement::StretchToMinimumMaximum ); @@ -749,28 +719,12 @@ void QgsGeoreferencerMainWindow::localHistogramStretch() mCanvas->refresh(); } -// Comfort slots -void QgsGeoreferencerMainWindow::jumpToGCP( uint theGCPIndex ) +void QgsGeoreferencerMainWindow::recenterOnPoint( const QgsPointXY &point ) { - if ( static_cast( theGCPIndex ) >= mPoints.size() ) - { - return; - } - - // qgsmapcanvas doesn't seem to have a method for recentering the map - QgsRectangle ext = mCanvas->extent(); - - QgsPointXY center = ext.center(); - QgsPointXY new_center = mPoints[theGCPIndex]->pixelCoords(); - - QgsPointXY diff( new_center.x() - center.x(), new_center.y() - center.y() ); - QgsRectangle new_extent( ext.xMinimum() + diff.x(), ext.yMinimum() + diff.y(), - ext.xMaximum() + diff.x(), ext.yMaximum() + diff.y() ); - mCanvas->setExtent( new_extent ); + mCanvas->setCenter( point ); mCanvas->refresh(); } -// This slot is called whenever the georeference canvas changes the displayed extent void QgsGeoreferencerMainWindow::extentsChangedGeorefCanvas() { // Guard against endless recursion by ping-pong updates @@ -787,7 +741,7 @@ void QgsGeoreferencerMainWindow::extentsChangedGeorefCanvas() } // Reproject the georeference plugin canvas into world coordinates and fit axis aligned bounding box - QgsRectangle rectMap = mGeorefTransform.hasCrs() ? mGeorefTransform.getBoundingBox( mCanvas->extent(), true ) : mCanvas->extent(); + QgsRectangle rectMap = mGeorefTransform.transformSourceExtent( mCanvas->extent(), true ); QgsRectangle boundingBox = transformViewportBoundingBox( rectMap, mGeorefTransform, true ); mExtentsChangedRecursionGuard = true; @@ -799,7 +753,6 @@ void QgsGeoreferencerMainWindow::extentsChangedGeorefCanvas() } } -// This slot is called whenever the qgis main canvas changes the displayed extent void QgsGeoreferencerMainWindow::extentsChangedQGisCanvas() { // Guard against endless recursion by ping-pong updates @@ -818,7 +771,7 @@ void QgsGeoreferencerMainWindow::extentsChangedQGisCanvas() // Reproject the canvas into raster coordinates and fit axis aligned bounding box QgsRectangle boundingBox = transformViewportBoundingBox( QgisApp::instance()->mapCanvas()->extent(), mGeorefTransform, false ); - QgsRectangle rectMap = mGeorefTransform.hasCrs() ? mGeorefTransform.getBoundingBox( boundingBox, false ) : boundingBox; + QgsRectangle rectMap = mGeorefTransform.transformSourceExtent( boundingBox, false ); mExtentsChangedRecursionGuard = true; // Just set the whole extent for now @@ -836,7 +789,6 @@ void QgsGeoreferencerMainWindow::updateCanvasRotation() mCanvas->refresh(); } -// Canvas info slots (copy/pasted from QGIS :) ) void QgsGeoreferencerMainWindow::showMouseCoords( const QgsPointXY &p ) { mCoordsLabel->setText( p.toString( mMousePrecisionDecimalPlaces ) ); @@ -876,8 +828,6 @@ void QgsGeoreferencerMainWindow::updateMouseCoordinatePrecision() mMousePrecisionDecimalPlaces = dp; } -// ------------------------------ private ---------------------------------- // -// Gui void QgsGeoreferencerMainWindow::createActions() { // File actions @@ -931,10 +881,10 @@ void QgsGeoreferencerMainWindow::createActions() connect( mActionZoomNext, &QAction::triggered, this, &QgsGeoreferencerMainWindow::zoomToNext ); mActionLinkGeorefToQgis->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/georeferencer/mActionLinkGeorefToQgis.png" ) ) ); - connect( mActionLinkGeorefToQgis, &QAction::triggered, this, &QgsGeoreferencerMainWindow::linkGeorefToQgis ); + connect( mActionLinkGeorefToQgis, &QAction::toggled, this, &QgsGeoreferencerMainWindow::linkGeorefToQgis ); mActionLinkQGisToGeoref->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/georeferencer/mActionLinkQGisToGeoref.png" ) ) ); - connect( mActionLinkQGisToGeoref, &QAction::triggered, this, &QgsGeoreferencerMainWindow::linkQGisToGeoref ); + connect( mActionLinkQGisToGeoref, &QAction::toggled, this, &QgsGeoreferencerMainWindow::linkQGisToGeoref ); // Settings actions mActionRasterProperties->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mActionRasterProperties.png" ) ) ); @@ -952,7 +902,7 @@ void QgsGeoreferencerMainWindow::createActions() connect( mActionFullHistogramStretch, &QAction::triggered, this, &QgsGeoreferencerMainWindow::fullHistogramStretch ); mActionFullHistogramStretch->setEnabled( false ); - mActionQuit->setShortcuts( QList() << QKeySequence( Qt::CTRL + Qt::Key_Q ) + mActionQuit->setShortcuts( QList() << QKeySequence( QStringLiteral( "CTRL+Q" ) ) << QKeySequence( Qt::Key_Escape ) ); connect( mActionQuit, &QAction::triggered, this, &QWidget::close ); } @@ -1052,7 +1002,6 @@ void QgsGeoreferencerMainWindow::createMenus() mPanelMenu = new QMenu( tr( "Panels" ) ); mPanelMenu->setObjectName( QStringLiteral( "mPanelMenu" ) ); mPanelMenu->addAction( dockWidgetGCPpoints->toggleViewAction() ); - // mPanelMenu->addAction(dockWidgetLogView->toggleViewAction()); mToolbarMenu = new QMenu( tr( "Toolbars" ) ); mToolbarMenu->setObjectName( QStringLiteral( "mToolbarMenu" ) ); @@ -1072,7 +1021,7 @@ void QgsGeoreferencerMainWindow::createMenus() menuView->addMenu( mPanelMenu ); menuView->addMenu( mToolbarMenu ); } - else // if ( layout == QDialogButtonBox::KdeLayout ) + else { menuSettings->addSeparator(); menuSettings->addMenu( mPanelMenu ); @@ -1082,20 +1031,11 @@ void QgsGeoreferencerMainWindow::createMenus() void QgsGeoreferencerMainWindow::createDockWidgets() { - // mLogViewer = new QPlainTextEdit; - // mLogViewer->setReadOnly(true); - // mLogViewer->setWordWrapMode(QTextOption::NoWrap); - // dockWidgetLogView->setWidget(mLogViewer); - mGCPListWidget = new QgsGCPListWidget( this ); mGCPListWidget->setGeorefTransform( &mGeorefTransform ); dockWidgetGCPpoints->setWidget( mGCPListWidget ); - connect( mGCPListWidget, &QgsGCPListWidget::jumpToGCP, this, &QgsGeoreferencerMainWindow::jumpToGCP ); -#if 0 - connect( mGCPListWidget, SIGNAL( replaceDataPoint( QgsGeorefDataPoint *, int ) ), - this, SLOT( replaceDataPoint( QgsGeorefDataPoint *, int ) ) ); -#endif + connect( mGCPListWidget, &QgsGCPListWidget::jumpToGCP, this, &QgsGeoreferencerMainWindow::recenterOnPoint ); connect( mGCPListWidget, static_cast( &QgsGCPListWidget::deleteDataPoint ), this, static_cast( &QgsGeoreferencerMainWindow::deleteDataPoint ) ); connect( mGCPListWidget, &QgsGCPListWidget::pointEnabled, this, &QgsGeoreferencerMainWindow::updateGeorefTransform ); @@ -1176,7 +1116,6 @@ void QgsGeoreferencerMainWindow::removeOldLayer() mCanvas->refresh(); } -// Mapcanvas Plugin void QgsGeoreferencerMainWindow::addRaster( const QString &file ) { QgsRasterLayer::LayerOptions options; @@ -1184,6 +1123,19 @@ void QgsGeoreferencerMainWindow::addRaster( const QString &file ) options.skipCrsValidation = true; mLayer = std::make_unique< QgsRasterLayer >( file, QStringLiteral( "Raster" ), QStringLiteral( "gdal" ), options ); + // guess a reasonable target CRS to use by default + if ( mLayer->crs().isValid() ) + { + // if source raster already is already georeferenced, assume we'll be keeping the same CRS + mTargetCrs = mLayer->crs(); + } + // otherwise use the previous target crs, unless that's never been set + else if ( !mTargetCrs.isValid() ) + { + // in which case we'll use the current project CRS + mTargetCrs = QgsProject::instance()->crs(); + } + // add layer to map canvas mCanvas->setLayers( QList() << mLayer.get() ); @@ -1191,7 +1143,7 @@ void QgsGeoreferencerMainWindow::addRaster( const QString &file ) mActionFullHistogramStretch->setEnabled( true ); // Status Bar - if ( mGeorefTransform.hasCrs() ) + if ( mGeorefTransform.hasExistingGeoreference() ) { QString authid = mLayer->crs().authid(); mEPSG->setText( authid ); @@ -1204,7 +1156,6 @@ void QgsGeoreferencerMainWindow::addRaster( const QString &file ) } } -// Settings void QgsGeoreferencerMainWindow::readSettings() { QgsSettings s; @@ -1235,57 +1186,26 @@ void QgsGeoreferencerMainWindow::writeSettings() s.setValue( QStringLiteral( "/Plugin-GeoReferencer/usezerofortrans" ), mUseZeroForTrans ); } -// GCP points -bool QgsGeoreferencerMainWindow::loadGCPs( /*bool verbose*/ ) +bool QgsGeoreferencerMainWindow::loadGCPs( QString &error ) { - QFile pointFile( mGCPpointsFileName ); - if ( !pointFile.open( QIODevice::ReadOnly ) ) - { + QgsCoordinateReferenceSystem actualDestinationCrs; + const QList< QgsGcpPoint > points = QgsGCPList::loadGcps( mGCPpointsFileName, + mTargetCrs, + actualDestinationCrs, + error ); + if ( !error.isEmpty() ) return false; - } + mTargetCrs = actualDestinationCrs; clearGCPData(); + mGCPListWidget->setTargetCrs( actualDestinationCrs, QgsProject::instance()->transformContext() ); - QTextStream points( &pointFile ); - QString line = points.readLine(); - int i = 0; - QgsCoordinateReferenceSystem proj; - if ( line.contains( "#CRS: " ) ) + for ( const QgsGcpPoint &point : points ) { - proj = QgsCoordinateReferenceSystem( line.remove( "#CRS: " ) ); - line = points.readLine(); + addPoint( point.sourcePoint(), point.destinationPoint(), point.destinationPointCrs(), point.isEnabled(), false ); } - else - proj = QgsProject::instance()->crs(); - - while ( !points.atEnd() ) - { - line = points.readLine(); - QStringList ls; - if ( line.contains( ',' ) ) // in previous format "\t" is delimiter of points in new - "," - ls = line.split( ',' ); // points from new georeferencer - else - ls = line.split( '\t' ); // points from prev georeferencer - - if ( ls.count() < 4 ) - return false; - - QgsPointXY mapCoords( ls.at( 0 ).toDouble(), ls.at( 1 ).toDouble() ); // map x,y - QgsPointXY pixelCoords( ls.at( 2 ).toDouble(), ls.at( 3 ).toDouble() ); // pixel x,y - if ( ls.count() == 5 ) - { - bool enable = ls.at( 4 ).toInt(); - addPoint( pixelCoords, mapCoords, proj, enable, false ); - } - else - addPoint( pixelCoords, mapCoords, proj, true, false ); - - ++i; - } - - mInitialPoints = mPoints; - // showMessageInLog(tr("GCP points loaded from"), mGCPpointsFileName); + mSavedPoints = mPoints.asPoints(); if ( mGCPsDirty ) { mGCPListWidget->setGCPList( &mPoints ); @@ -1299,35 +1219,15 @@ bool QgsGeoreferencerMainWindow::loadGCPs( /*bool verbose*/ ) void QgsGeoreferencerMainWindow::saveGCPs() { - QFile pointFile( mGCPpointsFileName ); - if ( pointFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) ) + QString error; + if ( mPoints.saveGcps( mGCPpointsFileName, mTargetCrs, QgsProject::instance()->transformContext(), error ) ) { - QTextStream points( &pointFile ); - points << QStringLiteral( "#CRS: %1" ).arg( mProjection.toWkt( QgsCoordinateReferenceSystem::WKT_PREFERRED ) ) << endl; - points << "mapX,mapY,pixelX,pixelY,enable,dX,dY,residual" << endl; - for ( QgsGeorefDataPoint *pt : std::as_const( mPoints ) ) - { - points << QStringLiteral( "%1,%2,%3,%4,%5,%6,%7,%8" ) - .arg( qgsDoubleToString( pt->transCoords().x() ), - qgsDoubleToString( pt->transCoords().y() ), - qgsDoubleToString( pt->pixelCoords().x() ), - qgsDoubleToString( pt->pixelCoords().y() ) ) - .arg( pt->isEnabled() ) - .arg( qgsDoubleToString( pt->residual().x() ), - qgsDoubleToString( pt->residual().y() ), - qgsDoubleToString( std::sqrt( pt->residual().x() * pt->residual().x() + pt->residual().y() * pt->residual().y() ) ) ) - << endl; - } - - mInitialPoints = mPoints; + mSavedPoints = mPoints.asPoints(); } else { - mMessageBar->pushMessage( tr( "Write Error" ), tr( "Could not write to GCP points file %1." ).arg( mGCPpointsFileName ), Qgis::MessageLevel::Critical ); - return; + mMessageBar->pushMessage( tr( "Write Error" ), error, Qgis::MessageLevel::Critical ); } - - // showMessageInLog(tr("GCP points saved in"), mGCPpointsFileName); } QgsGeoreferencerMainWindow::SaveGCPs QgsGeoreferencerMainWindow::checkNeedGCPSave() @@ -1335,7 +1235,7 @@ QgsGeoreferencerMainWindow::SaveGCPs QgsGeoreferencerMainWindow::checkNeedGCPSav if ( 0 == mPoints.count() ) return QgsGeoreferencerMainWindow::GCPDISCARD; - if ( !equalGCPlists( mInitialPoints, mPoints ) ) + if ( !equalGCPlists( mSavedPoints, mPoints ) ) { QMessageBox::StandardButton a = QMessageBox::question( this, tr( "Save GCPs" ), tr( "Save GCP points?" ), @@ -1349,23 +1249,17 @@ QgsGeoreferencerMainWindow::SaveGCPs QgsGeoreferencerMainWindow::checkNeedGCPSav { return QgsGeoreferencerMainWindow::GCPCANCEL; } - else if ( a == QMessageBox::Discard ) - { - return QgsGeoreferencerMainWindow::GCPDISCARD; - } } - - return QgsGeoreferencerMainWindow::GCPSILENTSAVE; + return QgsGeoreferencerMainWindow::GCPDISCARD; } -// Georeference bool QgsGeoreferencerMainWindow::georeference() { if ( !checkReadyGeoref() ) return false; - if ( mModifiedRasterFileName.isEmpty() && ( QgsGcpTransformerInterface::TransformMethod::Linear == mGeorefTransform.transformParametrisation() || - QgsGcpTransformerInterface::TransformMethod::Helmert == mGeorefTransform.transformParametrisation() ) ) + if ( mCreateWorldFileOnly && ( QgsGcpTransformerInterface::TransformMethod::Linear == mGeorefTransform.transformParametrisation() || + QgsGcpTransformerInterface::TransformMethod::Helmert == mGeorefTransform.transformParametrisation() ) ) { QgsPointXY origin; double pixelXSize, pixelYSize, rotation; @@ -1411,11 +1305,11 @@ bool QgsGeoreferencerMainWindow::georeference() } return true; } - else // Helmert, Polinom 1, Polinom 2, Polinom 3 + else { QgsImageWarper warper( this ); int res = warper.warpFile( mRasterFileName, mModifiedRasterFileName, mGeorefTransform, - mResamplingMethod, mUseZeroForTrans, mCompressionMethod, mProjection, mUserResX, mUserResY ); + mResamplingMethod, mUseZeroForTrans, mCompressionMethod, mTargetCrs, mUserResX, mUserResY ); if ( res == 0 ) // fault to compute GCP transform { //TODO: be more specific in the error message @@ -1438,9 +1332,9 @@ bool QgsGeoreferencerMainWindow::georeference() { writePDFMapFile( mPdfOutputMapFile, mGeorefTransform ); } - if ( !mSaveGcp.isEmpty() ) + if ( mSaveGcp ) { - mGCPpointsFileName = mModifiedRasterFileName + QLatin1String( ".points" ); + mGCPpointsFileName = mRasterFileName + QLatin1String( ".points" ); saveGCPs(); } return true; @@ -1786,8 +1680,11 @@ bool QgsGeoreferencerMainWindow::writePDFReportFile( const QString &fileName, co { currentGCPStrings << tr( "no" ); } - currentGCPStrings << QString::number( ( *gcpIt )->pixelCoords().x(), 'f', 0 ) << QString::number( ( *gcpIt )->pixelCoords().y(), 'f', 0 ) << QString::number( ( *gcpIt )->transCoords().x(), 'f', 3 ) - << QString::number( ( *gcpIt )->transCoords().y(), 'f', 3 ) << QString::number( residual.x() ) << QString::number( residual.y() ) << QString::number( residualTot ); + + const QgsPointXY transformedDestinationPoint = ( *gcpIt )->transformedDestinationPoint( mTargetCrs, QgsProject::instance()->transformContext() ); + + currentGCPStrings << QString::number( ( *gcpIt )->sourcePoint().x(), 'f', 0 ) << QString::number( ( *gcpIt )->sourcePoint().y(), 'f', 0 ) << QString::number( transformedDestinationPoint.x(), 'f', 3 ) + << QString::number( transformedDestinationPoint.y(), 'f', 3 ) << QString::number( residual.x() ) << QString::number( residual.y() ) << QString::number( residualTot ); gcpTableContents << currentGCPStrings; } @@ -1847,7 +1744,6 @@ void QgsGeoreferencerMainWindow::updateTransformParamLabel() mTransformParamLabel->setText( labelString ); } -// Gdal script void QgsGeoreferencerMainWindow::showGDALScript( const QStringList &commands ) { QString script = commands.join( QLatin1Char( '\n' ) ) + '\n'; @@ -1892,8 +1788,9 @@ QString QgsGeoreferencerMainWindow::generateGDALtranslateCommand( bool generateT for ( QgsGeorefDataPoint *pt : std::as_const( mPoints ) ) { - gdalCommand << QStringLiteral( "-gcp %1 %2 %3 %4" ).arg( pt->pixelCoords().x() ).arg( -pt->pixelCoords().y() ) - .arg( pt->transCoords().x() ).arg( pt->transCoords().y() ); + const QgsPointXY transformedDestinationPoint = pt->transformedDestinationPoint( mTargetCrs, QgsProject::instance()->transformContext() ); + gdalCommand << QStringLiteral( "-gcp %1 %2 %3 %4" ).arg( pt->sourcePoint().x() ).arg( -pt->sourcePoint().y() ) + .arg( transformedDestinationPoint.x() ).arg( transformedDestinationPoint.y() ); } QFileInfo rasterFileInfo( mRasterFileName ); @@ -1926,13 +1823,13 @@ QString QgsGeoreferencerMainWindow::generateGDALwarpCommand( const QString &resa gdalCommand << QStringLiteral( "-tr" ) << QString::number( targetResX, 'f' ) << QString::number( targetResY, 'f' ); } - if ( mProjection.authid().startsWith( QStringLiteral( "EPSG:" ), Qt::CaseInsensitive ) ) + if ( mTargetCrs.authid().startsWith( QStringLiteral( "EPSG:" ), Qt::CaseInsensitive ) ) { - gdalCommand << QStringLiteral( "-t_srs %1" ).arg( mProjection.authid() ); + gdalCommand << QStringLiteral( "-t_srs %1" ).arg( mTargetCrs.authid() ); } else { - gdalCommand << QStringLiteral( "-t_srs \"%1\"" ).arg( mProjection.toProj().simplified() ); + gdalCommand << QStringLiteral( "-t_srs \"%1\"" ).arg( mTargetCrs.toProj().simplified() ); } gdalCommand << QStringLiteral( "\"%1\"" ).arg( mTranslatedRasterFileName ) << QStringLiteral( "\"%1\"" ).arg( mModifiedRasterFileName ); @@ -1940,20 +1837,6 @@ QString QgsGeoreferencerMainWindow::generateGDALwarpCommand( const QString &resa return gdalCommand.join( QLatin1Char( ' ' ) ); } -// Log -//void QgsGeorefPluginGui::showMessageInLog(const QString &description, const QString &msg) -//{ -// QString logItem = QString("%1: %2").arg(description).arg(msg); -// -// mLogViewer->appendHtml(logItem); -//} -// -//void QgsGeorefPluginGui::clearLog() -//{ -// mLogViewer->clear(); -//} - -// Helpers bool QgsGeoreferencerMainWindow::checkReadyGeoref() { if ( mRasterFileName.isEmpty() ) @@ -1969,8 +1852,8 @@ bool QgsGeoreferencerMainWindow::checkReadyGeoref() return false; } - //MH: helmert transformation without warping disabled until qgis is able to read rotated rasters efficiently - if ( mModifiedRasterFileName.isEmpty() && QgsGcpTransformerInterface::TransformMethod::Linear != mTransformParam /*&& QgsGeorefTransform::Helmert != mTransformParam*/ ) + if ( mCreateWorldFileOnly + && ( QgsGcpTransformerInterface::TransformMethod::Linear != mTransformParam && QgsGcpTransformerInterface::TransformMethod::Helmert != mTransformParam ) ) { QMessageBox::information( this, tr( "Georeferencer" ), tr( "Please set output raster name." ) ); getTransformSettings(); @@ -1989,7 +1872,6 @@ bool QgsGeoreferencerMainWindow::checkReadyGeoref() if ( !updateGeorefTransform() ) { mMessageBar->pushMessage( tr( "Transform Failed" ), tr( "Failed to compute GCP transform: Transform is not solvable." ), Qgis::MessageLevel::Critical ); - // logRequaredGCPs(); return false; } @@ -1998,14 +1880,15 @@ bool QgsGeoreferencerMainWindow::checkReadyGeoref() bool QgsGeoreferencerMainWindow::updateGeorefTransform() { - QVector mapCoords, pixelCoords; + QVector sourceCoordinates; + QVector destinationCoords; if ( mGCPListWidget->gcpList() ) - mGCPListWidget->gcpList()->createGCPVectors( mapCoords, pixelCoords, mProjection ); + mGCPListWidget->gcpList()->createGCPVectors( sourceCoordinates, destinationCoords, mTargetCrs, QgsProject::instance()->transformContext() ); else return false; // Parametrize the transform with GCPs - if ( !mGeorefTransform.updateParametersFromGcps( pixelCoords, mapCoords, true ) ) + if ( !mGeorefTransform.updateParametersFromGcps( sourceCoordinates, destinationCoords, true ) ) { return false; } @@ -2127,7 +2010,7 @@ bool QgsGeoreferencerMainWindow::checkFileExisting( const QString &fileName, con return true; } -bool QgsGeoreferencerMainWindow::equalGCPlists( const QgsGCPList &list1, const QgsGCPList &list2 ) +bool QgsGeoreferencerMainWindow::equalGCPlists( const QList< QgsGcpPoint > &list1, const QgsGCPList &list2 ) { if ( list1.count() != list2.count() ) return false; @@ -2136,38 +2019,15 @@ bool QgsGeoreferencerMainWindow::equalGCPlists( const QgsGCPList &list1, const Q int j = 0; for ( int i = 0; i < count; ++i, ++j ) { - QgsGeorefDataPoint *p1 = list1.at( i ); - QgsGeorefDataPoint *p2 = list2.at( j ); - if ( p1->pixelCoords() != p2->pixelCoords() ) - return false; - - if ( p1->mapCoords() != p2->mapCoords() ) + const QgsGcpPoint p1 = list1.at( i ); + const QgsGcpPoint p2 = list2.at( j )->point(); + if ( p1 != p2 ) return false; } return true; } -//void QgsGeorefPluginGui::logTransformOptions() -//{ -// showMessageInLog(tr("Interpolation"), convertResamplingEnumToString(mResamplingMethod)); -// showMessageInLog(tr("Compression method"), mCompressionMethod); -// showMessageInLog(tr("Zero for transparency"), mUseZeroForTrans ? "true" : "false"); -//} -// -//void QgsGeorefPluginGui::logRequaredGCPs() -//{ -// if (mGeorefTransform.minimumGcpCount() != 0) -// { -// if ((uint)mPoints.size() >= mGeorefTransform.minimumGcpCount()) -// showMessageInLog(tr("Info"), tr("For georeferencing requared at least %1 GCP points") -// .arg(mGeorefTransform.minimumGcpCount())); -// else -// showMessageInLog(tr("Critical"), tr("For georeferencing requared at least %1 GCP points") -// .arg(mGeorefTransform.minimumGcpCount())); -// } -//} - void QgsGeoreferencerMainWindow::clearGCPData() { //force all list widget editors to close before removing data points @@ -2176,7 +2036,7 @@ void QgsGeoreferencerMainWindow::clearGCPData() qDeleteAll( mPoints ); mPoints.clear(); - mGCPListWidget->updateGCPList(); + mGCPListWidget->setGCPList( &mPoints ); delete mNewlyAddedPointItem; mNewlyAddedPointItem = nullptr; @@ -2191,7 +2051,6 @@ void QgsGeoreferencerMainWindow::invalidateCanvasCoords() for ( int i = 0; i < count; ++i, ++j ) { QgsGeorefDataPoint *p = mPoints.at( i ); - p->setCanvasCoords( QgsPointXY() ); p->updateCoords(); } } diff --git a/src/app/georeferencer/qgsgeorefmainwindow.h b/src/app/georeferencer/qgsgeorefmainwindow.h index 47952d91f841..dee1f37d3e45 100644 --- a/src/app/georeferencer/qgsgeorefmainwindow.h +++ b/src/app/georeferencer/qgsgeorefmainwindow.h @@ -45,6 +45,7 @@ class QgsGeorefToolDeletePoint; class QgsGeorefToolMovePoint; class QgsGeorefToolMovePoint; class QgsGCPCanvasItem; +class QgsGcpPoint; class QgsGeorefDockWidget : public QgsDockWidget { @@ -90,14 +91,24 @@ class QgsGeoreferencerMainWindow : public QMainWindow, private Ui::QgsGeorefPlug void linkQGisToGeoref( bool link ); // gcps - void addPoint( const QgsPointXY &pixelCoords, const QgsPointXY &mapCoords, - const QgsCoordinateReferenceSystem &crs, bool enable = true, bool finalize = true ); + + /** + * Adds a new reference point. + * \param sourceCoords MUST be in source layer coordinates, e.g. if source is already georeferenced then it is in layer coordinates NOT pixels + * \param destinationMapCoords + * \param destinationCrs + * \param enable + * \param finalize + */ + void addPoint( const QgsPointXY &sourceCoords, const QgsPointXY &destinationMapCoords, + const QgsCoordinateReferenceSystem &destinationCrs, bool enable = true, bool finalize = true ); + void deleteDataPoint( QPoint pixelCoords ); void deleteDataPoint( int index ); - void showCoordDialog( const QgsPointXY &pixelCoords ); + void showCoordDialog( const QgsPointXY &sourceCoordinates ); void selectPoint( QPoint ); - void movePoint( QPoint ); + void movePoint( QPoint canvasPixels ); void releasePoint( QPoint ); void loadGCPsDialog(); @@ -108,7 +119,7 @@ class QgsGeoreferencerMainWindow : public QMainWindow, private Ui::QgsGeorefPlug void showGeorefConfigDialog(); // comfort - void jumpToGCP( uint theGCPIndex ); + void recenterOnPoint( const QgsPointXY &point ); void extentsChangedGeorefCanvas(); void extentsChangedQGisCanvas(); void updateCanvasRotation(); @@ -128,7 +139,6 @@ class QgsGeoreferencerMainWindow : public QMainWindow, private Ui::QgsGeorefPlug enum SaveGCPs { GCPSAVE, - GCPSILENTSAVE, GCPDISCARD, GCPCANCEL }; @@ -152,7 +162,7 @@ class QgsGeoreferencerMainWindow : public QMainWindow, private Ui::QgsGeorefPlug void writeSettings(); // gcp points - bool loadGCPs( /*bool verbose = true*/ ); + bool loadGCPs( QString &error ); void saveGCPs(); QgsGeoreferencerMainWindow::SaveGCPs checkNeedGCPSave(); @@ -183,7 +193,7 @@ class QgsGeoreferencerMainWindow : public QMainWindow, private Ui::QgsGeorefPlug int polynomialOrder( QgsGeorefTransform::TransformMethod transform ); QString guessWorldFileName( const QString &rasterFileName ); bool checkFileExisting( const QString &fileName, const QString &title, const QString &question ); - bool equalGCPlists( const QgsGCPList &list1, const QgsGCPList &list2 ); + bool equalGCPlists( const QList &list1, const QgsGCPList &list2 ); void logTransformOptions(); void logRequaredGCPs(); void clearGCPData(); @@ -222,20 +232,22 @@ class QgsGeoreferencerMainWindow : public QMainWindow, private Ui::QgsGeorefPlug QString mWorldFileName; QString mTranslatedRasterFileName; QString mGCPpointsFileName; - QgsCoordinateReferenceSystem mProjection; + QgsCoordinateReferenceSystem mTargetCrs; QgsCoordinateReferenceSystem mLastGCPProjection; QString mPdfOutputFile; QString mPdfOutputMapFile; - QString mSaveGcp; + bool mSaveGcp = false; double mUserResX, mUserResY; // User specified target scale QgsGcpTransformerInterface::TransformMethod mTransformParam = QgsGcpTransformerInterface::TransformMethod::InvalidTransform; QgsImageWarper::ResamplingMethod mResamplingMethod; QgsGeorefTransform mGeorefTransform; QString mCompressionMethod; + bool mCreateWorldFileOnly = false; QgsGCPList mPoints; - QgsGCPList mInitialPoints; + QList< QgsGcpPoint > mSavedPoints; + QgsMapCanvas *mCanvas = nullptr; std::unique_ptr< QgsRasterLayer > mLayer; diff --git a/src/app/georeferencer/qgsgeoreftooladdpoint.h b/src/app/georeferencer/qgsgeoreftooladdpoint.h index 1bf4bc608ec2..1be56cbeb717 100644 --- a/src/app/georeferencer/qgsgeoreftooladdpoint.h +++ b/src/app/georeferencer/qgsgeoreftooladdpoint.h @@ -33,7 +33,7 @@ class QgsGeorefToolAddPoint : public QgsMapToolEmitPoint void canvasPressEvent( QgsMapMouseEvent *e ) override; signals: - void showCoordDialog( const QgsPointXY & ); + void showCoordDialog( const QgsPointXY &sourceCoordinates ); }; #endif // QGSGEOREFTOOLADDPOINT_H diff --git a/src/app/georeferencer/qgsgeoreftransform.cpp b/src/app/georeferencer/qgsgeoreftransform.cpp index df254f1b4d02..aba6313c8914 100644 --- a/src/app/georeferencer/qgsgeoreftransform.cpp +++ b/src/app/georeferencer/qgsgeoreftransform.cpp @@ -53,9 +53,14 @@ void QgsGeorefTransform::selectTransformParametrisation( TransformMethod paramet } } -void QgsGeorefTransform::setRasterChangeCoords( const QString &fileRaster ) +void QgsGeorefTransform::loadRaster( const QString &fileRaster ) { - mRasterChangeCoords.setRaster( fileRaster ); + mRasterChangeCoords.loadRaster( fileRaster ); +} + +QgsPointXY QgsGeorefTransform::toSourceCoordinate( const QgsPointXY &pixel ) const +{ + return mRasterChangeCoords.toXY( pixel ); } bool QgsGeorefTransform::providesAccurateInverseTransformation() const @@ -95,10 +100,10 @@ bool QgsGeorefTransform::updateParametersFromGcps( const QVector &so { return false; } - if ( mRasterChangeCoords.hasCrs() ) + if ( mRasterChangeCoords.hasExistingGeoreference() ) { - const QVector pixelCoordsCorrect = mRasterChangeCoords.getPixelCoords( sourceCoordinates ); - mParametersInitialized = mGeorefTransformImplementation->updateParametersFromGcps( sourceCoordinates, pixelCoordsCorrect, invertYAxis ); + const QVector sourcePixelCoordinates = mRasterChangeCoords.getPixelCoords( sourceCoordinates ); + mParametersInitialized = mGeorefTransformImplementation->updateParametersFromGcps( sourcePixelCoordinates, destinationCoordinates, invertYAxis ); } else { @@ -131,12 +136,12 @@ bool QgsGeorefTransform::transformRasterToWorld( const QgsPointXY &raster, QgsPo { // flip y coordinate due to different CS orientation const QgsPointXY raster_flipped( raster.x(), -raster.y() ); - return gdal_transform( raster_flipped, world, 0 ); + return transformPrivate( raster_flipped, world, false ); } bool QgsGeorefTransform::transformWorldToRaster( const QgsPointXY &world, QgsPointXY &raster ) { - const bool success = gdal_transform( world, raster, 1 ); + const bool success = transformPrivate( world, raster, true ); // flip y coordinate due to different CS orientation raster.setY( -raster.y() ); return success; @@ -186,13 +191,13 @@ bool QgsGeorefTransform::getOriginScaleRotation( QgsPointXY &origin, double &sca } -bool QgsGeorefTransform::gdal_transform( const QgsPointXY &src, QgsPointXY &dst, int dstToSrc ) const +bool QgsGeorefTransform::transformPrivate( const QgsPointXY &src, QgsPointXY &dst, bool inverseTransform ) const { // Copy the source coordinate for inplace transform double x = src.x(); double y = src.y(); - if ( !QgsGcpTransformerInterface::transform( x, y, dstToSrc == 1 ) ) + if ( !QgsGcpTransformerInterface::transform( x, y, inverseTransform ) ) return false; dst.setX( x ); diff --git a/src/app/georeferencer/qgsgeoreftransform.h b/src/app/georeferencer/qgsgeoreftransform.h index 9d67f118f9e8..2bbf93cf6a76 100644 --- a/src/app/georeferencer/qgsgeoreftransform.h +++ b/src/app/georeferencer/qgsgeoreftransform.h @@ -18,6 +18,7 @@ #define QGSGEOREFTRANSFORM_H #include +#include "qgis_app.h" #include "qgspoint.h" #include "qgsgcptransformer.h" #include "qgsrasterchangecoords.h" @@ -33,7 +34,7 @@ * Delegates to concrete implementations of \ref QgsGeorefInterface. For exception safety, * this is preferred over using the subclasses directly. */ -class QgsGeorefTransform : public QgsGcpTransformerInterface +class APP_EXPORT QgsGeorefTransform : public QgsGcpTransformerInterface { public: @@ -47,18 +48,30 @@ class QgsGeorefTransform : public QgsGcpTransformerInterface void selectTransformParametrisation( TransformMethod parametrisation ); /** - * Setting the mRasterChangeCoords for change type coordinate(map for pixel). + * Loads an existing raster image so that the source pixel to source layer conversion + * can be correctly initialized. */ - void setRasterChangeCoords( const QString &fileRaster ); + void loadRaster( const QString &fileRaster ); - //! \returns Whether has Coordinate Reference Systems in image - bool hasCrs() const { return mRasterChangeCoords.hasCrs(); } + //! \returns Whether has image already has existing georeference + bool hasExistingGeoreference() const { return mRasterChangeCoords.hasExistingGeoreference(); } - //! \returns Coordinates of image - QgsPointXY toColumnLine( const QgsPointXY &pntMap ) { return mRasterChangeCoords.toColumnLine( pntMap ); } + /** + * Returns the pixel coordinate from the source image given a layer coordinate from the source image. + * \see toSourceCoordinate() + */ + QgsPointXY toSourcePixel( const QgsPointXY &pntMap ) const { return mRasterChangeCoords.toColumnLine( pntMap ); } + + /** + * Returns the layer coordinate from the source image given a pixel coordinate from the source image. + * \see toSourcePixel() + */ + QgsPointXY toSourceCoordinate( const QgsPointXY &pixel ) const; - //! \returns Bounding box of image(transform to coordinate of Map or Image ) - QgsRectangle getBoundingBox( const QgsRectangle &rect, bool toPixel ) { return mRasterChangeCoords.getBoundingBox( rect, toPixel ); } + /** + * Transforms a bounding box of the source image from source coordinates to source pixels or vice versa. + */ + QgsRectangle transformSourceExtent( const QgsRectangle &rect, bool toPixel ) const { return mRasterChangeCoords.transformExtent( rect, toPixel ); } //! \brief The transform parametrisation currently in use. TransformMethod transformParametrisation() const; @@ -109,8 +122,7 @@ class QgsGeorefTransform : public QgsGcpTransformerInterface QgsGeorefTransform( const QgsGeorefTransform &other ); QgsGeorefTransform &operator= ( const QgsGeorefTransform & ) = delete; - // convenience wrapper around GDALTransformerFunc - bool gdal_transform( const QgsPointXY &src, QgsPointXY &dst, int dstToSrc ) const; + bool transformPrivate( const QgsPointXY &src, QgsPointXY &dst, bool inverseTransform ) const; QVector mSourceCoordinates; QVector mDestinationCoordinates; @@ -121,6 +133,8 @@ class QgsGeorefTransform : public QgsGcpTransformerInterface TransformMethod mTransformParametrisation = TransformMethod::InvalidTransform; bool mParametersInitialized = false; QgsRasterChangeCoords mRasterChangeCoords; + + friend class TestQgsGeoreferencer; }; #endif //QGSGEOREFTRANSFORM_H diff --git a/src/app/georeferencer/qgsmapcoordsdialog.cpp b/src/app/georeferencer/qgsmapcoordsdialog.cpp index ac9269d25c52..a0363d439ac7 100644 --- a/src/app/georeferencer/qgsmapcoordsdialog.cpp +++ b/src/app/georeferencer/qgsmapcoordsdialog.cpp @@ -26,11 +26,11 @@ #include "qgsproject.h" #include "qgsgcpcanvasitem.h" -QgsMapCoordsDialog::QgsMapCoordsDialog( QgsMapCanvas *qgisCanvas, const QgsPointXY &pixelCoords, QgsCoordinateReferenceSystem &rasterCrs, QWidget *parent ) +QgsMapCoordsDialog::QgsMapCoordsDialog( QgsMapCanvas *qgisCanvas, const QgsPointXY &sourceLayerCoordinates, QgsCoordinateReferenceSystem &rasterCrs, QWidget *parent ) : QDialog( parent, Qt::Dialog ) , mQgisCanvas( qgisCanvas ) , mRasterCrs( rasterCrs ) - , mPixelCoords( pixelCoords ) + , mSourceLayerCoordinates( sourceLayerCoordinates ) { setupUi( this ); QgsGui::enableAutoGeometryRestore( this ); @@ -103,7 +103,7 @@ void QgsMapCoordsDialog::buttonBox_accepted() if ( !ok ) y = dmsToDD( leYCoord->text() ); - emit pointAdded( mPixelCoords, QgsPointXY( x, y ), mProjectionSelector->crs().isValid() ? mProjectionSelector->crs() : mRasterCrs ); + emit pointAdded( mSourceLayerCoordinates, QgsPointXY( x, y ), mProjectionSelector->crs().isValid() ? mProjectionSelector->crs() : mRasterCrs ); close(); } diff --git a/src/app/georeferencer/qgsmapcoordsdialog.h b/src/app/georeferencer/qgsmapcoordsdialog.h index 8f942fd554e7..e72ba08ade36 100644 --- a/src/app/georeferencer/qgsmapcoordsdialog.h +++ b/src/app/georeferencer/qgsmapcoordsdialog.h @@ -60,7 +60,15 @@ class QgsMapCoordsDialog : public QDialog, private Ui::QgsMapCoordsDialogBase Q_OBJECT public: - QgsMapCoordsDialog( QgsMapCanvas *qgisCanvas, const QgsPointXY &pixelCoords, QgsCoordinateReferenceSystem &rasterCrs, QWidget *parent = nullptr ); + + /** + * Constructor for QgsMapCoordsDialog. + * \param qgisCanvas + * \param sourceCoordinates must be in source layer coordinates, NOT pixels (unless source image is completely non-referenced)! + * \param rasterCrs + * \param parent + */ + QgsMapCoordsDialog( QgsMapCanvas *qgisCanvas, const QgsPointXY &sourceCoordinates, QgsCoordinateReferenceSystem &rasterCrs, QWidget *parent = nullptr ); ~QgsMapCoordsDialog() override; private slots: @@ -73,7 +81,14 @@ class QgsMapCoordsDialog : public QDialog, private Ui::QgsMapCoordsDialogBase void setPrevTool(); signals: - void pointAdded( const QgsPointXY &a, const QgsPointXY &b, const QgsCoordinateReferenceSystem &crs ); + + /** + * Emitted when a point should be added through the dialog. + * \param sourceCoordinate source point, which MUST be in source layer coordinates not pixels + * \param destinationCoordinate + * \param destinationCrs + */ + void pointAdded( const QgsPointXY &sourceCoordinate, const QgsPointXY &destinationCoordinate, const QgsCoordinateReferenceSystem &destinationCrs ); private: double dmsToDD( const QString &dms ); @@ -90,7 +105,8 @@ class QgsMapCoordsDialog : public QDialog, private Ui::QgsMapCoordsDialogBase QgsCoordinateReferenceSystem mRasterCrs; - QgsPointXY mPixelCoords; + //! Source layer coordinates -- must be in source layer coordinates, not pixels (unless source image is completely non-referenced) + QgsPointXY mSourceLayerCoordinates; }; #endif diff --git a/src/app/georeferencer/qgsrasterchangecoords.cpp b/src/app/georeferencer/qgsrasterchangecoords.cpp index 7e12f6cfc16f..8b2fe2d229c2 100644 --- a/src/app/georeferencer/qgsrasterchangecoords.cpp +++ b/src/app/georeferencer/qgsrasterchangecoords.cpp @@ -22,15 +22,14 @@ #include -void QgsRasterChangeCoords::setRaster( const QString &fileRaster ) +void QgsRasterChangeCoords::loadRaster( const QString &fileRaster ) { GDALAllRegister(); const gdal::dataset_unique_ptr hDS( GDALOpen( fileRaster.toUtf8().constData(), GA_ReadOnly ) ); double adfGeoTransform[6]; if ( GDALGetProjectionRef( hDS.get() ) && GDALGetGeoTransform( hDS.get(), adfGeoTransform ) == CE_None ) - //if ( false ) { - mHasCrs = true; + mHasExistingGeoreference = true; mUL_X = adfGeoTransform[0]; mUL_Y = adfGeoTransform[3]; mResX = adfGeoTransform[1]; @@ -38,11 +37,11 @@ void QgsRasterChangeCoords::setRaster( const QString &fileRaster ) } else { - mHasCrs = false; + mHasExistingGeoreference = false; } } -QVector QgsRasterChangeCoords::getPixelCoords( const QVector &mapCoords ) +QVector QgsRasterChangeCoords::getPixelCoords( const QVector &mapCoords ) const { const int size = mapCoords.size(); QVector pixelCoords( size ); @@ -53,28 +52,36 @@ QVector QgsRasterChangeCoords::getPixelCoords( const QVector*func )( p1 ), ( this->*func )( p2 ) ); return rectReturn; } -QgsPointXY QgsRasterChangeCoords::toColumnLine( const QgsPointXY &pntMap ) +QgsPointXY QgsRasterChangeCoords::toColumnLine( const QgsPointXY &pntMap ) const { + if ( ! mHasExistingGeoreference ) + return QgsPointXY( pntMap.x(), pntMap.y() ); + const double col = ( pntMap.x() - mUL_X ) / mResX; const double line = ( mUL_Y - pntMap.y() ) / mResY; return QgsPointXY( col, line ); } -QgsPointXY QgsRasterChangeCoords::toXY( const QgsPointXY &pntPixel ) +QgsPointXY QgsRasterChangeCoords::toXY( const QgsPointXY &pntPixel ) const { + if ( ! mHasExistingGeoreference ) + return QgsPointXY( pntPixel.x(), pntPixel.y() ); + const double x = mUL_X + ( pntPixel.x() * mResX ); const double y = mUL_Y + ( pntPixel.y() * -mResY ); return QgsPointXY( x, y ); diff --git a/src/app/georeferencer/qgsrasterchangecoords.h b/src/app/georeferencer/qgsrasterchangecoords.h index 2622190b10fd..668aff8ff81e 100644 --- a/src/app/georeferencer/qgsrasterchangecoords.h +++ b/src/app/georeferencer/qgsrasterchangecoords.h @@ -18,26 +18,34 @@ #include +#include "qgis_app.h" #include "qgspointxy.h" #include "qgsrectangle.h" -class QgsRasterChangeCoords +class APP_EXPORT QgsRasterChangeCoords { public: QgsRasterChangeCoords() = default; - void setRaster( const QString &fileRaster ); - bool hasCrs() const { return mHasCrs; } - QVector getPixelCoords( const QVector &mapCoords ); - QgsRectangle getBoundingBox( const QgsRectangle &rect, bool toPixel ); - QgsPointXY toColumnLine( const QgsPointXY &pntMap ); - QgsPointXY toXY( const QgsPointXY &pntPixel ); + void loadRaster( const QString &fileRaster ); + bool hasExistingGeoreference() const { return mHasExistingGeoreference; } + QVector getPixelCoords( const QVector &mapCoords ) const; + + /** + * Transforms a rectangle extent of the source image from source coordinates to source pixels or vice versa. + */ + QgsRectangle transformExtent( const QgsRectangle &rect, bool toPixel ) const; + + QgsPointXY toColumnLine( const QgsPointXY &pntMap ) const; + QgsPointXY toXY( const QgsPointXY &pntPixel ) const; private: - bool mHasCrs = false; + bool mHasExistingGeoreference = false; double mUL_X = 0.; double mUL_Y = 0.; double mResX = 1.; double mResY = 1.; + + friend class TestQgsGeoreferencer; }; #endif // QGSRASTERCHANGECOORDS_H diff --git a/src/app/georeferencer/qgsresidualplotitem.cpp b/src/app/georeferencer/qgsresidualplotitem.cpp index 826e955c6d7d..bc2ba1a71f81 100644 --- a/src/app/georeferencer/qgsresidualplotitem.cpp +++ b/src/app/georeferencer/qgsresidualplotitem.cpp @@ -27,6 +27,12 @@ QgsResidualPlotItem::QgsResidualPlotItem( QgsLayout *layout ) setBackgroundEnabled( false ); } +QgsResidualPlotItem::~QgsResidualPlotItem() +{ + qDeleteAll( mGCPList ); + mGCPList.clear(); +} + QgsLayoutItem::Flags QgsResidualPlotItem::itemFlags() const { return QgsLayoutItem::FlagOverridesPaint; @@ -58,7 +64,7 @@ void QgsResidualPlotItem::paint( QPainter *painter, const QStyleOptionGraphicsIt QgsGCPList::const_iterator gcpIt = mGCPList.constBegin(); for ( ; gcpIt != mGCPList.constEnd(); ++gcpIt ) { - const QgsPointXY gcpCoords = ( *gcpIt )->pixelCoords(); + const QgsPointXY gcpCoords = ( *gcpIt )->sourcePoint(); const double gcpItemMMX = ( gcpCoords.x() - mExtent.xMinimum() ) / mExtent.width() * widthMM; const double gcpItemMMY = ( 1 - ( gcpCoords.y() - mExtent.yMinimum() ) / mExtent.height() ) * heightMM; @@ -86,7 +92,7 @@ void QgsResidualPlotItem::paint( QPainter *painter, const QStyleOptionGraphicsIt gcpIt = mGCPList.constBegin(); for ( ; gcpIt != mGCPList.constEnd(); ++gcpIt ) { - const QgsPointXY gcpCoords = ( *gcpIt )->pixelCoords(); + const QgsPointXY gcpCoords = ( *gcpIt )->sourcePoint(); const double gcpItemMMX = ( gcpCoords.x() - mExtent.xMinimum() ) / mExtent.width() * widthMM; const double gcpItemMMY = ( 1 - ( gcpCoords.y() - mExtent.yMinimum() ) / mExtent.height() ) * heightMM; if ( ( *gcpIt )->isEnabled() ) @@ -154,6 +160,17 @@ void QgsResidualPlotItem::paint( QPainter *painter, const QStyleOptionGraphicsIt } } +void QgsResidualPlotItem::setGCPList( const QgsGCPList &list ) +{ + qDeleteAll( mGCPList ); + mGCPList.clear(); + + for ( const QgsGeorefDataPoint *pt : list ) + { + mGCPList.append( new QgsGeorefDataPoint( *pt ) ); + } +} + void QgsResidualPlotItem::draw( QgsLayoutItemRenderContext & ) { diff --git a/src/app/georeferencer/qgsresidualplotitem.h b/src/app/georeferencer/qgsresidualplotitem.h index 80e98f57eb1d..793dfc96fd06 100644 --- a/src/app/georeferencer/qgsresidualplotitem.h +++ b/src/app/georeferencer/qgsresidualplotitem.h @@ -30,14 +30,15 @@ class QgsResidualPlotItem: public QgsLayoutItem public: explicit QgsResidualPlotItem( QgsLayout *layout ); + ~QgsResidualPlotItem() override; QgsLayoutItem::Flags itemFlags() const override; //! \brief Reimplementation of QCanvasItem::paint void paint( QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget ) override; - void setGCPList( const QgsGCPList &list ) { mGCPList = list; } - QgsGCPList GCPList() const { return mGCPList; } + void setGCPList( const QgsGCPList &list ); + const QgsGCPList &GCPList() const { return mGCPList; } void setExtent( const QgsRectangle &rect ) { mExtent = rect;} QgsRectangle extent() const { return mExtent; } diff --git a/src/app/georeferencer/qgstransformsettingsdialog.cpp b/src/app/georeferencer/qgstransformsettingsdialog.cpp index 2134437fde14..bb6f5514410b 100644 --- a/src/app/georeferencer/qgstransformsettingsdialog.cpp +++ b/src/app/georeferencer/qgstransformsettingsdialog.cpp @@ -27,11 +27,9 @@ #include "qgsgui.h" #include "qgshelp.h" -QgsTransformSettingsDialog::QgsTransformSettingsDialog( const QString &raster, const QString &output, - int countGCPpoints, QWidget *parent ) +QgsTransformSettingsDialog::QgsTransformSettingsDialog( const QString &raster, const QString &output, QWidget *parent ) : QDialog( parent ) , mSourceRasterFile( raster ) - , mCountGCPpoints( countGCPpoints ) { setupUi( this ); QgsSettings settings; @@ -91,27 +89,21 @@ QgsTransformSettingsDialog::QgsTransformSettingsDialog( const QString &raster, c cmbTransformType->addItem( tr( "Projective" ), static_cast( QgsGcpTransformerInterface::TransformMethod::Projective ) ); // Populate CompressionComboBox - mListCompression.append( QStringLiteral( "None" ) ); - mListCompression.append( QStringLiteral( "LZW" ) ); - mListCompression.append( QStringLiteral( "PACKBITS" ) ); - mListCompression.append( QStringLiteral( "DEFLATE" ) ); - QStringList listCompressionTr; - for ( const QString &item : std::as_const( mListCompression ) ) - { - listCompressionTr.append( tr( item.toLatin1().data() ) ); - } - cmbCompressionComboBox->addItems( listCompressionTr ); + cmbCompressionComboBox->addItem( tr( "None" ), QStringLiteral( "None" ) ); + cmbCompressionComboBox->addItem( tr( "LZW" ), QStringLiteral( "LZW" ) ); + cmbCompressionComboBox->addItem( tr( "PACKBITS" ), QStringLiteral( "PACKBITS" ) ); + cmbCompressionComboBox->addItem( tr( "DEFLATE" ), QStringLiteral( "DEFLATE" ) ); + + cmbResampling->addItem( tr( "Nearest Neighbour" ), QgsImageWarper::ResamplingMethod::NearestNeighbour ); + cmbResampling->addItem( tr( "Linear" ), QgsImageWarper::ResamplingMethod::Bilinear ); + cmbResampling->addItem( tr( "Cubic" ), QgsImageWarper::ResamplingMethod::Cubic ); + cmbResampling->addItem( tr( "Cubic Spline" ), QgsImageWarper::ResamplingMethod::CubicSpline ); + cmbResampling->addItem( tr( "Lanczos" ), QgsImageWarper::ResamplingMethod::Lanczos ); cmbTransformType->setCurrentIndex( settings.value( QStringLiteral( "/Plugin-GeoReferencer/lasttransformation" ), -1 ).toInt() ); cmbResampling->setCurrentIndex( settings.value( QStringLiteral( "/Plugin-GeoReferencer/lastresampling" ), 0 ).toInt() ); cmbCompressionComboBox->setCurrentIndex( settings.value( QStringLiteral( "/Plugin-GeoReferencer/lastcompression" ), 0 ).toInt() ); - QString targetCRSString = settings.value( QStringLiteral( "/Plugin-GeoReferencer/targetsrs" ) ).toString(); - QgsCoordinateReferenceSystem targetCRS = QgsCoordinateReferenceSystem::fromOgcWmsCrs( targetCRSString ); - mCrsSelector->setCrs( targetCRS ); - - mWorldFileCheckBox->setChecked( settings.value( QStringLiteral( "/Plugin-Georeferencer/word_file_checkbox" ), false ).toBool() ); - cbxUserResolution->setChecked( settings.value( QStringLiteral( "/Plugin-Georeferencer/user_specified_resolution" ), false ).toBool() ); bool ok; dsbHorizRes->setValue( settings.value( QStringLiteral( "/Plugin-GeoReferencer/user_specified_resx" ), .0 ).toDouble( &ok ) ); @@ -128,10 +120,30 @@ QgsTransformSettingsDialog::QgsTransformSettingsDialog( const QString &raster, c connect( buttonBox, &QDialogButtonBox::helpRequested, this, &QgsTransformSettingsDialog::showHelp ); } +void QgsTransformSettingsDialog::setTargetCrs( const QgsCoordinateReferenceSystem &crs ) +{ + mCrsSelector->setCrs( crs ); +} + +QgsCoordinateReferenceSystem QgsTransformSettingsDialog::targetCrs() const +{ + return mCrsSelector->crs(); +} + +bool QgsTransformSettingsDialog::createWorldFileOnly() const +{ + return mWorldFileCheckBox->isChecked(); +} + +void QgsTransformSettingsDialog::setCreateWorldFileOnly( bool enabled ) +{ + mWorldFileCheckBox->setChecked( enabled ); + mWorldFileCheckBox_stateChanged( mWorldFileCheckBox->checkState() ); +} + void QgsTransformSettingsDialog::getTransformSettings( QgsGeorefTransform::TransformMethod &tp, QgsImageWarper::ResamplingMethod &rm, - QString &comprMethod, QString &raster, - QgsCoordinateReferenceSystem &proj, QString &pdfMapFile, QString &pdfReportFile, QString &gcpPoints, bool &zt, bool &loadInQgis, + QString &comprMethod, QString &raster, QString &pdfMapFile, QString &pdfReportFile, bool &saveGcpPoints, bool &zt, bool &loadInQgis, double &resX, double &resY ) { if ( cmbTransformType->currentIndex() == -1 ) @@ -139,17 +151,10 @@ void QgsTransformSettingsDialog::getTransformSettings( QgsGeorefTransform::Trans else tp = static_cast< QgsGcpTransformerInterface::TransformMethod >( cmbTransformType->currentData().toInt() ); - rm = ( QgsImageWarper::ResamplingMethod )cmbResampling->currentIndex(); - comprMethod = mListCompression.at( cmbCompressionComboBox->currentIndex() ).toUpper(); - if ( mWorldFileCheckBox->isChecked() ) - { - raster.clear(); - } - else - { - raster = mOutputRaster->filePath(); - } - proj = mCrsSelector->crs(); + rm = static_cast< QgsImageWarper::ResamplingMethod >( cmbResampling->currentData().toInt() ); + comprMethod = cmbCompressionComboBox->currentData().toString(); + raster = mOutputRaster->filePath(); + pdfMapFile = mPdfMap->filePath(); pdfReportFile = mPdfReport->filePath(); zt = cbxZeroAsTrans->isChecked(); @@ -161,40 +166,7 @@ void QgsTransformSettingsDialog::getTransformSettings( QgsGeorefTransform::Trans resX = dsbHorizRes->value(); resY = dsbVerticalRes->value(); } - if ( saveGcpCheckBox->isChecked() ) - { - gcpPoints = mOutputRaster->filePath(); - } -} - -void QgsTransformSettingsDialog::resetSettings() -{ - QgsSettings s; - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/lasttransformation" ), -1 ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/lastresampling" ), 0 ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/lastcompression" ), 0 ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/targetsrs" ), QString() ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/zeroastrans" ), false ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/loadinqgis" ), false ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/save_gcp_points" ), false ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/user_specified_resolution" ), false ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/user_specified_resx" ), 1.0 ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/user_specified_resy" ), -1.0 ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/word_file_checkbox" ), false ); - s.setValue( QStringLiteral( "/Plugin-GeoReferencer/lastPDFReportDir" ), QDir::homePath() ); -} - -void QgsTransformSettingsDialog::changeEvent( QEvent *e ) -{ - QDialog::changeEvent( e ); - switch ( e->type() ) - { - case QEvent::LanguageChange: - retranslateUi( this ); - break; - default: - break; - } + saveGcpPoints = saveGcpCheckBox->isChecked(); } void QgsTransformSettingsDialog::accept() @@ -230,23 +202,23 @@ void QgsTransformSettingsDialog::accept() settings.setValue( QStringLiteral( "/Plugin-GeoReferencer/user_specified_resolution" ), cbxUserResolution->isChecked() ); settings.setValue( QStringLiteral( "/Plugin-GeoReferencer/user_specified_resx" ), dsbHorizRes->value() ); settings.setValue( QStringLiteral( "/Plugin-GeoReferencer/user_specified_resy" ), dsbVerticalRes->value() ); - settings.setValue( QStringLiteral( "/Plugin-GeoReferencer/word_file_checkbox" ), mWorldFileCheckBox->isChecked() ); settings.setValue( QStringLiteral( "/Plugin-GeoReferencer/save_gcp_points" ), saveGcpCheckBox->isChecked() ); - QDialog::accept(); } -void QgsTransformSettingsDialog::cmbTransformType_currentIndexChanged( const QString &text ) +void QgsTransformSettingsDialog::cmbTransformType_currentIndexChanged( const QString & ) { - if ( text == tr( "Linear" ) ) + if ( cmbTransformType->currentIndex() != -1 + && ( static_cast< QgsGcpTransformerInterface::TransformMethod >( cmbTransformType->currentData().toInt() ) == QgsGcpTransformerInterface::TransformMethod::Linear + || static_cast< QgsGcpTransformerInterface::TransformMethod >( cmbTransformType->currentData().toInt() ) == QgsGcpTransformerInterface::TransformMethod::Helmert ) ) { mWorldFileCheckBox->setEnabled( true ); } else { + // world file only option is only compatible with helmert/linear transforms mWorldFileCheckBox->setEnabled( false ); - // reset world file checkbox when transformation differ from Linear mWorldFileCheckBox->setChecked( false ); } } @@ -262,14 +234,6 @@ void QgsTransformSettingsDialog::mWorldFileCheckBox_stateChanged( int state ) mOutputRaster->setEnabled( enableOutputRaster ); } -bool QgsTransformSettingsDialog::checkGCPpoints( int count, int &minGCPpoints ) -{ - QgsGeorefTransform georefTransform; - georefTransform.selectTransformParametrisation( ( QgsGeorefTransform::TransformMethod )count ); - minGCPpoints = georefTransform.minimumGcpCount(); - return ( mCountGCPpoints >= minGCPpoints ); -} - QString QgsTransformSettingsDialog::generateModifiedRasterFileName( const QString &raster ) { if ( raster.isEmpty() ) @@ -286,33 +250,6 @@ QString QgsTransformSettingsDialog::generateModifiedRasterFileName( const QStrin return modifiedFileName; } -// Note this code is duplicated from qgisapp.cpp because -// I didn't want to make plugins on qgsapplication [TS] -QIcon QgsTransformSettingsDialog::getThemeIcon( const QString &name ) -{ - if ( QFile::exists( QgsApplication::activeThemePath() + name ) ) - { - return QIcon( QgsApplication::activeThemePath() + name ); - } - else if ( QFile::exists( QgsApplication::defaultThemePath() + name ) ) - { - return QIcon( QgsApplication::defaultThemePath() + name ); - } - else - { - QgsSettings settings; - QString themePath = ":/icons/" + settings.value( QStringLiteral( "Themes" ) ).toString() + name; - if ( QFile::exists( themePath ) ) - { - return QIcon( themePath ); - } - else - { - return QIcon( ":/icons/default" + name ); - } - } -} - void QgsTransformSettingsDialog::showHelp() { QgsHelp::openHelp( QStringLiteral( "working_with_raster/georeferencer.html#defining-the-transformation-settings" ) ); diff --git a/src/app/georeferencer/qgstransformsettingsdialog.h b/src/app/georeferencer/qgstransformsettingsdialog.h index 41b52599fd9e..13e0132006e4 100644 --- a/src/app/georeferencer/qgstransformsettingsdialog.h +++ b/src/app/georeferencer/qgstransformsettingsdialog.h @@ -27,34 +27,45 @@ class QgsTransformSettingsDialog : public QDialog, private Ui::QgsTransformSetti Q_OBJECT public: - QgsTransformSettingsDialog( const QString &raster, const QString &output, - int countGCPpoints, QWidget *parent = nullptr ); + QgsTransformSettingsDialog( const QString &raster, const QString &output, QWidget *parent = nullptr ); + + /** + * Sets the selected target \a crs. + */ + void setTargetCrs( const QgsCoordinateReferenceSystem &crs ); + + /** + * Returns the selected target CRS. + */ + QgsCoordinateReferenceSystem targetCrs() const; + + /** + * Returns TRUE if the create world file only option is set. + */ + bool createWorldFileOnly() const; + + /** + * Sets whether the create world file only option should be set. + */ + void setCreateWorldFileOnly( bool enabled ); void getTransformSettings( QgsGeorefTransform::TransformMethod &tp, QgsImageWarper::ResamplingMethod &rm, QString &comprMethod, - QString &raster, QgsCoordinateReferenceSystem &proj, QString &pdfMapFile, QString &pdfReportFile, QString &gcpPoints, bool &zt, bool &loadInQgis, + QString &raster, QString &pdfMapFile, QString &pdfReportFile, bool &saveGcpPoints, bool &zt, bool &loadInQgis, double &resX, double &resY ); - static void resetSettings(); protected: - void changeEvent( QEvent *e ) override; void accept() override; private slots: void cmbTransformType_currentIndexChanged( const QString &text ); void mWorldFileCheckBox_stateChanged( int state ); - QIcon getThemeIcon( const QString &name ); void showHelp(); private: - bool checkGCPpoints( int count, int &minGCPpoints ); QString generateModifiedRasterFileName( const QString &raster ); QString mSourceRasterFile; - - int mCountGCPpoints; - - QStringList mListCompression; }; #endif // QGSTRANSFORMSETTINGSDIALOG_H diff --git a/src/ui/georeferencer/qgstransformsettingsdialogbase.ui b/src/ui/georeferencer/qgstransformsettingsdialogbase.ui index fe2d9a833d06..bf4ab0cf1f65 100644 --- a/src/ui/georeferencer/qgstransformsettingsdialogbase.ui +++ b/src/ui/georeferencer/qgstransformsettingsdialogbase.ui @@ -240,33 +240,8 @@ - 0 + -1 - - - Nearest neighbour - - - - - Linear - - - - - Cubic - - - - - Cubic Spline - - - - - Lanczos - - diff --git a/tests/src/app/CMakeLists.txt b/tests/src/app/CMakeLists.txt index 33589d1b3aa8..9da78d504ab7 100644 --- a/tests/src/app/CMakeLists.txt +++ b/tests/src/app/CMakeLists.txt @@ -17,6 +17,7 @@ set(TESTS testqgsapplocatorfilters.cpp testqgsdecorationscalebar.cpp testqgsfieldcalculator.cpp + testqgsgeoreferencer.cpp testqgsmaptooleditannotation.cpp testqgsmaptoolidentifyaction.cpp testqgsmaptoollabel.cpp diff --git a/tests/src/app/testqgsgeoreferencer.cpp b/tests/src/app/testqgsgeoreferencer.cpp new file mode 100644 index 000000000000..2c8d5d207c2b --- /dev/null +++ b/tests/src/app/testqgsgeoreferencer.cpp @@ -0,0 +1,796 @@ +/*************************************************************************** + testqgsgeoreferencer.cpp + -------------------------- + Date : 2022-02-02 + Copyright : (C) 2022 by Nyall Dawson + Email : nyall dot dawson at gmail dot com + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ +#include "qgstest.h" +#include "qgisapp.h" +#include "qgsapplication.h" +#include "qgsvectorlayer.h" +#include "qgsfeature.h" +#include "qgsfeatureiterator.h" +#include "qgsgeometry.h" +#include "qgsvectordataprovider.h" +#include "qgsfieldcalculator.h" +#include "qgsproject.h" +#include "qgsmapcanvas.h" +#include "georeferencer/qgsgeoreftransform.h" +#include "georeferencer/qgsgeorefdatapoint.h" +#include "georeferencer/qgsgcplist.h" +#include "georeferencer/qgsgcplistmodel.h" + +/** + * \ingroup UnitTests + * This is a unit test for georeferencer + */ +class TestQgsGeoreferencer : public QObject +{ + Q_OBJECT + public: + TestQgsGeoreferencer(); + + private slots: + void initTestCase();// will be called before the first testfunction is executed. + void cleanupTestCase();// will be called after the last testfunction was executed. + void init() {} // will be called before each testfunction is executed. + void cleanup() {} // will be called after every testfunction. + void testGcpPoint(); + void testGeorefDataPoint(); + void testGcpList(); + void testSaveLoadGcps(); + void testSaveLoadGcpsNoCrs(); + void testTransformImageNoGeoference(); + void testTransformImageWithExistingGeoreference(); + void testRasterChangeCoords(); + void testUpdateResiduals(); + void testListModel(); + void testListModelCrs(); + + private: + QgisApp *mQgisApp = nullptr; +}; + +TestQgsGeoreferencer::TestQgsGeoreferencer() = default; + +//runs before all tests +void TestQgsGeoreferencer::initTestCase() +{ + qDebug() << "TestQgisAppClipboard::initTestCase()"; + // init QGIS's paths - true means that all path will be inited from prefix + QgsApplication::init(); + QgsApplication::initQgis(); + mQgisApp = new QgisApp(); +} + +//runs after all tests +void TestQgsGeoreferencer::cleanupTestCase() +{ + QgsApplication::exitQgis(); +} + +void TestQgsGeoreferencer::testGcpPoint() +{ + QgsGcpPoint p( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ); + + QCOMPARE( p.sourcePoint(), QgsPointXY( 1, 2 ) ); + p.setSourcePoint( QgsPointXY( 11, 22 ) ); + QCOMPARE( p.sourcePoint(), QgsPointXY( 11, 22 ) ); + + QCOMPARE( p.destinationPoint(), QgsPointXY( 3, 4 ) ); + p.setDestinationPoint( QgsPointXY( 33, 44 ) ); + QCOMPARE( p.destinationPoint(), QgsPointXY( 33, 44 ) ); + + QCOMPARE( p.destinationPointCrs().authid(), QStringLiteral( "EPSG:3111" ) ); + p.setDestinationPointCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ) ); + QCOMPARE( p.destinationPointCrs().authid(), QStringLiteral( "EPSG:28356" ) ); + + QVERIFY( !p.isEnabled() ); + p.setEnabled( true ); + QVERIFY( p.isEnabled() ); + + // equality operator + QVERIFY( QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) + == QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) ); + QVERIFY( QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) + != QgsGcpPoint( QgsPointXY( 11, 22 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) ); + QVERIFY( QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) + != QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 33, 44 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) ); + QVERIFY( QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) + != QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ), false ) ); + QVERIFY( QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) + != QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), true ) ); + + + // transform destination point + QgsGcpPoint p2( QgsPointXY( 1, 2 ), QgsPointXY( 150, -30 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), false ); + const QgsPointXY res = p2.transformedDestinationPoint( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), QgsProject::instance()->transformContext() ); + QGSCOMPARENEAR( res.x(), 16697923, 10000 ); + QGSCOMPARENEAR( res.y(), -3503549, 10000 ); +} + +void TestQgsGeoreferencer::testGeorefDataPoint() +{ + QgsMapCanvas c1; + QgsMapCanvas c2; + QgsGeorefDataPoint p( &c1, &c2, + QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), + false ); + + QCOMPARE( p.sourcePoint(), QgsPointXY( 1, 2 ) ); + p.setSourcePoint( QgsPointXY( 11, 22 ) ); + QCOMPARE( p.sourcePoint(), QgsPointXY( 11, 22 ) ); + + QCOMPARE( p.destinationPoint(), QgsPointXY( 3, 4 ) ); + p.setDestinationPoint( QgsPointXY( 33, 44 ) ); + QCOMPARE( p.destinationPoint(), QgsPointXY( 33, 44 ) ); + + QCOMPARE( p.destinationPointCrs().authid(), QStringLiteral( "EPSG:3111" ) ); + + QVERIFY( !p.isEnabled() ); + p.setEnabled( true ); + QVERIFY( p.isEnabled() ); + + QCOMPARE( p.point(), QgsGcpPoint( QgsPointXY( 11, 22 ), + QgsPointXY( 33, 44 ), + QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), + true ) ); + + + // transform destination point + QgsGeorefDataPoint p2( &c1, &c2, QgsPointXY( 1, 2 ), QgsPointXY( 150, -30 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), false ); + const QgsPointXY res = p2.transformedDestinationPoint( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), QgsProject::instance()->transformContext() ); + QGSCOMPARENEAR( res.x(), 16697923, 10000 ); + QGSCOMPARENEAR( res.y(), -3503549, 10000 ); +} + +void TestQgsGeoreferencer::testGcpList() +{ + QgsGCPList list; + QCOMPARE( list.countEnabledPoints(), 0 ); + QVERIFY( list.asPoints().isEmpty() ); + + QgsMapCanvas c1; + QgsMapCanvas c2; + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), + false ) ); + QCOMPARE( list.countEnabledPoints(), 0 ); + QCOMPARE( list.asPoints(), + { + QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ) + } + ); + + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 11, 22 ), QgsPointXY( 33, 44 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ), + true ) ); + QCOMPARE( list.countEnabledPoints(), 1 ); + QCOMPARE( list.asPoints(), + QList< QgsGcpPoint >( + { + QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ), + QgsGcpPoint( QgsPointXY( 11, 22 ), QgsPointXY( 33, 44 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ), true ) + } ) ); + + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 111, 222 ), QgsPointXY( 333, 444 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ), + true ) ); + QCOMPARE( list.countEnabledPoints(), 2 ); + QCOMPARE( list.asPoints(), + QList< QgsGcpPoint >( + { + QgsGcpPoint( QgsPointXY( 1, 2 ), QgsPointXY( 3, 4 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), false ), + QgsGcpPoint( QgsPointXY( 11, 22 ), QgsPointXY( 33, 44 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ), true ), + QgsGcpPoint( QgsPointXY( 111, 222 ), QgsPointXY( 333, 444 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:28356" ) ), true ) + } ) ); + + + qDeleteAll( list ); + list.clear(); + + // create gcp vectors + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 111, 222 ), QgsPointXY( -30, 40 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 11, 22 ), QgsPointXY( 16697923, -3503549 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + true ) ); + // disabled! + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 33, 44 ), QgsPointXY( 100, 200 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + false ) ); + + QVector< QgsPointXY > sourcePoints; + QVector< QgsPointXY > destinationPoints; + list.createGCPVectors( sourcePoints, destinationPoints, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), QgsProject::instance()->transformContext() ); + QCOMPARE( sourcePoints.size(), 2 ); + QCOMPARE( sourcePoints.at( 0 ).x(), 111 ); + QCOMPARE( sourcePoints.at( 0 ).y(), 222 ); + QCOMPARE( sourcePoints.at( 1 ).x(), 11 ); + QCOMPARE( sourcePoints.at( 1 ).y(), 22 ); + + QCOMPARE( destinationPoints.size(), 2 ); + QGSCOMPARENEAR( destinationPoints.at( 0 ).x(), -3339584, 10000 ); + QGSCOMPARENEAR( destinationPoints.at( 0 ).y(), 4865942, 10000 ); + QCOMPARE( destinationPoints.at( 1 ).x(), 16697923 ); + QCOMPARE( destinationPoints.at( 1 ).y(), -3503549 ); + +} + +void TestQgsGeoreferencer::testSaveLoadGcps() +{ + // test saving and loading GCPs + QgsGCPList list; + QgsMapCanvas c1; + QgsMapCanvas c2; + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 111, 222 ), QgsPointXY( -30, 40 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 11, 22 ), QgsPointXY( 16697923, -3503549 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + true ) ); + // disabled! + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 33, 44 ), QgsPointXY( 100, 200 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + false ) ); + + QTemporaryDir dir; + QVERIFY( dir.isValid() ); + const QString tempFilename = dir.filePath( QStringLiteral( "test.points" ) ); + + QString error; + + QVERIFY( list.saveGcps( tempFilename, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + QgsProject::instance()->transformContext(), error ) ); + QVERIFY( error.isEmpty() ); + + + // load + QgsCoordinateReferenceSystem actualDestinationCrs; + QList res = QgsGCPList::loadGcps( QStringLiteral( "not real" ), + QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), + actualDestinationCrs, + error ); + QVERIFY( !error.isEmpty() ); + + res = QgsGCPList::loadGcps( tempFilename, + QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), + actualDestinationCrs, + error ); + QVERIFY( error.isEmpty() ); + QCOMPARE( res.size(), 3 ); + // should be loaded from txt + QCOMPARE( actualDestinationCrs.authid(), QStringLiteral( "EPSG:3857" ) ); + + QCOMPARE( res.at( 0 ).sourcePoint().x(), 111 ); + QCOMPARE( res.at( 0 ).sourcePoint().y(), 222 ); + QGSCOMPARENEAR( res.at( 0 ).destinationPoint().x(), -3339584, 10000 ); + QGSCOMPARENEAR( res.at( 0 ).destinationPoint().y(), 4865942, 10000 ); + QVERIFY( res.at( 0 ).isEnabled() ); + QCOMPARE( res.at( 0 ).destinationPointCrs().authid(), QStringLiteral( "EPSG:3857" ) ); + + QCOMPARE( res.at( 1 ).sourcePoint().x(), 11 ); + QCOMPARE( res.at( 1 ).sourcePoint().y(), 22 ); + QCOMPARE( res.at( 1 ).destinationPoint().x(), 16697923 ); + QCOMPARE( res.at( 1 ).destinationPoint().y(), -3503549 ); + QVERIFY( res.at( 1 ).isEnabled() ); + QCOMPARE( res.at( 1 ).destinationPointCrs().authid(), QStringLiteral( "EPSG:3857" ) ); + + QCOMPARE( res.at( 2 ).sourcePoint().x(), 33 ); + QCOMPARE( res.at( 2 ).sourcePoint().y(), 44 ); + QCOMPARE( res.at( 2 ).destinationPoint().x(), 100 ); + QCOMPARE( res.at( 2 ).destinationPoint().y(), 200 ); + QVERIFY( !res.at( 2 ).isEnabled() ); + QCOMPARE( res.at( 2 ).destinationPointCrs().authid(), QStringLiteral( "EPSG:3857" ) ); +} + +void TestQgsGeoreferencer::testSaveLoadGcpsNoCrs() +{ + // test saving and loading GCPs when no destination CRS is set for the points + QgsGCPList list; + QgsMapCanvas c1; + QgsMapCanvas c2; + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 111, 222 ), QgsPointXY( -30, 40 ), QgsCoordinateReferenceSystem(), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 11, 22 ), QgsPointXY( 34, -50 ), QgsCoordinateReferenceSystem(), + true ) ); + // disabled! + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 33, 44 ), QgsPointXY( 100, 200 ), QgsCoordinateReferenceSystem(), + false ) ); + + QTemporaryDir dir; + QVERIFY( dir.isValid() ); + const QString tempFilename = dir.filePath( QStringLiteral( "test2.points" ) ); + + QString error; + + QVERIFY( list.saveGcps( tempFilename, QgsCoordinateReferenceSystem(), + QgsProject::instance()->transformContext(), error ) ); + QVERIFY( error.isEmpty() ); + + + // load + QgsCoordinateReferenceSystem actualDestinationCrs; + QList res = QgsGCPList::loadGcps( tempFilename, + QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3111" ) ), + actualDestinationCrs, + error ); + QVERIFY( error.isEmpty() ); + QCOMPARE( res.size(), 3 ); + // should fallback to default CRS + QCOMPARE( actualDestinationCrs.authid(), QStringLiteral( "EPSG:3111" ) ); + + QCOMPARE( res.at( 0 ).sourcePoint().x(), 111 ); + QCOMPARE( res.at( 0 ).sourcePoint().y(), 222 ); + QCOMPARE( res.at( 0 ).destinationPoint().x(), -30 ); + QCOMPARE( res.at( 0 ).destinationPoint().y(), 40 ); + QVERIFY( res.at( 0 ).isEnabled() ); + QCOMPARE( res.at( 0 ).destinationPointCrs().authid(), QStringLiteral( "EPSG:3111" ) ); + + QCOMPARE( res.at( 1 ).sourcePoint().x(), 11 ); + QCOMPARE( res.at( 1 ).sourcePoint().y(), 22 ); + QCOMPARE( res.at( 1 ).destinationPoint().x(), 34 ); + QCOMPARE( res.at( 1 ).destinationPoint().y(), -50 ); + QVERIFY( res.at( 1 ).isEnabled() ); + QCOMPARE( res.at( 1 ).destinationPointCrs().authid(), QStringLiteral( "EPSG:3111" ) ); + + QCOMPARE( res.at( 2 ).sourcePoint().x(), 33 ); + QCOMPARE( res.at( 2 ).sourcePoint().y(), 44 ); + QCOMPARE( res.at( 2 ).destinationPoint().x(), 100 ); + QCOMPARE( res.at( 2 ).destinationPoint().y(), 200 ); + QVERIFY( !res.at( 2 ).isEnabled() ); + QCOMPARE( res.at( 2 ).destinationPointCrs().authid(), QStringLiteral( "EPSG:3111" ) ); +} + +void TestQgsGeoreferencer::testTransformImageNoGeoference() +{ + QgsGeorefTransform transform( QgsGcpTransformerInterface::TransformMethod::Linear ); + // this image has no georeferencing set + transform.loadRaster( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/rgb256x256.png" ) ); + + QVERIFY( !transform.hasExistingGeoreference() ); + + QgsPointXY res; + // should be treating source coordinates and source pixels as identical + res = transform.toSourceCoordinate( QgsPointXY( 0, 0 ) ); + QCOMPARE( res.x(), 0.0 ); + QCOMPARE( res.y(), 0.0 ); + res = transform.toSourceCoordinate( QgsPointXY( 100, 200 ) ); + QCOMPARE( res.x(), 100.0 ); + QCOMPARE( res.y(), 200.0 ); + + res = transform.toSourcePixel( QgsPointXY( 0, 0 ) ); + QCOMPARE( res.x(), 0.0 ); + QCOMPARE( res.y(), 0.0 ); + res = transform.toSourcePixel( QgsPointXY( 100, 200 ) ); + QCOMPARE( res.x(), 100.0 ); + QCOMPARE( res.y(), 200.0 ); + + QgsRectangle rect = transform.transformSourceExtent( QgsRectangle( 0, 0, 100, 200 ), true ); + QCOMPARE( rect.xMinimum(), 0.0 ); + QCOMPARE( rect.yMinimum(), 0.0 ); + QCOMPARE( rect.xMaximum(), 100.0 ); + QCOMPARE( rect.yMaximum(), 200.0 ); + rect = transform.transformSourceExtent( QgsRectangle( 0, 0, 100, 200 ), false ); + QCOMPARE( rect.xMinimum(), 0.0 ); + QCOMPARE( rect.yMinimum(), 0.0 ); + QCOMPARE( rect.xMaximum(), 100.0 ); + QCOMPARE( rect.yMaximum(), 200.0 ); + + QVERIFY( transform.updateParametersFromGcps( {QgsPointXY( 0, 0 ), QgsPointXY( 10, 0 ), QgsPointXY( 0, 30 ), QgsPointXY( 10, 30 )}, + {QgsPointXY( 10, 5 ), QgsPointXY( 16, 5 ), QgsPointXY( 10, 8 ), QgsPointXY( 16, 8 )}, true ) ); + + QVERIFY( transform.transform( QgsPointXY( 0, 5 ), res, true ) ); + QCOMPARE( res.x(), 10 ); + QCOMPARE( res.y(), 5.5 ); + QVERIFY( transform.transform( QgsPointXY( 9, 25 ), res, true ) ); + QCOMPARE( res.x(), 15.4 ); + QCOMPARE( res.y(), 7.5 ); + // reverse transform + QVERIFY( transform.transform( QgsPointXY( 10, 5.5 ), res, false ) ); + QCOMPARE( res.x(), 0.0 ); + QCOMPARE( res.y(), 5.0 ); + QVERIFY( transform.transform( QgsPointXY( 15.4, 7.5 ), res, false ) ); + QCOMPARE( res.x(), 9.0 ); + QCOMPARE( res.y(), 25.0 ); +} + +void TestQgsGeoreferencer::testTransformImageWithExistingGeoreference() +{ + // load an image which is already georeferenced + QgsGeorefTransform transform( QgsGcpTransformerInterface::TransformMethod::Linear ); + transform.loadRaster( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.tif" ) ); + + QVERIFY( transform.mRasterChangeCoords.mHasExistingGeoreference ); + QGSCOMPARENEAR( transform.mRasterChangeCoords.mResX, 57, 0.00001 ); + QGSCOMPARENEAR( transform.mRasterChangeCoords.mResY, -57, 0.00001 ); + QGSCOMPARENEAR( transform.mRasterChangeCoords.mUL_X, 781662.375, 0.01 ); + QGSCOMPARENEAR( transform.mRasterChangeCoords.mUL_Y, 3350923.125, 0.01 ); + + QgsPointXY res; + res = transform.toSourceCoordinate( QgsPointXY( 0, 0 ) ); + QGSCOMPARENEAR( res.x(), 781662.375, 0.1 ); + QGSCOMPARENEAR( res.y(), 3350923.125, 0.1 ); + res = transform.toSourceCoordinate( QgsPointXY( 100, 200 ) ); + QGSCOMPARENEAR( res.x(), 787362.375, 0.1 ); + QGSCOMPARENEAR( res.y(), 3362323.125, 0.1 ); + + res = transform.toSourcePixel( QgsPointXY( 781662.375, 3350923.125 ) ); + QGSCOMPARENEAR( res.x(), 0.0, 0.1 ); + QGSCOMPARENEAR( res.y(), 0.0, 0.1 ); + res = transform.toSourcePixel( QgsPointXY( 787362.375, 3362323.125 ) ); + QGSCOMPARENEAR( res.x(), 100.0, 0.1 ); + QGSCOMPARENEAR( res.y(), 200.0, 0.1 ); + + res = transform.mRasterChangeCoords.toColumnLine( QgsPointXY( 783414, 3350122 ) ); + QGSCOMPARENEAR( res.x(), 30.7302631579, 0.01 ); + QGSCOMPARENEAR( res.y(), -14.0548245614, 0.01 ); + res = transform.mRasterChangeCoords.toXY( QgsPointXY( 30.7302631579, -14.0548245614 ) ); + QGSCOMPARENEAR( res.x(), 783414, 10 ); + QGSCOMPARENEAR( res.y(), 3350122, 10 ); + + QgsRectangle rect = transform.transformSourceExtent( QgsRectangle( 781662.375, 3350923.125, 787362.375, 3362323.125 ), true ); + QGSCOMPARENEAR( rect.xMinimum(), 0.0, 0.1 ); + QGSCOMPARENEAR( rect.yMinimum(), 0.0, 0.1 ); + QGSCOMPARENEAR( rect.xMaximum(), 100.0, 0.1 ); + QGSCOMPARENEAR( rect.yMaximum(), 200.0, 0.1 ); + rect = transform.transformSourceExtent( QgsRectangle( 0, 0, 100, 200 ), false ); + QGSCOMPARENEAR( rect.xMinimum(), 781662.375, 0.1 ); + QGSCOMPARENEAR( rect.yMinimum(), 3350923.125, 0.1 ); + QGSCOMPARENEAR( rect.xMaximum(), 787362.375, 0.1 ); + QGSCOMPARENEAR( rect.yMaximum(), 3362323.125, 0.1 ); + + QVector pixelCoords = transform.mRasterChangeCoords.getPixelCoords( + { + QgsPointXY( 783414, 3350122 ), + QgsPointXY( 791344, 3349795 ), + QgsPointXY( 783077, 3340937 ), + QgsPointXY( 791134, 3341401 ) + } ) ; + + QCOMPARE( pixelCoords.size(), 4 ); + QGSCOMPARENEAR( pixelCoords.at( 0 ).x(), 30.7302631579, 0.01 ); + QGSCOMPARENEAR( pixelCoords.at( 0 ).y(), -14.0548245614, 0.01 ); + QGSCOMPARENEAR( pixelCoords.at( 1 ).x(), 169.8530701754, 0.01 ); + QGSCOMPARENEAR( pixelCoords.at( 1 ).y(), -19.7916666667, 0.01 ); + QGSCOMPARENEAR( pixelCoords.at( 2 ).x(), 24.8179824561, 0.01 ); + QGSCOMPARENEAR( pixelCoords.at( 2 ).y(), -175.1951754386, 0.01 ); + QGSCOMPARENEAR( pixelCoords.at( 3 ).x(), 166.168859649, 0.01 ); + QGSCOMPARENEAR( pixelCoords.at( 3 ).y(), -167.0548245614, 0.01 ); + + QVERIFY( transform.hasExistingGeoreference() ); + + // when calling updateParametersFromGcps the source list MUST be in source layer CRS, not pixels! + QVERIFY( transform.updateParametersFromGcps( {QgsPointXY( 783414, 3350122 ), QgsPointXY( 791344, 3349795 ), QgsPointXY( 783077, 334093 ), QgsPointXY( 791134, 3341401 )}, + {QgsPointXY( 783414, 3350122 ), QgsPointXY( 791344, 3349795 ), QgsPointXY( 783077, 334093 ), QgsPointXY( 791134, 3341401 )}, true ) ); + + QVERIFY( transform.transform( QgsPointXY( 30.7302631579, -14.0548245614 ), res, true ) ); + QGSCOMPARENEAR( res.x(), 783414, 1 ); + QGSCOMPARENEAR( res.y(), 3350122, 1 ); + QVERIFY( transform.transform( QgsPointXY( 166.168859649, -167.0548245614 ), res, true ) ); + QGSCOMPARENEAR( res.x(), 791134, 1 ); + QGSCOMPARENEAR( res.y(), 3341401, 1 ); + // reverse transform + QVERIFY( transform.transform( QgsPointXY( 783414, 3350122 ), res, false ) ); + QGSCOMPARENEAR( res.x(), 30.7302631579, 0.1 ); + QGSCOMPARENEAR( res.y(), -14.0548245614, 0.1 ); + QVERIFY( transform.transform( QgsPointXY( 791134, 3341401 ), res, false ) ); + QGSCOMPARENEAR( res.x(), 166.168859649, 0.1 ); + QGSCOMPARENEAR( res.y(), -167.0548245614, 0.1 ); + + // with shift of 100, 200 + QVERIFY( transform.updateParametersFromGcps( {QgsPointXY( 783414, 3350122 ), QgsPointXY( 791344, 3349795 ), QgsPointXY( 783077, 334093 ), QgsPointXY( 791134, 3341401 )}, + {QgsPointXY( 783514, 3350322 ), QgsPointXY( 791444, 3349995 ), QgsPointXY( 783177, 334293 ), QgsPointXY( 791234, 3341601 )}, true ) ); + + QVERIFY( transform.transform( QgsPointXY( 30.7302631579, -14.0548245614 ), res, true ) ); + QGSCOMPARENEAR( res.x(), 783514, 1 ); + QGSCOMPARENEAR( res.y(), 3350322, 1 ); + QVERIFY( transform.transform( QgsPointXY( 166.168859649, -167.0548245614 ), res, true ) ); + QGSCOMPARENEAR( res.x(), 791234, 1 ); + QGSCOMPARENEAR( res.y(), 3341601, 1 ); + // reverse transform + QVERIFY( transform.transform( QgsPointXY( 783514, 3350322 ), res, false ) ); + QGSCOMPARENEAR( res.x(), 30.7302631579, 0.1 ); + QGSCOMPARENEAR( res.y(), -14.0548245614, 0.1 ); + QVERIFY( transform.transform( QgsPointXY( 791234, 3341601 ), res, false ) ); + QGSCOMPARENEAR( res.x(), 166.168859649, 0.1 ); + QGSCOMPARENEAR( res.y(), -167.0548245614, 0.1 ); +} + +void TestQgsGeoreferencer::testRasterChangeCoords() +{ + QgsRasterChangeCoords transform; + QVERIFY( !transform.hasExistingGeoreference() ); + + transform.loadRaster( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.tif" ) ); + QVERIFY( transform.hasExistingGeoreference() ); + QGSCOMPARENEAR( transform.toXY( QgsPointXY( 0, 0 ) ).x(), 781662.375, 0.001 ); + QGSCOMPARENEAR( transform.toXY( QgsPointXY( 0, 0 ) ).y(), 3350923.125, 0.001 ); + QGSCOMPARENEAR( transform.toXY( QgsPointXY( 100, 0 ) ).x(), 787362.375, 0.001 ); + QGSCOMPARENEAR( transform.toXY( QgsPointXY( 100, 0 ) ).y(), 3350923.125, 0.001 ); + QGSCOMPARENEAR( transform.toXY( QgsPointXY( 100, 200 ) ).x(), 787362.375, 0.001 ); + QGSCOMPARENEAR( transform.toXY( QgsPointXY( 100, 200 ) ).y(), 3362323.125, 0.001 ); + + QGSCOMPARENEAR( transform.toColumnLine( QgsPointXY( 781662.375, 3350923.125 ) ).x(), 0.0, 0.0001 ); + QGSCOMPARENEAR( transform.toColumnLine( QgsPointXY( 781662.375, 3350923.125 ) ).y(), 0.0, 0.0001 ); + QGSCOMPARENEAR( transform.toColumnLine( QgsPointXY( 787362.375, 3350923.125 ) ).x(), 100.0, 0.0001 ); + QGSCOMPARENEAR( transform.toColumnLine( QgsPointXY( 787362.375, 3350923.125 ) ).y(), 0.0, 0.0001 ); + QGSCOMPARENEAR( transform.toColumnLine( QgsPointXY( 787362.375, 3362323.125 ) ).x(), 100.0, 0.0001 ); + QGSCOMPARENEAR( transform.toColumnLine( QgsPointXY( 787362.375, 3362323.125 ) ).y(), 200.0, 0.0001 ); + + // load a raster with no georeferencing + transform.loadRaster( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/rgb256x256.png" ) ); + QVERIFY( !transform.hasExistingGeoreference() ); + // should be treat layer coordinates and pixels as identical + QCOMPARE( transform.toXY( QgsPointXY( 0, 0 ) ).x(), 0.0 ); + QCOMPARE( transform.toXY( QgsPointXY( 0, 0 ) ).y(), 0.0 ); + QCOMPARE( transform.toXY( QgsPointXY( 100, 0 ) ).x(), 100.0 ); + QCOMPARE( transform.toXY( QgsPointXY( 100, 0 ) ).y(), 0.0 ); + QCOMPARE( transform.toXY( QgsPointXY( 100, 200 ) ).x(), 100.0 ); + QCOMPARE( transform.toXY( QgsPointXY( 100, 200 ) ).y(), 200.0 ); + + QCOMPARE( transform.toColumnLine( QgsPointXY( 0, 0 ) ).x(), 0.0 ); + QCOMPARE( transform.toColumnLine( QgsPointXY( 0, 0 ) ).y(), 0.0 ); + QCOMPARE( transform.toColumnLine( QgsPointXY( 100, 0 ) ).x(), 100.0 ); + QCOMPARE( transform.toColumnLine( QgsPointXY( 100, 0 ) ).y(), 0.0 ); + QCOMPARE( transform.toColumnLine( QgsPointXY( 100, 200 ) ).x(), 100.0 ); + QCOMPARE( transform.toColumnLine( QgsPointXY( 100, 200 ) ).y(), 200.0 ); +} + +void TestQgsGeoreferencer::testUpdateResiduals() +{ + // test updating residuals + QgsGeorefTransform transform( QgsGcpTransformerInterface::TransformMethod::Linear ); + transform.loadRaster( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.tif" ) ); + QVERIFY( transform.hasExistingGeoreference() ); + + QgsGCPList list; + QgsMapCanvas c1; + QgsMapCanvas c2; + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 781662.375, 3350923.125 ), QgsPointXY( -30, 40 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 787362.375, 3350923.125 ), QgsPointXY( 16697923, -3503549 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 787362.375, 3362323.125 ), QgsPointXY( -35, 42 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), + true ) ); + + list.updateResiduals( &transform, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), QgsProject::instance()->transformContext(), QgsUnitTypes::RenderPixels ); + QGSCOMPARENEAR( list.at( 0 )->residual().x(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 0 )->residual().y(), -189.189, 0.1 ); + QGSCOMPARENEAR( list.at( 1 )->residual().x(), 105.7142, 0.1 ); + QGSCOMPARENEAR( list.at( 1 )->residual().y(), 189.189, 0.1 ); + QGSCOMPARENEAR( list.at( 2 )->residual().x(), -105.7142, 0.1 ); + QGSCOMPARENEAR( list.at( 2 )->residual().y(), 0, 0.00001 ); + + // in map units + list.updateResiduals( &transform, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), QgsProject::instance()->transformContext(), QgsUnitTypes::RenderMapUnits ); + QGSCOMPARENEAR( list.at( 0 )->residual().x(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 0 )->residual().y(), -34.999, 0.1 ); + QGSCOMPARENEAR( list.at( 1 )->residual().x(), -92.499, 0.1 ); + QGSCOMPARENEAR( list.at( 1 )->residual().y(), 34.99999, 0.1 ); + QGSCOMPARENEAR( list.at( 2 )->residual().x(), 92.4999972, 0.1 ); + QGSCOMPARENEAR( list.at( 2 )->residual().y(), 0, 0.00001 ); + + // different target CRS + list.updateResiduals( &transform, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), QgsProject::instance()->transformContext(), QgsUnitTypes::RenderPixels ); + QGSCOMPARENEAR( list.at( 0 )->residual().x(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 0 )->residual().y(), -186.828, 0.1 ); + QGSCOMPARENEAR( list.at( 1 )->residual().x(), 105.7142, 0.1 ); + QGSCOMPARENEAR( list.at( 1 )->residual().y(), 186.828, 0.1 ); + QGSCOMPARENEAR( list.at( 2 )->residual().x(), -105.7142, 0.1 ); + QGSCOMPARENEAR( list.at( 2 )->residual().y(), 0, 0.00001 ); + + // projective transform -- except 0 residuals here + QgsGeorefTransform projective( QgsGcpTransformerInterface::TransformMethod::Projective ); + projective.loadRaster( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.tif" ) ); + QVERIFY( projective.hasExistingGeoreference() ); + + list.updateResiduals( &projective, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), QgsProject::instance()->transformContext(), QgsUnitTypes::RenderPixels ); + QGSCOMPARENEAR( list.at( 0 )->residual().x(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 0 )->residual().y(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 1 )->residual().x(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 1 )->residual().y(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 2 )->residual().x(), 0, 0.00001 ); + QGSCOMPARENEAR( list.at( 2 )->residual().y(), 0, 0.00001 ); +} + +void TestQgsGeoreferencer::testListModel() +{ + // test the gcp list model + QgsGCPList list; + QgsMapCanvas c1; + QgsMapCanvas c2; + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 781662.375, 3350923.125 ), QgsPointXY( -30, 40 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 787362.375, 3350923.125 ), QgsPointXY( 16697923, -3503549 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 787362.375, 3362323.125 ), QgsPointXY( -35, 42 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), + true ) ); + QgsGeorefTransform transform( QgsGcpTransformerInterface::TransformMethod::Linear ); + transform.loadRaster( QStringLiteral( TEST_DATA_DIR ) + QStringLiteral( "/landsat.tif" ) ); + QVERIFY( transform.hasExistingGeoreference() ); + list.updateResiduals( &transform, QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), QgsProject::instance()->transformContext(), QgsUnitTypes::RenderPixels ); + + QgsGCPListModel model; + QCOMPARE( model.rowCount(), 0 ); + QCOMPARE( model.columnCount(), 9 ); + + model.setGCPList( &list ); + model.setTargetCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), QgsProject::instance()->transformContext() ); + + QCOMPARE( model.rowCount(), 3 ); + QCOMPARE( model.data( model.index( 0, 1 ) ).toString(), QStringLiteral( "0" ) ); + QCOMPARE( model.data( model.index( 0, 2 ) ).toString(), QStringLiteral( "781662.38" ) ); + QCOMPARE( model.data( model.index( 0, 3 ) ).toString(), QStringLiteral( "3350923.13" ) ); + QCOMPARE( model.data( model.index( 0, 4 ) ).toString(), QStringLiteral( "-30.000000" ) ); + QCOMPARE( model.data( model.index( 0, 5 ) ).toString(), QStringLiteral( "40.000000" ) ); + QCOMPARE( model.data( model.index( 0, 4 ), Qt::ToolTipRole ).toString(), QStringLiteral( "-30.000000
EPSG:4326 - WGS 84" ) ); + QCOMPARE( model.data( model.index( 0, 5 ), Qt::ToolTipRole ).toString(), QStringLiteral( "40.000000
EPSG:4326 - WGS 84" ) ); + + QCOMPARE( model.data( model.index( 0, 6 ) ).toString(), QStringLiteral( "n/a" ) ); + QCOMPARE( model.data( model.index( 0, 7 ) ).toString(), QStringLiteral( "n/a" ) ); + QCOMPARE( model.data( model.index( 0, 8 ) ).toString(), QStringLiteral( "n/a" ) ); + + QCOMPARE( model.data( model.index( 1, 1 ) ).toString(), QStringLiteral( "1" ) ); + QCOMPARE( model.data( model.index( 1, 2 ) ).toString(), QStringLiteral( "787362.38" ) ); + QCOMPARE( model.data( model.index( 1, 3 ) ).toString(), QStringLiteral( "3350923.13" ) ); + QCOMPARE( model.data( model.index( 1, 4 ) ).toString(), QStringLiteral( "149.999994" ) ); + QCOMPARE( model.data( model.index( 1, 5 ) ).toString(), QStringLiteral( "-29.999993" ) ); + // tooltip should show target CRS details for user clarification + QCOMPARE( model.data( model.index( 1, 4 ), Qt::ToolTipRole ).toString(), QStringLiteral( "149.999994
EPSG:4326 - WGS 84" ) ); + QCOMPARE( model.data( model.index( 1, 5 ), Qt::ToolTipRole ).toString(), QStringLiteral( "-29.999993
EPSG:4326 - WGS 84" ) ); + QCOMPARE( model.data( model.index( 1, 6 ) ).toString(), QStringLiteral( "n/a" ) ); + QCOMPARE( model.data( model.index( 1, 7 ) ).toString(), QStringLiteral( "n/a" ) ); + QCOMPARE( model.data( model.index( 1, 8 ) ).toString(), QStringLiteral( "n/a" ) ); + + QCOMPARE( model.data( model.index( 2, 1 ) ).toString(), QStringLiteral( "2" ) ); + QCOMPARE( model.data( model.index( 2, 2 ) ).toString(), QStringLiteral( "787362.38" ) ); + QCOMPARE( model.data( model.index( 2, 3 ) ).toString(), QStringLiteral( "3362323.13" ) ); + QCOMPARE( model.data( model.index( 2, 4 ) ).toString(), QStringLiteral( "-35.000000" ) ); + QCOMPARE( model.data( model.index( 2, 5 ) ).toString(), QStringLiteral( "42.000000" ) ); + QCOMPARE( model.data( model.index( 2, 6 ) ).toString(), QStringLiteral( "n/a" ) ); + QCOMPARE( model.data( model.index( 2, 7 ) ).toString(), QStringLiteral( "n/a" ) ); + QCOMPARE( model.data( model.index( 2, 8 ) ).toString(), QStringLiteral( "n/a" ) ); + + // with transform set residuals should be visible + model.setGeorefTransform( &transform ); + QCOMPARE( model.data( model.index( 0, 6 ) ).toString(), QStringLiteral( "0.000000" ) ); + QCOMPARE( model.data( model.index( 0, 7 ) ).toString(), QStringLiteral( "-189.189188" ) ); + QCOMPARE( model.data( model.index( 0, 8 ) ).toString(), QStringLiteral( "189.189188" ) ); + + QCOMPARE( model.data( model.index( 1, 6 ) ).toString(), QStringLiteral( "105.714286" ) ); + QCOMPARE( model.data( model.index( 1, 7 ) ).toString(), QStringLiteral( "189.189188" ) ); + QCOMPARE( model.data( model.index( 1, 8 ) ).toString(), QStringLiteral( "216.721155" ) ); + + QCOMPARE( model.data( model.index( 2, 6 ) ).toString(), QStringLiteral( "-105.714286" ) ); + QCOMPARE( model.data( model.index( 2, 7 ) ).toString(), QStringLiteral( "0.000000" ) ); + QCOMPARE( model.data( model.index( 2, 8 ) ).toString(), QStringLiteral( "105.714286" ) ); + + // set data + // these columns are read-only + QVERIFY( !model.setData( model.index( 0, 0 ), 11 ) ); + QVERIFY( !model.setData( model.index( 0, 1 ), 11 ) ); + QVERIFY( !model.setData( model.index( 0, 6 ), 11 ) ); + QVERIFY( !model.setData( model.index( 0, 7 ), 11 ) ); + QVERIFY( !model.setData( model.index( 0, 8 ), 11 ) ); + + QVERIFY( model.setData( model.index( 0, 2 ), 777777.77 ) ); + QCOMPARE( model.data( model.index( 0, 2 ) ).toString(), QStringLiteral( "777777.77" ) ); + QCOMPARE( list.at( 0 )->sourcePoint().x(), 777777.77 ); + + QVERIFY( model.setData( model.index( 0, 3 ), 3333333.33 ) ); + QCOMPARE( model.data( model.index( 0, 3 ) ).toString(), QStringLiteral( "3333333.33" ) ); + QCOMPARE( list.at( 0 )->sourcePoint().y(), 3333333.33 ); + + QVERIFY( model.setData( model.index( 0, 4 ), 1660000.77 ) ); + QCOMPARE( model.data( model.index( 0, 4 ) ).toString(), QStringLiteral( "1660000.77" ) ); + QCOMPARE( list.at( 0 )->destinationPoint().x(), 1660000.77 ); + + QVERIFY( model.setData( model.index( 0, 5 ), 4433333.33 ) ); + QCOMPARE( model.data( model.index( 0, 5 ) ).toString(), QStringLiteral( "4433333.33" ) ); + QCOMPARE( list.at( 0 )->destinationPoint().y(), 4433333.33 ); + + // disable point + QVERIFY( model.setData( model.index( 0, 0 ), Qt::Unchecked, Qt::CheckStateRole ) ); + QCOMPARE( model.data( model.index( 0, 0 ), Qt::CheckStateRole ), Qt::Unchecked ); + QVERIFY( !list.at( 0 )->isEnabled() ); + // enable point + QVERIFY( model.setData( model.index( 0, 0 ), Qt::Checked, Qt::CheckStateRole ) ); + QCOMPARE( model.data( model.index( 0, 0 ), Qt::CheckStateRole ), Qt::Checked ); + QVERIFY( list.at( 0 )->isEnabled() ); +} + +void TestQgsGeoreferencer::testListModelCrs() +{ + // test destination crs handling in list model + QgsGCPList list; + QgsMapCanvas c1; + QgsMapCanvas c2; + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 781662.375, 3350923.125 ), QgsPointXY( -30, 40 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 787362.375, 3350923.125 ), QgsPointXY( 16697923, -3503549 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + true ) ); + list.append( new QgsGeorefDataPoint( &c1, &c2, + QgsPointXY( 787362.375, 3362323.125 ), QgsPointXY( 17697923, -3403549 ), QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:3857" ) ), + true ) ); + + QgsGCPListModel model; + model.setGCPList( &list ); + model.setTargetCrs( QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ), QgsProject::instance()->transformContext() ); + + // all destination points are shown in target crs + QCOMPARE( model.data( model.index( 0, 4 ) ).toString(), QStringLiteral( "-30.000000" ) ); + QCOMPARE( model.data( model.index( 0, 5 ) ).toString(), QStringLiteral( "40.000000" ) ); + QCOMPARE( model.data( model.index( 1, 4 ) ).toString(), QStringLiteral( "149.999994" ) ); + QCOMPARE( model.data( model.index( 1, 5 ) ).toString(), QStringLiteral( "-29.999993" ) ); + QCOMPARE( model.data( model.index( 2, 4 ) ).toString(), QStringLiteral( "158.983147" ) ); + QCOMPARE( model.data( model.index( 2, 5 ) ).toString(), QStringLiteral( "-29.218996" ) ); + + // setting a point's destination x or y will update that point to being stored in the target crs + QVERIFY( model.setData( model.index( 0, 4 ), -31.0 ) ); + QCOMPARE( model.data( model.index( 0, 4 ) ).toString(), QStringLiteral( "-31.000000" ) ); + QCOMPARE( model.data( model.index( 0, 5 ) ).toString(), QStringLiteral( "40.000000" ) ); + QCOMPARE( list.at( 0 )->destinationPoint().x(), -31.0 ); + QCOMPARE( list.at( 0 )->destinationPoint().y(), 40.0 ); + QCOMPARE( list.at( 0 )->destinationPointCrs().authid(), QStringLiteral( "EPSG:4326" ) ); + + QVERIFY( model.setData( model.index( 0, 5 ), 41.0 ) ); + QCOMPARE( model.data( model.index( 0, 4 ) ).toString(), QStringLiteral( "-31.000000" ) ); + QCOMPARE( model.data( model.index( 0, 5 ) ).toString(), QStringLiteral( "41.000000" ) ); + QCOMPARE( list.at( 0 )->destinationPoint().x(), -31.0 ); + QCOMPARE( list.at( 0 )->destinationPoint().y(), 41.0 ); + QCOMPARE( list.at( 0 )->destinationPointCrs().authid(), QStringLiteral( "EPSG:4326" ) ); + + // destination point was originally in EPSG:3857, should be changed to 4326 when destination x is set + QVERIFY( model.setData( model.index( 1, 4 ), 148 ) ); + QCOMPARE( model.data( model.index( 1, 4 ) ).toString(), QStringLiteral( "148.000000" ) ); + QCOMPARE( model.data( model.index( 1, 5 ) ).toString(), QStringLiteral( "-29.999993" ) ); + QCOMPARE( list.at( 1 )->destinationPoint().x(), 148 ); + QGSCOMPARENEAR( list.at( 1 )->destinationPoint().y(), -30, 0.001 ); + QCOMPARE( list.at( 1 )->destinationPointCrs().authid(), QStringLiteral( "EPSG:4326" ) ); + + QVERIFY( model.setData( model.index( 1, 5 ), -32.0 ) ); + QCOMPARE( model.data( model.index( 1, 4 ) ).toString(), QStringLiteral( "148.000000" ) ); + QCOMPARE( model.data( model.index( 1, 5 ) ).toString(), QStringLiteral( "-32.000000" ) ); + QCOMPARE( list.at( 1 )->destinationPoint().x(), 148 ); + QCOMPARE( list.at( 1 )->destinationPoint().y(), -32 ); + QCOMPARE( list.at( 1 )->destinationPointCrs().authid(), QStringLiteral( "EPSG:4326" ) ); + + // destination point was originally in EPSG:3857, should be changed to 4326 when destination y is set + QVERIFY( model.setData( model.index( 2, 5 ), -29.0 ) ); + QCOMPARE( model.data( model.index( 2, 4 ) ).toString(), QStringLiteral( "158.983147" ) ); + QCOMPARE( model.data( model.index( 2, 5 ) ).toString(), QStringLiteral( "-29.000000" ) ); + QGSCOMPARENEAR( list.at( 2 )->destinationPoint().x(), 158.9831, 0.001 ); + QCOMPARE( list.at( 2 )->destinationPoint().y(), -29 ); + QCOMPARE( list.at( 2 )->destinationPointCrs().authid(), QStringLiteral( "EPSG:4326" ) ); + + QVERIFY( model.setData( model.index( 2, 4 ), 159 ) ); + QCOMPARE( model.data( model.index( 2, 4 ) ).toString(), QStringLiteral( "159.000000" ) ); + QCOMPARE( model.data( model.index( 2, 5 ) ).toString(), QStringLiteral( "-29.000000" ) ); + QCOMPARE( list.at( 2 )->destinationPoint().x(), 159 ); + QCOMPARE( list.at( 2 )->destinationPoint().y(), -29 ); + QCOMPARE( list.at( 2 )->destinationPointCrs().authid(), QStringLiteral( "EPSG:4326" ) ); +} + +QGSTEST_MAIN( TestQgsGeoreferencer ) +#include "testqgsgeoreferencer.moc"