File indexing completed on 2024-03-24 03:52:20
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 ¢erPosition, 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 }