diff --git a/src/lib/marble/ClipPainter.cpp b/src/lib/marble/ClipPainter.cpp index 23f3e401f..5b72b26d1 100644 --- a/src/lib/marble/ClipPainter.cpp +++ b/src/lib/marble/ClipPainter.cpp @@ -1,1302 +1,1307 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2009 Torsten Rahn // Copyright 2007 Inge Wallin // #include "ClipPainter.h" #include #include "MarbleDebug.h" namespace Marble { class ClipPainterPrivate { public: explicit ClipPainterPrivate( ClipPainter * parent ); ClipPainter * q; // true if clipping is on. bool m_doClip; // The limits qreal m_left; qreal m_right; qreal m_top; qreal m_bottom; // Used in the paint process of vectors.. int m_currentSector; int m_previousSector; // int m_debugNodeCount; QPointF m_currentPoint; QPointF m_previousPoint; inline int sector( const QPointF & point ) const; inline QPointF clipTop( qreal m, const QPointF & point ) const; inline QPointF clipLeft( qreal m, const QPointF & point ) const; inline QPointF clipBottom( qreal m, const QPointF & point ) const; inline QPointF clipRight( qreal m, const QPointF & point ) const; inline void initClipRect(); inline void clipPolyObject ( const QPolygonF & sourcePolygon, QVector & clippedPolyObjects, bool isClosed ); inline void clipMultiple( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, bool isClosed ); inline void clipOnce( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, bool isClosed ); inline void clipOnceCorner( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, const QPointF& corner, const QPointF& point, bool isClosed ) const; inline void clipOnceEdge( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, const QPointF& point, bool isClosed ) const; void labelPosition(const QPolygonF& polygon, QVector& labelNodes, LabelPositionFlags labelPositionFlags); bool pointAllowsLabel( const QPointF& point ); QPointF interpolateLabelPoint( const QPointF& previousPoint, const QPointF& currentPoint, LabelPositionFlags labelPositionFlags ); static inline qreal _m( const QPointF & start, const QPointF & end ); void debugDrawNodes( const QPolygonF & ); qreal m_labelAreaMargin; int m_debugPenBatchColor; int m_debugBrushBatchColor; int m_debugPolygonsLevel; bool m_debugBatchRender; }; } using namespace Marble; // #define MARBLE_DEBUG ClipPainter::ClipPainter(QPaintDevice * pd, bool clip) : QPainter( pd ), d( new ClipPainterPrivate( this ) ) { d->initClipRect(); // m_debugNodeCount = 0; d->m_doClip = clip; } ClipPainter::ClipPainter() : d( new ClipPainterPrivate( this ) ) { } ClipPainter::~ClipPainter() { delete d; } void ClipPainter::setScreenClip(bool enable) { d->m_doClip = enable; } bool ClipPainter::hasScreenClip() const { return d->m_doClip; } void ClipPainter::drawPolygon ( const QPolygonF & polygon, Qt::FillRule fillRule ) { if ( d->m_doClip ) { d->initClipRect(); QVector clippedPolyObjects; d->clipPolyObject( polygon, clippedPolyObjects, true ); foreach( const QPolygonF & clippedPolyObject, clippedPolyObjects ) { if ( clippedPolyObject.size() > 2 ) { // mDebug() << "Size: " << clippedPolyObject.size(); if (d->m_debugPolygonsLevel) { QBrush brush = QPainter::brush(); QBrush originalBrush = brush; QColor color = brush.color(); color.setAlpha(color.alpha()*0.75); brush.setColor(color); QPainter::setBrush(brush); QPainter::drawPolygon ( clippedPolyObject, fillRule ); QPainter::setBrush(originalBrush); d->debugDrawNodes( clippedPolyObject ); } else { QPainter::drawPolygon ( clippedPolyObject, fillRule ); } } } } else { if (d->m_debugPolygonsLevel) { QBrush brush = QPainter::brush(); QBrush originalBrush = brush; QColor color = brush.color(); color.setAlpha(color.alpha()*0.75); brush.setColor(color); QPainter::setBrush(brush); QPainter::drawPolygon ( polygon, fillRule ); QPainter::setBrush(originalBrush); d->debugDrawNodes( polygon ); } else { QPainter::drawPolygon ( polygon, fillRule ); } } } void ClipPainter::drawPolyline( const QPolygonF & polygon ) { if ( d->m_doClip ) { d->initClipRect(); QVector clippedPolyObjects; d->clipPolyObject( polygon, clippedPolyObjects, false ); foreach( const QPolygonF & clippedPolyObject, clippedPolyObjects ) { if ( clippedPolyObject.size() > 1 ) { if (d->m_debugPolygonsLevel) { QPen pen = QPainter::pen(); QPen originalPen = pen; QColor color = pen.color(); color.setAlpha(color.alpha()*0.75); pen.setColor(color); QPainter::setPen(pen); QPainter::drawPolyline ( clippedPolyObject ); QPainter::setPen(originalPen); d->debugDrawNodes( clippedPolyObject ); } else { QPainter::drawPolyline ( clippedPolyObject ); } } } } else { if (d->m_debugPolygonsLevel) { QPen pen = QPainter::pen(); QPen originalPen = pen; QColor color = pen.color(); color.setAlpha(color.alpha()*0.75); pen.setColor(color); QPainter::setPen(pen); QPainter::drawPolyline ( polygon ); QPainter::setPen(originalPen); d->debugDrawNodes( polygon ); } else { QPainter::drawPolyline ( polygon ); } } } void ClipPainter::drawPolyline(const QPolygonF & polygon, QVector& labelNodes, LabelPositionFlags positionFlags) { if ( d->m_doClip ) { d->initClipRect(); QVector clippedPolyObjects; d->clipPolyObject( polygon, clippedPolyObjects, false ); foreach( const QPolygonF & clippedPolyObject, clippedPolyObjects ) { if (d->m_debugPolygonsLevel) { QPen pen = QPainter::pen(); QPen originalPen = pen; QColor color = pen.color(); color.setAlpha(color.alpha()*0.75); pen.setColor(color); QPainter::setPen(pen); QPainter::drawPolyline ( clippedPolyObject ); QPainter::setPen(originalPen); d->debugDrawNodes( clippedPolyObject ); } else { QPainter::drawPolyline ( clippedPolyObject ); } } } else { if (d->m_debugPolygonsLevel) { QPen pen = QPainter::pen(); QPen originalPen = pen; QColor color = pen.color(); color.setAlpha(color.alpha()*0.75); pen.setColor(color); QPainter::setPen(pen); QPainter::drawPolyline ( polygon ); QPainter::setPen(originalPen); d->debugDrawNodes( polygon ); } else { QPainter::drawPolyline ( polygon ); } d->labelPosition( polygon, labelNodes, positionFlags ); } } +void ClipPainter::labelPosition(const QPolygonF & polygon, QVector& labelNodes, + LabelPositionFlags labelPositionFlags) { + d->labelPosition(polygon, labelNodes, labelPositionFlags); +} + void ClipPainter::setPen(const QColor &color) { if (d->m_debugBatchRender) { qDebug() << Q_FUNC_INFO; } setPen(QPen(color)); } void ClipPainter::setPen(Qt::PenStyle style) { if (d->m_debugBatchRender) { qDebug() << Q_FUNC_INFO; } setPen(QPen(style)); } void ClipPainter::setPen(const QPen & pen) { if (d->m_debugBatchRender) { qDebug() << Q_FUNC_INFO; if (pen != QPainter::pen()) { qDebug() << "--" << pen.color() << QPainter::pen().color() ; QPen newPen = pen; newPen.setColor((Qt::GlobalColor)(d->m_debugPenBatchColor)); QPainter::setPen(newPen); d->m_debugPenBatchColor++; d->m_debugPenBatchColor %= 20; } else { qDebug() << "++"; QPainter::setPen(pen); } } else { QPainter::setPen(pen); } } void ClipPainter::setBrush(const QBrush & brush) { if (d->m_debugBatchRender) { qDebug() << Q_FUNC_INFO; if (brush != QPainter::brush()) { qDebug() << "--" << brush.color() << QPainter::brush().color() ; QBrush batchColor(QColor((Qt::GlobalColor)(d->m_debugBrushBatchColor))); QPainter::setBrush(batchColor); d->m_debugBrushBatchColor++; d->m_debugBrushBatchColor %= 20; } else { qDebug() << "++"; QPainter::setBrush(brush); } } else { QPainter::setBrush(brush); } } void ClipPainterPrivate::labelPosition(const QPolygonF & polygon, QVector& labelNodes, LabelPositionFlags labelPositionFlags) { bool currentAllowsLabel = false; if ( labelPositionFlags.testFlag( LineCenter ) ) { // The Label at the center of the polyline: int labelPosition = static_cast( polygon.size() / 2.0 ); if ( polygon.size() > 0 ) { if ( labelPosition >= polygon.size() ) { labelPosition = polygon.size() - 1; } labelNodes << polygon.at( labelPosition ); } } if ( polygon.size() > 0 && labelPositionFlags.testFlag( LineStart ) ) { if ( pointAllowsLabel( polygon.first() ) ) { labelNodes << polygon.first(); } // The Label at the start of the polyline: for ( int it = 1; it < polygon.size(); ++it ) { currentAllowsLabel = pointAllowsLabel( polygon.at( it ) ); if ( currentAllowsLabel ) { // As polygon.size() > 0 it's ensured that it-1 exists. QPointF node = interpolateLabelPoint( polygon.at( it -1 ), polygon.at( it ), labelPositionFlags ); if ( node != QPointF( -1.0, -1.0 ) ) { labelNodes << node; } break; } } } if ( polygon.size() > 1 && labelPositionFlags.testFlag( LineEnd ) ) { if ( pointAllowsLabel( polygon.at( polygon.size() - 1 ) ) ) { labelNodes << polygon.at( polygon.size() - 1 ); } // The Label at the end of the polyline: for ( int it = polygon.size() - 2; it > 0; --it ) { currentAllowsLabel = pointAllowsLabel( polygon.at( it ) ); if ( currentAllowsLabel ) { QPointF node = interpolateLabelPoint( polygon.at( it + 1 ), polygon.at( it ), labelPositionFlags ); if ( node != QPointF( -1.0, -1.0 ) ) { labelNodes << node; } break; } } } } bool ClipPainterPrivate::pointAllowsLabel( const QPointF& point ) { if ( point.x() > m_labelAreaMargin && point.x() < q->viewport().width() - m_labelAreaMargin && point.y() > m_labelAreaMargin && point.y() < q->viewport().height() - m_labelAreaMargin ) { return true; } return false; } QPointF ClipPainterPrivate::interpolateLabelPoint( const QPointF& previousPoint, const QPointF& currentPoint, LabelPositionFlags labelPositionFlags ) { qreal m = _m( previousPoint, currentPoint ); if ( previousPoint.x() <= m_labelAreaMargin ) { if ( labelPositionFlags.testFlag( IgnoreXMargin ) ) { return QPointF( -1.0, -1.0 ); } return QPointF( m_labelAreaMargin, previousPoint.y() + ( m_labelAreaMargin - previousPoint.x() ) * m ); } else if ( previousPoint.x() >= q->viewport().width() - m_labelAreaMargin ) { if ( labelPositionFlags.testFlag( IgnoreXMargin ) ) { return QPointF( -1.0, -1.0 ); } return QPointF( q->viewport().width() - m_labelAreaMargin, previousPoint.y() - ( previousPoint.x() - q->viewport().width() + m_labelAreaMargin ) * m ); } if ( previousPoint.y() <= m_labelAreaMargin ) { if ( labelPositionFlags.testFlag( IgnoreYMargin ) ) { return QPointF( -1.0, -1.0 ); } return QPointF( previousPoint.x() + ( m_labelAreaMargin - previousPoint.y() ) / m, m_labelAreaMargin ); } else if ( previousPoint.y() >= q->viewport().height() - m_labelAreaMargin ) { if ( labelPositionFlags.testFlag( IgnoreYMargin ) ) { return QPointF( -1.0, -1.0 ); } return QPointF( previousPoint.x() - ( previousPoint.y() - q->viewport().height() + m_labelAreaMargin ) / m, q->viewport().height() - m_labelAreaMargin ); } // mDebug() << Q_FUNC_INFO << "Previous and current node position are allowed!"; return QPointF( -1.0, -1.0 ); } ClipPainterPrivate::ClipPainterPrivate( ClipPainter * parent ) : m_doClip( true ), m_left(0.0), m_right(0.0), m_top(0.0), m_bottom(0.0), m_currentSector(4), m_previousSector(4), m_currentPoint(QPointF()), m_previousPoint(QPointF()), m_labelAreaMargin(10.0), m_debugPenBatchColor(0), m_debugBrushBatchColor(0), m_debugPolygonsLevel(0), m_debugBatchRender(false) { q = parent; } void ClipPainterPrivate::initClipRect () { qreal penHalfWidth = q->pen().widthF() / 2.0 + 1.0; m_left = -penHalfWidth; m_right = (qreal)(q->device()->width()) + penHalfWidth; m_top = -penHalfWidth; m_bottom = (qreal)(q->device()->height()) + penHalfWidth; } qreal ClipPainterPrivate::_m( const QPointF & start, const QPointF & end ) { qreal divisor = end.x() - start.x(); if ( std::fabs( divisor ) < 0.000001 ) { // this is in screencoordinates so the difference // between 0, 0.000001 and -0.000001 isn't visible at all divisor = 0.000001; } return ( end.y() - start.y() ) / divisor; } QPointF ClipPainterPrivate::clipTop( qreal m, const QPointF & point ) const { return QPointF( ( m_top - point.y() ) / m + point.x(), m_top ); } QPointF ClipPainterPrivate::clipLeft( qreal m, const QPointF & point ) const { return QPointF( m_left, ( m_left - point.x() ) * m + point.y() ); } QPointF ClipPainterPrivate::clipBottom( qreal m, const QPointF & point ) const { return QPointF( ( m_bottom - point.y() ) / m + point.x(), m_bottom ); } QPointF ClipPainterPrivate::clipRight( qreal m, const QPointF & point ) const { return QPointF( m_right, ( m_right - point.x() ) * m + point.y() ); } int ClipPainterPrivate::sector( const QPointF & point ) const { // If we think of the image borders as (infinitely long) parallel // lines then the plane is divided into 9 sectors. Each of these // sections is identified by a unique keynumber (currentSector): // // 0 | 1 | 2 // --+---+-- // 3 | 4 | 5 <- sector number "4" represents the onscreen sector / viewport // --+---+-- // 6 | 7 | 8 // // Figure out the section of the current point. int xSector = 1; if ( point.x() < m_left ) xSector = 0; else if ( point.x() > m_right ) xSector = 2; int ySector = 3; if ( point.y() < m_top ) ySector = 0; else if ( point.y() > m_bottom ) ySector = 6; // By adding xSector and ySector we get a // sector number of the values shown in the ASCII-art graph above. return ySector + xSector; } void ClipPainterPrivate::clipPolyObject ( const QPolygonF & polygon, QVector & clippedPolyObjects, bool isClosed ) { // mDebug() << "ClipPainter enabled." ; // Only create a new polyObject as soon as we know for sure that // the current point is on the screen. QPolygonF clippedPolyObject = QPolygonF(); const QVector::const_iterator itStartPoint = polygon.constBegin(); const QVector::const_iterator itEndPoint = polygon.constEnd(); QVector::const_iterator itPoint = itStartPoint; // We use a while loop to be able to cover linestrings as well as linear rings: // Linear rings require to tessellate the path from the last node to the first node // which isn't really convenient to achieve with a for loop ... bool processingLastNode = false; while ( itPoint != itEndPoint ) { m_currentPoint = (*itPoint); // mDebug() << "m_currentPoint.x()" << m_currentPoint.x() << "m_currentPOint.y()" << m_currentPoint.y(); // Figure out the sector of the current point. m_currentSector = sector( m_currentPoint ); // Initialize the variables related to the previous point. if ( itPoint == itStartPoint && processingLastNode == false ) { if ( isClosed ) { m_previousPoint = polygon.last(); // Figure out the sector of the previous point. m_previousSector = sector( m_previousPoint ); } else { m_previousSector = m_currentSector; } } // If the current point reaches a new sector, take care of clipping. if ( m_currentSector != m_previousSector ) { if ( m_currentSector == 4 || m_previousSector == 4 ) { // In this case the current or the previous point is visible on the // screen but not both. Hence we only need to clip once and require // only one interpolation for both cases. clipOnce( clippedPolyObject, clippedPolyObjects, isClosed ); } else { // This case mostly deals with lines that reach from one // sector that is located off screen to another one that // is located off screen. In this situation the line // can get clipped once, twice, or not at all. clipMultiple( clippedPolyObject, clippedPolyObjects, isClosed ); } m_previousSector = m_currentSector; } // If the current point is onscreen, just add it to our final polygon. if ( m_currentSector == 4 ) { clippedPolyObject << m_currentPoint; #ifdef MARBLE_DEBUG ++(m_debugNodeCount); #endif } m_previousPoint = m_currentPoint; // Now let's handle the case where we have a (closed) polygon and where the // last point of the polyline is outside the viewport and the start point // is inside the viewport. This needs special treatment if ( processingLastNode ) { break; } ++itPoint; if ( itPoint == itEndPoint && isClosed ) { itPoint = itStartPoint; processingLastNode = true; } } // Only add the pointer if there's node data available. if ( !clippedPolyObject.isEmpty() ) { clippedPolyObjects << clippedPolyObject; } } void ClipPainterPrivate::clipMultiple( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, bool isClosed ) { Q_UNUSED( clippedPolyObjects ) Q_UNUSED( isClosed ) // Take care of adding nodes in the image corners if the iterator // traverses off screen sections. qreal m = _m( m_previousPoint, m_currentPoint ); switch ( m_currentSector ) { case 0: if ( m_previousSector == 5 ) { QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointRight.y() > m_top ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_top ); } if ( pointTop.x() >= m_left && pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointLeft.y() > m_top ) clippedPolyObject << pointLeft; } else if ( m_previousSector == 7 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointBottom.x() > m_left ) { clippedPolyObject << pointBottom; } else { clippedPolyObject << QPointF( m_left, m_bottom ); } if ( pointLeft.y() >= m_top && pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; if ( pointTop.x() > m_left ) clippedPolyObject << pointTop; } else if ( m_previousSector == 8 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointBottom.x() > m_left && pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointRight.y() > m_top && pointRight.y() < m_bottom ) clippedPolyObject << pointRight; if ( pointTop.x() > m_left && pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointLeft.y() > m_top && pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; if ( pointBottom.x() <= m_left && pointLeft.y() >= m_bottom ) clippedPolyObject << QPointF( m_left, m_bottom ); if ( pointTop.x() >= m_right && pointRight.y() <= m_top ) clippedPolyObject << QPointF( m_right, m_top ); } clippedPolyObject << QPointF( m_left, m_top ); break; case 1: if ( m_previousSector == 3 ) { QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); if ( pointLeft.y() > m_top ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_top ); } if ( pointTop.x() > m_left ) clippedPolyObject << pointTop; } else if ( m_previousSector == 5 ) { QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); if ( pointRight.y() > m_top ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_top ); } if ( pointTop.x() < m_right ) clippedPolyObject << pointTop; } else if ( m_previousSector == 6 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); if ( pointBottom.x() > m_left ) clippedPolyObject << pointBottom; if ( pointLeft.y() > m_top && pointLeft.y() <= m_bottom ) clippedPolyObject << pointLeft; if ( pointTop.x() > m_left ) { clippedPolyObject << pointTop; } else { clippedPolyObject << QPointF( m_left, m_top ); } } else if ( m_previousSector == 7 ) { clippedPolyObject << clipBottom( m, m_previousPoint ); clippedPolyObject << clipTop( m, m_currentPoint ); } else if ( m_previousSector == 8 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); if ( pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointRight.y() > m_top && pointRight.y() <= m_bottom ) clippedPolyObject << pointRight; if ( pointTop.x() < m_right ) { clippedPolyObject << pointTop; } else { clippedPolyObject << QPointF( m_right, m_top ); } } break; case 2: if ( m_previousSector == 3 ) { QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); if ( pointLeft.y() > m_top ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_top ); } if ( pointTop.x() > m_left && pointTop.x() <= m_right ) clippedPolyObject << pointTop; if ( pointRight.y() > m_top ) clippedPolyObject << pointRight; } else if ( m_previousSector == 7 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); if ( pointBottom.x() < m_right ) { clippedPolyObject << pointBottom; } else { clippedPolyObject << QPointF( m_right, m_bottom ); } if ( pointRight.y() >= m_top && pointRight.y() < m_bottom ) clippedPolyObject << pointRight; if ( pointTop.x() < m_right ) clippedPolyObject << pointTop; } else if ( m_previousSector == 6 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointRight = clipRight( m, m_previousPoint ); if ( pointBottom.x() > m_left && pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointLeft.y() > m_top && pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; if ( pointTop.x() > m_left && pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointRight.y() > m_top && pointRight.y() < m_bottom ) clippedPolyObject << pointRight; if ( pointBottom.x() >= m_right && pointRight.y() >= m_bottom ) clippedPolyObject << QPointF( m_right, m_bottom ); if ( pointTop.x() <= m_left && pointLeft.y() <= m_top ) clippedPolyObject << QPointF( m_left, m_top ); } clippedPolyObject << QPointF( m_right, m_top ); break; case 3: if ( m_previousSector == 7 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointBottom.x() > m_left ) clippedPolyObject << pointBottom; if ( pointLeft.y() < m_bottom ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_bottom ); } } else if ( m_previousSector == 1 ) { QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointTop.x() > m_left ) clippedPolyObject << pointTop; if ( pointLeft.y() > m_top ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_top ); } } else if ( m_previousSector == 8 ) { QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointRight.y() < m_bottom ) clippedPolyObject << pointRight; if ( pointBottom.x() > m_left && pointBottom.x() <= m_right ) clippedPolyObject << pointBottom; if ( pointLeft.y() < m_bottom ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_bottom ); } } else if ( m_previousSector == 5 ) { clippedPolyObject << clipRight( m, m_previousPoint ); clippedPolyObject << clipLeft( m, m_currentPoint ); } else if ( m_previousSector == 2 ) { QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointRight.y() > m_top ) clippedPolyObject << pointRight; if ( pointTop.x() > m_left && pointTop.x() <= m_right ) clippedPolyObject << pointTop; if ( pointLeft.y() > m_top ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_top ); } } break; case 5: if ( m_previousSector == 7 ) { QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); if ( pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointRight.y() < m_bottom ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_bottom ); } } else if ( m_previousSector == 1 ) { QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); if ( pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointRight.y() > m_top ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_top ); } } else if ( m_previousSector == 6 ) { QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); if ( pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; if ( pointBottom.x() >= m_left && pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointRight.y() < m_bottom ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_bottom ); } } else if ( m_previousSector == 3 ) { clippedPolyObject << clipLeft( m, m_previousPoint ); clippedPolyObject << clipRight( m, m_currentPoint ); } else if ( m_previousSector == 0 ) { QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); if ( pointLeft.y() > m_top ) clippedPolyObject << pointLeft; if ( pointTop.x() >= m_left && pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointRight.y() > m_top ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_top ); } } break; case 6: if ( m_previousSector == 5 ) { QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointRight.y() < m_bottom ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_bottom ); } if ( pointBottom.x() >= m_left && pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; } else if ( m_previousSector == 1 ) { QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); if ( pointTop.x() > m_left ) { clippedPolyObject << pointTop; } else { clippedPolyObject << QPointF( m_left, m_top ); } if ( pointLeft.y() > m_top && pointLeft.y() <= m_bottom ) clippedPolyObject << pointLeft; if ( pointBottom.x() > m_left ) clippedPolyObject << pointBottom; } else if ( m_previousSector == 2 ) { QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); if ( pointTop.x() > m_left && pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointRight.y() > m_top && pointRight.y() < m_bottom ) clippedPolyObject << pointRight; if ( pointBottom.x() > m_left && pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointLeft.y() > m_top && pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; if ( pointBottom.x() >= m_right && pointRight.y() >= m_bottom ) clippedPolyObject << QPointF( m_right, m_bottom ); if ( pointTop.x() <= m_left && pointLeft.y() <= m_top ) clippedPolyObject << QPointF( m_left, m_top ); } clippedPolyObject << QPointF( m_left, m_bottom ); break; case 7: if ( m_previousSector == 3 ) { QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); if ( pointLeft.y() < m_bottom ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_bottom ); } if ( pointBottom.x() > m_left ) clippedPolyObject << pointBottom; } else if ( m_previousSector == 5 ) { QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); if ( pointRight.y() < m_bottom ) { clippedPolyObject << pointRight; } else { clippedPolyObject << QPointF( m_right, m_bottom ); } if ( pointBottom.x() < m_right ) clippedPolyObject << pointBottom; } else if ( m_previousSector == 0 ) { QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); if ( pointTop.x() > m_left ) clippedPolyObject << pointTop; if ( pointLeft.y() >= m_top && pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; if ( pointBottom.x() > m_left ) { clippedPolyObject << pointBottom; } else { clippedPolyObject << QPointF( m_left, m_bottom ); } } else if ( m_previousSector == 1 ) { clippedPolyObject << clipTop( m, m_previousPoint ); clippedPolyObject << clipBottom( m, m_currentPoint ); } else if ( m_previousSector == 2 ) { QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); if ( pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointRight.y() >= m_top && pointRight.y() < m_bottom ) clippedPolyObject << pointRight; if ( pointBottom.x() < m_right ) { clippedPolyObject << pointBottom; } else { clippedPolyObject << QPointF( m_right, m_bottom ); } } break; case 8: if ( m_previousSector == 3 ) { QPointF pointLeft = clipLeft( m, m_previousPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); if ( pointLeft.y() < m_bottom ) { clippedPolyObject << pointLeft; } else { clippedPolyObject << QPointF( m_left, m_bottom ); } if ( pointBottom.x() > m_left && pointBottom.x() <= m_right ) clippedPolyObject << pointBottom; if ( pointRight.y() < m_bottom ) clippedPolyObject << pointRight; } else if ( m_previousSector == 1 ) { QPointF pointTop = clipTop( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_currentPoint ); QPointF pointBottom = clipBottom( m, m_currentPoint ); if ( pointTop.x() < m_right ) { clippedPolyObject << pointTop; } else { clippedPolyObject << QPointF( m_right, m_top ); } if ( pointRight.y() > m_top && pointRight.y() <= m_bottom ) clippedPolyObject << pointRight; if ( pointBottom.x() < m_right ) clippedPolyObject << pointBottom; } else if ( m_previousSector == 0 ) { QPointF pointTop = clipTop( m, m_currentPoint ); QPointF pointLeft = clipLeft( m, m_currentPoint ); QPointF pointBottom = clipBottom( m, m_previousPoint ); QPointF pointRight = clipRight( m, m_previousPoint ); if ( pointTop.x() > m_left && pointTop.x() < m_right ) clippedPolyObject << pointTop; if ( pointLeft.y() > m_top && pointLeft.y() < m_bottom ) clippedPolyObject << pointLeft; if ( pointBottom.x() > m_left && pointBottom.x() < m_right ) clippedPolyObject << pointBottom; if ( pointRight.y() > m_top && pointRight.y() < m_bottom ) clippedPolyObject << pointRight; if ( pointBottom.x() <= m_left && pointLeft.y() >= m_bottom ) clippedPolyObject << QPointF( m_left, m_bottom ); if ( pointTop.x() >= m_right && pointRight.y() <= m_top ) clippedPolyObject << QPointF( m_right, m_top ); } clippedPolyObject << QPointF( m_right, m_bottom ); break; default: break; } } void ClipPainterPrivate::clipOnceCorner( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, const QPointF& corner, const QPointF& point, bool isClosed ) const { Q_UNUSED( clippedPolyObjects ) Q_UNUSED( isClosed ) if ( m_currentSector == 4) { // Appearing clippedPolyObject << corner; clippedPolyObject << point; } else { // Disappearing clippedPolyObject << point; clippedPolyObject << corner; } } void ClipPainterPrivate::clipOnceEdge( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, const QPointF& point, bool isClosed ) const { if ( m_currentSector == 4) { // Appearing if ( !isClosed ) { clippedPolyObject = QPolygonF(); } clippedPolyObject << point; } else { // Disappearing clippedPolyObject << point; if ( !isClosed ) { clippedPolyObjects << clippedPolyObject; } } } void ClipPainterPrivate::clipOnce( QPolygonF & clippedPolyObject, QVector & clippedPolyObjects, bool isClosed ) { // Interpolate border points (linear interpolation) QPointF point; // Calculating the slope. qreal m = _m( m_previousPoint, m_currentPoint ); // Calculate in which sector the end of the line is located that is off screen int offscreenpos = ( m_currentSector == 4 ) ? m_previousSector : m_currentSector; // "Rise over run" for all possible situations . switch ( offscreenpos ) { case 0: // topleft point = clipTop( m, m_previousPoint ); if ( point.x() < m_left ) { point = clipLeft( m, point ); } clipOnceCorner( clippedPolyObject, clippedPolyObjects, QPointF( m_left, m_top ), point, isClosed ); break; case 1: // top point = clipTop( m, m_previousPoint ); clipOnceEdge( clippedPolyObject, clippedPolyObjects, point, isClosed ); break; case 2: // topright point = clipTop( m, m_previousPoint ); if ( point.x() > m_right ) { point = clipRight( m, point ); } clipOnceCorner( clippedPolyObject, clippedPolyObjects, QPointF( m_right, m_top ), point, isClosed ); break; case 3: // left point = clipLeft( m, m_previousPoint ); clipOnceEdge( clippedPolyObject, clippedPolyObjects, point, isClosed ); break; case 5: // right point = clipRight( m, m_previousPoint ); clipOnceEdge( clippedPolyObject, clippedPolyObjects, point, isClosed ); break; case 6: // bottomleft point = clipBottom( m, m_previousPoint ); if ( point.x() < m_left ) { point = clipLeft( m, point ); } clipOnceCorner( clippedPolyObject, clippedPolyObjects, QPointF( m_left, m_bottom ), point, isClosed ); break; case 7: // bottom point = clipBottom( m, m_previousPoint ); clipOnceEdge( clippedPolyObject, clippedPolyObjects, point, isClosed ); break; case 8: // bottomright point = clipBottom( m, m_previousPoint ); if ( point.x() > m_right ) { point = clipRight( m, point ); } clipOnceCorner( clippedPolyObject, clippedPolyObjects, QPointF( m_right, m_bottom ), point, isClosed ); break; default: break; } } void ClipPainter::setDebugPolygonsLevel( int level ) { d->m_debugPolygonsLevel = level; } void ClipPainter::setDebugBatchRender( bool enabled ) { d->m_debugBatchRender = enabled; } void ClipPainterPrivate::debugDrawNodes( const QPolygonF & polygon ) { q->save(); q->setRenderHint( QPainter::Antialiasing, false ); q->setPen( Qt::red ); q->setBrush(QBrush("#40FF0000")); const QVector::const_iterator itStartPoint = polygon.constBegin(); const QVector::const_iterator itEndPoint = polygon.constEnd(); QVector::const_iterator itPoint = itStartPoint; int i = 0; for (; itPoint != itEndPoint; ++itPoint ) { ++i; if ( itPoint == itStartPoint || itPoint == itStartPoint + 1 || itPoint == itStartPoint + 2 ) { q->setPen( Qt::darkGreen ); q->setBrush(QBrush("#4000FF00")); if ( itPoint == itStartPoint ) { q->drawRect( itPoint->x() - 6.0, itPoint->y() - 6.0 , 12.0, 12.0 ); } else if ( itPoint == itStartPoint + 1 ) { q->drawRect( itPoint->x() - 4.0, itPoint->y() - 4.0 , 8.0, 8.0 ); } else { q->drawRect( itPoint->x() - 2.0, itPoint->y() - 2.0 , 4.0, 4.0 ); } q->setPen( Qt::red ); q->setBrush(QBrush("#40FF0000")); } else if ( itPoint == itEndPoint - 1 || itPoint == itEndPoint - 2 || itPoint == itEndPoint - 3 ) { q->setPen( Qt::blue ); q->setBrush(QBrush("#400000FF")); if ( itPoint == itEndPoint - 3 ) { q->drawRect( itPoint->x() - 6.0, itPoint->y() - 6.0 , 12.0, 12.0 ); } else if ( itPoint == itEndPoint - 2 ) { q->drawRect( itPoint->x() - 4.0, itPoint->y() - 4.0 , 8.0, 8.0 ); } else { q->drawRect( itPoint->x() - 2.0, itPoint->y() - 2.0 , 4.0, 4.0 ); } q->setPen( Qt::red ); q->setBrush(QBrush("#400000FF")); } else { q->drawRect( itPoint->x() - 4, itPoint->y() - 4 , 8.0, 8.0 ); } if (m_debugPolygonsLevel == 2) { q->setFont(QFont(QStringLiteral("Sans Serif"), 7)); q->setPen("black"); q->drawText(itPoint->x() + 6.0, itPoint->y() + (15 - (i * 5) % 30) , QString::number(i)); } } q->restore(); } diff --git a/src/lib/marble/ClipPainter.h b/src/lib/marble/ClipPainter.h index cfb81a488..101bcc143 100644 --- a/src/lib/marble/ClipPainter.h +++ b/src/lib/marble/ClipPainter.h @@ -1,85 +1,88 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2009 Torsten Rahn // Copyright 2007 Inge Wallin // #ifndef MARBLE_CLIPPAINTER_H #define MARBLE_CLIPPAINTER_H #include #include "marble_export.h" #include "MarbleGlobal.h" class QPaintDevice; class QPolygonF; class QPointF; namespace Marble { /** * @short A QPainter that does viewport clipping for polygons * * This class introduces fast polygon/polyline clipping for QPainter * to increase the performance. * Clipping is accomplished using an algorithm (by Torsten Rahn) that * processes each polyline once. * To keep things fast each possible scenario of two subsequent * points is implemented case by case in a specialized handler which * creates interpolated points and helper points. */ // The reason for this class is a terrible bug in some versions of the // X Server. Suppose the widget size is, say, 1000 x 1000 and we have // a high zoom so that we want to draw a vector from (-100000, // -100000) to (100000, 100000). Then the X server will create a // bitmap that is at least 100000 x 100000 and in the process eat all // available memory. // // So we introduce the ClipPainter that clips all polylines and polygons // to the area that is actually visible in the viewport. // // @internal class ClipPainterPrivate; class MARBLE_EXPORT ClipPainter : public QPainter { public: ClipPainter(); ClipPainter(QPaintDevice*, bool); ~ClipPainter(); void setScreenClip( bool enable ); bool hasScreenClip() const; void drawPolygon( const QPolygonF &, Qt::FillRule fillRule = Qt::OddEvenFill ); void drawPolyline( const QPolygonF & ); void drawPolyline( const QPolygonF &, QVector& labelNodes, LabelPositionFlags labelPositionFlag = LineCenter ); + void labelPosition(const QPolygonF & polygon, QVector& labelNodes, + LabelPositionFlags labelPositionFlags); + void setPen(const QColor &); void setPen(const QPen & pen); void setPen(Qt::PenStyle style); void setBrush(const QBrush & brush); void setDebugPolygonsLevel( int ); void setDebugBatchRender( bool ); // void clearNodeCount(){ m_debugNodeCount = 0; } // int nodeCount(){ return m_debugNodeCount; } private: ClipPainterPrivate * const d; }; } #endif diff --git a/src/lib/marble/GeoPainter.cpp b/src/lib/marble/GeoPainter.cpp index 4a5859b5c..e7f5f1ce9 100644 --- a/src/lib/marble/GeoPainter.cpp +++ b/src/lib/marble/GeoPainter.cpp @@ -1,990 +1,1002 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2006-2009 Torsten Rahn #include "GeoPainter.h" #include "GeoPainter_p.h" #include #include #include #include #include "MarbleDebug.h" #include "GeoDataCoordinates.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataLineString.h" #include "GeoDataLinearRing.h" #include "GeoDataPoint.h" #include "GeoDataPolygon.h" #include "MarbleGlobal.h" #include "ViewportParams.h" #include "AbstractProjection.h" // #define MARBLE_DEBUG using namespace Marble; GeoPainterPrivate::GeoPainterPrivate( GeoPainter* q, const ViewportParams *viewport, MapQuality mapQuality ) : m_viewport( viewport ), m_mapQuality( mapQuality ), m_x( new qreal[100] ), m_parent(q) { } GeoPainterPrivate::~GeoPainterPrivate() { delete[] m_x; } void GeoPainterPrivate::createAnnotationLayout ( qreal x, qreal y, const QSizeF& bubbleSize, qreal bubbleOffsetX, qreal bubbleOffsetY, qreal xRnd, qreal yRnd, QPainterPath& path, QRectF& rect ) { // TODO: MOVE this into an own Annotation class qreal arrowPosition = 0.3; qreal arrowWidth = 12.0; qreal width = bubbleSize.width(); qreal height = bubbleSize.height(); qreal dx = ( bubbleOffsetX > 0 ) ? 1.0 : -1.0; // x-Mirror qreal dy = ( bubbleOffsetY < 0 ) ? 1.0 : -1.0; // y-Mirror qreal x0 = ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) - xRnd *dx; qreal x1 = ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ); qreal x2 = ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) + xRnd * dx; qreal x3 = ( x + bubbleOffsetX ) - dx * arrowWidth / 2.0; qreal x4 = ( x + bubbleOffsetX ) + dx * arrowWidth / 2.0; qreal x5 = ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd )- xRnd * dx; qreal x6 = ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd ); qreal x7 = ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd ) + xRnd * dx; qreal y0 = ( y + bubbleOffsetY ); qreal y1 = ( y + bubbleOffsetY ) - dy * yRnd; qreal y2 = ( y + bubbleOffsetY ) - dy * 2 * yRnd; qreal y5 = ( y + bubbleOffsetY ) - dy * ( height - 2 * yRnd ); qreal y6 = ( y + bubbleOffsetY ) - dy * ( height - yRnd ); qreal y7 = ( y + bubbleOffsetY ) - dy * height; QPointF p1 ( x, y ); // pointing point QPointF p2 ( x4, y0 ); QPointF p3 ( x6, y0 ); QPointF p4 ( x7, y1 ); QPointF p5 ( x7, y6 ); QPointF p6 ( x6, y7 ); QPointF p7 ( x1, y7 ); QPointF p8 ( x0, y6 ); QPointF p9 ( x0, y1 ); QPointF p10( x1, y0 ); QPointF p11( x3, y0 ); QRectF bubbleBoundingBox( QPointF( x0, y7 ), QPointF( x7, y0 ) ); path.moveTo( p1 ); path.lineTo( p2 ); path.lineTo( p3 ); QRectF bottomRight( QPointF( x5, y2 ), QPointF( x7, y0 ) ); path.arcTo( bottomRight, 270.0, 90.0 ); path.lineTo( p5 ); QRectF topRight( QPointF( x5, y7 ), QPointF( x7, y5 ) ); path.arcTo( topRight, 0.0, 90.0 ); path.lineTo( p7 ); QRectF topLeft( QPointF( x0, y7 ), QPointF( x2, y5 ) ); path.arcTo( topLeft, 90.0, 90.0 ); path.lineTo( p9 ); QRectF bottomLeft( QPointF( x0, y2 ), QPointF( x2, y0 ) ); path.arcTo( bottomLeft, 180.0, 90.0 ); path.lineTo( p10 ); path.lineTo( p11 ); path.lineTo( p1 ); qreal left = ( dx > 0 ) ? x1 : x6; qreal right = ( dx > 0 ) ? x6 : x1; qreal top = ( dy > 0 ) ? y6 : y1; qreal bottom = ( dy > 0 ) ? y1 : y6; rect.setTopLeft( QPointF( left, top ) ); rect.setBottomRight( QPointF( right, bottom ) ); } GeoDataLinearRing GeoPainterPrivate::createLinearRingFromGeoRect( const GeoDataCoordinates & centerCoordinates, qreal width, qreal height ) { qreal lon = 0.0; qreal lat = 0.0; qreal altitude = centerCoordinates.altitude(); centerCoordinates.geoCoordinates( lon, lat, GeoDataCoordinates::Degree ); lon = GeoDataCoordinates::normalizeLon( lon, GeoDataCoordinates::Degree ); lat = GeoDataCoordinates::normalizeLat( lat, GeoDataCoordinates::Degree ); qreal west = GeoDataCoordinates::normalizeLon( lon - width * 0.5, GeoDataCoordinates::Degree ); qreal east = GeoDataCoordinates::normalizeLon( lon + width * 0.5, GeoDataCoordinates::Degree ); qreal north = GeoDataCoordinates::normalizeLat( lat + height * 0.5, GeoDataCoordinates::Degree ); qreal south = GeoDataCoordinates::normalizeLat( lat - height * 0.5, GeoDataCoordinates::Degree ); GeoDataCoordinates southWest( west, south, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates southEast( east, south, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates northEast( east, north, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates northWest( west, north, altitude, GeoDataCoordinates::Degree ); GeoDataLinearRing rectangle( Tessellate | RespectLatitudeCircle ); // If the width of the rect is larger as 180 degree, we have to enforce the long way. if ( width >= 180 ) { qreal center = lon; GeoDataCoordinates southCenter( center, south, altitude, GeoDataCoordinates::Degree ); GeoDataCoordinates northCenter( center, north, altitude, GeoDataCoordinates::Degree ); rectangle << southWest << southCenter << southEast << northEast << northCenter << northWest; } else { rectangle << southWest << southEast << northEast << northWest; } return rectangle; } bool GeoPainterPrivate::doClip( const ViewportParams *viewport ) { if ( !viewport->currentProjection()->isClippedToSphere() ) return true; const qint64 radius = viewport->radius() * viewport->currentProjection()->clippingRadius(); return ( radius > viewport->width() / 2 || radius > viewport->height() / 2 ); } qreal GeoPainterPrivate::normalizeAngle(qreal angle) { angle = fmodf(angle, 360); return angle < 0 ? angle + 360 : angle; } void GeoPainterPrivate::drawTextRotated( const QPointF &startPoint, qreal angle, const QString &text ) { QRectF textRect(startPoint, m_parent->fontMetrics().size( 0, text)); QTransform const oldTransform = m_parent->transform(); m_parent->translate(startPoint); m_parent->rotate(angle); m_parent->translate( -startPoint - QPointF(0.0, m_parent->fontMetrics().height()/2.0) ); m_parent->drawText( textRect, text); m_parent->setTransform(oldTransform); } // ------------------------------------------------------------------------------------------------- GeoPainter::GeoPainter( QPaintDevice* pd, const ViewportParams *viewport, MapQuality mapQuality ) : ClipPainter( pd, GeoPainterPrivate::doClip( viewport ) ), d( new GeoPainterPrivate( this, viewport, mapQuality ) ) { const bool antialiased = mapQuality == HighQuality || mapQuality == PrintQuality; setRenderHint( QPainter::Antialiasing, antialiased ); ClipPainter::setScreenClip(false); } GeoPainter::~GeoPainter() { delete d; } MapQuality GeoPainter::mapQuality() const { return d->m_mapQuality; } void GeoPainter::drawAnnotation( const GeoDataCoordinates & position, const QString & text, QSizeF bubbleSize, qreal bubbleOffsetX, qreal bubbleOffsetY, qreal xRnd, qreal yRnd ) { int pointRepeatNum; qreal y; bool globeHidesPoint; if ( bubbleSize.height() <= 0 ) { QRectF rect = QRectF( QPointF( 0.0, 0.0 ), bubbleSize - QSizeF( 2 * xRnd, 0.0 ) ); qreal idealTextHeight = boundingRect( rect, Qt::TextWordWrap, text ).height(); bubbleSize.setHeight( 2 * yRnd + idealTextHeight ); } bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { QPainterPath path; QRectF rect; d->createAnnotationLayout( d->m_x[it], y, bubbleSize, bubbleOffsetX, bubbleOffsetY, xRnd, yRnd, path, rect ); QPainter::drawPath( path ); QPainter::drawText( rect, Qt::TextWordWrap, text, &rect ); } } } void GeoPainter::drawPoint ( const GeoDataCoordinates & position ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { QPainter::drawPoint(QPointF(d->m_x[it], y)); } } } QRegion GeoPainter::regionFromPoint ( const GeoDataCoordinates & position, qreal width ) const { return regionFromRect( position, width, width, false, 3 ); } void GeoPainter::drawPoint( const GeoDataPoint & point ) { drawPoint( point.coordinates() ); } QRegion GeoPainter::regionFromPoint ( const GeoDataPoint & point, qreal width ) const { return regionFromRect( point.coordinates(), width, width, false, 3 ); } void GeoPainter::drawText ( const GeoDataCoordinates & position, const QString & text, qreal xOffset, qreal yOffset, qreal width, qreal height, const QTextOption & option ) { // Of course in theory we could have the "isGeoProjected" parameter used // for drawText as well. However this would require us to convert all // glyphs to PainterPaths / QPolygons. From QPolygons we could create // GeoDataPolygons which could get painted on screen. Any patches appreciated ;-) int pointRepeatNum; qreal y; bool globeHidesPoint; QSizeF textSize( fontMetrics().width( text ), fontMetrics().height() ); bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, textSize, globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - yOffset; for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] + xOffset; if (width == 0.0 && height == 0.0) { QPainter::drawText(QPointF(posX, posY), text); } else { const QRectF boundingRect(posX, posY, width, height); QPainter::drawText( boundingRect, text, option ); } } } } void GeoPainter::drawEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected ) { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal rx = width / 2.0; const qreal ry = height / 2.0; for( int it = 0; it < pointRepeatNum; ++it ) { QPainter::drawEllipse(QPointF(d->m_x[it], y), rx, ry); } } } else { // Initialize variables const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree ); const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree ); const qreal altitude = centerPosition.altitude(); // Ensure a valid latitude range: if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) { return; } // Don't show the ellipse if it's too small: GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height, centerLon + 0.5 * width, centerLon - 0.5 * width, GeoDataCoordinates::Degree ); if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) || !d->m_viewport->resolves( ellipseBox ) ) return; GeoDataLinearRing ellipse; // Optimizing the precision by determining the size which the // ellipse covers on the screen: const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG; // To create a circle shape even for very small precision we require uneven numbers: const int precision = qMin( width / degreeResolution / 8 + 1, 81 ); // Calculate the shape of the upper half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision); const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } // Calculate the shape of the lower half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) - 1.0; const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } drawPolygon( ellipse ); } } QRegion GeoPainter::regionFromEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected, qreal strokeWidth ) const { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); QRegion regions; if ( visible ) { // only a hint, a backend could still ignore it, but we cannot know more const bool antialiased = testRenderHint(QPainter::Antialiasing); const qreal halfStrokeWidth = strokeWidth/2.0; const int startY = antialiased ? (qFloor(y - halfStrokeWidth)) : (qFloor(y+0.5 - halfStrokeWidth)); const int endY = antialiased ? (qCeil(y + height + halfStrokeWidth)) : (qFloor(y+0.5 + height + halfStrokeWidth)); // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { const qreal x = d->m_x[it]; const int startX = antialiased ? (qFloor(x - halfStrokeWidth)) : (qFloor(x+0.5 - halfStrokeWidth)); const int endX = antialiased ? (qCeil(x + width + halfStrokeWidth)) : (qFloor(x+0.5 + width + halfStrokeWidth)); regions += QRegion(startX, startY, endX - startX, endY - startY, QRegion::Ellipse); } } return regions; } else { // Initialize variables const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree ); const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree ); const qreal altitude = centerPosition.altitude(); // Ensure a valid latitude range: if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) { return QRegion(); } // Don't show the ellipse if it's too small: GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height, centerLon + 0.5 * width, centerLon - 0.5 * width, GeoDataCoordinates::Degree ); if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) || !d->m_viewport->resolves( ellipseBox ) ) return QRegion(); GeoDataLinearRing ellipse; // Optimizing the precision by determining the size which the // ellipse covers on the screen: const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG; // To create a circle shape even for very small precision we require uneven numbers: const int precision = qMin( width / degreeResolution / 8 + 1, 81 ); // Calculate the shape of the upper half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision); const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } // Calculate the shape of the lower half of the ellipse: for ( int i = 0; i <= precision; ++i ) { const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) - 1.0; const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t ); const qreal lon = centerLon + 0.5 * width * t; ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree ); } return regionFromPolygon( ellipse, Qt::OddEvenFill, strokeWidth ); } } void GeoPainter::drawImage ( const GeoDataCoordinates & centerPosition, const QImage & image /*, bool isGeoProjected */ ) { // isGeoProjected = true would project the image/pixmap onto the globe. This // requires to deal with the TextureMapping classes -> should get // implemented later on int pointRepeatNum; qreal y; bool globeHidesPoint; // if ( !isGeoProjected ) { bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, image.size(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - (image.height() / 2.0); for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - (image.width() / 2.0); QPainter::drawImage(QPointF(posX, posY), image); } } // } } void GeoPainter::drawPixmap ( const GeoDataCoordinates & centerPosition, const QPixmap & pixmap /* , bool isGeoProjected */ ) { int pointRepeatNum; qreal y; bool globeHidesPoint; // if ( !isGeoProjected ) { // FIXME: Better visibility detection that takes the circle geometry into account bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, pixmap.size(), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - (pixmap.height() / 2.0); for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - (pixmap.width() / 2.0); QPainter::drawPixmap(QPointF(posX, posY), pixmap); } } // } } QRegion GeoPainter::regionFromPixmapRect(const GeoDataCoordinates & centerCoordinates, int width, int height, int margin) const { const int fullWidth = width + 2 * margin; const int fullHeight = height + 2 * margin; int pointRepeatNum; qreal y; bool globeHidesPoint; const bool visible = d->m_viewport->screenCoordinates(centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF(fullWidth, fullHeight), globeHidesPoint); QRegion regions; if (visible) { // cmp. GeoPainter::drawPixmap() position calculation // QPainter::drawPixmap seems to qRound the passed position const int posY = qRound(y - (height / 2.0)) - margin; for (int it = 0; it < pointRepeatNum; ++it) { const int posX = qRound(d->m_x[it] - (width / 2.0)) - margin; regions += QRegion(posX, posY, width, height); } } return regions; } +void GeoPainter::polygonsFromLineString( const GeoDataLineString &lineString, + QVector &polygons ) +{ + // Immediately leave this method now if: + // - the object is not visible in the viewport or if + // - the size of the object is below the resolution of the viewport + if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) || + ! d->m_viewport->resolves( lineString.latLonAltBox() ) + ) + { + // mDebug() << "LineString doesn't get displayed on the viewport"; + return; + } + + d->m_viewport->screenCoordinates( lineString, polygons ); +} + void GeoPainter::drawPolyline ( const GeoDataLineString & lineString, const QString& labelText, LabelPositionFlags labelPositionFlags, const QColor& labelColor) { // no labels to draw? // TODO: !labelColor.isValid() || labelColor.alpha() == 0 does not work, // something injects invalid labelColor for city streets if (labelText.isEmpty() || labelPositionFlags.testFlag(NoLabel) || labelColor == Qt::transparent) { drawPolyline(lineString); return; } - // Immediately leave this method now if: - // - the object is not visible in the viewport or if - // - the size of the object is below the resolution of the viewport - if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) || - ! d->m_viewport->resolves( lineString.latLonAltBox() ) - ) - { - // mDebug() << "LineString doesn't get displayed on the viewport"; - return; + QVector polygons; + polygonsFromLineString(lineString, polygons); + if (polygons.empty()) return; + + foreach(const QPolygonF* itPolygon, polygons) { + ClipPainter::drawPolyline(*itPolygon); } - QVector polygons; - d->m_viewport->screenCoordinates( lineString, polygons ); + drawLabelsForPolygons(polygons, + labelText, + labelPositionFlags, + labelColor); + + qDeleteAll( polygons ); +} + +void GeoPainter::drawLabelsForPolygons( const QVector &polygons, + const QString& labelText, + LabelPositionFlags labelPositionFlags, + const QColor& labelColor ) +{ + QPen const oldPen = pen(); if (labelPositionFlags.testFlag(FollowLine)) { const qreal maximumLabelFontSize = 20; qreal fontSize = pen().widthF() * 0.45; fontSize = qMin( fontSize, maximumLabelFontSize ); + if (fontSize < 6.0 || labelColor == "transparent") { + return; + } + QFont font = this->font(); + font.setPointSizeF(fontSize); + setFont(font); + int labelWidth = fontMetrics().width( labelText ); + if (labelText.size() < 20) { + labelWidth *= (20.0 / labelText.size()); + } + setPen(labelColor); + QVector labelNodes; QRectF viewportRect = QRectF(QPointF(0, 0), d->m_viewport->size()); foreach( QPolygonF* itPolygon, polygons ) { if (!itPolygon->boundingRect().intersects(viewportRect)) { continue; } labelNodes.clear(); - ClipPainter::drawPolyline( *itPolygon, labelNodes, labelPositionFlags ); - save(); + QPainterPath path; + path.addPolygon(*itPolygon); + qreal pathLength = path.length(); + if (pathLength == 0) continue; - if (fontSize >= 6.0) { - QFont font = this->font(); - font.setPointSizeF(fontSize); - setFont(font); - int labelWidth = fontMetrics().width( labelText ); - if (labelText.size() < 20) { - labelWidth *= (20.0 / labelText.size()); - } + int maxNumLabels = static_cast(pathLength / labelWidth); - QPainterPath path; - path.addPolygon(*itPolygon); - qreal pathLength = path.length(); - if (pathLength == 0) continue; + if (maxNumLabels > 0) { + qreal textRelativeLength = labelWidth / pathLength; + int numLabels = 1; + if (maxNumLabels > 1) { + numLabels = maxNumLabels/2; + } + qreal offset = (1.0 - numLabels*textRelativeLength)/numLabels; + qreal startPercent = offset/2.0; + + for (int k = 0; k < numLabels; ++k, startPercent += textRelativeLength + offset) { + QPointF point = path.pointAtPercent(startPercent); + QPointF endPoint = path.pointAtPercent(startPercent + textRelativeLength); + + if ( viewport().contains(point.toPoint()) || viewport().contains(endPoint.toPoint()) ) { + qreal angle = -path.angleAtPercent(startPercent); + qreal angle2 = -path.angleAtPercent(startPercent + textRelativeLength); + angle = GeoPainterPrivate::normalizeAngle(angle); + angle2 = GeoPainterPrivate::normalizeAngle(angle2); + bool upsideDown = angle > 90.0 && angle < 270.0; + + if ( qAbs(angle - angle2) < 3.0 ) { + if ( upsideDown ) { + angle += 180.0; + point = path.pointAtPercent(startPercent + textRelativeLength); + } - int maxNumLabels = static_cast(pathLength / labelWidth); + d->drawTextRotated(point, angle, labelText); + } else { + for (int i = 0; i < labelText.length(); ++i) { + qreal currentGlyphTextLength = fontMetrics().width(labelText.left(i)) / pathLength; - if (maxNumLabels > 0) { - qreal textRelativeLength = labelWidth / pathLength; - int numLabels = 1; - if (maxNumLabels > 1) { - numLabels = maxNumLabels/2; - } - qreal offset = (1.0 - numLabels*textRelativeLength)/numLabels; - qreal startPercent = offset/2.0; - - setPen(labelColor); - - for (int k = 0; k < numLabels; ++k, startPercent += textRelativeLength + offset) { - QPointF point = path.pointAtPercent(startPercent); - QPointF endPoint = path.pointAtPercent(startPercent + textRelativeLength); - - if ( viewport().contains(point.toPoint()) || viewport().contains(endPoint.toPoint()) ) { - qreal angle = -path.angleAtPercent(startPercent); - qreal angle2 = -path.angleAtPercent(startPercent + textRelativeLength); - angle = GeoPainterPrivate::normalizeAngle(angle); - angle2 = GeoPainterPrivate::normalizeAngle(angle2); - bool upsideDown = angle > 90.0 && angle < 270.0; - - if ( qAbs(angle - angle2) < 3.0 ) { - if ( upsideDown ) { - angle += 180.0; - point = path.pointAtPercent(startPercent + textRelativeLength); + if ( !upsideDown ) { + angle = -path.angleAtPercent(startPercent + currentGlyphTextLength); + point = path.pointAtPercent(startPercent + currentGlyphTextLength); } - - d->drawTextRotated(point, angle, labelText); - } else { - for (int i = 0; i < labelText.length(); ++i) { - qreal currentGlyphTextLength = fontMetrics().width(labelText.left(i)) / pathLength; - - if ( !upsideDown ) { - angle = -path.angleAtPercent(startPercent + currentGlyphTextLength); - point = path.pointAtPercent(startPercent + currentGlyphTextLength); - } - else { - angle = -path.angleAtPercent(startPercent + textRelativeLength - currentGlyphTextLength) + 180; - point = path.pointAtPercent(startPercent + textRelativeLength - currentGlyphTextLength); - } - - d->drawTextRotated(point, angle, labelText.at(i)); + else { + angle = -path.angleAtPercent(startPercent + textRelativeLength - currentGlyphTextLength) + 180; + point = path.pointAtPercent(startPercent + textRelativeLength - currentGlyphTextLength); } + + d->drawTextRotated(point, angle, labelText.at(i)); } } } } } - restore(); } } else { + setPen(labelColor); + int labelWidth = fontMetrics().width( labelText ); int labelAscent = fontMetrics().ascent(); QVector labelNodes; foreach( QPolygonF* itPolygon, polygons ) { labelNodes.clear(); - ClipPainter::drawPolyline( *itPolygon, labelNodes, labelPositionFlags ); + ClipPainter::labelPosition( *itPolygon, labelNodes, labelPositionFlags ); if (!labelNodes.isEmpty()) { - QPen const oldPen = pen(); - setPen(labelColor); foreach ( const QPointF& labelNode, labelNodes ) { QPointF labelPosition = labelNode + QPointF( 3.0, -2.0 ); // FIXME: This is a Q&D fix. qreal xmax = viewport().width() - 10.0 - labelWidth; - if ( labelPosition.x() > xmax ) labelPosition.setX( xmax ); + if ( labelPosition.x() > xmax ) labelPosition.setX( xmax ); qreal ymin = 10.0 + labelAscent; if ( labelPosition.y() < ymin ) labelPosition.setY( ymin ); qreal ymax = viewport().height() - 10.0 - labelAscent; if ( labelPosition.y() > ymax ) labelPosition.setY( ymax ); drawText( QRectF( labelPosition, fontMetrics().size( 0, labelText) ), labelText ); } - setPen(oldPen); } } } - qDeleteAll( polygons ); + setPen(oldPen); } - void GeoPainter::drawPolyline(const GeoDataLineString& lineString) { - // Immediately leave this method now if: - // - the object is not visible in the viewport or if - // - the size of the object is below the resolution of the viewport - if (!d->m_viewport->viewLatLonAltBox().intersects(lineString.latLonAltBox()) || - !d->m_viewport->resolves(lineString.latLonAltBox())) { - // mDebug() << "LineString doesn't get displayed on the viewport"; - return; - } - QVector polygons; - d->m_viewport->screenCoordinates(lineString, polygons); + polygonsFromLineString(lineString, polygons); + if (polygons.empty()) return; foreach(const QPolygonF* itPolygon, polygons) { ClipPainter::drawPolyline(*itPolygon); } qDeleteAll(polygons); } QRegion GeoPainter::regionFromPolyline ( const GeoDataLineString & lineString, qreal strokeWidth ) const { // Immediately leave this method now if: // - the object is not visible in the viewport or if // - the size of the object is below the resolution of the viewport if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) || ! d->m_viewport->resolves( lineString.latLonAltBox() ) ) { // mDebug() << "LineString doesn't get displayed on the viewport"; return QRegion(); } QPainterPath painterPath; QVector polygons; d->m_viewport->screenCoordinates( lineString, polygons ); foreach( QPolygonF* itPolygon, polygons ) { painterPath.addPolygon( *itPolygon ); } qDeleteAll( polygons ); QPainterPathStroker stroker; stroker.setWidth( strokeWidth ); QPainterPath strokePath = stroker.createStroke( painterPath ); return QRegion( strokePath.toFillPolygon().toPolygon(), Qt::WindingFill ); } void GeoPainter::drawPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule ) { // Immediately leave this method now if: // - the object is not visible in the viewport or if // - the size of the object is below the resolution of the viewport if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) || ! d->m_viewport->resolves( linearRing.latLonAltBox() ) ) { // mDebug() << "Polygon doesn't get displayed on the viewport"; return; } QVector polygons; d->m_viewport->screenCoordinates( linearRing, polygons ); foreach( QPolygonF* itPolygon, polygons ) { ClipPainter::drawPolygon( *itPolygon, fillRule ); } qDeleteAll( polygons ); } QRegion GeoPainter::regionFromPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule, qreal strokeWidth ) const { // Immediately leave this method now if: // - the object is not visible in the viewport or if // - the size of the object is below the resolution of the viewport if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) || ! d->m_viewport->resolves( linearRing.latLonAltBox() ) ) { return QRegion(); } QRegion regions; QVector polygons; d->m_viewport->screenCoordinates( linearRing, polygons ); if ( strokeWidth == 0 ) { // This is the faster way foreach( QPolygonF* itPolygon, polygons ) { regions += QRegion ( (*itPolygon).toPolygon(), fillRule ); } } else { QPainterPath painterPath; foreach( QPolygonF* itPolygon, polygons ) { painterPath.addPolygon( *itPolygon ); } QPainterPathStroker stroker; stroker.setWidth( strokeWidth ); QPainterPath strokePath = stroker.createStroke( painterPath ); painterPath = painterPath.united( strokePath ); regions = QRegion( painterPath.toFillPolygon().toPolygon() ); } qDeleteAll( polygons ); return regions; } void GeoPainter::drawPolygon ( const GeoDataPolygon & polygon, Qt::FillRule fillRule ) { // If the object is not visible in the viewport return if ( ! d->m_viewport->viewLatLonAltBox().intersects( polygon.outerBoundary().latLonAltBox() ) || // If the size of the object is below the resolution of the viewport then return ! d->m_viewport->resolves( polygon.outerBoundary().latLonAltBox() ) ) { // mDebug() << "Polygon doesn't get displayed on the viewport"; return; } // mDebug() << "Drawing Polygon"; QVector outerPolygons; QVector innerPolygons; d->m_viewport->screenCoordinates( polygon.outerBoundary(), outerPolygons ); QPen const oldPen = pen(); // When inner boundaries exist, the outline of the polygon must be painted // separately to avoid connections between the outer and inner boundaries // To avoid performance penalties the separate painting is only done when // it's really needed. See review 105019 for details. bool const hasInnerBoundaries = !polygon.innerBoundaries().isEmpty(); bool innerBoundariesOnScreen = false; if ( hasInnerBoundaries ) { QVector innerBoundaries = polygon.innerBoundaries(); const GeoDataLatLonAltBox & viewLatLonAltBox = d->m_viewport->viewLatLonAltBox(); foreach( const GeoDataLinearRing& itInnerBoundary, innerBoundaries ) { if ( viewLatLonAltBox.intersects(itInnerBoundary.latLonAltBox()) && d->m_viewport->resolves(itInnerBoundary.latLonAltBox()) ) { innerBoundariesOnScreen = true; break; } } if (innerBoundariesOnScreen) { // Cut the outer polygons to the viewport QVector viewportPolygon = QPolygonF(QRectF(0, 0, d->m_viewport->width(), d->m_viewport->height())); foreach(QPolygonF* outerPolygon, outerPolygons) { *outerPolygon = outerPolygon->intersected(QPolygonF(viewportPolygon)); } setPen( QPen( Qt::NoPen ) ); // Create the inner screen polygons foreach( const GeoDataLinearRing& itInnerBoundary, innerBoundaries ) { QVector innerPolygonsPerBoundary; d->m_viewport->screenCoordinates( itInnerBoundary, innerPolygonsPerBoundary ); foreach( QPolygonF* innerPolygonPerBoundary, innerPolygonsPerBoundary ) { innerPolygons << innerPolygonPerBoundary; } } } } foreach( QPolygonF* outerPolygon, outerPolygons ) { if (hasInnerBoundaries && innerBoundariesOnScreen) { QRegion clip(outerPolygon->toPolygon()); foreach(QPolygonF* innerPolygon, innerPolygons) { clip-=QRegion(innerPolygon->toPolygon()); } ClipPainter::setClipRegion(clip); } ClipPainter::drawPolygon( *outerPolygon, fillRule ); } if ( hasInnerBoundaries && innerBoundariesOnScreen ) { setPen( oldPen ); foreach( const QPolygonF* outerPolygon, outerPolygons ) { ClipPainter::drawPolyline( *outerPolygon ); } foreach( const QPolygonF* innerPolygon, innerPolygons ) { ClipPainter::drawPolyline( *innerPolygon ); } } qDeleteAll(outerPolygons); qDeleteAll(innerPolygons); } void GeoPainter::drawRect ( const GeoDataCoordinates & centerCoordinates, qreal width, qreal height, bool isGeoProjected ) { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - height / 2.0; for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - width / 2.0; QPainter::drawRect(QRectF(posX, posY, width, height)); } } } else { drawPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ), Qt::OddEvenFill ); } } QRegion GeoPainter::regionFromRect ( const GeoDataCoordinates & centerCoordinates, qreal width, qreal height, bool isGeoProjected, qreal strokeWidth ) const { if ( !isGeoProjected ) { int pointRepeatNum; qreal y; bool globeHidesPoint; bool visible = d->m_viewport->screenCoordinates( centerCoordinates, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); QRegion regions; if ( visible ) { // only a hint, a backend could still ignore it, but we cannot know more const bool antialiased = testRenderHint(QPainter::Antialiasing); const qreal halfStrokeWidth = strokeWidth/2.0; const int startY = antialiased ? (qFloor(y - halfStrokeWidth)) : (qFloor(y+0.5 - halfStrokeWidth)); const int endY = antialiased ? (qCeil(y + height + halfStrokeWidth)) : (qFloor(y+0.5 + height + halfStrokeWidth)); // Draw all the x-repeat-instances of the point on the screen for( int it = 0; it < pointRepeatNum; ++it ) { const qreal x = d->m_x[it]; const int startX = antialiased ? (qFloor(x - halfStrokeWidth)) : (qFloor(x+0.5 - halfStrokeWidth)); const int endX = antialiased ? (qCeil(x + width + halfStrokeWidth)) : (qFloor(x+0.5 + width + halfStrokeWidth)); regions += QRegion(startX, startY, endX - startX, endY - startY); } } return regions; } else { return regionFromPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ), Qt::OddEvenFill, strokeWidth ); } } void GeoPainter::drawRoundedRect(const GeoDataCoordinates ¢erPosition, qreal width, qreal height, qreal xRnd, qreal yRnd) { int pointRepeatNum; qreal y; bool globeHidesPoint; // FIXME: Better visibility detection that takes the circle geometry into account bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint ); if ( visible ) { // Draw all the x-repeat-instances of the point on the screen const qreal posY = y - height / 2.0; for( int it = 0; it < pointRepeatNum; ++it ) { const qreal posX = d->m_x[it] - width / 2.0; QPainter::drawRoundedRect(QRectF(posX, posY, width, height), xRnd, yRnd); } } } diff --git a/src/lib/marble/GeoPainter.h b/src/lib/marble/GeoPainter.h index 27d93a7ee..60b057153 100644 --- a/src/lib/marble/GeoPainter.h +++ b/src/lib/marble/GeoPainter.h @@ -1,491 +1,517 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2008-2009 Torsten Rahn // #ifndef MARBLE_GEOPAINTER_H #define MARBLE_GEOPAINTER_H #include "marble_export.h" // Marble #include "MarbleGlobal.h" #include "ClipPainter.h" #include class QImage; class QPaintDevice; class QRegion; class QString; namespace Marble { class ViewportParams; class GeoPainterPrivate; class GeoDataCoordinates; class GeoDataLineString; class GeoDataLinearRing; class GeoDataPoint; class GeoDataPolygon; /*! \class GeoPainter \brief A painter that allows to draw geometric primitives on the map. This class allows application developers to draw simple geometric shapes and objects onto the map. The API is modeled after the QPainter API. The GeoPainter provides a wide range of methods that are using geographic ("geodesic") coordinates to position the item. For example a point or the nodes of a polygon can fully be described in geographic coordinates. In all these cases the position of the object is specified in geographic coordinates. There are however some cases where there are two viable cases: \li the shape of the object could still use screen coordinates (like a label or an icon). \li Alternatively the shape of the object can get projected according to the current projection (e.g. a texture projected onto the spherical surface) If screen coordinates are used then e.g. width and height are assumed to be expressed in pixels, otherwise degrees are used. Painter transformations (e.g. translate) always happen in screen coordinates. Like in QPainter drawing objects onto a widget should always considered to be a volatile operation. This means that e.g. placemarks don't get added to the globe permanently. So the drawing needs to be done on every repaint to prevent that drawings will disappear during the next paint event. So if you want to add placemarks to your map widget permanently (i.e. you don't want to take care of repainting) then you need to use other solutions such as the KML import of the Marble framework or Marble's GeoGraphicsItems. \note By default the GeoPainter automatically filters geographical content in order to provide fast painting: \li Geographically positioned objects which are outside the viewport are not drawn at all. Parts of objects which are specified through geographic coordinates (like polygons, ellipses etc.) get cut off if they are not placed within the viewport. \li Objects which have a shape that is specified through geographic coordinates get filtered according to the viewport resolution: If the object is much smaller than a pixel then it won't get drawn at all. */ class MARBLE_EXPORT GeoPainter : public ClipPainter { public: /*! \brief Creates a new geo painter. To create a geo painter it's necessary to provide \a paintDevice as a canvas and the viewportParams to specify the map projection inside the viewport. */ GeoPainter( QPaintDevice * paintDevice, const ViewportParams *viewportParams, MapQuality mapQuality = NormalQuality ); /*! \brief Destroys the geo painter. */ ~GeoPainter(); /*! \brief Returns the map quality. \return The map quality that got assigned to the painter. */ MapQuality mapQuality() const; /*! \brief Draws a text annotation that points to a geodesic position. The annotation consists of a bubble with the specified \a text inside. By choosing an appropriate pen for the painter it's possible to change the color and line style of the bubble outline and the text. The brush chosen for the painter is used to paint the background of the bubble The optional parameters which describe the layout of the bubble are similar to those used by QPainter::drawRoundedRect(). Unlike in QPainter the rounded corners are not specified in percentage but in pixels to provide for optimal aesthetics. By choosing a positive or negative bubbleOffset it's possible to place the annotation on top, bottom, left or right of the annotated position. \param position The geodesic position \param text The text contained by the bubble \param bubbleSize The size of the bubble that holds the annotation text. A height of 0 can be used to have the height calculated automatically to fit the needed text height. \param bubbleOffsetX The x-axis offset between the annotated position and the "root" of the speech bubble's "arrow". \param bubbleOffsetY The y-axis offset between the annotated position and the "root" of the speech bubble's "arrow". \param xRnd Specifies the geometry of the rounded corners in pixels along the x-axis. \param yRnd Specifies the geometry of the rounded corners in pixels along the y-axis. \see GeoDataCoordinates */ void drawAnnotation( const GeoDataCoordinates & position, const QString & text, QSizeF bubbleSize = QSizeF( 130, 100 ), qreal bubbleOffsetX = -10, qreal bubbleOffsetY = -30, qreal xRnd = 5, qreal yRnd = 5 ); /*! \brief Draws a single point at a given geographic position. The point is drawn using the painter's pen color. \see GeoDataCoordinates */ void drawPoint ( const GeoDataCoordinates & position ); /*! \brief Creates a region for a given geographic position. A QRegion object is created that represents the area covered by GeoPainter::drawPoint( GeoDataCoordinates ). It can be used e.g. for input event handling of objects that have been painted using GeoPainter::drawPoint( GeoDataCoordinates ). The width allows to set the "stroke width" for the region. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. \see GeoDataCoordinates */ QRegion regionFromPoint ( const GeoDataCoordinates & position, qreal strokeWidth = 3) const; /*! \brief Draws a single point at a given geographic position. The point is drawn using the painter's pen color. \see GeoDataPoint */ void drawPoint ( const GeoDataPoint & point ); /*! \brief Create a region for a given geographic position. A QRegion object is created that represents the area covered by GeoPainter::drawPoint( GeoDataPoint ). It can be used e.g. for input event handling of objects that have been painted using GeoPainter::drawPoint( GeoDataPoint ). The width allows to set the "stroke width" for the region. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. */ QRegion regionFromPoint ( const GeoDataPoint & point, qreal strokeWidth = 3) const; /*! \brief Draws the given text at a given geographic position. The \a text is drawn starting at the given \a position using the painter's font property. The text rendering is performed in screen coordinates and is not subject to the current projection. An offset given in screenPixels can be provided via xOffset and yOffset in order to tweak the text position. By optionally adding a width, height and text options the text flow can be further influenced. */ void drawText ( const GeoDataCoordinates & position, const QString & text, qreal xOffset = 0.0, qreal yOffset = 0.0, qreal width = 0.0, qreal height = 0.0, const QTextOption & option = QTextOption() ); /*! \brief Draws an ellipse at the given position. The ellipse is placed with its center located at the given \a centerPosition. For the outline it uses the painter's pen and for the background the painter's brush. If \a isGeoProjected is true then the outline of the ellipse is drawn in geographic coordinates. In this case the \a width and the \a height are interpreted to be degrees. If \a isGeoProjected is false then the outline of the ellipse is drawn in screen coordinates. In this case the \a width and the \a height are interpreted to be pixels. \see GeoDataCoordinates */ void drawEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false ); /*! \brief Creates a region for an ellipse at a given position A QRegion object is created that represents the area covered by GeoPainter::drawEllipse(). As such it can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawEllipse(). The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. \see GeoDataCoordinates */ QRegion regionFromEllipse ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false, qreal strokeWidth = 3 ) const; /*! \brief Draws an image at the given position. The image is placed with its center located at the given \a centerPosition. The image rendering is performed in screen coordinates and is not subject to the current projection. \see GeoDataCoordinates */ void drawImage ( const GeoDataCoordinates & centerPosition, const QImage & image /* , bool isGeoProjected = false */ ); /*! \brief Draws a pixmap at the given position. The pixmap is placed with its center located at the given \a centerPosition. The image rendering is performed in screen coordinates and is not subject to the current projection. \see GeoDataCoordinates */ void drawPixmap ( const GeoDataCoordinates & centerPosition, const QPixmap & pixmap /*, bool isGeoProjected = false */ ); /*! \brief Creates a region for a rectangle for a pixmap at a given position. A QRegion object is created that represents the area covered by GeoPainter::drawPixmap(). This can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawPixmap(). The \a margin allows to extrude the QRegion by "margin" pixels on every side. \see GeoDataCoordinates */ QRegion regionFromPixmapRect(const GeoDataCoordinates ¢erCoordinates, int width, int height, int margin = 0) const; +/*! + \brief Helper method for safe and quick linestring conversion. + + In general drawPolyline() should be used instead. However + in situations where the same linestring is supposed to be + drawn multiple times it's a good idea to cache the + screen polygons using this method. + + \see GeoDataLineString +*/ + void polygonsFromLineString( const GeoDataLineString &lineString, + QVector &polygons ); + + /*! \brief Draws a given line string (a "polyline") with a label. The \a lineString is drawn using the current pen. It's possible to provide a \a labelText for the \a lineString. The text is rendered using the painter's font property. The position of the \a labelText can be specified using the \a labelPositionFlags. \see GeoDataLineString */ void drawPolyline ( const GeoDataLineString & lineString, const QString& labelText, LabelPositionFlags labelPositionFlags = LineCenter, const QColor& labelcolor = Qt::black); +/*! + \brief Draws Labels for a given set of screen polygons. + + In common cases the drawPolyline overload can be used instead. + However in certain more complex cases this particular method + might be helpful for further optimization. +*/ + + void drawLabelsForPolygons( const QVector &polygons, + const QString& labelText, + LabelPositionFlags labelPositionFlags, + const QColor& labelColor ); + /*! \brief Draws a given line string (a "polyline"). The \a lineString is drawn using the current pen. \see GeoDataLineString */ void drawPolyline(const GeoDataLineString & lineString); - /*! \brief Creates a region for a given line string (a "polyline"). A QRegion object is created that represents the area covered by GeoPainter::drawPolyline( GeoDataLineString ). As such it can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawPolyline( GeoDataLineString ). The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. \see GeoDataLineString */ QRegion regionFromPolyline ( const GeoDataLineString & lineString, qreal strokeWidth = 3 ) const; /*! \brief Draws a given linear ring (a "polygon without holes"). The outline of the \a linearRing is drawn using the current pen. The background is painted using the current brush of the painter. Like in QPainter::drawPolygon() the \a fillRule specifies the fill algorithm that is used to fill the polygon. \see GeoDataLinearRing */ void drawPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule = Qt::OddEvenFill ); /*! \brief Creates a region for a given linear ring (a "polygon without holes"). A QRegion object is created that represents the area covered by GeoPainter::drawPolygon( GeoDataLinearRing ). As such it can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawPolygon( GeoDataLinearRing ). Like in drawPolygon() the \a fillRule specifies the fill algorithm that is used to fill the polygon. The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. For the polygon case a "cosmetic" strokeWidth of zero should provide the best performance. \see GeoDataLinearRing */ QRegion regionFromPolygon ( const GeoDataLinearRing & linearRing, Qt::FillRule fillRule, qreal strokeWidth = 3 ) const; /*! \brief Draws a given polygon (which may contain holes). The outline of the \a polygon is drawn using the current pen. The background is painted using the current brush of the painter. Like in QPainter::drawPolygon() the \a fillRule specifies the fill algorithm that is used to fill the polygon. \see GeoDataPolygon */ void drawPolygon ( const GeoDataPolygon & polygon, Qt::FillRule fillRule = Qt::OddEvenFill ); /*! \brief Draws a rectangle at the given position. The rectangle is placed with its center located at the given \a centerPosition. For the outline it uses the painter's pen and for the background the painter's brush. If \a isGeoProjected is true then the outline of the rectangle is drawn in geographic coordinates. In this case the \a width and the \a height are interpreted to be degrees. If \a isGeoProjected is false then the outline of the rectangle is drawn in screen coordinates. In this case the \a width and the \a height are interpreted to be pixels. \see GeoDataCoordinates */ void drawRect ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false ); /*! \brief Creates a region for a rectangle at a given position. A QRegion object is created that represents the area covered by GeoPainter::drawRect(). This can be used e.g. for input event handling for objects that have been painted using GeoPainter::drawRect(). The isGeoProjected parameter is used the same way as for GeoPainter::drawRect(). The \a strokeWidth allows to extrude the QRegion by half the amount of "stroke width" pixels. For input event handling it's always advisable to use a width that is slightly bigger than the width of the painter's pen. This is especially true for small objects. \see GeoDataCoordinates */ QRegion regionFromRect ( const GeoDataCoordinates & centerPosition, qreal width, qreal height, bool isGeoProjected = false, qreal strokeWidth = 3 ) const; /*! \brief Draws a rectangle with rounded corners at the given position. The rectangle is placed with its center located at the given \a centerPosition. For the outline it uses the painter's pen and for the background the painter's brush. Unlike in QPainter::drawRoundedRect() the rounded corners are not specified in percentage but in pixels to provide for optimal aesthetics. \param width Width of the rectangle in pixels \param height Height of the rectangle in pixels \param xRnd Specifies the geometry of the rounded corners in pixels along the x-axis. \param yRnd Specifies the geometry of the rounded corners in pixels along the y-axis. \see GeoDataCoordinates */ void drawRoundedRect(const GeoDataCoordinates ¢erPosition, qreal width, qreal height, qreal xRnd = 25.0, qreal yRnd = 25.0); // Reenabling QPainter+ClipPainter methods. using QPainter::drawText; using QPainter::drawEllipse; using QPainter::drawImage; using QPainter::drawPixmap; using QPainter::drawPoint; using ClipPainter::drawPolyline; using ClipPainter::drawPolygon; using QPainter::drawRect; using QPainter::drawRoundedRect; private: Q_DISABLE_COPY( GeoPainter ) GeoPainterPrivate * const d; }; } #endif diff --git a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp index 5f573128e..7236dc57c 100644 --- a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp @@ -1,157 +1,156 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2011 Konstantin Oblaukhov // #include "AbstractGeoPolygonGraphicsItem.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoPainter.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataPlacemark.h" #include "GeoDataPolyStyle.h" #include "GeoDataTypes.h" #include "OsmPlacemarkData.h" #include "MarbleDebug.h" #include "ViewportParams.h" #include namespace Marble { QPixmapCache AbstractGeoPolygonGraphicsItem::m_textureCache = QPixmapCache(); AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon) : GeoGraphicsItem(placemark), m_polygon(polygon), m_ring(0) { } AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing *ring) : GeoGraphicsItem(placemark), m_polygon(0), m_ring(ring) { } const GeoDataLatLonAltBox& AbstractGeoPolygonGraphicsItem::latLonAltBox() const { if( m_polygon ) { return m_polygon->latLonAltBox(); } return m_ring->latLonAltBox(); } void AbstractGeoPolygonGraphicsItem::paint( GeoPainter* painter, const ViewportParams* viewport, const QString &layer ) { Q_UNUSED(layer); painter->save(); configurePainter(painter, viewport); if ( m_polygon ) { painter->drawPolygon( *m_polygon ); } else if ( m_ring ) { painter->drawPolygon( *m_ring ); } painter->restore(); } QPen AbstractGeoPolygonGraphicsItem::configurePainter(GeoPainter *painter, const ViewportParams *viewport) { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { - painter->setPen( QPen() ); + painter->setPen( QPen() ); // "style-less" polygons: a 1px black solid line } else { const GeoDataPolyStyle& polyStyle = style->polyStyle(); - if (!polyStyle.outline()) { - currentPen.setColor( Qt::transparent ); - } - else { + if (polyStyle.outline()) { // polygons without outline: Qt::NoPen (not drawn) const GeoDataLineStyle& lineStyle = style->lineStyle(); currentPen.setColor(lineStyle.paintedColor()); currentPen.setWidthF(lineStyle.width()); currentPen.setCapStyle(lineStyle.capStyle()); currentPen.setStyle(lineStyle.penStyle()); - } - painter->setPen(currentPen); + if (painter->pen().color() != currentPen.color()) { + painter->setPen(currentPen); + } + } if (!polyStyle.fill()) { painter->setBrush(QColor(Qt::transparent)); } else { const QColor paintedColor = polyStyle.paintedColor(); if (painter->brush().color() != paintedColor || painter->brush().style() != polyStyle.brushStyle()) { if (!polyStyle.texturePath().isEmpty() || !polyStyle.textureImage().isNull()) { GeoDataCoordinates coords = latLonAltBox().center(); qreal x, y; viewport->screenCoordinates(coords, x, y); QBrush brush(texture(polyStyle.texturePath(), polyStyle.textureImage(), paintedColor)); painter->setBrush(brush); painter->setBrushOrigin(QPoint(x,y)); } else { painter->setBrush(QBrush(paintedColor, polyStyle.brushStyle())); } } } } return currentPen; } int AbstractGeoPolygonGraphicsItem::extractElevation(const GeoDataPlacemark &placemark) { int elevation = 0; const OsmPlacemarkData &osmData = placemark.osmData(); const auto tagIter = osmData.findTag(QStringLiteral("ele")); if (tagIter != osmData.tagsEnd()) { elevation = tagIter.value().toInt(); } return elevation; } QPixmap AbstractGeoPolygonGraphicsItem::texture(const QString &texturePath, const QImage &textureImage, const QColor &color) { QString const key = QString("%1/%2").arg(color.rgba()).arg(texturePath); QPixmap texture; if (!m_textureCache.find(key, texture)) { if (textureImage.hasAlphaChannel()) { QImage image = QImage (textureImage.size(), QImage::Format_ARGB32_Premultiplied); image.fill(color); QPainter imagePainter(&image); imagePainter.drawImage(0, 0, textureImage); imagePainter.end(); texture = QPixmap::fromImage(image); } else { texture = QPixmap::fromImage(textureImage); } m_textureCache.insert(key, texture); } return texture; } } diff --git a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp index 676861455..b1fc55581 100644 --- a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp @@ -1,295 +1,312 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2009 Andrew Manson // #include "GeoLineStringGraphicsItem.h" #include "GeoDataLineString.h" #include "GeoDataLineStyle.h" #include "GeoDataLabelStyle.h" #include "GeoDataPlacemark.h" #include "GeoDataPolyStyle.h" #include "GeoPainter.h" #include "StyleBuilder.h" #include "ViewportParams.h" #include "GeoDataStyle.h" #include "MarbleDebug.h" #include "MarbleMath.h" #include namespace Marble { GeoLineStringGraphicsItem::GeoLineStringGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLineString *lineString) : GeoGraphicsItem(placemark), m_lineString(lineString), - m_renderLineString(lineString) + m_renderLineString(lineString), + m_renderLabel(false) { QString const category = StyleBuilder::visualCategoryName(placemark->visualCategory()); QStringList paintLayers; paintLayers << QLatin1String("LineString/") + category + QLatin1String("/outline"); paintLayers << QLatin1String("LineString/") + category + QLatin1String("/inline"); paintLayers << QLatin1String("LineString/") + category + QLatin1String("/label"); setPaintLayers(paintLayers); } void GeoLineStringGraphicsItem::setLineString( const GeoDataLineString* lineString ) { m_lineString = lineString; m_renderLineString = lineString; } const GeoDataLineString *GeoLineStringGraphicsItem::lineString() const { return m_lineString; } GeoDataLineString GeoLineStringGraphicsItem::merge(const QVector &lineStrings_) { if (lineStrings_.isEmpty()) { return GeoDataLineString(); } Q_ASSERT(!lineStrings_.isEmpty()); auto lineStrings = lineStrings_; GeoDataLineString result = *lineStrings.first(); lineStrings.pop_front(); for (bool matched = true; matched && !lineStrings.isEmpty();) { matched = false; for (auto lineString: lineStrings) { if (canMerge(result.first(), lineString->first())) { result.remove(0); result.reverse(); result << *lineString; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.last(), lineString->first())) { result.remove(result.size()-1); result << *lineString; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.first(), lineString->last())) { GeoDataLineString behind = result; result = *lineString; behind.remove(0); result << behind; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.last(), lineString->last())) { GeoDataLineString behind = *lineString; behind.reverse(); behind.remove(0); result << behind; lineStrings.removeOne(lineString); matched = true; break; } } if (!matched) { return GeoDataLineString(); } } return lineStrings.isEmpty() ? result : GeoDataLineString(); } void GeoLineStringGraphicsItem::setMergedLineString(const GeoDataLineString &mergedLineString) { m_mergedLineString = mergedLineString; m_renderLineString = mergedLineString.isEmpty() ? m_lineString : &m_mergedLineString; } const GeoDataLatLonAltBox& GeoLineStringGraphicsItem::latLonAltBox() const { return m_renderLineString->latLonAltBox(); } void GeoLineStringGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport , const QString &layer) { - int const tileLevel = qLn( viewport->radius() * 4 / 256 ) / qLn( 2.0 ); + int const tileLevel = qLn( viewport->radius() / 64.0 ) / qLn( 2.0 ); setRenderContext(RenderContext(tileLevel)); if (layer.endsWith(QLatin1String("/outline"))) { + m_cachedPolygons.clear(); + painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons); + if (m_cachedPolygons.empty()) return; if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) { - paintOutline(painter, viewport); + paintOutline(painter, viewport, m_cachedPolygons); } - } else if (layer.endsWith(QLatin1String("/label"))) { - paintLabel(painter, viewport); } else if (layer.endsWith(QLatin1String("/inline"))) { - paintInline(painter, viewport); + if (m_cachedPolygons.empty()) return; + paintInline(painter, viewport, m_cachedPolygons); + } else if (layer.endsWith(QLatin1String("/label"))) { + if (!m_cachedPolygons.empty()) { + if (m_renderLabel) { + paintLabel(painter, viewport, m_cachedPolygons); + } + } + qDeleteAll(m_cachedPolygons); } else { - painter->drawPolyline(*m_renderLineString); + m_cachedPolygons.clear(); + painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons); + if (m_cachedPolygons.empty()) return; + foreach(const QPolygonF* itPolygon, m_cachedPolygons) { + painter->drawPolyline(*itPolygon); + } + qDeleteAll(m_cachedPolygons); } } -void GeoLineStringGraphicsItem::paintInline(GeoPainter* painter, const ViewportParams* viewport) +void GeoLineStringGraphicsItem::paintInline(GeoPainter* painter, const ViewportParams* viewport, const QVector &polygons) { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } painter->save(); LabelPositionFlags labelPositionFlags = NoLabel; QPen currentPen = configurePainter(painter, viewport, labelPositionFlags); GeoDataStyle::ConstPtr style = this->style(); const GeoDataLineStyle& lineStyle = style->lineStyle(); if (lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine) { const float currentPenWidth = currentPen.widthF(); if (currentPenWidth > 2.5f) { currentPen.setWidthF(currentPenWidth - 2.0f); } currentPen.setColor(style->polyStyle().paintedColor()); painter->setPen( currentPen ); } - painter->drawPolyline(*m_renderLineString); + foreach(const QPolygonF* itPolygon, polygons) { + painter->drawPolyline(*itPolygon); + } painter->restore(); } -void GeoLineStringGraphicsItem::paintOutline(GeoPainter *painter, const ViewportParams *viewport) +void GeoLineStringGraphicsItem::paintOutline(GeoPainter *painter, const ViewportParams *viewport, const QVector &polygons) { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } painter->save(); LabelPositionFlags labelPositionFlags = NoLabel; QPen currentPen = configurePainter(painter, viewport, labelPositionFlags); + m_renderLabel = !( currentPen.widthF() < 8.0f ); + if (!( currentPen.widthF() < 2.5f )) { - painter->drawPolyline(*m_renderLineString); + foreach(const QPolygonF* itPolygon, polygons) { + painter->drawPolyline(*itPolygon); + } } painter->restore(); } -void GeoLineStringGraphicsItem::paintLabel(GeoPainter *painter, const ViewportParams *viewport) +void GeoLineStringGraphicsItem::paintLabel(GeoPainter *painter, const ViewportParams *viewport, const QVector &polygons) { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } painter->save(); LabelPositionFlags labelPositionFlags = NoLabel; QPen currentPen = configurePainter(painter, viewport, labelPositionFlags); - if (!( currentPen.widthF() < 2.5f )) { - GeoDataStyle::ConstPtr style = this->style(); - - QPen pen = QPen(QColor(Qt::transparent)); - pen.setWidthF(currentPen.widthF()); - painter->setPen(pen); - // Activate the lines below to paint a label background which - // prevents antialiasing overpainting glitches, but leads to - // other glitches. - //QColor const color = style->polyStyle().paintedColor(); - //painter->setBackground(QBrush(color)); - //painter->setBackgroundMode(Qt::OpaqueMode); - const GeoDataLabelStyle& labelStyle = style->labelStyle(); - painter->setFont(labelStyle.font()); - painter->drawPolyline( *m_renderLineString, feature()->name(), FollowLine, - labelStyle.paintedColor()); - } + GeoDataStyle::ConstPtr style = this->style(); + const GeoDataLabelStyle& labelStyle = style->labelStyle(); + + // Activate the lines below to paint a label background which + // prevents antialiasing overpainting glitches, but leads to + // other glitches. + //QColor const color = style->polyStyle().paintedColor(); + //painter->setBackground(QBrush(color)); + //painter->setBackgroundMode(Qt::OpaqueMode); + + painter->drawLabelsForPolygons(polygons, feature()->name(), FollowLine, + labelStyle.paintedColor()); painter->restore(); } QPen GeoLineStringGraphicsItem::configurePainter(GeoPainter *painter, const ViewportParams *viewport, LabelPositionFlags &labelPositionFlags) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); } else { const GeoDataLineStyle& lineStyle = style->lineStyle(); const QColor linePaintedColor = lineStyle.paintedColor(); if (currentPen.color() != linePaintedColor) { currentPen.setColor(linePaintedColor); } const float lineWidth = lineStyle.width(); const float linePhysicalWidth = lineStyle.physicalWidth(); if (currentPen.widthF() != lineWidth || linePhysicalWidth != 0.0) { const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth; if (scaledLinePhysicalWidth < lineWidth) { currentPen.setWidthF(lineWidth); } else { currentPen.setWidthF(scaledLinePhysicalWidth); } } else if (lineWidth != 0.0 ) { currentPen.setWidthF(lineWidth); } const Qt::PenCapStyle lineCapStyle = lineStyle.capStyle(); if (currentPen.capStyle() != lineCapStyle) { currentPen.setCapStyle(lineCapStyle); } if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) { const Qt::PenStyle linePenStyle = lineStyle.penStyle(); if (currentPen.style() != linePenStyle) { currentPen.setStyle(linePenStyle); } if (linePenStyle == Qt::CustomDashLine) { currentPen.setDashPattern(lineStyle.dashPattern()); } } else { currentPen.setStyle(Qt::SolidLine); } if ( painter->mapQuality() != Marble::HighQuality && painter->mapQuality() != Marble::PrintQuality ) { QColor penColor = currentPen.color(); penColor.setAlpha( 255 ); currentPen.setColor( penColor ); } if ( painter->pen() != currentPen ) painter->setPen( currentPen ); if (lineStyle.background()) { QBrush brush = painter->background(); brush.setColor(style->polyStyle().paintedColor()); painter->setBackground( brush ); painter->setBackgroundMode( Qt::OpaqueMode ); } // label styles const GeoDataLabelStyle& labelStyle = style->labelStyle(); painter->setFont(labelStyle.font() ); switch (labelStyle.alignment()) { case GeoDataLabelStyle::Corner: case GeoDataLabelStyle::Right: labelPositionFlags |= LineStart; break; case GeoDataLabelStyle::Center: labelPositionFlags |= LineCenter; break; } } return currentPen; } bool GeoLineStringGraphicsItem::canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b) { return distanceSphere(a, b) * EARTH_RADIUS < 0.1; } } diff --git a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h index 721761748..f1b74e3f8 100644 --- a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h +++ b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h @@ -1,53 +1,55 @@ // // This file is part of the Marble Virtual Globe. // // This program is free software licensed under the GNU LGPL. You can // find a copy of this license in LICENSE.txt in the top directory of // the source code. // // Copyright 2009 Andrew Manson // #ifndef MARBLE_GEOLINESTRINGGRAPHICSITEM_H #define MARBLE_GEOLINESTRINGGRAPHICSITEM_H #include "GeoGraphicsItem.h" #include "GeoDataCoordinates.h" #include "GeoDataLineString.h" #include "MarbleGlobal.h" #include "marble_export.h" namespace Marble { class GeoDataPlacemark; class MARBLE_EXPORT GeoLineStringGraphicsItem : public GeoGraphicsItem { public: explicit GeoLineStringGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLineString *lineString); void setLineString( const GeoDataLineString* lineString ); const GeoDataLineString* lineString() const; static GeoDataLineString merge(const QVector &lineStrings); void setMergedLineString(const GeoDataLineString &sharedLineString); virtual const GeoDataLatLonAltBox& latLonAltBox() const; void paint(GeoPainter* painter, const ViewportParams *viewport, const QString &layer); private: - void paintOutline(GeoPainter *painter, const ViewportParams *viewport); - void paintInline(GeoPainter *painter, const ViewportParams *viewport); - void paintLabel(GeoPainter *painter, const ViewportParams *viewport); + void paintOutline(GeoPainter *painter, const ViewportParams *viewport, const QVector &polygons); + void paintInline(GeoPainter *painter, const ViewportParams *viewport, const QVector &polygons); + void paintLabel(GeoPainter *painter, const ViewportParams *viewport, const QVector &polygons); QPen configurePainter(GeoPainter* painter, const ViewportParams *viewport, LabelPositionFlags &labelPositionFlags) const; static bool canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b); const GeoDataLineString *m_lineString; const GeoDataLineString *m_renderLineString; GeoDataLineString m_mergedLineString; + QVector m_cachedPolygons; + bool m_renderLabel; }; } #endif