Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport release-3_22] Fix lots of georeferencer issues #47276

Merged
merged 47 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
5953cfd
Minor cleanup to rename ambiguous method
nyalldawson Feb 2, 2022
2ce753d
Start on test suite for georeferencer
nyalldawson Feb 2, 2022
bac71a9
Spelling
nyalldawson Feb 2, 2022
2fc2812
Rename method for clarity
nyalldawson Feb 2, 2022
7f079c2
Cleanup some georeferencer API to disambiguate if points are source/
nyalldawson Feb 2, 2022
0d02d78
[georeferencer] Fix incorrect pixel to layer coordinate update causes…
nyalldawson Feb 3, 2022
4571418
Add tests for conversion of source coordinate to pixel
nyalldawson Feb 3, 2022
d51b6bc
Rename method for clarity
nyalldawson Feb 3, 2022
0f963ea
Simplify call
nyalldawson Feb 3, 2022
9c33bc7
Add tests for source pixel to coord rect conversion, rename for clarity
nyalldawson Feb 3, 2022
1e0f1c2
Rename things to clarify that they aren't always pixel values
nyalldawson Feb 3, 2022
0640d00
Fix loading existing GCPs for images which are already referenced res…
nyalldawson Feb 3, 2022
fbd3cfa
Remove fragile cached transformed destination point -- this needlessl…
nyalldawson Feb 3, 2022
9acb8ec
Const
nyalldawson Feb 3, 2022
d5575b6
When calling QgsGeorefTransform::updateParametersFromGcps the source …
nyalldawson Feb 3, 2022
a198bf4
Saving gcps should ALWAYS use source pixel coordinates
nyalldawson Feb 3, 2022
2300217
Don't misuse a string as a bool
nyalldawson Feb 3, 2022
7a23890
When autosaving the georeferencer gcp points, we MUST use the original
nyalldawson Feb 3, 2022
f852443
Fix incorrect display of residual lines for previously georeferenced …
nyalldawson Feb 3, 2022
0ce7c5f
Remove unused method/member
nyalldawson Feb 7, 2022
2127c0c
Fix misleading override of QList::size
nyalldawson Feb 7, 2022
4c0bf94
Cleanup georeferencer GCP data class by separating data point
nyalldawson Feb 7, 2022
80b58f7
Fix memory leaks and make gcp container class safer to use
nyalldawson Feb 8, 2022
f63cb67
Don't cache transformed destination points
nyalldawson Feb 8, 2022
e35c057
Add test for createGCPVectors
nyalldawson Feb 8, 2022
4c1bae1
[georeferencer] Fix recenter on points fails when some points in list
nyalldawson Feb 8, 2022
38cab6b
When saving/loading GCP files, use source layer coordinates for source
nyalldawson Feb 8, 2022
1a96f29
Fix logic determining when link georef to qgis canvas actions are
nyalldawson Feb 8, 2022
fdb41bd
Fix shortcut
nyalldawson Feb 8, 2022
5584eaa
Fix build on older Qt
nyalldawson Feb 8, 2022
c4610ad
wip
nyalldawson Feb 9, 2022
f7b8573
Fix reading destination CRS from .points files
nyalldawson Feb 9, 2022
1b0260b
Never silently overwrite gcp point files without prompting user
nyalldawson Feb 9, 2022
3d1bff5
Move code for calculating residuals to QgsGCPList out of model
nyalldawson Feb 9, 2022
b380a0c
Use a proper model for gcp list, fix use of delegates, fix gcp
nyalldawson Feb 9, 2022
20a8b07
[georeferencer] Cleanup target CRS handling, and ensure destination
nyalldawson Feb 9, 2022
e3399e5
Remove unused code
nyalldawson Feb 9, 2022
5599bb9
Cleanup dialog code
nyalldawson Feb 9, 2022
c822fde
When loading gcp points for a raster which is already
nyalldawson Feb 9, 2022
4e72461
Fix reading and writing invalid crs information to gcp points files
nyalldawson Feb 9, 2022
df4f6ac
More robust
nyalldawson Feb 9, 2022
18bd04b
When changing destination points coordinates in table, automatically
nyalldawson Feb 10, 2022
c8027f4
Cleanup code, fix deleting points
nyalldawson Feb 10, 2022
b480c55
Show CRS details in tooltips for destination X/Y values
nyalldawson Feb 10, 2022
c7423f2
Use an appropriate number of decimal places for display
nyalldawson Feb 10, 2022
f188c09
Cleanup handling of 'Create world file only' option
nyalldawson Feb 10, 2022
66d0835
Create world file only option is also compatible with Helmert transforms
nyalldawson Feb 10, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/app/georeferencer/qgsgcpcanvasitem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 )
Expand Down Expand Up @@ -216,7 +220,7 @@ double QgsGCPCanvasItem::residualToScreenFactor() const
}
}

return 1.0 / ( mapUnitsPerScreenPixel * mapUnitsPerRasterPixel );
return mapUnitsPerRasterPixel / mapUnitsPerScreenPixel;
}

void QgsGCPCanvasItem::checkBoundingRectChange()
Expand Down
243 changes: 196 additions & 47 deletions src/app/georeferencer/qgsgcplist.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,83 +18,232 @@
#include "qgscoordinatereferencesystem.h"
#include "qgscoordinatetransform.h"
#include "qgsproject.h"
#include "qgsgeoreftransform.h"

#include "qgsgcplist.h"
#include <QDir>
#include <QTextStream>

QgsGCPList::QgsGCPList( const QgsGCPList &list )
: QList<QgsGeorefDataPoint *>()
void QgsGCPList::createGCPVectors( QVector<QgsPointXY> &sourcePoints, QVector<QgsPointXY> &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<QgsPointXY> &mapCoords, QVector<QgsPointXY> &pixelCoords, const QgsCoordinateReferenceSystem targetCrs )
void QgsGCPList::updateResiduals( QgsGeorefTransform *georefTransform, const QgsCoordinateReferenceSystem &targetCrs, const QgsCoordinateTransformContext &context, QgsUnitTypes::RenderUnit residualUnit )
{
mapCoords = QVector<QgsPointXY>( size() );
pixelCoords = QVector<QgsPointXY>( size() );
QgsPointXY transCoords;
for ( int i = 0, j = 0; i < sizeAll(); i++ )
bool bTransformUpdated = false;
QVector<QgsPointXY> sourceCoordinates;
QVector<QgsPointXY> 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<QgsGcpPoint> QgsGCPList::asPoints() const
{
if ( QList<QgsGeorefDataPoint *>::isEmpty() )
return 0;

int s = 0;
const_iterator it = begin();
while ( it != end() )
QList<QgsGcpPoint> 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<QgsGeorefDataPoint *>::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<QgsGcpPoint> 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<QgsGcpPoint> 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;
}
Loading