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 ¢erPosition, 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 }