diff --git a/src/plugins/georeferencer/qgsresidualplotitem.cpp b/src/plugins/georeferencer/qgsresidualplotitem.cpp new file mode 100644 index 000000000000..df4a381cab10 --- /dev/null +++ b/src/plugins/georeferencer/qgsresidualplotitem.cpp @@ -0,0 +1,207 @@ +#include "qgsresidualplotitem.h" +#include "qgsgeorefdatapoint.h" +#include +#include +#ifndef Q_OS_MACX +#include +#else +#include +#endif + +QgsResidualPlotItem::QgsResidualPlotItem( QgsComposition* c ): QgsComposerItem( c ), mConvertScaleToMapUnits( false ), mPixelToMapUnits( 1.0 ) +{ + +} + +QgsResidualPlotItem::~QgsResidualPlotItem() +{ + +} + +void QgsResidualPlotItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget ) +{ + if ( mGCPList.size() < 1 || !painter ) + { + return; + } + + double widthMM = rect().width(); + double heightMM = rect().height(); + + QPen enabledPen( QColor( 0, 0, 255, 255 ) ); + enabledPen.setWidthF( 0.4 ); + QPen disabledPen( QColor( 0, 0, 255, 127 ) ); + disabledPen.setWidthF( 0.3 ); + QBrush enabledBrush( QColor( 255, 0, 0, 255 ) ); + QBrush disabledBrush( QColor( 255, 0, 0, 127 ) ); + + //draw all points and collect minimal mm/pixel ratio + double minMMPixelRatio = DBL_MAX; + double mmPixelRatio = 1; + + painter->setRenderHint( QPainter::Antialiasing, true ); + + QgsGCPList::const_iterator gcpIt = mGCPList.constBegin(); + for ( ; gcpIt != mGCPList.constEnd(); ++gcpIt ) + { + QgsPoint gcpCoords = ( *gcpIt )->pixelCoords(); + double gcpItemMMX = ( gcpCoords.x() - mExtent.xMinimum() ) / mExtent.width() * widthMM; + double gcpItemMMY = ( 1 - ( gcpCoords.y() - mExtent.yMinimum() ) / mExtent.height() ) * heightMM; + + if (( *gcpIt )->isEnabled() ) + { + painter->setPen( enabledPen ); + painter->setBrush( enabledBrush ); + } + else + { + painter->setPen( disabledPen ); + painter->setBrush( disabledBrush ); + } + painter->drawRect( QRectF( gcpItemMMX - 1, gcpItemMMY - 1, 2, 2 ) ); + drawText( painter, gcpItemMMX + 2, gcpItemMMY + 2, QString::number(( *gcpIt )->id() ), QFont() ); + + mmPixelRatio = maxMMToPixelRatioForGCP( *gcpIt, gcpItemMMX, gcpItemMMY ); + if ( mmPixelRatio < minMMPixelRatio ) + { + minMMPixelRatio = mmPixelRatio; + } + } + + //draw residual arrows + gcpIt = mGCPList.constBegin(); + for ( ; gcpIt != mGCPList.constEnd(); ++gcpIt ) + { + QgsPoint gcpCoords = ( *gcpIt )->pixelCoords(); + double gcpItemMMX = ( gcpCoords.x() - mExtent.xMinimum() ) / mExtent.width() * widthMM; + double gcpItemMMY = ( 1 - ( gcpCoords.y() - mExtent.yMinimum() ) / mExtent.height() ) * heightMM; + if (( *gcpIt )->isEnabled() ) + { + painter->setPen( enabledPen ); + } + else + { + painter->setPen( disabledPen ); + } + + QPointF p1( gcpItemMMX, gcpItemMMY ); + QPointF p2( gcpItemMMX + ( *gcpIt )->residual().x() * minMMPixelRatio, gcpItemMMY + ( *gcpIt )->residual().y() * minMMPixelRatio ); + painter->drawLine( p1, p2 ); + painter->setBrush( QBrush( painter->pen().color() ) ); + drawArrowHead( painter, p2.x(), p2.y(), angle( p1, p2 ), 1 ); + } + + //draw scale bar + double initialScaleBarWidth = rect().width() / 5; + int nUnits; + double scaleBarWidth; + if ( mConvertScaleToMapUnits ) //map units + { + nUnits = initialScaleBarWidth / minMMPixelRatio * mPixelToMapUnits; + scaleBarWidth = nUnits * minMMPixelRatio / mPixelToMapUnits; + } + else //pixels + { + nUnits = initialScaleBarWidth / minMMPixelRatio; + scaleBarWidth = nUnits * minMMPixelRatio; + } + + painter->setPen( QColor( 0, 0, 0 ) ); + painter->drawLine( QPointF( 5, rect().height() - 5 ), QPointF( 5 + scaleBarWidth, rect().height() - 5 ) ); + painter->drawLine( QPointF( 5, rect().height() - 5 ), QPointF( 5, rect().height() - 7 ) ); + painter->drawLine( QPointF( 5 + scaleBarWidth, rect().height() - 5 ), QPointF( 5 + scaleBarWidth, rect().height() - 7 ) ); + QFont scaleBarFont; + if ( mConvertScaleToMapUnits ) + { + drawText( painter, 5, rect().height() - 4 + fontAscentMillimeters( scaleBarFont ), QString( "%1 map units" ).arg( nUnits ), QFont() ); + } + else + { + drawText( painter, 5, rect().height() - 4 + fontAscentMillimeters( scaleBarFont ), QString( "%1 pixels" ).arg( nUnits ), QFont() ); + } + + drawFrame( painter ); + if ( isSelected() ) + { + drawSelectionBoxes( painter ); + } +} + +double QgsResidualPlotItem::maxMMToPixelRatioForGCP( const QgsGeorefDataPoint* p, double pixelXMM, double pixelYMM ) +{ + if ( !p ) + { + return 0; + } + + //calculate intersections with upper / lower frame edge depending on the residual y sign + double upDownDist = DBL_MAX; //distance to frame intersection with lower or upper frame + double leftRightDist = DBL_MAX; //distance to frame intersection with left or right frame + + QPointF residual = p->residual(); + QLineF residualLine( pixelXMM, pixelYMM, pixelXMM + residual.x(), pixelYMM + residual.y() ); + QPointF intersectionPoint; + double dx, dy; + + if ( residual.y() > 0 ) + { + QLineF lowerFrameLine( 0, rect().height(), rect().width(), rect().height() ); + if ( residualLine.intersect( lowerFrameLine, &intersectionPoint ) != QLineF::NoIntersection ) + { + upDownDist = dist( QPointF( pixelXMM, pixelYMM ) , intersectionPoint ); + } + } + else if ( residual.y() < 0 ) + { + QLineF upperFrameLine( 0, 0, mExtent.xMaximum(), 0 ); + if ( residualLine.intersect( upperFrameLine, &intersectionPoint ) != QLineF::NoIntersection ) + { + upDownDist = dist( QPointF( pixelXMM, pixelYMM ) , intersectionPoint ); + } + } + + //calculate intersection with left / right frame edge depending on the residual x sign + if ( residual.x() > 0 ) + { + QLineF rightFrameLine( rect().width(), 0, rect().width(), rect().height() ); + if ( residualLine.intersect( rightFrameLine, &intersectionPoint ) != QLineF::NoIntersection ) + { + leftRightDist = dist( QPointF( pixelXMM, pixelYMM ) , intersectionPoint ); + } + } + else if ( residual.x() < 0 ) + { + QLineF leftFrameLine( 0, 0 , 0, rect().height() ); + if ( residualLine.intersect( leftFrameLine, &intersectionPoint ) != QLineF::NoIntersection ) + { + leftRightDist = dist( QPointF( pixelXMM, pixelYMM ) , intersectionPoint ); + } + } + + double resTot = sqrt( residual.x() * residual.x() + residual.y() * residual.y() ); + if ( leftRightDist <= upDownDist ) + { + return leftRightDist / resTot; + } + else + { + return upDownDist / resTot; + } +} + +bool QgsResidualPlotItem::writeXML( QDomElement& elem, QDomDocument & doc ) const +{ + return false; +} + +bool QgsResidualPlotItem::readXML( const QDomElement& itemElem, const QDomDocument& doc ) +{ + return false; +} + +double QgsResidualPlotItem::dist( const QPointF& p1, const QPointF& p2 ) const +{ + double dx = p2.x() - p1.x(); + double dy = p2.y() - p1.y(); + return sqrt( dx * dx + dy * dy ); +} diff --git a/src/plugins/georeferencer/qgsresidualplotitem.h b/src/plugins/georeferencer/qgsresidualplotitem.h new file mode 100644 index 000000000000..c11dfb0f5645 --- /dev/null +++ b/src/plugins/georeferencer/qgsresidualplotitem.h @@ -0,0 +1,51 @@ +#ifndef QGSRESIDUALPLOTITEM_H +#define QGSRESIDUALPLOTITEM_H + +#include "qgscomposeritem.h" +#include "qgsgcplist.h" +#include "qgsrectangle.h" + +/**A composer item to visualise the distribution of georeference residuals. For the visualisation, \ +the length of the residual arrows are scaled*/ +class QgsResidualPlotItem: public QgsComposerItem +{ + public: + QgsResidualPlotItem( QgsComposition* c ); + ~QgsResidualPlotItem(); + + /** \brief Reimplementation of QCanvasItem::paint*/ + virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget ); + + void setGCPList( const QgsGCPList& list ) { mGCPList = list; } + QgsGCPList GCPList() const { return mGCPList; } + + void setExtent( const QgsRectangle& rect ) { mExtent = rect;} + QgsRectangle extent() const { return mExtent; } + + void setPixelToMapUnits( double d ) { mPixelToMapUnits = d; } + double pixelToMapUnits() const { return mPixelToMapUnits; } + + void setConvertScaleToMapUnits( bool convert ) { mConvertScaleToMapUnits = convert; } + bool convertScaleToMapUnits() const { return mConvertScaleToMapUnits; } + + virtual bool writeXML( QDomElement& elem, QDomDocument & doc ) const; + virtual bool readXML( const QDomElement& itemElem, const QDomDocument& doc ); + + private: + //gcp list + QgsGCPList mGCPList; + + QgsRectangle mExtent; + /**True if the scale bar units should be converted to map units. This can be done for transformation where the scaling in all directions is the same (helmert)*/ + bool mConvertScaleToMapUnits; + /**Scale factor from pixels to map units*/ + double mPixelToMapUnits; + + /**Calculates maximal possible mm to pixel ratio such that the residual arrow is still inside the frame*/ + double maxMMToPixelRatioForGCP( const QgsGeorefDataPoint* p, double pixelXMM, double pixelYMM ); + + /**Returns distance between two points*/ + double dist( const QPointF& p1, const QPointF& p2 ) const; +}; + +#endif // QGSRESIDUALPLOTITEM_H