File indexing completed on 2025-01-05 03:59:02

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2011 Konstantin Oblaukhov <oblaukhov.konstantin@gmail.com>
0004 //
0005 
0006 #include "BuildingGraphicsItem.h"
0007 
0008 #include <QScreen>
0009 #include <QApplication>
0010 
0011 #include "ViewportParams.h"
0012 #include "GeoDataPlacemark.h"
0013 #include "GeoDataLinearRing.h"
0014 #include "GeoDataPolygon.h"
0015 #include "GeoDataBuilding.h"
0016 #include "GeoDataMultiGeometry.h"
0017 #include "GeoDataPolyStyle.h"
0018 #include "OsmPlacemarkData.h"
0019 #include "GeoPainter.h"
0020 
0021 #include "digikam_debug.h"
0022 
0023 namespace Marble
0024 {
0025 
0026 BuildingGraphicsItem::BuildingGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataBuilding *building)
0027     : AbstractGeoPolygonGraphicsItem(placemark, building)
0028 {
0029     if (const auto ring = geodata_cast<GeoDataLinearRing>(&building->multiGeometry()->at(0))) {
0030         setLinearRing(ring);
0031     } else if (const auto poly = geodata_cast<GeoDataPolygon>(&building->multiGeometry()->at(0))) {
0032         setPolygon(poly);
0033     }
0034 
0035     setZValue(building->height());
0036     Q_ASSERT(building->height() > 0.0);
0037 
0038     QStringList paintLayers;
0039     paintLayers << QStringLiteral("Polygon/Building/frame")
0040                 << QStringLiteral("Polygon/Building/roof");
0041     setPaintLayers(paintLayers);
0042 }
0043 
0044 BuildingGraphicsItem::~BuildingGraphicsItem()
0045 {
0046     qDeleteAll(m_cachedOuterPolygons);
0047     qDeleteAll(m_cachedInnerPolygons);
0048     qDeleteAll(m_cachedOuterRoofPolygons);
0049     qDeleteAll(m_cachedInnerRoofPolygons);
0050 }
0051 
0052 void BuildingGraphicsItem::initializeBuildingPainting(const GeoPainter* painter, const ViewportParams *viewport,
0053                                                       bool &drawAccurate3D, bool &isCameraAboveBuilding ) const
0054 {
0055     drawAccurate3D = false;
0056     isCameraAboveBuilding = false;
0057 
0058     auto const screen = QApplication::screens().first();
0059     double const physicalSize = 1.0; // mm
0060     int const pixelSize = qRound(physicalSize * screen->physicalDotsPerInch() / (IN2M * M2MM));
0061 
0062     QPointF offsetAtCorner = buildingOffset(QPointF(0, 0), viewport, &isCameraAboveBuilding);
0063     qreal maxOffset = qMax( qAbs( offsetAtCorner.x() ), qAbs( offsetAtCorner.y() ) );
0064     drawAccurate3D = painter->mapQuality() == HighQuality ? maxOffset > pixelSize : maxOffset > 1.5 * pixelSize;
0065 }
0066 
0067 void BuildingGraphicsItem::updatePolygons(const ViewportParams &viewport,
0068                                           QVector<QPolygonF*>& outerPolygons,
0069                                           QVector<QPolygonF*>& innerPolygons,
0070                                           bool &hasInnerBoundaries) const
0071 {
0072     // Since subtracting one fully contained polygon from another results in a single
0073     // polygon with a "connecting line" between the inner and outer part we need
0074     // to first paint the inner area with no pen and then the outlines with the correct pen.
0075     hasInnerBoundaries = polygon() ? !polygon()->innerBoundaries().isEmpty() : false;
0076     if (polygon()) {
0077         if (hasInnerBoundaries) {
0078             screenPolygons(viewport, polygon(), innerPolygons, outerPolygons);
0079         }
0080         else {
0081             viewport.screenCoordinates(polygon()->outerBoundary(), outerPolygons);
0082         }
0083     } else if (ring()) {
0084         viewport.screenCoordinates(*ring(), outerPolygons);
0085     }
0086 }
0087 
0088 QPointF BuildingGraphicsItem::centroid(const QPolygonF &polygon, double &area)
0089 {
0090     auto centroid = QPointF(0.0, 0.0);
0091     area = 0.0;
0092     for (qsizetype i=0, n=polygon.size(); i<n; ++i) {
0093         auto const x0 = polygon[i].x();
0094         auto const y0 = polygon[i].y();
0095         auto const j = i == n-1 ? 0 : i+1;
0096         auto const x1 = polygon[j].x();
0097         auto const y1 = polygon[j].y();
0098         auto const a = x0*y1 - x1*y0;
0099         area += a;
0100         centroid.rx() += (x0 + x1)*a;
0101         centroid.ry() += (y0 + y1)*a;
0102     }
0103 
0104     area *= 0.5;
0105     return area != 0 ? centroid / (6.0*area) : polygon.boundingRect().center();
0106 }
0107 
0108 QPointF BuildingGraphicsItem::buildingOffset(const QPointF &point, const ViewportParams *viewport, bool* isCameraAboveBuilding) const
0109 {
0110     qreal const cameraFactor = 0.5 * tan(0.5 * 110 * DEG2RAD);
0111     Q_ASSERT(building()->height() > 0.0);
0112     qreal const buildingFactor = building()->height() / EARTH_RADIUS;
0113 
0114     qreal const cameraHeightPixel = viewport->width() * cameraFactor;
0115     qreal buildingHeightPixel = viewport->radius() * buildingFactor;
0116     qreal const cameraDistance = cameraHeightPixel-buildingHeightPixel;
0117 
0118     if (isCameraAboveBuilding) {
0119         *isCameraAboveBuilding = cameraDistance > 0;
0120     }
0121 
0122     qreal const cc = cameraDistance * cameraHeightPixel;
0123     qreal const cb = cameraDistance * buildingHeightPixel;
0124 
0125     // The following lines calculate the same result, but are potentially slower due
0126     // to using more trigonometric method calls
0127     // qreal const alpha1 = atan2(offsetX, cameraHeightPixel);
0128     // qreal const alpha2 = atan2(offsetX, cameraHeightPixel-buildingHeightPixel);
0129     // qreal const shiftX = 2 * (cameraHeightPixel-buildingHeightPixel) * sin(0.5*(alpha2-alpha1));
0130 
0131     qreal const offsetX = point.x() - viewport->width() / 2.0;
0132     qreal const offsetY = point.y() - viewport->height() / 2.0;
0133 
0134     qreal const shiftX = offsetX * cb / (cc + offsetX);
0135     qreal const shiftY = offsetY * cb / (cc + offsetY);
0136 
0137     return QPointF(shiftX, shiftY);
0138 }
0139 
0140 void BuildingGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel)
0141 {
0142     // Just display flat buildings for tile level 17
0143     if (tileZoomLevel == 17) {
0144         setZValue(0.0);
0145         if (layer.endsWith(QLatin1String("/roof"))) {
0146             AbstractGeoPolygonGraphicsItem::paint(painter, viewport, layer, tileZoomLevel );
0147         }
0148         return;
0149     }
0150     setZValue(building()->height());
0151 
0152     // For level 18, 19 .. render 3D buildings in perspective
0153     if (layer.endsWith(QLatin1String("/frame"))) {
0154         qDeleteAll(m_cachedOuterPolygons);
0155         qDeleteAll(m_cachedInnerPolygons);
0156         qDeleteAll(m_cachedOuterRoofPolygons);
0157         qDeleteAll(m_cachedInnerRoofPolygons);
0158         m_cachedOuterPolygons.clear();
0159         m_cachedInnerPolygons.clear();
0160         m_cachedOuterRoofPolygons.clear();
0161         m_cachedInnerRoofPolygons.clear();
0162         updatePolygons(*viewport, m_cachedOuterPolygons,
0163                                  m_cachedInnerPolygons,
0164                                  m_hasInnerBoundaries);
0165         if (m_cachedOuterPolygons.isEmpty()) {
0166             return;
0167         }
0168         paintFrame(painter, viewport);
0169     } else if (layer.endsWith(QLatin1String("/roof"))) {
0170         if (m_cachedOuterPolygons.isEmpty()) {
0171             return;
0172         }
0173         paintRoof(painter, viewport);
0174     } else {
0175         qCDebug(DIGIKAM_MARBLE_LOG) << "Didn't expect to have to paint layer " << layer << ", ignoring it.";
0176     }
0177 }
0178 
0179 void BuildingGraphicsItem::paintRoof(GeoPainter* painter, const ViewportParams* viewport)
0180 {
0181     bool drawAccurate3D;
0182     bool isCameraAboveBuilding;
0183     initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
0184     if (!isCameraAboveBuilding) {
0185         return; // do not render roof if we look inside the building
0186     }
0187 
0188     bool isValid = true;
0189     if (s_previousStyle != style().data()) {
0190         isValid = configurePainter(painter, *viewport);
0191 
0192         QFont font = painter->font(); // TODO: better font configuration
0193         if (font.pointSize() != 10) {
0194             font.setPointSize( 10 );
0195             painter->setFont(font);
0196         }
0197     }
0198     s_previousStyle = style().data();
0199 
0200     if (!isValid) return;
0201 
0202     // first paint the area (and the outline if there are no inner boundaries)
0203 
0204     if ( drawAccurate3D) {
0205         if (m_hasInnerBoundaries) {
0206 
0207             QPen const currentPen = painter->pen();
0208 
0209             painter->setPen(Qt::NoPen);
0210             QVector<QPolygonF*> fillPolygons =
0211                     painter->createFillPolygons( m_cachedOuterRoofPolygons,
0212                                                  m_cachedInnerRoofPolygons );
0213 
0214             for( const QPolygonF* fillPolygon: fillPolygons ) {
0215                 painter->drawPolygon(*fillPolygon);
0216             }
0217 
0218             painter->setPen(currentPen);
0219 
0220             for( const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) {
0221                 painter->drawPolyline( *outerRoof );
0222             }
0223             for( const QPolygonF* innerRoof: m_cachedInnerRoofPolygons ) {
0224                 painter->drawPolyline( *innerRoof );
0225             }
0226             qDeleteAll(fillPolygons);
0227         }
0228         else {
0229             for( const QPolygonF* outerRoof: m_cachedOuterRoofPolygons ) {
0230                 painter->drawPolygon( *outerRoof );
0231             }
0232         }
0233     }
0234     else {
0235         QPointF const offset = buildingOffset(m_cachedOuterPolygons[0]->boundingRect().center(), viewport);
0236         painter->translate(offset);
0237 
0238         if (m_hasInnerBoundaries) {
0239 
0240             QPen const currentPen = painter->pen();
0241 
0242             painter->setPen(Qt::NoPen);
0243             QVector<QPolygonF*> fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons,
0244                                                                    m_cachedInnerPolygons );
0245 
0246             for( const QPolygonF* fillPolygon: fillPolygons ) {
0247                 painter->drawPolygon(*fillPolygon);
0248             }
0249 
0250             painter->setPen(currentPen);
0251 
0252             for( const QPolygonF* outerPolygon:  m_cachedOuterPolygons ) {
0253                 painter->drawPolyline( *outerPolygon );
0254             }
0255             for( const QPolygonF* innerPolygon:  m_cachedInnerPolygons ) {
0256                 painter->drawPolyline( *innerPolygon );
0257             }
0258             qDeleteAll(fillPolygons);
0259         }
0260         else {
0261             for( const QPolygonF* outerPolygon:  m_cachedOuterPolygons ) {
0262                 painter->drawPolygon( *outerPolygon );
0263             }
0264         }
0265         painter->translate(-offset);
0266 
0267     }
0268 
0269 
0270     qreal maxSize(0.0);
0271     double maxArea = 0.0;
0272 
0273     for (int i = 0; i < m_cachedOuterRoofPolygons.size(); ++i) {
0274         const QPolygonF *outerRoof = m_cachedOuterRoofPolygons[i];
0275 
0276         QPointF roofCenter;
0277 
0278         // Label position calculation
0279         if (!building()->name().isEmpty() || !building()->entries().isEmpty()) {
0280             QSizeF const polygonSize = outerRoof->boundingRect().size();
0281             qreal size = polygonSize.width() * polygonSize.height();
0282             if (size > maxSize) {
0283                 maxSize = size;
0284                 double area;
0285                 roofCenter = centroid(*outerRoof, area);
0286                 maxArea = qMax(area, maxArea);
0287             }
0288         }
0289 
0290         // Draw the housenumber labels
0291         if (drawAccurate3D && !building()->name().isEmpty() && !roofCenter.isNull()) {
0292             double const w2 = 0.5 * painter->fontMetrics().horizontalAdvance(building()->name());
0293             double const ascent = painter->fontMetrics().ascent();
0294             double const descent = painter->fontMetrics().descent();
0295             double const a2 = 0.5 * painter->fontMetrics().ascent();
0296             QPointF const textPosition = roofCenter - QPointF(w2, -a2);
0297             if (outerRoof->containsPoint(textPosition + QPointF(-2, -ascent), Qt::OddEvenFill)
0298                     && outerRoof->containsPoint(textPosition + QPointF(-2, descent), Qt::OddEvenFill)
0299                     && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, descent), Qt::OddEvenFill)
0300                     && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, -ascent), Qt::OddEvenFill)
0301                     ) {
0302                 painter->drawTextFragment(roofCenter.toPoint(), building()->name(),
0303                                          painter->font().pointSize(), painter->brush().color());
0304             }
0305         }
0306     }
0307 
0308     // Render additional housenumbers at building entries
0309     if (!building()->entries().isEmpty() && maxArea > 1600 * building()->entries().size()) {
0310         for(const auto &entry: building()->entries()) {
0311             qreal x, y;
0312             viewport->screenCoordinates(entry.point, x, y);
0313             QPointF point(x, y);
0314             point += buildingOffset(point, viewport);
0315             painter->drawTextFragment(point.toPoint(),
0316                                      building()->name(), painter->font().pointSize(), painter->brush().color(),
0317                                      GeoPainter::RoundFrame);
0318         }
0319     }
0320 }
0321 
0322 void BuildingGraphicsItem::paintFrame(GeoPainter *painter, const ViewportParams *viewport)
0323 {
0324     // TODO: how does this match the Q_ASSERT in the constructor?
0325     if (building()->height() == 0.0) {
0326         return;
0327     }
0328 
0329     if ((polygon() && !viewport->resolves(polygon()->outerBoundary().latLonAltBox(), 4))
0330             || (ring() && !viewport->resolves(ring()->latLonAltBox(), 4))) {
0331         return;
0332     }
0333 
0334     bool drawAccurate3D;
0335     bool isCameraAboveBuilding;
0336     initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding);
0337 
0338     bool isValid = true;
0339     if (s_previousStyle != style().data()) {
0340         isValid = configurePainterForFrame(painter);
0341     }
0342     s_previousStyle = style().data();
0343 
0344     if (!isValid) return;
0345 
0346     if ( drawAccurate3D && isCameraAboveBuilding ) {
0347         for (const QPolygonF *outline: m_cachedOuterPolygons) {
0348             if (outline->isEmpty()) {
0349                 continue;
0350             }
0351             // draw the building sides
0352             int const size = outline->size();
0353             QPolygonF * outerRoof = new QPolygonF;
0354             outerRoof->reserve(outline->size());
0355             QPointF a = (*outline)[0];
0356             QPointF shiftA = a + buildingOffset(a, viewport);
0357             outerRoof->append(shiftA);
0358             for (int i=1; i<size; ++i) {
0359                 QPointF const & b = (*outline)[i];
0360                 QPointF const shiftB = b + buildingOffset(b, viewport);
0361                 // perform backface culling
0362                 bool backface = (b.x() - a.x()) * (shiftA.y() - a.y())
0363                         - (b.y() - a.y()) * (shiftA.x() - a.x()) >= 0;
0364                 if (!backface) {
0365                     QPolygonF buildingSide;
0366                     buildingSide.reserve(4);
0367                     buildingSide << a << shiftA << shiftB << b;
0368                     painter->drawPolygon(buildingSide);
0369                 }
0370                 a = b;
0371                 shiftA = shiftB;
0372                 outerRoof->append(shiftA);
0373             }
0374             m_cachedOuterRoofPolygons.append(outerRoof);
0375         }
0376         for (const QPolygonF *outline: m_cachedInnerPolygons) {
0377             if (outline->isEmpty()) {
0378                 continue;
0379             }
0380             // draw the building sides
0381             int const size = outline->size();
0382             QPolygonF * innerRoof = new QPolygonF;
0383             innerRoof->reserve(outline->size());
0384             QPointF a = (*outline)[0];
0385             QPointF shiftA = a + buildingOffset(a, viewport);
0386             innerRoof->append(shiftA);
0387             for (int i=1; i<size; ++i) {
0388                 QPointF const & b = (*outline)[i];
0389                 QPointF const shiftB = b + buildingOffset(b, viewport);
0390                 // perform backface culling
0391                 bool backface = (b.x() - a.x()) * (shiftA.y() - a.y())
0392                         - (b.y() - a.y()) * (shiftA.x() - a.x()) >= 0;
0393                 if (backface) {
0394                     QPolygonF buildingSide;
0395                     buildingSide.reserve(4);
0396                     buildingSide << a << shiftA << shiftB << b;
0397                     painter->drawPolygon(buildingSide);
0398                 }
0399                 a = b;
0400                 shiftA = shiftB;
0401                 innerRoof->append(shiftA);
0402             }
0403             m_cachedInnerRoofPolygons.append(innerRoof);
0404         }
0405     } else {
0406             // don't draw the building sides - just draw the base frame instead
0407             QVector<QPolygonF*> fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons,
0408                                                                    m_cachedInnerPolygons );
0409 
0410             for( QPolygonF* fillPolygon: fillPolygons ) {
0411                 painter->drawPolygon(*fillPolygon);
0412             }
0413             qDeleteAll(fillPolygons);
0414     }
0415 }
0416 
0417 void BuildingGraphicsItem::screenPolygons(const ViewportParams &viewport, const GeoDataPolygon *polygon,
0418                                                     QVector<QPolygonF *> &innerPolygons,
0419                                                     QVector<QPolygonF *> &outerPolygons
0420                                                     )
0421 {
0422     Q_ASSERT(polygon);
0423 
0424     viewport.screenCoordinates(polygon->outerBoundary(), outerPolygons);
0425 
0426     QVector<GeoDataLinearRing> const & innerBoundaries = polygon->innerBoundaries();
0427     for (const GeoDataLinearRing &innerBoundary: innerBoundaries) {
0428         QVector<QPolygonF*> innerPolygonsPerBoundary;
0429         viewport.screenCoordinates(innerBoundary, innerPolygonsPerBoundary);
0430 
0431         innerPolygons.reserve(innerPolygons.size() + innerPolygonsPerBoundary.size());
0432         for( QPolygonF* innerPolygonPerBoundary: innerPolygonsPerBoundary ) {
0433             innerPolygons << innerPolygonPerBoundary;
0434         }
0435     }
0436 }
0437 
0438 bool BuildingGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *viewport) const
0439 {
0440     if (m_cachedOuterPolygons.isEmpty()) {
0441         // Level 17
0442         return AbstractGeoPolygonGraphicsItem::contains(screenPosition, viewport);
0443     }
0444 
0445     QPointF const point = screenPosition;
0446     for (auto polygon: m_cachedOuterRoofPolygons) {
0447         if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0448             for (auto polygon: m_cachedInnerRoofPolygons) {
0449                 if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0450                     return false;
0451                 }
0452             }
0453             return true;
0454         }
0455     }
0456     for (auto polygon: m_cachedOuterPolygons) {
0457         if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0458             for (auto polygon: m_cachedInnerPolygons) {
0459                 if (polygon->containsPoint(point, Qt::OddEvenFill)) {
0460                     return false;
0461                 }
0462             }
0463             return true;
0464         }
0465     }
0466     return false;
0467 }
0468 
0469 bool BuildingGraphicsItem::configurePainterForFrame(GeoPainter *painter) const
0470 {
0471     QPen currentPen = painter->pen();
0472 
0473     GeoDataStyle::ConstPtr style = this->style();
0474     if (!style) {
0475         painter->setPen( QPen() );
0476     }
0477     else {
0478         const GeoDataPolyStyle& polyStyle = style->polyStyle();
0479 
0480         if (currentPen.style() != Qt::NoPen) {
0481             painter->setPen(Qt::NoPen);
0482         }
0483 
0484         if (!polyStyle.fill()) {
0485             return false;
0486         }
0487         else {
0488             const QColor paintedColor = polyStyle.paintedColor().darker(150);
0489             if (painter->brush().color() != paintedColor) {
0490                 painter->setBrush(paintedColor);
0491             }
0492         }
0493     }
0494 
0495     return true;
0496 }
0497 
0498 }