GeoPolygonGraphicsItem.cpp 17.7 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2011      Konstantin Oblaukhov <oblaukhov.konstantin@gmail.com>
//

#include "GeoPolygonGraphicsItem.h"
12
13
14

#include "GeoDataLinearRing.h"
#include "GeoDataPolygon.h"
15
#include "GeoPainter.h"
16
17
#include "GeoDataTypes.h"
#include "GeoDataPlacemark.h"
18
19
#include "ViewportParams.h"
#include "GeoDataStyle.h"
20
#include "MarbleDirs.h"
21
#include "OsmPlacemarkData.h"
22

23
24
25
#include <QVector2D>
#include <QtCore/qmath.h>

26
27
28
namespace Marble
{

29
30
GeoPolygonGraphicsItem::GeoPolygonGraphicsItem( const GeoDataFeature *feature, const GeoDataPolygon* polygon )
        : GeoGraphicsItem( feature ),
31
          m_polygon( polygon ),
32
33
          m_ring( 0 ),
          m_buildingHeight(0.0)
34
35
36
{
}

37
38
GeoPolygonGraphicsItem::GeoPolygonGraphicsItem( const GeoDataFeature *feature, const GeoDataLinearRing* ring )
        : GeoGraphicsItem( feature ),
39
          m_polygon( 0 ),
40
41
          m_ring( ring ),
          m_buildingHeight(0.0)
42
{
43
44
}

45
46
47
48
const float GeoPolygonGraphicsItem::s_decorationZValue = -0.001;

void GeoPolygonGraphicsItem::createDecorations()
{
49
    if (isDecoration() || (!m_polygon && !m_ring )) {
50
51
52
53
54
55
        return;
    }

    GeoPolygonGraphicsItem *fake3D;

    switch ( feature()->visualCategory() ) {
56
    case GeoDataFeature::Building:
57
        //case GeoDataFeature::AccomodationCamping:
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
    case GeoDataFeature::AccomodationHostel:
    case GeoDataFeature::AccomodationHotel:
    case GeoDataFeature::AccomodationMotel:
    case GeoDataFeature::AccomodationYouthHostel:
    case GeoDataFeature::AmenityLibrary:
    case GeoDataFeature::EducationCollege:
    case GeoDataFeature::EducationSchool:
    case GeoDataFeature::EducationUniversity:
    case GeoDataFeature::FoodBar:
    case GeoDataFeature::FoodBiergarten:
    case GeoDataFeature::FoodCafe:
    case GeoDataFeature::FoodFastFood:
    case GeoDataFeature::FoodPub:
    case GeoDataFeature::FoodRestaurant:
    case GeoDataFeature::HealthDoctors:
    case GeoDataFeature::HealthHospital:
    case GeoDataFeature::HealthPharmacy:
    case GeoDataFeature::MoneyAtm:
    case GeoDataFeature::MoneyBank:
    case GeoDataFeature::ShopBeverages:
    case GeoDataFeature::ShopHifi:
    case GeoDataFeature::ShopSupermarket:
        //case GeoDataFeature::TouristAttraction:
        //case GeoDataFeature::TouristCastle:
    case GeoDataFeature::TouristCinema:
        //case GeoDataFeature::TouristMonument:
    case GeoDataFeature::TouristMuseum:
        //case GeoDataFeature::TouristRuin:
    case GeoDataFeature::TouristTheatre:
        //case GeoDataFeature::TouristThemePark:
        //case GeoDataFeature::TouristViewPoint:
        //case GeoDataFeature::TouristZoo:
    case GeoDataFeature::ReligionPlaceOfWorship:
    case GeoDataFeature::ReligionBahai:
    case GeoDataFeature::ReligionBuddhist:
    case GeoDataFeature::ReligionChristian:
    case GeoDataFeature::ReligionHindu:
    case GeoDataFeature::ReligionJain:
    case GeoDataFeature::ReligionJewish:
    case GeoDataFeature::ReligionShinto:
    case GeoDataFeature::ReligionSikh:
    {
100
        fake3D = m_polygon ? new GeoPolygonGraphicsItem( feature(), m_polygon ) : new GeoPolygonGraphicsItem( feature(), m_ring );
101
102
103
104
105
106
107
108
109
110
111
        fake3D->setZValue(this->zValue() + s_decorationZValue);
        double const height = extractBuildingHeight(8.0);
        m_buildingHeight = qBound(1.0, height, 1000.0);
        fake3D->m_buildingHeight = m_buildingHeight;
        Q_ASSERT(m_buildingHeight > 0.0);
    }
        break;

    default:
        fake3D = nullptr;
        break;
112
113
114
115
116
    }

    this->addDecoration(fake3D);
}

117
QPointF GeoPolygonGraphicsItem::buildingOffset(const QPointF &point, const ViewportParams *viewport, bool* isCameraAboveBuilding) const
118
119
{
    qreal const cameraFactor = 0.5 * tan(0.5 * 110 * DEG2RAD);
120
121
    Q_ASSERT(m_buildingHeight > 0.0);
    qreal const buildingFactor = m_buildingHeight / EARTH_RADIUS;
122
123

    qreal const cameraHeightPixel = viewport->width() * cameraFactor;
124
    qreal buildingHeightPixel = viewport->radius() * buildingFactor;
125
126
    qreal const cameraDistance = cameraHeightPixel-buildingHeightPixel;

127
    if (isCameraAboveBuilding) {
128
        *isCameraAboveBuilding = cameraDistance > 0;
129
    }
130

131
132
133
    qreal const cc = cameraDistance * cameraHeightPixel;
    qreal const cb = cameraDistance * buildingHeightPixel;

134
135
136
137
138
    // The following lines calculate the same result, but are potentially slower due
    // to using more trigonometric method calls
    // qreal const alpha1 = atan2(offsetX, cameraHeightPixel);
    // qreal const alpha2 = atan2(offsetX, cameraHeightPixel-buildingHeightPixel);
    // qreal const shiftX = 2 * (cameraHeightPixel-buildingHeightPixel) * sin(0.5*(alpha2-alpha1));
139

140
    qreal const offsetX = point.x() - viewport->width() / 2.0;
141
    qreal const offsetY = point.y() - viewport->height() / 2.0;
142
143
144

    qreal const shiftX = offsetX * cb / (cc + offsetX);
    qreal const shiftY = offsetY * cb / (cc + offsetY);
145
146
147
148

    return QPointF(shiftX, shiftY);
}

149
150
151
152
153
154
155
156
157
158
double GeoPolygonGraphicsItem::extractBuildingHeight(double defaultValue) const
{
    if (feature()->nodeType() == GeoDataTypes::GeoDataPlacemarkType) {
        GeoDataPlacemark const * placemark = static_cast<GeoDataPlacemark const *>(feature());
        if (placemark->osmData().containsTagKey("height")) {
            /** @todo Also parse non-SI units, see https://wiki.openstreetmap.org/wiki/Key:height#Height_of_buildings */
            QString const heightValue = placemark->osmData().tagValue("height").replace(" meters", QString()).replace(" m", QString());
            bool extracted = false;
            double height = heightValue.toDouble(&extracted);
            return extracted ? height : defaultValue;
159
160
161
162
163
        } else if (placemark->osmData().containsTagKey("building:levels")) {
            int const levels = placemark->osmData().tagValue("building:levels").toInt();
            int const skipLevels = placemark->osmData().tagValue("building:min_level").toInt();
            /** @todo Is 35 as an upper bound for the number of levels sane? */
            return 3.0 * qBound(1, 1+levels-skipLevels, 35);
164
165
166
167
168
169
        }
    }

    return defaultValue;
}

170
const GeoDataLatLonAltBox& GeoPolygonGraphicsItem::latLonAltBox() const
171
172
173
{
    if( m_polygon ) {
        return m_polygon->latLonAltBox();
174
    } else if ( m_ring ) {
175
        return m_ring->latLonAltBox();
176
    } else {
177
        return GeoGraphicsItem::latLonAltBox();
178
    }
179
180
}

181
void GeoPolygonGraphicsItem::paint( GeoPainter* painter, const ViewportParams* viewport )
182
183
184
{
    painter->save();

185
    bool const isBuildingFrame = isDecoration();
186
    bool const isBuildingRoof = !isDecoration() && !decorations().isEmpty();
187
188
189

    QPen currentPen = painter->pen();

190
191
    if ( !style() ) {
        painter->setPen( QPen() );
192
    }
193
    else {
194
        if ( !style()->polyStyle().outline() || isBuildingFrame ) {
195
196
197
198
            currentPen.setColor( Qt::transparent );
        }
        else {
            if ( currentPen.color() != style()->lineStyle().paintedColor() ||
199
                 currentPen.widthF() != style()->lineStyle().width() ) {
200
201
202
203
204
205
206
207
208
209
                currentPen.setColor( style()->lineStyle().paintedColor() );
                currentPen.setWidthF( style()->lineStyle().width() );
            }

            if ( currentPen.capStyle() != style()->lineStyle().capStyle() )
                currentPen.setCapStyle( style()->lineStyle().capStyle() );

            if ( currentPen.style() != style()->lineStyle().penStyle() )
                currentPen.setStyle( style()->lineStyle().penStyle() );
        }
210

211
212
        if ( painter->pen() != currentPen )
            painter->setPen( currentPen );
213

214
215
216
        if ( !style()->polyStyle().fill() ) {
            if ( painter->brush().color() != Qt::transparent )
                painter->setBrush( QColor( Qt::transparent ) );
217
        }
218
        else {
219
220
221
            if ( isBuildingFrame ) {
                painter->setBrush( style()->polyStyle().paintedColor().darker(150) );
            } else if ( painter->brush().color() != style()->polyStyle().paintedColor() ) {
222
223
                QImage textureImage = style()->polyStyle().textureImage();
                if( !textureImage.isNull()){
224
                    GeoDataCoordinates coords = latLonAltBox().center();
225
226
                    qreal x, y;
                    viewport->screenCoordinates(coords, x, y);
Torsten Rahn's avatar
Torsten Rahn committed
227
                    if (m_cachedTexturePath != style()->polyStyle().texturePath() || m_cachedTextureColor != style()->polyStyle().paintedColor() ) {
228
229
230
231
232
233
234
235
236
                        if (textureImage.hasAlphaChannel()) {
                            m_cachedTexture = QImage ( textureImage.size(), QImage::Format_ARGB32_Premultiplied );
                            m_cachedTexture.fill(style()->polyStyle().paintedColor());
                            QPainter imagePainter(&m_cachedTexture );
                            imagePainter.drawImage(0, 0, textureImage);
                        }
                        else {
                            m_cachedTexture = textureImage;
                        }
Torsten Rahn's avatar
Torsten Rahn committed
237
238
239
                        m_cachedTexturePath = style()->polyStyle().texturePath();
                        m_cachedTextureColor = style()->polyStyle().paintedColor();
                    }
240
                    QBrush brush(m_cachedTexture);
241
                    painter->setBrush(brush);
242
                    painter->setBrushOrigin(QPoint(x,y));
243
244
245
                } else {
                    painter->setBrush( style()->polyStyle().paintedColor() );
                }
246
            }
247
248
249
        }
    }

250
251
252
253
254
    if ( isBuildingFrame || isBuildingRoof ) {

        bool drawAccurate3D = false;
        bool isCameraAboveBuilding = false;

255
256
        QPointF offsetAtCorner = buildingOffset(QPointF(0, 0), viewport, &isCameraAboveBuilding);
        qreal maxOffset = qMax( qAbs( offsetAtCorner.x() ), qAbs( offsetAtCorner.y() ) );
257
        drawAccurate3D = painter->mapQuality() == HighQuality ? maxOffset > 5.0 : maxOffset > 8.0;
258

259
260
261
        // Since subtracting one fully contained polygon from another results in a single
        // polygon with a "connecting line" between the inner and outer part we need
        // to first paint the inner area with no pen and then the outlines with the correct pen.
262
        QVector<QPolygonF*> outlinePolygons;
263
        QVector<QPolygonF*> innerPolygons;
264
        bool const hasInnerBoundaries = m_polygon ? !m_polygon->innerBoundaries().isEmpty() : false;
265
        if (m_polygon) {
266
            if (hasInnerBoundaries) {
267
                screenPolygons(viewport, m_polygon, innerPolygons, outlinePolygons);
268
            }
269
            viewport->screenCoordinates(m_polygon->outerBoundary(), outlinePolygons);
270
        } else if (m_ring) {
271
            viewport->screenCoordinates(*m_ring, outlinePolygons);
272
        }
273
274

        if ( isBuildingFrame ) {
275
276
            foreach(QPolygonF* outlinePolygon, outlinePolygons) {
                if (outlinePolygon->isEmpty()) {
277
278
279
280
                    continue;
                }
                if ( drawAccurate3D && isCameraAboveBuilding ) {
                    // draw the building sides
281
282
                    int const size = outlinePolygon->size();
                    QPointF & a = (*outlinePolygon)[0];
283
284
                    QPointF shiftA = a + buildingOffset(a, viewport);
                    for (int i=1; i<size; ++i) {
285
                        QPointF const & b = (*outlinePolygon)[i];
286
287
                        QPointF const shiftB = b + buildingOffset(b, viewport);
                        QPolygonF buildingSide = QPolygonF() << a << shiftA << shiftB << b;
288
289
290
291
                        if (hasInnerBoundaries) {
                            //smoothen away our loss of antialiasing due to the QRegion Qt-bug workaround
                            painter->setPen(QPen(painter->brush().color(), 1.5));
                        }
292
293
294
295
296
297
                        painter->drawPolygon(buildingSide);
                        a = b;
                        shiftA = shiftB;
                    }
                } else {
                    // don't draw the building sides - just draw the base frame instead
298
                    if (hasInnerBoundaries) {
299
                        QRegion clip(outlinePolygon->toPolygon());
300
301
302
303
304
305

                        foreach(QPolygonF* clipPolygon, innerPolygons) {
                            clip-=QRegion(clipPolygon->toPolygon());
                        }
                        painter->setClipRegion(clip);
                    }
306
                    painter->drawPolygon(*outlinePolygon);
307
308
                }
            }
309
310
311
312
313
        } else if (isBuildingRoof) {
            if (!isCameraAboveBuilding) {
                painter->restore();
                return; // do not render roof if we look inside the building
            }
314

315
316
317
318
319
320
321
322
323
324
            bool const hasIcon = !style()->iconStyle().iconPath().isEmpty();
            qreal maxSize(0.0);
            QPointF roofCenter;

            if (hasInnerBoundaries) {
                painter->setPen(Qt::NoPen);
            }

            // first paint the area and icon (and the outline if there are no inner boundaries)

325
326
            foreach(QPolygonF* outlinePolygon, outlinePolygons) {
                QRectF const boundingRect = outlinePolygon->boundingRect();
327
328
329
330
331
332
333
334
335
336
                if (hasIcon) {
                    QSizeF const polygonSize = boundingRect.size();
                    qreal size = polygonSize.width() * polygonSize.height();
                    if (size > maxSize) {
                        maxSize = size;
                        roofCenter = boundingRect.center() + buildingOffset(boundingRect.center(), viewport);
                    }
                }
                if ( drawAccurate3D) {
                    QPolygonF buildingRoof;
337
                    foreach(const QPointF &point, *outlinePolygon) {
338
339
                        buildingRoof << point + buildingOffset(point, viewport);
                    }
340
341
342
343
344
345
346
347
348
349
350
351
                    if (hasInnerBoundaries) {
                        QRegion clip(buildingRoof.toPolygon());

                        foreach(QPolygonF* innerPolygon, innerPolygons) {
                            QPolygonF buildingInner;
                            foreach(const QPointF &point, *innerPolygon) {
                                buildingInner << point + buildingOffset(point, viewport);
                            }
                            clip-=QRegion(buildingInner.toPolygon());
                        }
                        painter->setClipRegion(clip);
                    }
352
353
354
355
                    painter->drawPolygon(buildingRoof);
                } else {
                    QPointF const offset = buildingOffset(boundingRect.center(), viewport);
                    painter->translate(offset);
356
                    if (hasInnerBoundaries) {
357
                        QRegion clip(outlinePolygon->toPolygon());
358
359
360
361
362
363

                        foreach(QPolygonF* clipPolygon, innerPolygons) {
                            clip-=QRegion(clipPolygon->toPolygon());
                        }
                        painter->setClipRegion(clip);
                    }
364
                    painter->drawPolygon(*outlinePolygon);
365
                    painter->translate(-offset);
366
367
368
369
370
                }
                if (hasIcon && !roofCenter.isNull()) {
                    QImage const icon = style()->iconStyle().scaledIcon();
                    QPointF const iconCenter(icon.size().width()/2.0, icon.size().height()/2.0);
                    painter->drawImage(roofCenter-iconCenter, icon);
371
372
                }
            }
373
374
375
376
377

            // then paint the outlines if there are inner boundaries

            if (hasInnerBoundaries) {
                painter->setPen(currentPen);
378
                foreach(QPolygonF * polygon, outlinePolygons) {
Torsten Rahn's avatar
Torsten Rahn committed
379
                    QRectF const boundingRect = polygon->boundingRect();
380
381
                    if ( drawAccurate3D) {
                        QPolygonF buildingRoof;
Torsten Rahn's avatar
Torsten Rahn committed
382
                        foreach(const QPointF &point, *polygon) {
383
384
385
386
387
388
                            buildingRoof << point + buildingOffset(point, viewport);
                        }
                        painter->drawPolyline(buildingRoof);
                    } else {
                        QPointF const offset = buildingOffset(boundingRect.center(), viewport);
                        painter->translate(offset);
Torsten Rahn's avatar
Torsten Rahn committed
389
                        painter->drawPolyline(*polygon);
390
                        painter->translate(-offset);
391
                    }
392
393
394
                }
            }
        }
395
        qDeleteAll(outlinePolygons);
396

397
    } else {
398
        if ( m_polygon ) {
399
            painter->drawPolygon( *m_polygon );
400
401
402
        } else if ( m_ring ) {
            painter->drawPolygon( *m_ring );
        }
403
404
405
406
407
408

        bool const hasIcon = !style()->iconStyle().iconPath().isEmpty();
        if (hasIcon) {
            QImage const icon = style()->iconStyle().scaledIcon();
            painter->drawImage(latLonAltBox().center(), icon);
        }
409
    }
410

411
412
413
    painter->restore();
}

414
void GeoPolygonGraphicsItem::screenPolygons(const ViewportParams *viewport, const GeoDataPolygon * polygon, QVector<QPolygonF*> & innerPolygons, QVector<QPolygonF*> & outlines) {
415

416
417
    Q_ASSERT(polygon);

418
419
420
    QVector<QPolygonF*> outerPolygons;
    viewport->screenCoordinates( polygon->outerBoundary(), outerPolygons );

421
    outlines << outerPolygons;
422
423
424
425
426

    bool const hasInnerBoundaries = !m_polygon->innerBoundaries().isEmpty();

    QVector<GeoDataLinearRing> innerBoundaries = polygon->innerBoundaries();
    foreach( const GeoDataLinearRing& itInnerBoundary, innerBoundaries ) {
427
428
        QVector<QPolygonF*> innerPolygonsPerBoundary;
        viewport->screenCoordinates( itInnerBoundary, innerPolygonsPerBoundary );
429
430

        if ( hasInnerBoundaries ) {
431
432
433
            outlines << innerPolygonsPerBoundary;
            foreach( QPolygonF* innerPolygonPerBoundary, innerPolygonsPerBoundary ) {
                innerPolygons << innerPolygonPerBoundary;
434
435
436
437
438
439
440
            }
        }

    }
}


441
}