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 }