1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2006-2009 Torsten Rahn <tackat@kde.org>
4 
5 
6 #include "GeoPainter.h"
7 #include "GeoPainter_p.h"
8 
9 #include <QList>
10 #include <QPainterPath>
11 #include <QPixmapCache>
12 #include <QRegion>
13 #include <qmath.h>
14 
15 #include "MarbleDebug.h"
16 
17 #include "GeoDataCoordinates.h"
18 #include "GeoDataLatLonAltBox.h"
19 #include "GeoDataLineString.h"
20 #include "GeoDataLinearRing.h"
21 #include "GeoDataPoint.h"
22 #include "GeoDataPolygon.h"
23 
24 #include "ViewportParams.h"
25 #include "AbstractProjection.h"
26 
27 // #define MARBLE_DEBUG
28 
29 using namespace Marble;
30 
GeoPainterPrivate(GeoPainter * q,const ViewportParams * viewport,MapQuality mapQuality)31 GeoPainterPrivate::GeoPainterPrivate( GeoPainter* q, const ViewportParams *viewport, MapQuality mapQuality )
32         : m_viewport( viewport ),
33         m_mapQuality( mapQuality ),
34         m_x( new qreal[100] ),
35         m_parent(q)
36 {
37 }
38 
~GeoPainterPrivate()39 GeoPainterPrivate::~GeoPainterPrivate()
40 {
41     delete[] m_x;
42 }
43 
createAnnotationLayout(qreal x,qreal y,const QSizeF & bubbleSize,qreal bubbleOffsetX,qreal bubbleOffsetY,qreal xRnd,qreal yRnd,QPainterPath & path,QRectF & rect)44 void GeoPainterPrivate::createAnnotationLayout (  qreal x, qreal y,
45                                                   const QSizeF& bubbleSize,
46                                                   qreal bubbleOffsetX, qreal bubbleOffsetY,
47                                                   qreal xRnd, qreal yRnd,
48                                                   QPainterPath& path, QRectF& rect )
49 {
50     // TODO: MOVE this into an own Annotation class
51     qreal arrowPosition = 0.3;
52     qreal arrowWidth = 12.0;
53 
54     qreal width =  bubbleSize.width();
55     qreal height = bubbleSize.height();
56 
57     qreal dx =  ( bubbleOffsetX > 0 ) ? 1.0 : -1.0; // x-Mirror
58     qreal dy =  ( bubbleOffsetY < 0 ) ? 1.0 : -1.0; // y-Mirror
59 
60     qreal x0 =  ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) - xRnd *dx;
61     qreal x1 =  ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd );
62     qreal x2 =  ( x + bubbleOffsetX ) - dx * ( 1.0 - arrowPosition ) * ( width - 2.0 * xRnd ) + xRnd * dx;
63     qreal x3 =  ( x + bubbleOffsetX ) - dx * arrowWidth / 2.0;
64     qreal x4 =  ( x + bubbleOffsetX ) + dx * arrowWidth / 2.0;
65     qreal x5 =  ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd )- xRnd * dx;
66     qreal x6 =  ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd );
67     qreal x7 =  ( x + bubbleOffsetX ) + dx * arrowPosition * ( width - 2.0 * xRnd ) + xRnd * dx;
68 
69     qreal y0 =  ( y + bubbleOffsetY );
70     qreal y1 =  ( y + bubbleOffsetY ) - dy * yRnd;
71     qreal y2 =  ( y + bubbleOffsetY ) - dy * 2 * yRnd;
72     qreal y5 =  ( y + bubbleOffsetY ) - dy * ( height - 2 * yRnd );
73     qreal y6 =  ( y + bubbleOffsetY ) - dy * ( height - yRnd );
74     qreal y7 =  ( y + bubbleOffsetY ) - dy * height;
75 
76     QPointF p1 ( x, y ); // pointing point
77     QPointF p2 ( x4, y0 );
78     QPointF p3 ( x6, y0 );
79     QPointF p4 ( x7, y1 );
80     QPointF p5 ( x7, y6 );
81     QPointF p6 ( x6, y7 );
82     QPointF p7 ( x1, y7 );
83     QPointF p8 ( x0, y6 );
84     QPointF p9 ( x0, y1 );
85     QPointF p10( x1, y0 );
86     QPointF p11( x3, y0 );
87 
88     QRectF bubbleBoundingBox(  QPointF( x0, y7 ), QPointF( x7, y0 ) );
89 
90     path.moveTo( p1 );
91     path.lineTo( p2 );
92 
93     path.lineTo( p3 );
94     QRectF bottomRight( QPointF( x5, y2 ), QPointF( x7, y0 ) );
95     path.arcTo( bottomRight, 270.0, 90.0 );
96 
97     path.lineTo( p5 );
98     QRectF topRight( QPointF( x5, y7 ), QPointF( x7, y5 ) );
99     path.arcTo( topRight, 0.0, 90.0 );
100 
101     path.lineTo( p7 );
102     QRectF topLeft( QPointF( x0, y7 ), QPointF( x2, y5 ) );
103     path.arcTo( topLeft, 90.0, 90.0 );
104 
105     path.lineTo( p9 );
106     QRectF bottomLeft( QPointF( x0, y2 ), QPointF( x2, y0 ) );
107     path.arcTo( bottomLeft, 180.0, 90.0 );
108 
109     path.lineTo( p10 );
110     path.lineTo( p11 );
111     path.lineTo( p1 );
112 
113     qreal left   = ( dx > 0 ) ? x1 : x6;
114     qreal right  = ( dx > 0 ) ? x6 : x1;
115     qreal top    = ( dy > 0 ) ? y6 : y1;
116     qreal bottom = ( dy > 0 ) ? y1 : y6;
117 
118     rect.setTopLeft( QPointF( left, top ) );
119     rect.setBottomRight( QPointF( right, bottom ) );
120 }
121 
createLinearRingFromGeoRect(const GeoDataCoordinates & centerCoordinates,qreal width,qreal height)122 GeoDataLinearRing GeoPainterPrivate::createLinearRingFromGeoRect( const GeoDataCoordinates & centerCoordinates,
123                                                                   qreal width, qreal height )
124 {
125     qreal lon = 0.0;
126     qreal lat = 0.0;
127     qreal altitude = centerCoordinates.altitude();
128     centerCoordinates.geoCoordinates( lon, lat, GeoDataCoordinates::Degree );
129 
130     lon = GeoDataCoordinates::normalizeLon( lon, GeoDataCoordinates::Degree );
131     lat = GeoDataCoordinates::normalizeLat( lat, GeoDataCoordinates::Degree );
132 
133     qreal west = GeoDataCoordinates::normalizeLon( lon - width * 0.5, GeoDataCoordinates::Degree );
134     qreal east =  GeoDataCoordinates::normalizeLon( lon + width * 0.5, GeoDataCoordinates::Degree );
135 
136     qreal north = GeoDataCoordinates::normalizeLat( lat + height * 0.5, GeoDataCoordinates::Degree );
137     qreal south = GeoDataCoordinates::normalizeLat( lat - height * 0.5, GeoDataCoordinates::Degree );
138 
139     GeoDataCoordinates southWest( west, south,
140                                   altitude, GeoDataCoordinates::Degree );
141     GeoDataCoordinates southEast( east, south,
142                                   altitude, GeoDataCoordinates::Degree );
143     GeoDataCoordinates northEast( east, north,
144                                   altitude, GeoDataCoordinates::Degree );
145     GeoDataCoordinates northWest( west, north,
146                                   altitude, GeoDataCoordinates::Degree );
147 
148     GeoDataLinearRing rectangle( Tessellate | RespectLatitudeCircle );
149 
150     // If the width of the rect is larger as 180 degree, we have to enforce the long way.
151     if ( width >= 180 ) {
152         qreal center = lon;
153         GeoDataCoordinates southCenter( center, south, altitude, GeoDataCoordinates::Degree );
154         GeoDataCoordinates northCenter( center, north, altitude, GeoDataCoordinates::Degree );
155 
156         rectangle << southWest << southCenter << southEast << northEast << northCenter << northWest;
157     }
158     else {
159         rectangle << southWest << southEast << northEast << northWest;
160     }
161 
162     return rectangle;
163 }
164 
doClip(const ViewportParams * viewport)165 bool GeoPainterPrivate::doClip( const ViewportParams *viewport )
166 {
167     if ( !viewport->currentProjection()->isClippedToSphere() )
168         return true;
169 
170     const qint64  radius = viewport->radius() * viewport->currentProjection()->clippingRadius();
171 
172     return ( radius > viewport->width() / 2 || radius > viewport->height() / 2 );
173 }
174 
normalizeAngle(qreal angle)175 qreal GeoPainterPrivate::normalizeAngle(qreal angle)
176 {
177     angle = fmodf(angle, 360);
178     return angle < 0 ? angle + 360 : angle;
179 }
180 
drawTextRotated(const QPointF & startPoint,qreal angle,const QString & text)181 void GeoPainterPrivate::drawTextRotated( const QPointF &startPoint, qreal angle, const QString &text )
182 {
183     QRectF textRect(startPoint, m_parent->fontMetrics().size( 0, text));
184     QTransform const oldTransform = m_parent->transform();
185     m_parent->translate(startPoint);
186     m_parent->rotate(angle);
187     m_parent->translate( -startPoint - QPointF(0.0, m_parent->fontMetrics().height()/2.0)  );
188 
189     m_parent->drawText( textRect, text);
190     m_parent->setTransform(oldTransform);
191 }
192 
193 // -------------------------------------------------------------------------------------------------
194 
GeoPainter(QPaintDevice * pd,const ViewportParams * viewport,MapQuality mapQuality)195 GeoPainter::GeoPainter( QPaintDevice* pd, const ViewportParams *viewport, MapQuality mapQuality )
196     : ClipPainter( pd, GeoPainterPrivate::doClip( viewport ) ),
197       d( new GeoPainterPrivate( this, viewport, mapQuality ) )
198 {
199     const bool antialiased = mapQuality == HighQuality || mapQuality == PrintQuality;
200     setRenderHint( QPainter::Antialiasing, antialiased );
201     ClipPainter::setScreenClip(false);
202 }
203 
204 
~GeoPainter()205 GeoPainter::~GeoPainter()
206 {
207     delete d;
208 }
209 
210 
mapQuality() const211 MapQuality GeoPainter::mapQuality() const
212 {
213     return d->m_mapQuality;
214 }
215 
216 
drawAnnotation(const GeoDataCoordinates & position,const QString & text,QSizeF bubbleSize,qreal bubbleOffsetX,qreal bubbleOffsetY,qreal xRnd,qreal yRnd)217 void GeoPainter::drawAnnotation( const GeoDataCoordinates & position,
218                                  const QString & text, QSizeF bubbleSize,
219                                  qreal bubbleOffsetX, qreal bubbleOffsetY,
220                                  qreal xRnd, qreal yRnd )
221 {
222     int pointRepeatNum;
223     qreal y;
224     bool globeHidesPoint;
225 
226     if ( bubbleSize.height() <= 0 ) {
227         QRectF rect = QRectF( QPointF( 0.0, 0.0 ), bubbleSize - QSizeF( 2 * xRnd, 0.0 ) );
228         qreal idealTextHeight = boundingRect( rect, Qt::TextWordWrap, text ).height();
229         bubbleSize.setHeight( 2 * yRnd + idealTextHeight );
230     }
231 
232     bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint );
233 
234     if ( visible ) {
235         // Draw all the x-repeat-instances of the point on the screen
236         for( int it = 0; it < pointRepeatNum; ++it ) {
237             QPainterPath path;
238             QRectF rect;
239             d->createAnnotationLayout( d->m_x[it], y, bubbleSize, bubbleOffsetX, bubbleOffsetY,  xRnd, yRnd, path, rect );
240             QPainter::drawPath( path );
241             QPainter::drawText( rect, Qt::TextWordWrap, text, &rect );
242         }
243     }
244 }
245 
246 
drawPoint(const GeoDataCoordinates & position)247 void GeoPainter::drawPoint (  const GeoDataCoordinates & position )
248 {
249     int pointRepeatNum;
250     qreal y;
251     bool globeHidesPoint;
252 
253     bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, QSizeF(), globeHidesPoint );
254 
255     if ( visible ) {
256         // Draw all the x-repeat-instances of the point on the screen
257         for( int it = 0; it < pointRepeatNum; ++it ) {
258             QPainter::drawPoint(QPointF(d->m_x[it], y));
259         }
260     }
261 }
262 
263 
regionFromPoint(const GeoDataCoordinates & position,qreal width) const264 QRegion GeoPainter::regionFromPoint ( const GeoDataCoordinates & position,
265                                       qreal width ) const
266 {
267     return regionFromRect( position, width, width, false, 3 );
268 }
269 
270 
drawPoint(const GeoDataPoint & point)271 void GeoPainter::drawPoint( const GeoDataPoint & point )
272 {
273     drawPoint( point.coordinates() );
274 }
275 
276 
regionFromPoint(const GeoDataPoint & point,qreal width) const277 QRegion GeoPainter::regionFromPoint ( const GeoDataPoint & point,
278                                       qreal width ) const
279 {
280     return regionFromRect( point.coordinates(), width, width, false, 3 );
281 }
282 
283 
drawText(const GeoDataCoordinates & position,const QString & text,qreal xOffset,qreal yOffset,qreal width,qreal height,const QTextOption & option)284 void GeoPainter::drawText ( const GeoDataCoordinates & position,
285                             const QString & text,
286                             qreal xOffset, qreal yOffset,
287                             qreal width, qreal height,
288                             const QTextOption & option )
289 {
290     // Of course in theory we could have the "isGeoProjected" parameter used
291     // for drawText as well. However this would require us to convert all
292     // glyphs to PainterPaths / QPolygons. From QPolygons we could create
293     // GeoDataPolygons which could get painted on screen. Any patches appreciated ;-)
294 
295     int pointRepeatNum;
296     qreal y;
297     bool globeHidesPoint;
298 
299     QSizeF textSize( fontMetrics().width( text ), fontMetrics().height() );
300 
301     bool visible = d->m_viewport->screenCoordinates( position, d->m_x, y, pointRepeatNum, textSize, globeHidesPoint );
302 
303     if ( visible ) {
304         // Draw all the x-repeat-instances of the point on the screen
305         const qreal posY = y - yOffset;
306         for( int it = 0; it < pointRepeatNum; ++it ) {
307             const qreal posX = d->m_x[it] + xOffset;
308             if (width == 0.0 && height == 0.0) {
309                 QPainter::drawText(QPointF(posX, posY), text);
310             }
311             else {
312                 const QRectF boundingRect(posX, posY, width, height);
313                 QPainter::drawText( boundingRect, text, option );
314             }
315         }
316     }
317 }
318 
319 
drawEllipse(const GeoDataCoordinates & centerPosition,qreal width,qreal height,bool isGeoProjected)320 void GeoPainter::drawEllipse ( const GeoDataCoordinates & centerPosition,
321                                qreal width, qreal height,
322                                bool isGeoProjected )
323 {
324     if ( !isGeoProjected ) {
325         int pointRepeatNum;
326         qreal y;
327         bool globeHidesPoint;
328 
329         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
330 
331         if ( visible ) {
332             // Draw all the x-repeat-instances of the point on the screen
333             const qreal rx = width / 2.0;
334             const qreal ry = height / 2.0;
335             for( int it = 0; it < pointRepeatNum; ++it ) {
336                 QPainter::drawEllipse(QPointF(d->m_x[it], y), rx, ry);
337             }
338         }
339     }
340     else {
341         // Initialize variables
342         const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree );
343         const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree );
344         const qreal altitude = centerPosition.altitude();
345 
346         // Ensure a valid latitude range:
347         if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) {
348             return;
349         }
350 
351         // Don't show the ellipse if it's too small:
352         GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height,
353                                      centerLon + 0.5 * width,  centerLon - 0.5 * width,
354                                      GeoDataCoordinates::Degree );
355         if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) ||
356              !d->m_viewport->resolves( ellipseBox ) ) return;
357 
358         GeoDataLinearRing ellipse;
359 
360         // Optimizing the precision by determining the size which the
361         // ellipse covers on the screen:
362         const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG;
363         // To create a circle shape even for very small precision we require uneven numbers:
364         const int precision = qMin<qreal>( width / degreeResolution / 8 + 1, 81 );
365 
366         // Calculate the shape of the upper half of the ellipse:
367         for ( int i = 0; i <= precision; ++i ) {
368             const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
369             const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t );
370             const qreal lon = centerLon + 0.5 * width * t;
371             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
372         }
373         // Calculate the shape of the lower half of the ellipse:
374         for ( int i = 0; i <= precision; ++i ) {
375             const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) -  1.0;
376             const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t );
377             const qreal lon = centerLon + 0.5 * width * t;
378             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
379         }
380 
381         drawPolygon( ellipse );
382 
383     }
384 
385 }
386 
387 
regionFromEllipse(const GeoDataCoordinates & centerPosition,qreal width,qreal height,bool isGeoProjected,qreal strokeWidth) const388 QRegion GeoPainter::regionFromEllipse ( const GeoDataCoordinates & centerPosition,
389                                         qreal width, qreal height,
390                                         bool isGeoProjected,
391                                         qreal strokeWidth ) const
392 {
393     if ( !isGeoProjected ) {
394         int pointRepeatNum;
395         qreal y;
396         bool globeHidesPoint;
397 
398         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
399 
400         QRegion regions;
401 
402         if ( visible ) {
403             // only a hint, a backend could still ignore it, but we cannot know more
404             const bool antialiased = testRenderHint(QPainter::Antialiasing);
405 
406             const qreal halfStrokeWidth = strokeWidth/2.0;
407             const int startY = antialiased ? (qFloor(y - halfStrokeWidth)) : (qFloor(y+0.5 - halfStrokeWidth));
408             const int endY = antialiased ? (qCeil(y + height + halfStrokeWidth)) : (qFloor(y+0.5 + height + halfStrokeWidth));
409             // Draw all the x-repeat-instances of the point on the screen
410             for( int it = 0; it < pointRepeatNum; ++it ) {
411                 const qreal x = d->m_x[it];
412                 const int startX = antialiased ? (qFloor(x - halfStrokeWidth)) : (qFloor(x+0.5 - halfStrokeWidth));
413                 const int endX = antialiased ? (qCeil(x + width + halfStrokeWidth)) : (qFloor(x+0.5 + width +  halfStrokeWidth));
414 
415                 regions += QRegion(startX, startY, endX - startX, endY - startY, QRegion::Ellipse);
416             }
417         }
418         return regions;
419     }
420     else {
421         // Initialize variables
422         const qreal centerLon = centerPosition.longitude( GeoDataCoordinates::Degree );
423         const qreal centerLat = centerPosition.latitude( GeoDataCoordinates::Degree );
424         const qreal altitude = centerPosition.altitude();
425 
426         // Ensure a valid latitude range:
427         if ( centerLat + 0.5 * height > 90.0 || centerLat - 0.5 * height < -90.0 ) {
428             return QRegion();
429         }
430 
431         // Don't show the ellipse if it's too small:
432         GeoDataLatLonBox ellipseBox( centerLat + 0.5 * height, centerLat - 0.5 * height,
433                                      centerLon + 0.5 * width,  centerLon - 0.5 * width,
434                                      GeoDataCoordinates::Degree );
435         if ( !d->m_viewport->viewLatLonAltBox().intersects( ellipseBox ) ||
436              !d->m_viewport->resolves( ellipseBox ) ) return QRegion();
437 
438         GeoDataLinearRing ellipse;
439 
440         // Optimizing the precision by determining the size which the
441         // ellipse covers on the screen:
442         const qreal degreeResolution = d->m_viewport->angularResolution() * RAD2DEG;
443         // To create a circle shape even for very small precision we require uneven numbers:
444         const int precision = qMin<qreal>( width / degreeResolution / 8 + 1, 81 );
445 
446         // Calculate the shape of the upper half of the ellipse:
447         for ( int i = 0; i <= precision; ++i ) {
448             const qreal t = 1.0 - 2.0 * (qreal)(i) / (qreal)(precision);
449             const qreal lat = centerLat + 0.5 * height * sqrt( 1.0 - t * t );
450             const qreal lon = centerLon + 0.5 * width * t;
451             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
452         }
453         // Calculate the shape of the lower half of the ellipse:
454         for ( int i = 0; i <= precision; ++i ) {
455             const qreal t = 2.0 * (qreal)(i) / (qreal)(precision) -  1.0;
456             const qreal lat = centerLat - 0.5 * height * sqrt( 1.0 - t * t );
457             const qreal lon = centerLon + 0.5 * width * t;
458             ellipse << GeoDataCoordinates( lon, lat, altitude, GeoDataCoordinates::Degree );
459         }
460 
461         return regionFromPolygon( ellipse, Qt::OddEvenFill, strokeWidth );
462     }
463 }
464 
465 
drawImage(const GeoDataCoordinates & centerPosition,const QImage & image)466 void GeoPainter::drawImage ( const GeoDataCoordinates & centerPosition,
467                              const QImage & image /*, bool isGeoProjected */ )
468 {
469     // isGeoProjected = true would project the image/pixmap onto the globe. This
470     // requires to deal with the TextureMapping classes -> should get
471     // implemented later on
472 
473     int pointRepeatNum;
474     qreal y;
475     bool globeHidesPoint;
476 
477 //    if ( !isGeoProjected ) {
478         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, image.size(), globeHidesPoint );
479 
480         if ( visible ) {
481             // Draw all the x-repeat-instances of the point on the screen
482             const qreal posY = y - (image.height() / 2.0);
483             for( int it = 0; it < pointRepeatNum; ++it ) {
484                 const qreal posX = d->m_x[it] - (image.width() / 2.0);
485                 QPainter::drawImage(QPointF(posX, posY), image);
486             }
487         }
488 //    }
489 }
490 
491 
drawPixmap(const GeoDataCoordinates & centerPosition,const QPixmap & pixmap)492 void GeoPainter::drawPixmap ( const GeoDataCoordinates & centerPosition,
493                               const QPixmap & pixmap /* , bool isGeoProjected */ )
494 {
495     int pointRepeatNum;
496     qreal y;
497     bool globeHidesPoint;
498 
499 //    if ( !isGeoProjected ) {
500         // FIXME: Better visibility detection that takes the circle geometry into account
501         bool visible = d->m_viewport->screenCoordinates( centerPosition, d->m_x, y, pointRepeatNum, pixmap.size(), globeHidesPoint );
502 
503         if ( visible ) {
504             // Draw all the x-repeat-instances of the point on the screen
505             const qreal posY = y - (pixmap.height() / 2.0);
506             for( int it = 0; it < pointRepeatNum; ++it ) {
507                 const qreal posX = d->m_x[it] - (pixmap.width() / 2.0);
508                 QPainter::drawPixmap(QPointF(posX, posY), pixmap);
509             }
510         }
511 //    }
512 }
513 
514 
regionFromPixmapRect(const GeoDataCoordinates & centerCoordinates,int width,int height,int margin) const515 QRegion GeoPainter::regionFromPixmapRect(const GeoDataCoordinates & centerCoordinates,
516                                          int width, int height,
517                                          int margin) const
518 {
519     const int fullWidth = width + 2 * margin;
520     const int fullHeight = height + 2 * margin;
521     int pointRepeatNum;
522     qreal y;
523     bool globeHidesPoint;
524 
525     const bool visible = d->m_viewport->screenCoordinates(centerCoordinates,
526                                                           d->m_x, y, pointRepeatNum,
527                                                           QSizeF(fullWidth, fullHeight), globeHidesPoint);
528 
529     QRegion regions;
530 
531     if (visible) {
532         // cmp. GeoPainter::drawPixmap() position calculation
533         // QPainter::drawPixmap seems to qRound the passed position
534         const int posY = qRound(y - (height / 2.0)) - margin;
535         for (int it = 0; it < pointRepeatNum; ++it) {
536             const int posX = qRound(d->m_x[it] - (width / 2.0)) - margin;
537             regions += QRegion(posX, posY, width, height);
538         }
539     }
540 
541     return regions;
542 }
543 
polygonsFromLineString(const GeoDataLineString & lineString,QVector<QPolygonF * > & polygons) const544 void GeoPainter::polygonsFromLineString( const GeoDataLineString &lineString,
545                                          QVector<QPolygonF*> &polygons ) const
546 {
547     // Immediately leave this method now if:
548     // - the object is not visible in the viewport or if
549     // - the size of the object is below the resolution of the viewport
550     if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) ||
551          ! d->m_viewport->resolves( lineString.latLonAltBox() )
552         )
553     {
554         // mDebug() << "LineString doesn't get displayed on the viewport";
555         return;
556     }
557 
558     d->m_viewport->screenCoordinates( lineString, polygons );
559 }
560 
561 
drawPolyline(const GeoDataLineString & lineString,const QString & labelText,LabelPositionFlags labelPositionFlags,const QColor & labelColor)562 void GeoPainter::drawPolyline ( const GeoDataLineString & lineString,
563                                 const QString& labelText,
564                                 LabelPositionFlags labelPositionFlags,
565                                 const QColor& labelColor)
566 {
567     // no labels to draw?
568     // TODO: !labelColor.isValid() || labelColor.alpha() == 0 does not work,
569     // something injects invalid labelColor for city streets
570     if (labelText.isEmpty() || labelPositionFlags.testFlag(NoLabel) ||
571         labelColor == Qt::transparent) {
572         drawPolyline(lineString);
573         return;
574     }
575 
576     QVector<QPolygonF*> polygons;
577     polygonsFromLineString(lineString, polygons);
578     if (polygons.empty()) return;
579 
580     for(const QPolygonF* itPolygon: polygons) {
581         ClipPainter::drawPolyline(*itPolygon);
582     }
583 
584     drawLabelsForPolygons(polygons,
585                           labelText,
586                           labelPositionFlags,
587                           labelColor);
588 
589     qDeleteAll( polygons );
590 }
591 
drawLabelsForPolygons(const QVector<QPolygonF * > & polygons,const QString & labelText,LabelPositionFlags labelPositionFlags,const QColor & labelColor)592 void GeoPainter::drawLabelsForPolygons( const QVector<QPolygonF*> &polygons,
593                                         const QString& labelText,
594                                         LabelPositionFlags labelPositionFlags,
595                                         const QColor& labelColor )
596 {
597     if (labelText.isEmpty()) {
598         return;
599     }
600     QPen const oldPen = pen();
601 
602     if (labelPositionFlags.testFlag(FollowLine)) {
603         const qreal maximumLabelFontSize = 20;
604         qreal fontSize = pen().widthF() * 0.45;
605         fontSize = qMin( fontSize, maximumLabelFontSize );
606 
607         if (fontSize < 6.0 || labelColor == "transparent") {
608             return;
609         }
610         QFont font = this->font();
611         font.setPointSizeF(fontSize);
612         setFont(font);
613         int labelWidth = fontMetrics().width( labelText );
614         if (labelText.size() < 20) {
615             labelWidth *= (20.0 / labelText.size());
616         }
617         setPen(labelColor);
618 
619         QVector<QPointF> labelNodes;
620         QRectF viewportRect = QRectF(QPointF(0, 0), d->m_viewport->size());
621         for( QPolygonF* itPolygon: polygons ) {
622             if (!itPolygon->boundingRect().intersects(viewportRect)) {
623                 continue;
624             }
625 
626             labelNodes.clear();
627 
628             QPainterPath path;
629             path.addPolygon(*itPolygon);
630             qreal pathLength = path.length();
631             if (pathLength == 0) continue;
632 
633             int maxNumLabels = static_cast<int>(pathLength / labelWidth);
634 
635             if (maxNumLabels > 0) {
636                 qreal textRelativeLength = labelWidth / pathLength;
637                 int numLabels = 1;
638                 if (maxNumLabels > 1) {
639                     numLabels = maxNumLabels/2;
640                 }
641                 qreal offset = (1.0 - numLabels*textRelativeLength)/numLabels;
642                 qreal startPercent = offset/2.0;
643 
644                 for (int k = 0; k < numLabels; ++k, startPercent += textRelativeLength + offset) {
645                     QPointF point = path.pointAtPercent(startPercent);
646                     QPointF endPoint = path.pointAtPercent(startPercent + textRelativeLength);
647 
648                     if ( viewport().contains(point.toPoint()) || viewport().contains(endPoint.toPoint()) ) {
649                         qreal angle = -path.angleAtPercent(startPercent);
650                         qreal angle2 = -path.angleAtPercent(startPercent + textRelativeLength);
651                         angle = GeoPainterPrivate::normalizeAngle(angle);
652                         angle2 = GeoPainterPrivate::normalizeAngle(angle2);
653                         bool upsideDown = angle > 90.0 && angle < 270.0;
654 
655                         if ( qAbs(angle - angle2) < 3.0 ) {
656                             if ( upsideDown ) {
657                                 angle += 180.0;
658                                 point = path.pointAtPercent(startPercent + textRelativeLength);
659                             }
660 
661                             d->drawTextRotated(point, angle, labelText);
662                         } else {
663                             for (int i = 0; i < labelText.length(); ++i) {
664                                 qreal currentGlyphTextLength = fontMetrics().width(labelText.left(i)) / pathLength;
665 
666                                 if ( !upsideDown ) {
667                                     angle = -path.angleAtPercent(startPercent + currentGlyphTextLength);
668                                     point = path.pointAtPercent(startPercent + currentGlyphTextLength);
669                                 }
670                                 else {
671                                     angle = -path.angleAtPercent(startPercent + textRelativeLength - currentGlyphTextLength) + 180;
672                                     point = path.pointAtPercent(startPercent + textRelativeLength - currentGlyphTextLength);
673                                 }
674 
675                                 d->drawTextRotated(point, angle, labelText.at(i));
676                             }
677                         }
678                     }
679                 }
680             }
681         }
682     } else {
683         setPen(labelColor);
684 
685         int labelWidth = fontMetrics().width( labelText );
686         int labelAscent = fontMetrics().ascent();
687 
688         QVector<QPointF> labelNodes;
689         for( QPolygonF* itPolygon: polygons ) {
690             labelNodes.clear();
691             ClipPainter::labelPosition( *itPolygon, labelNodes, labelPositionFlags );
692             if (!labelNodes.isEmpty()) {
693                 for ( const QPointF& labelNode: labelNodes ) {
694                     QPointF labelPosition = labelNode + QPointF( 3.0, -2.0 );
695 
696                     // FIXME: This is a Q&D fix.
697                     qreal xmax = viewport().width() - 10.0 - labelWidth;
698                     if ( labelPosition.x() > xmax ) labelPosition.setX( xmax );
699                     qreal ymin = 10.0 + labelAscent;
700                     if ( labelPosition.y() < ymin ) labelPosition.setY( ymin );
701                     qreal ymax = viewport().height() - 10.0 - labelAscent;
702                     if ( labelPosition.y() > ymax ) labelPosition.setY( ymax );
703 
704                     drawText( QRectF( labelPosition, fontMetrics().size( 0, labelText) ), labelText );
705                 }
706             }
707         }
708     }
709     setPen(oldPen);
710 }
711 
drawPolyline(const GeoDataLineString & lineString)712 void GeoPainter::drawPolyline(const GeoDataLineString& lineString)
713 {
714     QVector<QPolygonF*> polygons;
715     polygonsFromLineString(lineString, polygons);
716     if (polygons.empty()) return;
717 
718     for(const QPolygonF* itPolygon: polygons) {
719         ClipPainter::drawPolyline(*itPolygon);
720     }
721 
722     qDeleteAll(polygons);
723 }
724 
725 
regionFromPolyline(const GeoDataLineString & lineString,qreal strokeWidth) const726 QRegion GeoPainter::regionFromPolyline ( const GeoDataLineString & lineString,
727                                          qreal strokeWidth ) const
728 {
729     // Immediately leave this method now if:
730     // - the object is not visible in the viewport or if
731     // - the size of the object is below the resolution of the viewport
732     if ( ! d->m_viewport->viewLatLonAltBox().intersects( lineString.latLonAltBox() ) ||
733          ! d->m_viewport->resolves( lineString.latLonAltBox() )
734         )
735     {
736         // mDebug() << "LineString doesn't get displayed on the viewport";
737         return QRegion();
738     }
739 
740     QPainterPath painterPath;
741 
742     QVector<QPolygonF*> polygons;
743     d->m_viewport->screenCoordinates( lineString, polygons );
744 
745     for( QPolygonF* itPolygon: polygons ) {
746         painterPath.addPolygon( *itPolygon );
747     }
748 
749     qDeleteAll( polygons );
750 
751     QPainterPathStroker stroker;
752     stroker.setWidth( strokeWidth );
753     QPainterPath strokePath = stroker.createStroke( painterPath );
754 
755     return QRegion( strokePath.toFillPolygon().toPolygon(), Qt::WindingFill );
756 }
757 
758 
drawPolygon(const GeoDataLinearRing & linearRing,Qt::FillRule fillRule)759 void GeoPainter::drawPolygon ( const GeoDataLinearRing & linearRing,
760                                Qt::FillRule fillRule )
761 {
762     // Immediately leave this method now if:
763     // - the object is not visible in the viewport or if
764     // - the size of the object is below the resolution of the viewport
765     if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) ||
766          ! d->m_viewport->resolves( linearRing.latLonAltBox() )
767         )
768     {
769         // mDebug() << "Polygon doesn't get displayed on the viewport";
770         return;
771     }
772 
773     QVector<QPolygonF*> polygons;
774     d->m_viewport->screenCoordinates( linearRing, polygons );
775 
776     for( QPolygonF* itPolygon: polygons ) {
777         ClipPainter::drawPolygon( *itPolygon, fillRule );
778     }
779 
780     qDeleteAll( polygons );
781 }
782 
783 
regionFromPolygon(const GeoDataLinearRing & linearRing,Qt::FillRule fillRule,qreal strokeWidth) const784 QRegion GeoPainter::regionFromPolygon ( const GeoDataLinearRing & linearRing,
785                                         Qt::FillRule fillRule, qreal strokeWidth ) const
786 {
787     // Immediately leave this method now if:
788     // - the object is not visible in the viewport or if
789     // - the size of the object is below the resolution of the viewport
790     if ( ! d->m_viewport->viewLatLonAltBox().intersects( linearRing.latLonAltBox() ) ||
791          ! d->m_viewport->resolves( linearRing.latLonAltBox() )
792         )
793     {
794         return QRegion();
795     }
796 
797     QRegion regions;
798 
799     QVector<QPolygonF*> polygons;
800     d->m_viewport->screenCoordinates( linearRing, polygons );
801 
802     if ( strokeWidth == 0 ) {
803         // This is the faster way
804         for( QPolygonF* itPolygon: polygons ) {
805             regions += QRegion ( (*itPolygon).toPolygon(), fillRule );
806         }
807     }
808     else {
809         QPainterPath painterPath;
810         for( QPolygonF* itPolygon: polygons ) {
811             painterPath.addPolygon( *itPolygon );
812         }
813 
814         QPainterPathStroker stroker;
815         stroker.setWidth( strokeWidth );
816         QPainterPath strokePath = stroker.createStroke( painterPath );
817         painterPath = painterPath.united( strokePath );
818         regions = QRegion( painterPath.toFillPolygon().toPolygon() );
819     }
820 
821     qDeleteAll( polygons );
822 
823     return regions;
824 }
825 
826 
drawPolygon(const GeoDataPolygon & polygon,Qt::FillRule fillRule)827 void GeoPainter::drawPolygon ( const GeoDataPolygon & polygon,
828                                Qt::FillRule fillRule )
829 {
830     // If the object is not visible in the viewport return
831     if ( ! d->m_viewport->viewLatLonAltBox().intersects( polygon.outerBoundary().latLonAltBox() ) ||
832     // If the size of the object is below the resolution of the viewport then return
833          ! d->m_viewport->resolves( polygon.outerBoundary().latLonAltBox() )
834         )
835     {
836         // mDebug() << "Polygon doesn't get displayed on the viewport";
837         return;
838     }
839     // mDebug() << "Drawing Polygon";
840 
841     QVector<QPolygonF*> outerPolygons;
842     QVector<QPolygonF*> innerPolygons;
843     d->m_viewport->screenCoordinates( polygon.outerBoundary(), outerPolygons );
844 
845     QPen const currentPen = pen();
846 
847     bool const hasInnerBoundaries = !polygon.innerBoundaries().isEmpty();
848     bool innerBoundariesOnScreen = false;
849 
850     if ( hasInnerBoundaries ) {
851         QVector<GeoDataLinearRing> const & innerBoundaries = polygon.innerBoundaries();
852 
853         const GeoDataLatLonAltBox & viewLatLonAltBox = d->m_viewport->viewLatLonAltBox();
854         for( const GeoDataLinearRing& itInnerBoundary: innerBoundaries ) {
855             if ( viewLatLonAltBox.intersects(itInnerBoundary.latLonAltBox())
856                  && d->m_viewport->resolves(itInnerBoundary.latLonAltBox()), 4 )  {
857                 innerBoundariesOnScreen = true;
858                 break;
859             }
860         }
861 
862         if (innerBoundariesOnScreen) {
863             // Create the inner screen polygons
864             for( const GeoDataLinearRing& itInnerBoundary: innerBoundaries ) {
865                 QVector<QPolygonF*> innerPolygonsPerBoundary;
866 
867                 d->m_viewport->screenCoordinates( itInnerBoundary, innerPolygonsPerBoundary );
868 
869                 for( QPolygonF* innerPolygonPerBoundary: innerPolygonsPerBoundary ) {
870                     innerPolygons << innerPolygonPerBoundary;
871                 }
872             }
873 
874             setPen(Qt::NoPen);
875             QVector<QPolygonF*> fillPolygons = createFillPolygons( outerPolygons,
876                                                                    innerPolygons );
877 
878             for( const QPolygonF* fillPolygon: fillPolygons ) {
879                 ClipPainter::drawPolygon(*fillPolygon, fillRule);
880             }
881 
882             setPen(currentPen);
883 
884             for( const QPolygonF* outerPolygon: outerPolygons ) {
885                 ClipPainter::drawPolyline( *outerPolygon );
886             }
887             for( const QPolygonF* innerPolygon: innerPolygons ) {
888                 ClipPainter::drawPolyline( *innerPolygon );
889             }
890 
891             qDeleteAll(fillPolygons);
892         }
893     }
894 
895     if ( !hasInnerBoundaries || !innerBoundariesOnScreen ) {
896         drawPolygon( polygon.outerBoundary(), fillRule );
897     }
898 
899     qDeleteAll(outerPolygons);
900     qDeleteAll(innerPolygons);
901 }
902 
createFillPolygons(const QVector<QPolygonF * > & outerPolygons,const QVector<QPolygonF * > & innerPolygons) const903 QVector<QPolygonF*> GeoPainter::createFillPolygons( const QVector<QPolygonF*> & outerPolygons,
904                                                     const QVector<QPolygonF*> & innerPolygons ) const
905 {
906     QVector<QPolygonF*> fillPolygons;
907     fillPolygons.reserve(outerPolygons.size());
908 
909     for( const QPolygonF* outerPolygon: outerPolygons ) {
910         QPolygonF* fillPolygon = new QPolygonF;
911         *fillPolygon << *outerPolygon;
912         *fillPolygon << outerPolygon->first();
913 
914         for( const QPolygonF* innerPolygon: innerPolygons ) {
915             *fillPolygon << *innerPolygon;
916             *fillPolygon << innerPolygon->first();
917             *fillPolygon << outerPolygon->first();
918         }
919 
920         fillPolygons << fillPolygon;
921     }
922 
923     return fillPolygons;
924 }
925 
926 
drawRect(const GeoDataCoordinates & centerCoordinates,qreal width,qreal height,bool isGeoProjected)927 void GeoPainter::drawRect ( const GeoDataCoordinates & centerCoordinates,
928                             qreal width, qreal height,
929                             bool isGeoProjected )
930 {
931     if ( !isGeoProjected ) {
932         int pointRepeatNum;
933         qreal y;
934         bool globeHidesPoint;
935 
936         bool visible = d->m_viewport->screenCoordinates( centerCoordinates,
937                        d->m_x, y, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
938 
939         if ( visible ) {
940             // Draw all the x-repeat-instances of the point on the screen
941             const qreal posY = y - height / 2.0;
942             for( int it = 0; it < pointRepeatNum; ++it ) {
943                 const qreal posX = d->m_x[it] - width / 2.0;
944                 QPainter::drawRect(QRectF(posX, posY, width, height));
945             }
946         }
947     }
948     else {
949         drawPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ),
950                      Qt::OddEvenFill );
951     }
952 }
953 
954 
regionFromRect(const GeoDataCoordinates & centerCoordinates,qreal width,qreal height,bool isGeoProjected,qreal strokeWidth) const955 QRegion GeoPainter::regionFromRect ( const GeoDataCoordinates & centerCoordinates,
956                                      qreal width, qreal height,
957                                      bool isGeoProjected,
958                                      qreal strokeWidth ) const
959 {
960     if ( !isGeoProjected ) {
961         int pointRepeatNum;
962         qreal centerY;
963         bool globeHidesPoint;
964 
965         bool visible = d->m_viewport->screenCoordinates( centerCoordinates,
966                        d->m_x, centerY, pointRepeatNum, QSizeF( width, height ), globeHidesPoint );
967 
968         QRegion regions;
969 
970         if ( visible ) {
971             // only a hint, a backend could still ignore it, but we cannot know more
972             const bool antialiased = testRenderHint(QPainter::Antialiasing);
973 
974             const qreal halfStrokeWidth = strokeWidth/2.0;
975             const int topY = centerY - height/2.0;
976             const int startY = antialiased ? (qFloor(topY - halfStrokeWidth)) : (qFloor(topY+0.5 - halfStrokeWidth));
977             const int endY = antialiased ? (qCeil(topY + height + halfStrokeWidth)) : (qFloor(centerY+0.5 + height + halfStrokeWidth));
978             // Draw all the x-repeat-instances of the point on the screen
979             for( int it = 0; it < pointRepeatNum; ++it ) {
980                 const qreal leftX = d->m_x[it] - width/2.0;
981                 const int startX = antialiased ? (qFloor(leftX - halfStrokeWidth)) : (qFloor(leftX+0.5 - halfStrokeWidth));
982                 const int endX = antialiased ? (qCeil(leftX + width + halfStrokeWidth)) : (qFloor(leftX+0.5 + width +  halfStrokeWidth));
983                 regions += QRegion(startX, startY, endX - startX, endY - startY);
984             }
985         }
986         return regions;
987     }
988     else {
989         return regionFromPolygon( d->createLinearRingFromGeoRect( centerCoordinates, width, height ),
990                                   Qt::OddEvenFill, strokeWidth );
991     }
992 }
993 
994 
drawRoundedRect(const GeoDataCoordinates & centerPosition,qreal width,qreal height,qreal xRnd,qreal yRnd)995 void GeoPainter::drawRoundedRect(const GeoDataCoordinates &centerPosition,
996                                  qreal width, qreal height,
997                                  qreal xRnd, qreal yRnd)
998 {
999         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 
drawTextFragment(const QPoint & position,const QString & text,const qreal fontSize,const QColor & color,const Frames & flags)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.width(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 }
1072