File indexing completed on 2025-01-05 03:59:16

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