File indexing completed on 2024-04-21 14:50:45

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2006-2009 Torsten Rahn <tackat@kde.org>
0004 
0005 
0006 #include "GeoPainter.h"
0007 #include "GeoPainter_p.h"
0008 
0009 #include <QList>
0010 #include <QPainterPath>
0011 #include <QPixmapCache>
0012 #include <QRegion>
0013 #include <qmath.h>
0014 
0015 #include "MarbleDebug.h"
0016 
0017 #include "GeoDataCoordinates.h"
0018 #include "GeoDataLatLonAltBox.h"
0019 #include "GeoDataLineString.h"
0020 #include "GeoDataLinearRing.h"
0021 #include "GeoDataPoint.h"
0022 #include "GeoDataPolygon.h"
0023 
0024 #include "ViewportParams.h"
0025 #include "AbstractProjection.h"
0026 
0027 // #define MARBLE_DEBUG
0028 
0029 using namespace Marble;
0030 
0031 GeoPainterPrivate::GeoPainterPrivate( GeoPainter* q, const ViewportParams *viewport, MapQuality mapQuality )
0032         : m_viewport( viewport ),
0033         m_mapQuality( mapQuality ),
0034         m_x( new qreal[100] ),
0035         m_parent(q)
0036 {
0037 }
0038 
0039 GeoPainterPrivate::~GeoPainterPrivate()
0040 {
0041     delete[] m_x;
0042 }
0043 
0044 void GeoPainterPrivate::createAnnotationLayout (  qreal x, qreal y,
0045                                                   const QSizeF& bubbleSize,
0046                                                   qreal bubbleOffsetX, qreal bubbleOffsetY,
0047                                                   qreal xRnd, qreal yRnd,
0048                                                   QPainterPath& path, QRectF& rect )
0049 {
0050     // TODO: MOVE this into an own Annotation class
0051     qreal arrowPosition = 0.3;
0052     qreal arrowWidth = 12.0;
0053 
0054     qreal width =  bubbleSize.width();
0055     qreal height = bubbleSize.height();
0056 
0057     qreal dx =  ( bubbleOffsetX > 0 ) ? 1.0 : -1.0; // x-Mirror
0058     qreal dy =  ( bubbleOffsetY < 0 ) ? 1.0 : -1.0; // y-Mirror
0059 
0060     qreal x0 =  ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) - xRnd *dx;
0061     qreal x1 =  ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd );
0062     qreal x2 =  ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) + xRnd * dx;
0063     qreal x3 =  ( x + bubbleOffsetX ) - dx * arrowWidth / 2.0;
0064     qreal x4 =  ( x + bubbleOffsetX ) + dx * arrowWidth / 2.0;
0065     qreal x5 =  ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd )- xRnd * dx;
0066     qreal x6 =  ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd );
0067     qreal x7 =  ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd ) + xRnd * dx;
0068 
0069     qreal y0 =  ( y + bubbleOffsetY );
0070     qreal y1 =  ( y + bubbleOffsetY ) - dy * yRnd;
0071     qreal y2 =  ( y + bubbleOffsetY ) - dy * 2 * yRnd;
0072     qreal y5 =  ( y + bubbleOffsetY ) - dy * ( height - 2 * yRnd );
0073     qreal y6 =  ( y + bubbleOffsetY ) - dy * ( height - yRnd );
0074     qreal y7 =  ( y + bubbleOffsetY ) - dy * height;
0075 
0076     QPointF p1 ( x, y ); // pointing point
0077     QPointF p2 ( x4, y0 );
0078     QPointF p3 ( x6, y0 );
0079     QPointF p4 ( x7, y1 );
0080     QPointF p5 ( x7, y6 );
0081     QPointF p6 ( x6, y7 );
0082     QPointF p7 ( x1, y7 );
0083     QPointF p8 ( x0, y6 );
0084     QPointF p9 ( x0, y1 );
0085     QPointF p10( x1, y0 );
0086     QPointF p11( x3, y0 );
0087 
0088     QRectF bubbleBoundingBox(  QPointF( x0, y7 ), QPointF( x7, y0 ) );
0089 
0090     path.moveTo( p1 );
0091     path.lineTo( p2 );
0092 
0093     path.lineTo( p3 );
0094     QRectF bottomRight( QPointF( x5, y2 ), QPointF( x7, y0 ) );
0095     path.arcTo( bottomRight, 270.0, 90.0 );
0096 
0097     path.lineTo( p5 );
0098     QRectF topRight( QPointF( x5, y7 ), QPointF( x7, y5 ) );
0099     path.arcTo( topRight, 0.0, 90.0 );
0100 
0101     path.lineTo( p7 );
0102     QRectF topLeft( QPointF( x0, y7 ), QPointF( x2, y5 ) );
0103     path.arcTo( topLeft, 90.0, 90.0 );
0104 
0105     path.lineTo( p9 );
0106     QRectF bottomLeft( QPointF( x0, y2 ), QPointF( x2, y0 ) );
0107     path.arcTo( bottomLeft, 180.0, 90.0 );
0108 
0109     path.lineTo( p10 );
0110     path.lineTo( p11 );
0111     path.lineTo( p1 );
0112 
0113     qreal left   = ( dx > 0 ) ? x1 : x6;
0114     qreal right  = ( dx > 0 ) ? x6 : x1;
0115     qreal top    = ( dy > 0 ) ? y6 : y1;
0116     qreal bottom = ( dy > 0 ) ? y1 : y6;
0117 
0118     rect.setTopLeft( QPointF( left, top ) );
0119     rect.setBottomRight( QPointF( right, bottom ) );
0120 }
0121 
0122 GeoDataLinearRing GeoPainterPrivate::createLinearRingFromGeoRect( const GeoDataCoordinates & centerCoordinates,
0123                                                                   qreal width, qreal height )
0124 {
0125     qreal lon = 0.0;
0126     qreal lat = 0.0;
0127     qreal altitude = centerCoordinates.altitude();
0128     centerCoordinates.geoCoordinates( lon, lat, GeoDataCoordinates::Degree );
0129 
0130     lon = GeoDataCoordinates::normalizeLon( lon, GeoDataCoordinates::Degree );
0131     lat = GeoDataCoordinates::normalizeLat( lat, GeoDataCoordinates::Degree );
0132 
0133     qreal west = GeoDataCoordinates::normalizeLon( lon - width * 0.5, GeoDataCoordinates::Degree );
0134     qreal east =  GeoDataCoordinates::normalizeLon( lon + width * 0.5, GeoDataCoordinates::Degree );
0135 
0136     qreal north = GeoDataCoordinates::normalizeLat( lat + height * 0.5, GeoDataCoordinates::Degree );
0137     qreal south = GeoDataCoordinates::normalizeLat( lat - height * 0.5, GeoDataCoordinates::Degree );
0138 
0139     GeoDataCoordinates southWest( west, south,
0140                                   altitude, GeoDataCoordinates::Degree );
0141     GeoDataCoordinates southEast( east, south,
0142                                   altitude, GeoDataCoordinates::Degree );
0143     GeoDataCoordinates northEast( east, north,
0144                                   altitude, GeoDataCoordinates::Degree );
0145     GeoDataCoordinates northWest( west, north,
0146                                   altitude, GeoDataCoordinates::Degree );
0147 
0148     GeoDataLinearRing rectangle( Tessellate | RespectLatitudeCircle );
0149 
0150     // If the width of the rect is larger as 180 degree, we have to enforce the long way.
0151     if ( width >= 180 ) {
0152         qreal center = lon;
0153         GeoDataCoordinates southCenter( center, south, altitude, GeoDataCoordinates::Degree );
0154         GeoDataCoordinates northCenter( center, north, altitude, GeoDataCoordinates::Degree );
0155 
0156         rectangle << southWest << southCenter << southEast << northEast << northCenter << northWest;
0157     }
0158     else {
0159         rectangle << southWest << southEast << northEast << northWest;
0160     }
0161 
0162     return rectangle;
0163 }
0164 
0165 bool GeoPainterPrivate::doClip( const ViewportParams *viewport )
0166 {
0167     if ( !viewport->currentProjection()->isClippedToSphere() )
0168         return true;
0169 
0170     const qint64  radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
0171 
0172     return ( radius > viewport->width() / 2 || radius > viewport->height() / 2 );
0173 }
0174 
0175 qreal GeoPainterPrivate::normalizeAngle(qreal angle)
0176 {
0177     angle = fmodf(angle, 360);
0178     return angle < 0 ? angle + 360 : angle;
0179 }
0180 
0181 void GeoPainterPrivate::drawTextRotated( const QPointF &startPoint, qreal angle, const QString &text )
0182 {
0183     QRectF textRect(startPoint, m_parent->fontMetrics().size( 0, text));
0184     QTransform const oldTransform = m_parent->transform();
0185     m_parent->translate(startPoint);
0186     m_parent->rotate(angle);
0187     m_parent->translate( -startPoint - QPointF(0.0, m_parent->fontMetrics().height()/2.0)  );
0188 
0189     m_parent->drawText( textRect, text);
0190     m_parent->setTransform(oldTransform);
0191 }
0192 
0193 // -------------------------------------------------------------------------------------------------
0194 
0195 GeoPainter::GeoPainter( QPaintDevice* pd, const ViewportParams *viewport, MapQuality mapQuality )
0196     : ClipPainter( pd, GeoPainterPrivate::doClip( viewport ) ),
0197       d( new GeoPainterPrivate( this, viewport, mapQuality ) )
0198 {
0199     const bool antialiased = mapQuality == HighQuality || mapQuality == PrintQuality;
0200     setRenderHint( QPainter::Antialiasing, antialiased );
0201     ClipPainter::setScreenClip(false);
0202 }
0203 
0204 
0205 GeoPainter::~GeoPainter()
0206 {
0207     delete d;
0208 }
0209 
0210 
0211 MapQuality GeoPainter::mapQuality() const
0212 {
0213     return d->m_mapQuality;
0214 }
0215 
0216 
0217 void GeoPainter::drawAnnotation( const GeoDataCoordinates & position,
0218                                  const QString & text, QSizeF bubbleSize,
0219                                  qreal bubbleOffsetX, qreal bubbleOffsetY,
0220                                  qreal xRnd, qreal yRnd )
0221 {
0222     int pointRepeatNum;
0223     qreal y;
0224     bool globeHidesPoint;
0225 
0226     if ( bubbleSize.height() <= 0 ) {
0227         QRectF rect = QRectF( QPointF( 0.0, 0.0 ), bubbleSize - QSizeF( 2 * xRnd, 0.0 ) );
0228         qreal idealTextHeight = boundingRect( rect, Qt::TextWordWrap, text ).height();
0229         bubbleSize.setHeight( 2 * yRnd + idealTextHeight );
0230     }
0231 
0232     bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint );
0233 
0234     if ( visible ) {
0235         // Draw all the x-repeat-instances of the point on the screen
0236         for( int it = 0; it < pointRepeatNum; ++it ) {
0237             QPainterPath path;
0238             QRectF rect;
0239             d->createAnnotationLayout( d->m_x[it], y, bubbleSize, bubbleOffsetX, bubbleOffsetY,  xRnd, yRnd, path, rect );
0240             QPainter::drawPath( path );
0241             QPainter::drawText( rect, Qt::TextWordWrap, text, &rect );
0242         }
0243     }
0244 }
0245 
0246 
0247 void GeoPainter::drawPoint (  const GeoDataCoordinates & position )
0248 {
0249     int pointRepeatNum;
0250     qreal y;
0251     bool globeHidesPoint;
0252 
0253     bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint );
0254 
0255     if ( visible ) {
0256         // Draw all the x-repeat-instances of the point on the screen
0257         for( int it = 0; it < pointRepeatNum; ++it ) {
0258             QPainter::drawPoint(QPointF(d->m_x[it], y));
0259         }
0260     }
0261 }
0262 
0263 
0264 QRegion GeoPainter::regionFromPoint ( const GeoDataCoordinates & position,
0265                                       qreal width ) const
0266 {
0267     return regionFromRect( position, width, width, false, 3 );
0268 }
0269 
0270 
0271 void GeoPainter::drawPoint( const GeoDataPoint & point )
0272 {
0273     drawPoint( point.coordinates() );
0274 }
0275 
0276 
0277 QRegion GeoPainter::regionFromPoint ( const GeoDataPoint & point,
0278                                       qreal width ) const
0279 {
0280     return regionFromRect( point.coordinates(), width, width, false, 3 );
0281 }
0282 
0283 
0284 void GeoPainter::drawText ( const GeoDataCoordinates & position,
0285                             const QString & text,
0286                             qreal xOffset, qreal yOffset,
0287                             qreal width, qreal height,
0288                             const QTextOption & option )
0289 {
0290     // Of course in theory we could have the "isGeoProjected" parameter used
0291     // for drawText as well. However this would require us to convert all
0292     // glyphs to PainterPaths / QPolygons. From QPolygons we could create
0293     // GeoDataPolygons which could get painted on screen. Any patches appreciated ;-)
0294 
0295     int pointRepeatNum;
0296     qreal y;
0297     bool globeHidesPoint;
0298 
0299     QSizeF textSize( fontMetrics().horizontalAdvance( text ), fontMetrics().height() );
0300 
0301     bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, textSize, globeHidesPoint );
0302 
0303     if ( visible ) {
0304         // Draw all the x-repeat-instances of the point on the screen
0305         const qreal posY = y - yOffset;
0306         for( int it = 0; it < pointRepeatNum; ++it ) {
0307             const qreal posX = d->m_x[it] + xOffset;
0308             if (width == 0.0 && height == 0.0) {
0309                 QPainter::drawText(QPointF(posX, posY), text);
0310             }
0311             else {
0312                 const QRectF boundingRect(posX, posY, width, height);
0313                 QPainter::drawText( boundingRect, text, option );
0314             }
0315         }
0316     }
0317 }
0318 
0319 
0320 void GeoPainter::drawEllipse ( const GeoDataCoordinates & centerPosition,
0321                                qreal width, qreal height,
0322                                bool isGeoProjected )
0323 {
0324     if ( !isGeoProjected ) {
0325         int pointRepeatNum;
0326         qreal y;
0327         bool globeHidesPoint;
0328 
0329         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
0330 
0331         if ( visible ) {
0332             // Draw all the x-repeat-instances of the point on the screen
0333             const qreal rx = width / 2.0;
0334             const qreal ry = height / 2.0;
0335             for( int it = 0; it < pointRepeatNum; ++it ) {
0336                 QPainter::drawEllipse(QPointF(d->m_x[it], y), rx, ry);
0337             }
0338         }
0339     }
0340     else {
0341         // Initialize variables
0342         const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree );
0343         const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree );
0344         const qreal altitude = centerPosition.altitude();
0345 
0346         // Ensure a valid latitude range: 
0347         if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) {
0348             return;
0349         }
0350 
0351         // Don't show the ellipse if it's too small:
0352         GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height,
0353                                      centerLon + 0.5 * width,  centerLon - 0.5 * width, 
0354                                      GeoDataCoordinates::Degree );
0355         if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) ||
0356              !d->m_viewport->resolves( ellipseBox ) ) return;
0357 
0358         GeoDataLinearRing ellipse;
0359 
0360         // Optimizing the precision by determining the size which the 
0361         // ellipse covers on the screen:
0362         const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG;
0363         // To create a circle shape even for very small precision we require uneven numbers:
0364         const int precision = qMin<qreal>( width / degreeResolution / 8 + 1, 81 );
0365 
0366         // Calculate the shape of the upper half of the ellipse:
0367         for ( int i = 0; i <= precision; ++i ) {
0368             const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
0369             const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t );
0370             const qreal lon = centerLon + 0.5 * width * t;
0371             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
0372         }
0373         // Calculate the shape of the lower half of the ellipse:
0374         for ( int i = 0; i <= precision; ++i ) {
0375             const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) -  1.0;
0376             const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t );
0377             const qreal lon = centerLon + 0.5 * width * t;
0378             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
0379         }
0380         
0381         drawPolygon( ellipse );
0382 
0383     }
0384 
0385 }
0386 
0387 
0388 QRegion GeoPainter::regionFromEllipse ( const GeoDataCoordinates & centerPosition,
0389                                         qreal width, qreal height,
0390                                         bool isGeoProjected,
0391                                         qreal strokeWidth ) const
0392 {
0393     if ( !isGeoProjected ) {
0394         int pointRepeatNum;
0395         qreal y;
0396         bool globeHidesPoint;
0397 
0398         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
0399 
0400         QRegion regions;
0401 
0402         if ( visible ) {
0403             // only a hint, a backend could still ignore it, but we cannot know more
0404             const bool antialiased = testRenderHint(QPainter::Antialiasing);
0405 
0406             const qreal halfStrokeWidth = strokeWidth/2.0;
0407             const int startY = antialiased ? (qFloor(y - halfStrokeWidth)) : (qFloor(y+0.5 - halfStrokeWidth));
0408             const int endY = antialiased ? (qCeil(y + height + halfStrokeWidth)) : (qFloor(y+0.5 + height + halfStrokeWidth));
0409             // Draw all the x-repeat-instances of the point on the screen
0410             for( int it = 0; it < pointRepeatNum; ++it ) {
0411                 const qreal x = d->m_x[it];
0412                 const int startX = antialiased ? (qFloor(x - halfStrokeWidth)) : (qFloor(x+0.5 - halfStrokeWidth));
0413                 const int endX = antialiased ? (qCeil(x + width + halfStrokeWidth)) : (qFloor(x+0.5 + width +  halfStrokeWidth));
0414 
0415                 regions += QRegion(startX, startY, endX - startX, endY - startY, QRegion::Ellipse);
0416             }
0417         }
0418         return regions;
0419     }
0420     else {
0421         // Initialize variables
0422         const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree );
0423         const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree );
0424         const qreal altitude = centerPosition.altitude();
0425 
0426         // Ensure a valid latitude range:
0427         if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) {
0428             return QRegion();
0429         }
0430 
0431         // Don't show the ellipse if it's too small:
0432         GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height,
0433                                      centerLon + 0.5 * width,  centerLon - 0.5 * width,
0434                                      GeoDataCoordinates::Degree );
0435         if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) ||
0436              !d->m_viewport->resolves( ellipseBox ) ) return QRegion();
0437 
0438         GeoDataLinearRing ellipse;
0439 
0440         // Optimizing the precision by determining the size which the
0441         // ellipse covers on the screen:
0442         const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG;
0443         // To create a circle shape even for very small precision we require uneven numbers:
0444         const int precision = qMin<qreal>( width / degreeResolution / 8 + 1, 81 );
0445 
0446         // Calculate the shape of the upper half of the ellipse:
0447         for ( int i = 0; i <= precision; ++i ) {
0448             const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
0449             const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t );
0450             const qreal lon = centerLon + 0.5 * width * t;
0451             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
0452         }
0453         // Calculate the shape of the lower half of the ellipse:
0454         for ( int i = 0; i <= precision; ++i ) {
0455             const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) -  1.0;
0456             const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t );
0457             const qreal lon = centerLon + 0.5 * width * t;
0458             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
0459         }
0460 
0461         return regionFromPolygon( ellipse, Qt::OddEvenFill, strokeWidth );
0462     }
0463 }
0464     
0465 
0466 void GeoPainter::drawImage ( const GeoDataCoordinates & centerPosition,
0467                              const QImage & image /*, bool isGeoProjected */ )
0468 {
0469     // isGeoProjected = true would project the image/pixmap onto the globe. This
0470     // requires to deal with the TextureMapping classes -> should get
0471     // implemented later on
0472 
0473     int pointRepeatNum;
0474     qreal y;
0475     bool globeHidesPoint;
0476 
0477 //    if ( !isGeoProjected ) {
0478         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, image.size(), globeHidesPoint );
0479 
0480         if ( visible ) {
0481             // Draw all the x-repeat-instances of the point on the screen
0482             const qreal posY = y - (image.height() / 2.0);
0483             for( int it = 0; it < pointRepeatNum; ++it ) {
0484                 const qreal posX = d->m_x[it] - (image.width() / 2.0);
0485                 QPainter::drawImage(QPointF(posX, posY), image);
0486             }
0487         }
0488 //    }
0489 }
0490 
0491 
0492 void GeoPainter::drawPixmap ( const GeoDataCoordinates & centerPosition,
0493                               const QPixmap & pixmap /* , bool isGeoProjected */ )
0494 {
0495     int pointRepeatNum;
0496     qreal y;
0497     bool globeHidesPoint;
0498 
0499 //    if ( !isGeoProjected ) {
0500         // FIXME: Better visibility detection that takes the circle geometry into account
0501         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, pixmap.size(), globeHidesPoint );
0502 
0503         if ( visible ) {
0504             // Draw all the x-repeat-instances of the point on the screen
0505             const qreal posY = y - (pixmap.height() / 2.0);
0506             for( int it = 0; it < pointRepeatNum; ++it ) {
0507                 const qreal posX = d->m_x[it] - (pixmap.width() / 2.0);
0508                 QPainter::drawPixmap(QPointF(posX, posY), pixmap);
0509             }
0510         }
0511 //    }
0512 }
0513 
0514 
0515 QRegion GeoPainter::regionFromPixmapRect(const GeoDataCoordinates & centerCoordinates,
0516                                          int width, int height,
0517                                          int margin) const
0518 {
0519     const int fullWidth = width + 2 * margin;
0520     const int fullHeight = height + 2 * margin;
0521     int pointRepeatNum;
0522     qreal y;
0523     bool globeHidesPoint;
0524 
0525     const bool visible = d->m_viewport->screenCoordinates(centerCoordinates,
0526                                                           d->m_x, y, pointRepeatNum,
0527                                                           QSizeF(fullWidth, fullHeight), globeHidesPoint);
0528 
0529     QRegion regions;
0530 
0531     if (visible) {
0532         // cmp. GeoPainter::drawPixmap() position calculation
0533         // QPainter::drawPixmap seems to qRound the passed position
0534         const int posY = qRound(y - (height / 2.0)) - margin;
0535         for (int it = 0; it < pointRepeatNum; ++it) {
0536             const int posX = qRound(d->m_x[it] - (width / 2.0)) - margin;
0537             regions += QRegion(posX, posY, width, height);
0538         }
0539     }
0540 
0541     return regions;
0542 }
0543 
0544 void GeoPainter::polygonsFromLineString( const GeoDataLineString &lineString,
0545                                          QVector<QPolygonF*> &polygons ) const
0546 {
0547     // Immediately leave this method now if:
0548     // - the object is not visible in the viewport or if
0549     // - the size of the object is below the resolution of the viewport
0550     if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) ||
0551          ! d->m_viewport->resolves( lineString.latLonAltBox() )
0552         )
0553     {
0554         // mDebug() << "LineString doesn't get displayed on the viewport";
0555         return;
0556     }
0557 
0558     d->m_viewport->screenCoordinates( lineString, polygons );
0559 }
0560 
0561 
0562 void GeoPainter::drawPolyline ( const GeoDataLineString & lineString,
0563                                 const QString& labelText,
0564                                 LabelPositionFlags labelPositionFlags,
0565                                 const QColor& labelColor)
0566 {
0567     // no labels to draw?
0568     // TODO: !labelColor.isValid() || labelColor.alpha() == 0 does not work,
0569     // something injects invalid labelColor for city streets
0570     if (labelText.isEmpty() || labelPositionFlags.testFlag(NoLabel) ||
0571         labelColor == Qt::transparent) {
0572         drawPolyline(lineString);
0573         return;
0574     }
0575 
0576     QVector<QPolygonF*> polygons;
0577     polygonsFromLineString(lineString, polygons);
0578     if (polygons.empty()) return;
0579 
0580     for(const QPolygonF* itPolygon: polygons) {
0581         ClipPainter::drawPolyline(*itPolygon);
0582     }
0583 
0584     drawLabelsForPolygons(polygons,
0585                           labelText,
0586                           labelPositionFlags,
0587                           labelColor);
0588 
0589     qDeleteAll( polygons );
0590 }
0591 
0592 void GeoPainter::drawLabelsForPolygons( const QVector<QPolygonF*> &polygons,
0593                                         const QString& labelText,
0594                                         LabelPositionFlags labelPositionFlags,
0595                                         const QColor& labelColor )
0596 {
0597     if (labelText.isEmpty()) {
0598         return;
0599     }
0600     QPen const oldPen = pen();
0601 
0602     if (labelPositionFlags.testFlag(FollowLine)) {
0603         const qreal maximumLabelFontSize = 20;
0604         qreal fontSize = pen().widthF() * 0.45;
0605         fontSize = qMin( fontSize, maximumLabelFontSize );
0606 
0607         if (fontSize < 6.0 || labelColor == "transparent") {
0608             return;
0609         }
0610         QFont font = this->font();
0611         font.setPointSizeF(fontSize);
0612         setFont(font);
0613         int labelWidth = fontMetrics().horizontalAdvance( labelText );
0614         if (labelText.size() < 20) {
0615             labelWidth *= (20.0 / labelText.size());
0616         }
0617         setPen(labelColor);
0618 
0619         QVector<QPointF> labelNodes;
0620         QRectF viewportRect = QRectF(QPointF(0, 0), d->m_viewport->size());
0621         for( QPolygonF* itPolygon: polygons ) {
0622             if (!itPolygon->boundingRect().intersects(viewportRect)) {
0623                 continue;
0624             }
0625 
0626             labelNodes.clear();
0627 
0628             QPainterPath path;
0629             path.addPolygon(*itPolygon);
0630             qreal pathLength = path.length();
0631             if (pathLength == 0) continue;
0632 
0633             int maxNumLabels = static_cast<int>(pathLength / labelWidth);
0634 
0635             if (maxNumLabels > 0) {
0636                 qreal textRelativeLength = labelWidth / pathLength;
0637                 int numLabels = 1;
0638                 if (maxNumLabels > 1) {
0639                     numLabels = maxNumLabels/2;
0640                 }
0641                 qreal offset = (1.0 - numLabels*textRelativeLength)/numLabels;
0642                 qreal startPercent = offset/2.0;
0643 
0644                 for (int k = 0; k < numLabels; ++k, startPercent += textRelativeLength + offset) {
0645                     QPointF point = path.pointAtPercent(startPercent);
0646                     QPointF endPoint = path.pointAtPercent(startPercent + textRelativeLength);
0647 
0648                     if ( viewport().contains(point.toPoint()) || viewport().contains(endPoint.toPoint()) ) {
0649                         qreal angle = -path.angleAtPercent(startPercent);
0650                         qreal angle2 = -path.angleAtPercent(startPercent + textRelativeLength);
0651                         angle = GeoPainterPrivate::normalizeAngle(angle);
0652                         angle2 = GeoPainterPrivate::normalizeAngle(angle2);
0653                         bool upsideDown = angle > 90.0 && angle < 270.0;
0654 
0655                         if ( qAbs(angle - angle2) < 3.0 ) {
0656                             if ( upsideDown ) {
0657                                 angle += 180.0;
0658                                 point = path.pointAtPercent(startPercent + textRelativeLength);
0659                             }
0660 
0661                             d->drawTextRotated(point, angle, labelText);
0662                         } else {
0663                             for (int i = 0; i < labelText.length(); ++i) {
0664                                 qreal currentGlyphTextLength = fontMetrics().horizontalAdvance(labelText.left(i)) / pathLength;
0665 
0666                                 if ( !upsideDown ) {
0667                                     angle = -path.angleAtPercent(startPercent + currentGlyphTextLength);
0668                                     point = path.pointAtPercent(startPercent + currentGlyphTextLength);
0669                                 }
0670                                 else {
0671                                     angle = -path.angleAtPercent(startPercent + textRelativeLength - currentGlyphTextLength) + 180;
0672                                     point = path.pointAtPercent(startPercent + textRelativeLength - currentGlyphTextLength);
0673                                 }
0674 
0675                                 d->drawTextRotated(point, angle, labelText.at(i));
0676                             }
0677                         }
0678                     }
0679                 }
0680             }
0681         }
0682     } else {
0683         setPen(labelColor);
0684 
0685         int labelWidth = fontMetrics().horizontalAdvance( labelText );
0686         int labelAscent = fontMetrics().ascent();
0687 
0688         QVector<QPointF> labelNodes;
0689         for( QPolygonF* itPolygon: polygons ) {
0690             labelNodes.clear();
0691             ClipPainter::labelPosition( *itPolygon, labelNodes, labelPositionFlags );
0692             if (!labelNodes.isEmpty()) {
0693                 for ( const QPointF& labelNode: labelNodes ) {
0694                     QPointF labelPosition = labelNode + QPointF( 3.0, -2.0 );
0695 
0696                     // FIXME: This is a Q&D fix.
0697                     qreal xmax = viewport().width() - 10.0 - labelWidth;
0698                     if ( labelPosition.x() > xmax ) labelPosition.setX( xmax );
0699                     qreal ymin = 10.0 + labelAscent;
0700                     if ( labelPosition.y() < ymin ) labelPosition.setY( ymin );
0701                     qreal ymax = viewport().height() - 10.0 - labelAscent;
0702                     if ( labelPosition.y() > ymax ) labelPosition.setY( ymax );
0703 
0704                     drawText( QRectF( labelPosition, fontMetrics().size( 0, labelText) ), labelText );
0705                 }
0706             }
0707         }
0708     }
0709     setPen(oldPen);
0710 }
0711 
0712 void GeoPainter::drawPolyline(const GeoDataLineString& lineString)
0713 {
0714     QVector<QPolygonF*> polygons;
0715     polygonsFromLineString(lineString, polygons);
0716     if (polygons.empty()) return;
0717 
0718     for(const QPolygonF* itPolygon: polygons) {
0719         ClipPainter::drawPolyline(*itPolygon);
0720     }
0721 
0722     qDeleteAll(polygons);
0723 }
0724 
0725 
0726 QRegion GeoPainter::regionFromPolyline ( const GeoDataLineString & lineString,
0727                                          qreal strokeWidth ) const
0728 {
0729     // Immediately leave this method now if:
0730     // - the object is not visible in the viewport or if
0731     // - the size of the object is below the resolution of the viewport
0732     if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) ||
0733          ! d->m_viewport->resolves( lineString.latLonAltBox() )
0734         )
0735     {
0736         // mDebug() << "LineString doesn't get displayed on the viewport";
0737         return QRegion();
0738     }
0739 
0740     QPainterPath painterPath;
0741 
0742     QVector<QPolygonF*> polygons;
0743     d->m_viewport->screenCoordinates( lineString, polygons );
0744 
0745     for( QPolygonF* itPolygon: polygons ) {
0746         painterPath.addPolygon( *itPolygon );
0747     }
0748 
0749     qDeleteAll( polygons );
0750 
0751     QPainterPathStroker stroker;
0752     stroker.setWidth( strokeWidth );
0753     QPainterPath strokePath = stroker.createStroke( painterPath );
0754 
0755     return QRegion( strokePath.toFillPolygon().toPolygon(), Qt::WindingFill );
0756 }
0757 
0758 
0759 void GeoPainter::drawPolygon ( const GeoDataLinearRing & linearRing,
0760                                Qt::FillRule fillRule )
0761 {
0762     // Immediately leave this method now if:
0763     // - the object is not visible in the viewport or if
0764     // - the size of the object is below the resolution of the viewport
0765     if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) ||
0766          ! d->m_viewport->resolves( linearRing.latLonAltBox() )
0767         )
0768     {
0769         // mDebug() << "Polygon doesn't get displayed on the viewport";
0770         return;
0771     }
0772 
0773     QVector<QPolygonF*> polygons;
0774     d->m_viewport->screenCoordinates( linearRing, polygons );
0775 
0776     for( QPolygonF* itPolygon: polygons ) {
0777         ClipPainter::drawPolygon( *itPolygon, fillRule );
0778     }
0779 
0780     qDeleteAll( polygons );
0781 }
0782 
0783 
0784 QRegion GeoPainter::regionFromPolygon ( const GeoDataLinearRing & linearRing,
0785                                         Qt::FillRule fillRule, qreal strokeWidth ) const
0786 {
0787     // Immediately leave this method now if:
0788     // - the object is not visible in the viewport or if
0789     // - the size of the object is below the resolution of the viewport
0790     if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) ||
0791          ! d->m_viewport->resolves( linearRing.latLonAltBox() )
0792         )
0793     {
0794         return QRegion();
0795     }
0796 
0797     QRegion regions;
0798 
0799     QVector<QPolygonF*> polygons;
0800     d->m_viewport->screenCoordinates( linearRing, polygons );
0801 
0802     if ( strokeWidth == 0 ) {
0803         // This is the faster way
0804         for( QPolygonF* itPolygon: polygons ) {
0805             regions += QRegion ( (*itPolygon).toPolygon(), fillRule );
0806         }
0807     }
0808     else {
0809         QPainterPath painterPath;
0810         for( QPolygonF* itPolygon: polygons ) {
0811             painterPath.addPolygon( *itPolygon );
0812         }
0813 
0814         QPainterPathStroker stroker;
0815         stroker.setWidth( strokeWidth );
0816         QPainterPath strokePath = stroker.createStroke( painterPath );
0817         painterPath = painterPath.united( strokePath );
0818         regions = QRegion( painterPath.toFillPolygon().toPolygon() );
0819     }
0820 
0821     qDeleteAll( polygons );
0822 
0823     return regions;
0824 }
0825 
0826 
0827 void GeoPainter::drawPolygon ( const GeoDataPolygon & polygon,
0828                                Qt::FillRule fillRule )
0829 {
0830     // If the object is not visible in the viewport return 
0831     if ( ! d->m_viewport->viewLatLonAltBox().intersects( polygon.outerBoundary().latLonAltBox() ) ||
0832     // If the size of the object is below the resolution of the viewport then return
0833          ! d->m_viewport->resolves( polygon.outerBoundary().latLonAltBox() )
0834         )
0835     {
0836         // mDebug() << "Polygon doesn't get displayed on the viewport";
0837         return;
0838     }
0839     // mDebug() << "Drawing Polygon";
0840 
0841     QVector<QPolygonF*> outerPolygons;
0842     QVector<QPolygonF*> innerPolygons;
0843     d->m_viewport->screenCoordinates( polygon.outerBoundary(), outerPolygons );
0844 
0845     QPen const currentPen = pen();
0846 
0847     bool const hasInnerBoundaries = !polygon.innerBoundaries().isEmpty();
0848     bool innerBoundariesOnScreen = false;
0849 
0850     if ( hasInnerBoundaries ) {
0851         QVector<GeoDataLinearRing> const & innerBoundaries = polygon.innerBoundaries();
0852 
0853         const GeoDataLatLonAltBox & viewLatLonAltBox = d->m_viewport->viewLatLonAltBox();
0854         for( const GeoDataLinearRing& itInnerBoundary: innerBoundaries ) {
0855             if ( viewLatLonAltBox.intersects(itInnerBoundary.latLonAltBox())
0856                  && d->m_viewport->resolves(itInnerBoundary.latLonAltBox()), 4 )  {
0857                 innerBoundariesOnScreen = true;
0858                 break;
0859             }
0860         }
0861 
0862         if (innerBoundariesOnScreen) {
0863             // Create the inner screen polygons
0864             for( const GeoDataLinearRing& itInnerBoundary: innerBoundaries ) {
0865                 QVector<QPolygonF*> innerPolygonsPerBoundary;
0866 
0867                 d->m_viewport->screenCoordinates( itInnerBoundary, innerPolygonsPerBoundary );
0868 
0869                 for( QPolygonF* innerPolygonPerBoundary: innerPolygonsPerBoundary ) {
0870                     innerPolygons << innerPolygonPerBoundary;
0871                 }
0872             }
0873 
0874             setPen(Qt::NoPen);
0875             QVector<QPolygonF*> fillPolygons = createFillPolygons( outerPolygons,
0876                                                                    innerPolygons );
0877 
0878             for( const QPolygonF* fillPolygon: fillPolygons ) {
0879                 ClipPainter::drawPolygon(*fillPolygon, fillRule);
0880             }
0881 
0882             setPen(currentPen);
0883 
0884             for( const QPolygonF* outerPolygon: outerPolygons ) {
0885                 ClipPainter::drawPolyline( *outerPolygon );
0886             }
0887             for( const QPolygonF* innerPolygon: innerPolygons ) {
0888                 ClipPainter::drawPolyline( *innerPolygon );
0889             }
0890 
0891             qDeleteAll(fillPolygons);
0892         }
0893     }
0894 
0895     if ( !hasInnerBoundaries || !innerBoundariesOnScreen ) {
0896         drawPolygon( polygon.outerBoundary(), fillRule );
0897     }
0898 
0899     qDeleteAll(outerPolygons);
0900     qDeleteAll(innerPolygons);
0901 }
0902 
0903 QVector<QPolygonF*> GeoPainter::createFillPolygons( const QVector<QPolygonF*> & outerPolygons,
0904                                                     const QVector<QPolygonF*> & innerPolygons ) const
0905 {
0906     QVector<QPolygonF*> fillPolygons;
0907     fillPolygons.reserve(outerPolygons.size());
0908 
0909     for( const QPolygonF* outerPolygon: outerPolygons ) {
0910         QPolygonF* fillPolygon = new QPolygonF;
0911         *fillPolygon << *outerPolygon;
0912         *fillPolygon << outerPolygon->first();
0913 
0914         for( const QPolygonF* innerPolygon: innerPolygons ) {
0915             *fillPolygon << *innerPolygon;
0916             *fillPolygon << innerPolygon->first();
0917             *fillPolygon << outerPolygon->first();
0918         }
0919 
0920         fillPolygons << fillPolygon;
0921     }
0922 
0923     return fillPolygons;
0924 }
0925 
0926 
0927 void GeoPainter::drawRect ( const GeoDataCoordinates & centerCoordinates,
0928                             qreal width, qreal height,
0929                             bool isGeoProjected )
0930 {
0931     if ( !isGeoProjected ) {
0932         int pointRepeatNum;
0933         qreal y;
0934         bool globeHidesPoint;
0935 
0936         bool visible = d->m_viewport->screenCoordinates( centerCoordinates,
0937                        d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
0938 
0939         if ( visible ) {
0940             // Draw all the x-repeat-instances of the point on the screen
0941             const qreal posY = y - height / 2.0;
0942             for( int it = 0; it < pointRepeatNum; ++it ) {
0943                 const qreal posX = d->m_x[it] - width / 2.0;
0944                 QPainter::drawRect(QRectF(posX, posY, width, height));
0945             }
0946         }
0947     }
0948     else {
0949         drawPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ),
0950                      Qt::OddEvenFill );
0951     }
0952 }
0953 
0954 
0955 QRegion GeoPainter::regionFromRect ( const GeoDataCoordinates & centerCoordinates,
0956                                      qreal width, qreal height,
0957                                      bool isGeoProjected,
0958                                      qreal strokeWidth ) const
0959 {
0960     if ( !isGeoProjected ) {
0961         int pointRepeatNum;
0962         qreal centerY;
0963         bool globeHidesPoint;
0964 
0965         bool visible = d->m_viewport->screenCoordinates( centerCoordinates,
0966                        d->m_x, centerY, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
0967 
0968         QRegion regions;
0969 
0970         if ( visible ) {
0971             // only a hint, a backend could still ignore it, but we cannot know more
0972             const bool antialiased = testRenderHint(QPainter::Antialiasing);
0973 
0974             const qreal halfStrokeWidth = strokeWidth/2.0;
0975             const int topY = centerY - height/2.0;
0976             const int startY = antialiased ? (qFloor(topY - halfStrokeWidth)) : (qFloor(topY+0.5 - halfStrokeWidth));
0977             const int endY = antialiased ? (qCeil(topY + height + halfStrokeWidth)) : (qFloor(centerY+0.5 + height + halfStrokeWidth));
0978             // Draw all the x-repeat-instances of the point on the screen
0979             for( int it = 0; it < pointRepeatNum; ++it ) {
0980                 const qreal leftX = d->m_x[it] - width/2.0;
0981                 const int startX = antialiased ? (qFloor(leftX - halfStrokeWidth)) : (qFloor(leftX+0.5 - halfStrokeWidth));
0982                 const int endX = antialiased ? (qCeil(leftX + width + halfStrokeWidth)) : (qFloor(leftX+0.5 + width +  halfStrokeWidth));
0983                 regions += QRegion(startX, startY, endX - startX, endY - startY);
0984             }
0985         }
0986         return regions;
0987     }
0988     else {
0989         return regionFromPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ),
0990                                   Qt::OddEvenFill, strokeWidth );
0991     }
0992 }
0993 
0994 
0995 void GeoPainter::drawRoundedRect(const GeoDataCoordinates &centerPosition,
0996                                  qreal width, qreal height,
0997                                  qreal xRnd, qreal yRnd)
0998 {
0999         int pointRepeatNum;
1000         qreal y;
1001         bool globeHidesPoint;
1002 
1003         // FIXME: Better visibility detection that takes the circle geometry into account
1004         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
1005 
1006         if ( visible ) {
1007             // Draw all the x-repeat-instances of the point on the screen
1008             const qreal posY = y - height / 2.0;
1009             for( int it = 0; it < pointRepeatNum; ++it ) {
1010                 const qreal posX = d->m_x[it] - width / 2.0;
1011                 QPainter::drawRoundedRect(QRectF(posX, posY, width, height), xRnd, yRnd);
1012             }
1013         }
1014 }
1015 
1016 
1017 void GeoPainter::drawTextFragment(const QPoint &position, const QString &text,
1018                                   const qreal fontSize, const QColor &color,
1019                                   const Frames &flags)
1020 {
1021     const QString key = text + ":" + QString::number(static_cast<int>(flags));
1022 
1023     QPixmap pixmap;
1024 
1025     if (!QPixmapCache::find(key, &pixmap)) {
1026         const bool hasRoundFrame = flags.testFlag(RoundFrame);
1027 
1028         QPixmap pixmap(10,10);
1029         QPainter textPainter;
1030 
1031         textPainter.begin(&pixmap);
1032         const QFontMetrics metrics = textPainter.fontMetrics();
1033         textPainter.end();
1034 
1035         const int width = metrics.horizontalAdvance(text);
1036         const int height = metrics.height();
1037         const QSize size = hasRoundFrame
1038                               ? QSize(qMax(1.2*width, 1.1*height), 1.2*height)
1039                               : QSize(width, height);
1040         pixmap = QPixmap(size);
1041         pixmap.fill(Qt::transparent);
1042         const QRect labelRect(QPoint(), size);
1043         textPainter.begin(&pixmap);
1044         QFont textFont = textPainter.font();
1045         textFont.setPointSize(fontSize);
1046         textPainter.setFont(textFont);
1047         textPainter.setRenderHint(QPainter::Antialiasing, true);
1048 
1049         const QColor brushColor = color;
1050         if (hasRoundFrame) {
1051             QColor lighterColor = brushColor.lighter(110);
1052             lighterColor.setAlphaF(0.9);
1053             textPainter.setBrush(lighterColor);
1054             textPainter.drawRoundedRect(labelRect, 3, 3);
1055         }
1056 
1057         textPainter.setBrush(brushColor);
1058         textPainter.drawText(labelRect, Qt::AlignHCenter , text);
1059 
1060         if (hasRoundFrame) {
1061             textPainter.setBrush(brushColor);
1062         }
1063 
1064         textPainter.end();
1065         QPixmapCache::insert(key, pixmap);
1066     }
1067 
1068     QPainter::drawPixmap(position.x() - pixmap.width()/2,
1069                           position.y() - pixmap.height()/2,
1070                           pixmap);
1071 }