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