diff --git a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp index 858337b0c..b5b06b6df 100644 --- a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.cpp @@ -1,185 +1,194 @@ // // 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 // #include "AbstractGeoPolygonGraphicsItem.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoPainter.h" #include "GeoDataLatLonAltBox.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataLineStyle.h" #include "GeoDataPlacemark.h" #include "GeoDataPolyStyle.h" #include "GeoDataTypes.h" #include "OsmPlacemarkData.h" #include "MarbleDebug.h" #include "ViewportParams.h" #include #include namespace Marble { -QPixmapCache AbstractGeoPolygonGraphicsItem::m_textureCache = QPixmapCache(); +QPixmapCache AbstractGeoPolygonGraphicsItem::s_textureCache = QPixmapCache(); + +quint64 AbstractGeoPolygonGraphicsItem::s_previousStyle = -1; AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon) : GeoGraphicsItem(placemark), m_polygon(polygon), m_ring(0) { } AbstractGeoPolygonGraphicsItem::AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing *ring) : GeoGraphicsItem(placemark), m_polygon(0), m_ring(ring) { } AbstractGeoPolygonGraphicsItem::~AbstractGeoPolygonGraphicsItem() { } const GeoDataLatLonAltBox& AbstractGeoPolygonGraphicsItem::latLonAltBox() const { if( m_polygon ) { return m_polygon->latLonAltBox(); } return m_ring->latLonAltBox(); } void AbstractGeoPolygonGraphicsItem::paint( GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel) { Q_UNUSED(layer); Q_UNUSED(tileZoomLevel); - configurePainter(painter, viewport); + bool isValid = true; + if (s_previousStyle != reinterpret_cast(style().data())) { + isValid = configurePainter(painter, viewport); + } + s_previousStyle = reinterpret_cast(style().data()); + + if (!isValid) return; + if ( m_polygon ) { bool innerResolved = false; for(auto const & ring : m_polygon->innerBoundaries()) { if (viewport->resolves(ring.latLonAltBox(), 4)) { innerResolved = true; break; } } if (innerResolved) { painter->drawPolygon(*m_polygon); } else { painter->drawPolygon(m_polygon->outerBoundary()); } } else if ( m_ring ) { painter->drawPolygon( *m_ring ); } } bool AbstractGeoPolygonGraphicsItem::configurePainter(GeoPainter *painter, const ViewportParams *viewport) { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); // "style-less" polygons: a 1px black solid line } else { const GeoDataPolyStyle& polyStyle = style->polyStyle(); if (polyStyle.outline()) { const GeoDataLineStyle& lineStyle = style->lineStyle(); // To save performance we avoid making changes to the painter's pen. // So we first take a copy of the actual painter pen, make changes to it // and only if the resulting pen is different from the actual pen // we replace the painter's pen with our new pen. // We want to avoid the mandatory detach in QPen::setColor(), // so we carefully check whether applying the setter is needed currentPen.setColor(lineStyle.paintedColor()); currentPen.setWidthF(lineStyle.width()); currentPen.setCapStyle(lineStyle.capStyle()); currentPen.setStyle(lineStyle.penStyle()); if (painter->pen().color() != currentPen.color()) { painter->setPen(currentPen); } } else { // polygons without outline: Qt::NoPen (not drawn) if (currentPen.style() != Qt::NoPen) { painter->setPen(Qt::NoPen); } } if (!polyStyle.fill()) { painter->setBrush(Qt::transparent); } else { const QColor paintedColor = polyStyle.paintedColor(); if (painter->brush().color() != paintedColor || painter->brush().style() != polyStyle.brushStyle()) { if (!polyStyle.texturePath().isEmpty() || !polyStyle.textureImage().isNull()) { GeoDataCoordinates coords = latLonAltBox().center(); qreal x, y; viewport->screenCoordinates(coords, x, y); QBrush brush(texture(polyStyle.texturePath(), paintedColor)); painter->setBrush(brush); painter->setBrushOrigin(QPoint(x,y)); } else { painter->setBrush(QBrush(paintedColor, polyStyle.brushStyle())); } } } } return true; } int AbstractGeoPolygonGraphicsItem::extractElevation(const GeoDataPlacemark &placemark) { int elevation = 0; const OsmPlacemarkData &osmData = placemark.osmData(); const auto tagIter = osmData.findTag(QStringLiteral("ele")); if (tagIter != osmData.tagsEnd()) { elevation = tagIter.value().toInt(); } return elevation; } QPixmap AbstractGeoPolygonGraphicsItem::texture(const QString &texturePath, const QColor &color) { QString const key = QString::number(color.rgba()) + '/' + texturePath; QPixmap texture; - if (!m_textureCache.find(key, texture)) { + if (!s_textureCache.find(key, texture)) { QImageReader imageReader(style()->polyStyle().resolvePath(texturePath)); texture = QPixmap::fromImageReader(&imageReader); if (texture.hasAlphaChannel()) { QPixmap pixmap (texture.size()); pixmap.fill(color); QPainter imagePainter(&pixmap); imagePainter.drawPixmap(0, 0, texture); imagePainter.end(); texture = pixmap; } - m_textureCache.insert(key, texture); + s_textureCache.insert(key, texture); } return texture; } } diff --git a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h index e344fd78d..13091734e 100644 --- a/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h +++ b/src/lib/marble/geodata/graphicsitem/AbstractGeoPolygonGraphicsItem.h @@ -1,58 +1,60 @@ // // 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 // #ifndef MARBLE_ABSTRACTGEOPOLYGONGRAPHICSITEM_H #define MARBLE_ABSTRACTGEOPOLYGONGRAPHICSITEM_H #include "GeoGraphicsItem.h" #include "marble_export.h" #include #include #include namespace Marble { class GeoDataLinearRing; class GeoDataPlacemark; class GeoDataPolygon; class MARBLE_EXPORT AbstractGeoPolygonGraphicsItem : public GeoGraphicsItem { protected: explicit AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon); explicit AbstractGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing *ring); ~AbstractGeoPolygonGraphicsItem(); public: virtual const GeoDataLatLonAltBox& latLonAltBox() const; virtual void paint(GeoPainter* painter, const ViewportParams *viewport, const QString &layer, int tileZoomLevel); + static quint64 s_previousStyle; + protected: bool configurePainter(GeoPainter* painter, const ViewportParams *viewport); inline const GeoDataPolygon *polygon() const { return m_polygon; } inline const GeoDataLinearRing *ring() const { return m_ring; } static int extractElevation(const GeoDataPlacemark &placemark); private: QPixmap texture(const QString &path, const QColor &color); const GeoDataPolygon *const m_polygon; const GeoDataLinearRing *const m_ring; - static QPixmapCache m_textureCache; + static QPixmapCache s_textureCache; }; } #endif diff --git a/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp index 5f0fbd328..55d21058e 100644 --- a/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/BuildingGeoPolygonGraphicsItem.cpp @@ -1,526 +1,536 @@ // // 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 // #include "BuildingGeoPolygonGraphicsItem.h" #include "MarbleDebug.h" #include "ViewportParams.h" #include "GeoDataTypes.h" #include "GeoDataPlacemark.h" #include "GeoDataLinearRing.h" #include "GeoDataPolygon.h" #include "GeoDataPolyStyle.h" #include "OsmPlacemarkData.h" #include "GeoPainter.h" #include #include namespace Marble { BuildingGeoPolygonGraphicsItem::BuildingGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataPolygon *polygon) : AbstractGeoPolygonGraphicsItem(placemark, polygon) , m_buildingHeight(extractBuildingHeight(*placemark)) , m_buildingText(extractBuildingLabel(*placemark)) , m_entries(extractNamedEntries(*placemark)) , m_hasInnerBoundaries(false) { setZValue(m_buildingHeight); Q_ASSERT(m_buildingHeight > 0.0); QStringList paintLayers; paintLayers << QStringLiteral("Polygon/Building/frame") << QStringLiteral("Polygon/Building/roof"); setPaintLayers(paintLayers); } BuildingGeoPolygonGraphicsItem::BuildingGeoPolygonGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLinearRing* ring) : AbstractGeoPolygonGraphicsItem(placemark, ring) , m_buildingHeight(extractBuildingHeight(*placemark)) , m_buildingText(extractBuildingLabel(*placemark)) , m_entries(extractNamedEntries(*placemark)) { setZValue(m_buildingHeight); Q_ASSERT(m_buildingHeight > 0.0); QStringList paintLayers; paintLayers << QStringLiteral("Polygon/Building/frame") << QStringLiteral("Polygon/Building/roof"); setPaintLayers(paintLayers); } void BuildingGeoPolygonGraphicsItem::initializeBuildingPainting(const GeoPainter* painter, const ViewportParams *viewport, bool &drawAccurate3D, bool &isCameraAboveBuilding ) const { drawAccurate3D = false; isCameraAboveBuilding = false; auto const screen = QApplication::screens().first(); double const physicalSize = 1.0; // mm int const pixelSize = qRound(physicalSize * screen->physicalDotsPerInch() / (IN2M * M2MM)); QPointF offsetAtCorner = buildingOffset(QPointF(0, 0), viewport, &isCameraAboveBuilding); qreal maxOffset = qMax( qAbs( offsetAtCorner.x() ), qAbs( offsetAtCorner.y() ) ); drawAccurate3D = painter->mapQuality() == HighQuality ? maxOffset > pixelSize : maxOffset > 1.5 * pixelSize; } void BuildingGeoPolygonGraphicsItem::updatePolygons( const ViewportParams *viewport, QVector& outerPolygons, QVector& innerPolygons, bool &hasInnerBoundaries ) { // 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. hasInnerBoundaries = polygon() ? !polygon()->innerBoundaries().isEmpty() : false; if (polygon()) { if (hasInnerBoundaries) { screenPolygons(viewport, polygon(), innerPolygons, outerPolygons); } else { viewport->screenCoordinates(polygon()->outerBoundary(), outerPolygons); } } else if (ring()) { viewport->screenCoordinates(*ring(), outerPolygons); } } QPointF BuildingGeoPolygonGraphicsItem::centroid(const QPolygonF &polygon, double &area) { auto centroid = QPointF(0.0, 0.0); area = 0.0; for (auto i=0, n=polygon.size(); i 0.0); qreal const buildingFactor = m_buildingHeight / EARTH_RADIUS; qreal const cameraHeightPixel = viewport->width() * cameraFactor; qreal buildingHeightPixel = viewport->radius() * buildingFactor; qreal const cameraDistance = cameraHeightPixel-buildingHeightPixel; if (isCameraAboveBuilding) { *isCameraAboveBuilding = cameraDistance > 0; } qreal const cc = cameraDistance * cameraHeightPixel; qreal const cb = cameraDistance * buildingHeightPixel; // 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)); qreal const offsetX = point.x() - viewport->width() / 2.0; qreal const offsetY = point.y() - viewport->height() / 2.0; qreal const shiftX = offsetX * cb / (cc + offsetX); qreal const shiftY = offsetY * cb / (cc + offsetY); return QPointF(shiftX, shiftY); } double BuildingGeoPolygonGraphicsItem::extractBuildingHeight(const GeoDataPlacemark &placemark) { double height = 8.0; const OsmPlacemarkData &osmData = placemark.osmData(); QHash::const_iterator tagIter; if ((tagIter = osmData.findTag(QStringLiteral("height"))) != osmData.tagsEnd()) { /** @todo Also parse non-SI units, see https://wiki.openstreetmap.org/wiki/Key:height#Height_of_buildings */ QString const heightValue = QString(tagIter.value()).remove(QStringLiteral(" meters")).remove(QStringLiteral(" m")); bool extracted = false; double extractedHeight = heightValue.toDouble(&extracted); if (extracted) { height = extractedHeight; } } else if ((tagIter = osmData.findTag(QStringLiteral("building:levels"))) != osmData.tagsEnd()) { int const levels = tagIter.value().toInt(); int const skipLevels = osmData.tagValue(QStringLiteral("building:min_level")).toInt(); /** @todo Is 35 as an upper bound for the number of levels sane? */ height = 3.0 * qBound(1, 1+levels-skipLevels, 35); } return qBound(1.0, height, 1000.0); } QString BuildingGeoPolygonGraphicsItem::extractBuildingLabel(const GeoDataPlacemark &placemark) { const OsmPlacemarkData &osmData = placemark.osmData(); auto tagIter = osmData.findTag(QStringLiteral("addr:housename")); if (tagIter != osmData.tagsEnd()) { return tagIter.value(); } tagIter = osmData.findTag(QStringLiteral("addr:housenumber")); if (tagIter != osmData.tagsEnd()) { return tagIter.value(); } return QString(); } QVector BuildingGeoPolygonGraphicsItem::extractNamedEntries(const GeoDataPlacemark &placemark) { QVector entries; const auto end = placemark.osmData().nodeReferencesEnd(); for (auto iter = placemark.osmData().nodeReferencesBegin(); iter != end; ++iter) { const auto tagIter = iter.value().findTag(QStringLiteral("addr:housenumber")); if (tagIter != iter.value().tagsEnd()) { NamedEntry entry; entry.point = iter.key(); entry.label = tagIter.value(); entries.push_back(entry); } } return entries; } void BuildingGeoPolygonGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport, const QString &layer, int tileZoomLevel) { // Just display flat buildings for tile level 17 if (tileZoomLevel == 17) { setZValue(0.0); if (layer.endsWith(QLatin1String("/frame"))) { AbstractGeoPolygonGraphicsItem::paint(painter, viewport, layer, tileZoomLevel ); } return; } setZValue(m_buildingHeight); // For level 18, 19 .. render 3D buildings in perspective if (layer.endsWith(QLatin1String("/frame"))) { Q_ASSERT(m_cachedOuterPolygons.isEmpty()); Q_ASSERT(m_cachedInnerPolygons.isEmpty()); Q_ASSERT(m_cachedOuterRoofPolygons.isEmpty()); Q_ASSERT(m_cachedInnerRoofPolygons.isEmpty()); updatePolygons(viewport, m_cachedOuterPolygons, m_cachedInnerPolygons, m_hasInnerBoundaries); if (m_cachedOuterPolygons.isEmpty()) { return; } paintFrame(painter, viewport); } else if (layer.endsWith(QLatin1String("/roof"))) { if (m_cachedOuterPolygons.isEmpty()) { return; } paintRoof(painter, viewport); qDeleteAll(m_cachedOuterPolygons); qDeleteAll(m_cachedOuterRoofPolygons); qDeleteAll(m_cachedInnerRoofPolygons); m_cachedOuterPolygons.clear(); m_cachedInnerPolygons.clear(); m_cachedOuterRoofPolygons.clear(); m_cachedInnerRoofPolygons.clear(); } else { mDebug() << "Didn't expect to have to paint layer " << layer << ", ignoring it."; } } void BuildingGeoPolygonGraphicsItem::paintRoof(GeoPainter* painter, const ViewportParams* viewport) { bool drawAccurate3D; bool isCameraAboveBuilding; initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding); if (!isCameraAboveBuilding) { return; // do not render roof if we look inside the building } - bool isValid = configurePainter(painter, viewport); + bool isValid = true; + if (s_previousStyle != reinterpret_cast(style().data())) { + isValid = configurePainter(painter, viewport); + } + s_previousStyle = reinterpret_cast(style().data()); + if (!isValid) return; qreal maxSize(0.0); QPointF roofCenter; // first paint the area (and the outline if there are no inner boundaries) double maxArea = 0.0; if ( drawAccurate3D) { if (m_hasInnerBoundaries) { QPen const currentPen = painter->pen(); painter->setPen(Qt::NoPen); QVector fillPolygons = painter->createFillPolygons( m_cachedOuterRoofPolygons, m_cachedInnerRoofPolygons ); foreach( const QPolygonF* fillPolygon, fillPolygons ) { painter->drawPolygon(*fillPolygon); } painter->setPen(currentPen); foreach( const QPolygonF* outerRoof, m_cachedOuterRoofPolygons ) { painter->drawPolyline( *outerRoof ); } foreach( const QPolygonF* innerRoof, m_cachedInnerRoofPolygons ) { painter->drawPolyline( *innerRoof ); } } else { foreach( const QPolygonF* outerRoof, m_cachedOuterRoofPolygons ) { painter->drawPolygon( *outerRoof ); } } } else { QPointF const offset = buildingOffset(m_cachedOuterPolygons[0]->boundingRect().center(), viewport); painter->translate(offset); if (m_hasInnerBoundaries) { QPen const currentPen = painter->pen(); painter->setPen(Qt::NoPen); QVector fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons, m_cachedInnerPolygons ); foreach( const QPolygonF* fillPolygon, fillPolygons ) { painter->drawPolygon(*fillPolygon); } painter->setPen(currentPen); foreach( const QPolygonF* outerPolygon, m_cachedOuterPolygons ) { painter->drawPolyline( *outerPolygon ); } foreach( const QPolygonF* innerPolygon, m_cachedInnerPolygons ) { painter->drawPolyline( *innerPolygon ); } } else { foreach( const QPolygonF* outerPolygon, m_cachedOuterPolygons ) { painter->drawPolygon( *outerPolygon ); } } painter->translate(-offset); } for (int i = 0; i < m_cachedOuterPolygons.size(); ++i) { QPolygonF* outerPolygon = m_cachedOuterPolygons[i]; QRectF const boundingRect = outerPolygon->boundingRect(); // Label position calculation if (!m_buildingText.isEmpty() || !m_entries.isEmpty()) { QSizeF const polygonSize = boundingRect.size(); qreal size = polygonSize.width() * polygonSize.height(); if (size > maxSize) { maxSize = size; double area; roofCenter = centroid(*outerPolygon, area); maxArea = qMax(area, maxArea); roofCenter += buildingOffset(roofCenter, viewport); } } // Draw the housenumber labels if (drawAccurate3D && !m_buildingText.isEmpty() && !roofCenter.isNull() && !m_cachedOuterRoofPolygons.isEmpty()) { QPolygonF * outerRoof = m_cachedOuterRoofPolygons[i]; double const w2 = 0.5 * painter->fontMetrics().width(m_buildingText); double const ascent = painter->fontMetrics().ascent(); double const descent = painter->fontMetrics().descent(); double const a2 = 0.5 * painter->fontMetrics().ascent(); QPointF const textPosition = roofCenter - QPointF(w2, -a2); if (outerRoof->containsPoint(textPosition + QPointF(-2, -ascent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(-2, descent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, descent), Qt::OddEvenFill) && outerRoof->containsPoint(textPosition + QPointF(2+2*w2, -ascent), Qt::OddEvenFill) ) { painter->addTextFragment((textPosition + QPointF(0, -2-ascent)).toPoint(), m_buildingText, painter->brush().color()); } } ++i; } // Render additional housenumbers at building entries if (!m_entries.isEmpty() && maxArea > 1600 * m_entries.size()) { foreach(const auto &entry, m_entries) { qreal x, y; viewport->screenCoordinates(entry.point, x, y); QPointF point(x, y); point += buildingOffset(point, viewport); painter->addTextFragment(point.toPoint(), m_buildingText, painter->brush().color(), QFlags() |= BatchedPlacemarkRenderer::RoundFrame); } } } void BuildingGeoPolygonGraphicsItem::paintFrame(GeoPainter *painter, const ViewportParams *viewport) { // TODO: how does this match the Q_ASSERT in the constructor? if (m_buildingHeight == 0.0) { return; } if ((polygon() && !viewport->resolves(polygon()->outerBoundary().latLonAltBox(), 4)) || (ring() && !viewport->resolves(ring()->latLonAltBox(), 4))) { return; } bool drawAccurate3D; bool isCameraAboveBuilding; initializeBuildingPainting(painter, viewport, drawAccurate3D, isCameraAboveBuilding); - bool isValid = configurePainterForFrame(painter); + bool isValid = true; + if (s_previousStyle != reinterpret_cast(style().data())) { + isValid = configurePainterForFrame(painter); + } + s_previousStyle = reinterpret_cast(style().data()); + if (!isValid) return; if ( drawAccurate3D && isCameraAboveBuilding ) { foreach(QPolygonF* outline, m_cachedOuterPolygons) { if (outline->isEmpty()) { continue; } // draw the building sides int const size = outline->size(); QPolygonF * outerRoof = new QPolygonF; outerRoof->reserve(outline->size()); QPointF & a = (*outline)[0]; QPointF shiftA = a + buildingOffset(a, viewport); outerRoof->append(shiftA); for (int i=1; i= 0; if (!backface) { QPolygonF buildingSide; buildingSide.reserve(4); buildingSide << a << shiftA << shiftB << b; painter->drawPolygon(buildingSide); } a = b; shiftA = shiftB; outerRoof->append(shiftA); } m_cachedOuterRoofPolygons.append(outerRoof); } foreach(QPolygonF* outline, m_cachedInnerPolygons) { if (outline->isEmpty()) { continue; } // draw the building sides int const size = outline->size(); QPolygonF * innerRoof = new QPolygonF; innerRoof->reserve(outline->size()); QPointF & a = (*outline)[0]; QPointF shiftA = a + buildingOffset(a, viewport); innerRoof->append(shiftA); for (int i=1; i= 0; if (backface) { QPolygonF buildingSide; buildingSide.reserve(4); buildingSide << a << shiftA << shiftB << b; painter->drawPolygon(buildingSide); } a = b; shiftA = shiftB; innerRoof->append(shiftA); } m_cachedInnerRoofPolygons.append(innerRoof); } } else { // don't draw the building sides - just draw the base frame instead QVector fillPolygons = painter->createFillPolygons( m_cachedOuterPolygons, m_cachedInnerPolygons ); foreach( QPolygonF* fillPolygon, fillPolygons ) { painter->drawPolygon(*fillPolygon); } } } void BuildingGeoPolygonGraphicsItem::screenPolygons(const ViewportParams *viewport, const GeoDataPolygon *polygon, QVector &innerPolygons, QVector &outerPolygons ) { Q_ASSERT(polygon); viewport->screenCoordinates( polygon->outerBoundary(), outerPolygons ); QVector const & innerBoundaries = polygon->innerBoundaries(); foreach (const GeoDataLinearRing &innerBoundary, innerBoundaries) { QVector innerPolygonsPerBoundary; viewport->screenCoordinates(innerBoundary, innerPolygonsPerBoundary); innerPolygons.reserve(innerPolygons.size() + innerPolygonsPerBoundary.size()); foreach( QPolygonF* innerPolygonPerBoundary, innerPolygonsPerBoundary ) { innerPolygons << innerPolygonPerBoundary; } } } bool BuildingGeoPolygonGraphicsItem::configurePainterForFrame(GeoPainter *painter) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); } else { const GeoDataPolyStyle& polyStyle = style->polyStyle(); if (currentPen.style() != Qt::NoPen) { painter->setPen(Qt::NoPen); } if (!polyStyle.fill()) { return false; } else { const QColor paintedColor = polyStyle.paintedColor().darker(150); if (painter->brush().color() != paintedColor) { painter->setBrush(paintedColor); } } } return true; } } diff --git a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp index 31a605af1..0e1215a56 100644 --- a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp +++ b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.cpp @@ -1,402 +1,412 @@ // // 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 2009 Andrew Manson // #include "GeoLineStringGraphicsItem.h" #include "GeoDataLineString.h" #include "GeoDataLineStyle.h" #include "GeoDataLabelStyle.h" #include "GeoDataPlacemark.h" #include "GeoDataPolyStyle.h" #include "GeoPainter.h" #include "StyleBuilder.h" #include "ViewportParams.h" #include "GeoDataStyle.h" #include "MarbleDebug.h" #include "MarbleMath.h" #include namespace Marble { +quint64 GeoLineStringGraphicsItem::s_previousStyle = 0; + GeoLineStringGraphicsItem::GeoLineStringGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLineString *lineString) : GeoGraphicsItem(placemark), m_lineString(lineString), m_renderLineString(lineString), m_renderLabel(false) { QString const category = StyleBuilder::visualCategoryName(placemark->visualCategory()); QStringList paintLayers; paintLayers << QLatin1String("LineString/") + category + QLatin1String("/outline"); paintLayers << QLatin1String("LineString/") + category + QLatin1String("/inline"); if (!feature()->name().isEmpty()) { paintLayers << QLatin1String("LineString/") + category + QLatin1String("/label"); } setPaintLayers(paintLayers); } void GeoLineStringGraphicsItem::setLineString( const GeoDataLineString* lineString ) { m_lineString = lineString; m_renderLineString = lineString; } const GeoDataLineString *GeoLineStringGraphicsItem::lineString() const { return m_lineString; } GeoDataLineString GeoLineStringGraphicsItem::merge(const QVector &lineStrings_) { if (lineStrings_.isEmpty()) { return GeoDataLineString(); } Q_ASSERT(!lineStrings_.isEmpty()); auto lineStrings = lineStrings_; GeoDataLineString result = *lineStrings.first(); lineStrings.pop_front(); for (bool matched = true; matched && !lineStrings.isEmpty();) { matched = false; for (auto lineString: lineStrings) { if (canMerge(result.first(), lineString->first())) { result.remove(0); result.reverse(); result << *lineString; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.last(), lineString->first())) { result.remove(result.size()-1); result << *lineString; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.first(), lineString->last())) { GeoDataLineString behind = result; result = *lineString; behind.remove(0); result << behind; lineStrings.removeOne(lineString); matched = true; break; } else if (canMerge(result.last(), lineString->last())) { GeoDataLineString behind = *lineString; behind.reverse(); behind.remove(0); result << behind; lineStrings.removeOne(lineString); matched = true; break; } } if (!matched) { return GeoDataLineString(); } } return lineStrings.isEmpty() ? result : GeoDataLineString(); } void GeoLineStringGraphicsItem::setMergedLineString(const GeoDataLineString &mergedLineString) { m_mergedLineString = mergedLineString; m_renderLineString = mergedLineString.isEmpty() ? m_lineString : &m_mergedLineString; } const GeoDataLatLonAltBox& GeoLineStringGraphicsItem::latLonAltBox() const { return m_renderLineString->latLonAltBox(); } void GeoLineStringGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport , const QString &layer, int tileLevel) { setRenderContext(RenderContext(tileLevel)); if (layer.endsWith(QLatin1String("/outline"))) { qDeleteAll(m_cachedPolygons); m_cachedPolygons.clear(); painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons); if (m_cachedPolygons.empty()) { return; } if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) { paintOutline(painter, viewport); } } else if (layer.endsWith(QLatin1String("/inline"))) { if (m_cachedPolygons.empty()) { return; } paintInline(painter, viewport); } else if (layer.endsWith(QLatin1String("/label"))) { if (!m_cachedPolygons.empty()) { if (m_renderLabel) { paintLabel(painter, viewport); } } qDeleteAll(m_cachedPolygons); m_cachedPolygons.clear(); } else { qDeleteAll(m_cachedPolygons); m_cachedPolygons.clear(); painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons); if (m_cachedPolygons.empty()) { return; } foreach(const QPolygonF* itPolygon, m_cachedPolygons) { painter->drawPolyline(*itPolygon); } qDeleteAll(m_cachedPolygons); m_cachedPolygons.clear(); } } void GeoLineStringGraphicsItem::paintInline(GeoPainter* painter, const ViewportParams* viewport) { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } - bool isValid = configurePainterForLine(painter, viewport, false); + bool isValid = true; + if (s_previousStyle != reinterpret_cast(style().data())) { + isValid = configurePainterForLine(painter, viewport, false); + } + s_previousStyle = reinterpret_cast(style().data()); m_renderLabel = painter->pen().widthF() >= 6.0f; if (isValid) { foreach(const QPolygonF* itPolygon, m_cachedPolygons) { painter->drawPolyline(*itPolygon); } } } void GeoLineStringGraphicsItem::paintOutline(GeoPainter *painter, const ViewportParams *viewport) const { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } - bool isValid = configurePainterForLine(painter, viewport, true); + bool isValid = true; + if (s_previousStyle != reinterpret_cast(style().data())) { + isValid = configurePainterForLine(painter, viewport, true); + } + s_previousStyle = reinterpret_cast(style().data()); if (isValid) { foreach(const QPolygonF* itPolygon, m_cachedPolygons) { painter->drawPolyline(*itPolygon); } } } void GeoLineStringGraphicsItem::paintLabel(GeoPainter *painter, const ViewportParams *viewport) const { if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) { return; } LabelPositionFlags labelPositionFlags = NoLabel; bool isValid = configurePainterForLabel(painter, viewport, labelPositionFlags); if (isValid) { GeoDataStyle::ConstPtr style = this->style(); // Activate the lines below to paint a label background which // prevents antialiasing overpainting glitches, but leads to // other glitches. //QColor const color = style->polyStyle().paintedColor(); //painter->setBackground(QBrush(color)); //painter->setBackgroundMode(Qt::OpaqueMode); const GeoDataLabelStyle& labelStyle = style->labelStyle(); painter->drawLabelsForPolygons(m_cachedPolygons, feature()->name(), FollowLine, labelStyle.paintedColor()); } } bool GeoLineStringGraphicsItem::configurePainterForLine(GeoPainter *painter, const ViewportParams *viewport, const bool isOutline) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); } else { if (isOutline && !style->polyStyle().outline()) { return false; } const GeoDataLineStyle& lineStyle = style->lineStyle(); // To save performance we avoid making changes to the painter's pen. // So we first take a copy of the actual painter pen, make changes to it // and only if the resulting pen is different from the actual pen // we replace the painter's pen with our new pen. // We want to avoid the mandatory detach in QPen::setColor(), // so we carefully check whether applying the setter is needed const QColor linePaintedColor = (!isOutline && (lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine)) ? style->polyStyle().paintedColor() : lineStyle.paintedColor(); if (currentPen.color() != linePaintedColor) { if (linePaintedColor.alpha() == 255) { currentPen.setColor(linePaintedColor); } else { if ( painter->mapQuality() != Marble::HighQuality && painter->mapQuality() != Marble::PrintQuality ) { QColor penColor = currentPen.color(); penColor.setAlpha( 255 ); if (currentPen.color() != penColor) { currentPen.setColor( penColor ); } } } } const float lineWidth = lineStyle.width(); const float linePhysicalWidth = lineStyle.physicalWidth(); float newLineWidth = lineWidth; if (linePhysicalWidth != 0.0) { const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth; newLineWidth = scaledLinePhysicalWidth > lineWidth ? scaledLinePhysicalWidth : lineWidth; } if (!isOutline && lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine) { if (newLineWidth > 2.5) { newLineWidth -= 2.0; } } const qreal lineDrawThreshold = isOutline ? 2.5 : 0.5; // We want to avoid the mandatory detach in QPen::setWidthF(), // so we carefully check whether applying the setter is needed if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) { if (newLineWidth < lineDrawThreshold) { return false; // Don't draw any outline and abort painter configuration early } currentPen.setWidthF(newLineWidth); } // No need to avoid detaches inside QPen::setCapsStyle() since Qt does that for us const Qt::PenCapStyle lineCapStyle = lineStyle.capStyle(); currentPen.setCapStyle(lineCapStyle); // No need to avoid detaches inside QPen::setStyle() since Qt does that for us if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) { const Qt::PenStyle linePenStyle = lineStyle.penStyle(); currentPen.setStyle(linePenStyle); if (linePenStyle == Qt::CustomDashLine) { // We want to avoid the mandatory detach in QPen::setDashPattern(), // so we carefully check whether applying the setter is needed if (currentPen.dashPattern() != lineStyle.dashPattern()) { currentPen.setDashPattern(lineStyle.dashPattern()); } } } else { currentPen.setStyle(Qt::SolidLine); } if ( painter->pen() != currentPen ) { painter->setPen( currentPen ); } // else qDebug() << "Detach and painter change successfully Avoided!" << Q_FUNC_INFO; if (!isOutline) { if (lineStyle.background()) { QBrush brush = painter->background(); brush.setColor(style->polyStyle().paintedColor()); painter->setBackground( brush ); painter->setBackgroundMode( Qt::OpaqueMode ); } } } return true; } bool GeoLineStringGraphicsItem::configurePainterForLabel(GeoPainter *painter, const ViewportParams *viewport, LabelPositionFlags &labelPositionFlags) const { QPen currentPen = painter->pen(); GeoDataStyle::ConstPtr style = this->style(); if (!style) { painter->setPen( QPen() ); } else { const GeoDataLineStyle& lineStyle = style->lineStyle(); // To save performance we avoid making changes to the painter's pen. // So we first take a copy of the actual painter pen, make changes to it // and only if the resulting pen is different from the actual pen // we replace the painter's pen with our new pen. // We want to avoid the mandatory detach in QPen::setColor(), // so we carefully check whether applying the setter is needed const float lineWidth = lineStyle.width(); const float linePhysicalWidth = lineStyle.physicalWidth(); float newLineWidth = lineWidth; if (linePhysicalWidth != 0.0) { const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth; newLineWidth = scaledLinePhysicalWidth > lineWidth ? scaledLinePhysicalWidth : lineWidth; } // We want to avoid the mandatory detach in QPen::setWidthF(), // so we carefully check whether applying the setter is needed if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) { if (newLineWidth < 6.0) { return false; // Don't draw any labels and abort painter configuration early } currentPen.setWidthF(newLineWidth); } if ( painter->pen() != currentPen ) { painter->setPen( currentPen ); } // else qDebug() << "Detach and painter change successfully Avoided!" << Q_FUNC_INFO; if (painter->brush().color() != Qt::transparent) { painter->setBrush(QColor(Qt::transparent)); } if (painter->backgroundMode() == Qt::OpaqueMode) { painter->setBackgroundMode(Qt::TransparentMode); painter->setBackground(QBrush(Qt::transparent)); } // label styles const GeoDataLabelStyle& labelStyle = style->labelStyle(); painter->setFont(labelStyle.font() ); switch (labelStyle.alignment()) { case GeoDataLabelStyle::Corner: case GeoDataLabelStyle::Right: labelPositionFlags |= LineStart; break; case GeoDataLabelStyle::Center: labelPositionFlags |= LineCenter; break; } } return true; } bool GeoLineStringGraphicsItem::canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b) { return distanceSphere(a, b) * EARTH_RADIUS < 0.1; } } diff --git a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h index 51887d111..8aefa16da 100644 --- a/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h +++ b/src/lib/marble/geodata/graphicsitem/GeoLineStringGraphicsItem.h @@ -1,58 +1,60 @@ // // 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 2009 Andrew Manson // #ifndef MARBLE_GEOLINESTRINGGRAPHICSITEM_H #define MARBLE_GEOLINESTRINGGRAPHICSITEM_H #include "GeoGraphicsItem.h" #include "GeoDataCoordinates.h" #include "GeoDataLineString.h" #include "MarbleGlobal.h" #include "marble_export.h" namespace Marble { class GeoDataPlacemark; class MARBLE_EXPORT GeoLineStringGraphicsItem : public GeoGraphicsItem { public: explicit GeoLineStringGraphicsItem(const GeoDataPlacemark *placemark, const GeoDataLineString *lineString); void setLineString( const GeoDataLineString* lineString ); const GeoDataLineString* lineString() const; static GeoDataLineString merge(const QVector &lineStrings); void setMergedLineString(const GeoDataLineString &sharedLineString); virtual const GeoDataLatLonAltBox& latLonAltBox() const; void paint(GeoPainter* painter, const ViewportParams *viewport, const QString &layer, int tileZoomLevel); + static quint64 s_previousStyle; + private: void paintOutline(GeoPainter *painter, const ViewportParams *viewport) const; void paintInline(GeoPainter *painter, const ViewportParams *viewport); void paintLabel(GeoPainter *painter, const ViewportParams *viewport) const; bool configurePainterForLine(GeoPainter* painter, const ViewportParams *viewport, const bool isOutline = false) const; bool configurePainterForLabel(GeoPainter* painter, const ViewportParams *viewport, LabelPositionFlags &labelPositionFlags) const; static bool canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b); const GeoDataLineString *m_lineString; const GeoDataLineString *m_renderLineString; GeoDataLineString m_mergedLineString; QVector m_cachedPolygons; bool m_renderLabel; }; } #endif diff --git a/src/lib/marble/layers/GeometryLayer.cpp b/src/lib/marble/layers/GeometryLayer.cpp index dd7a01c77..89100508c 100644 --- a/src/lib/marble/layers/GeometryLayer.cpp +++ b/src/lib/marble/layers/GeometryLayer.cpp @@ -1,605 +1,609 @@ // // 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 2008-2009 Patrick Spendrin // Copyright 2010 Thibaut Gridel // Copyright 2011-2012 Bernhard Beschow // Copyright 2014 Gábor Péterffy // #include "GeometryLayer.h" // Marble #include "GeoDataLatLonAltBox.h" #include "GeoDataDocument.h" #include "GeoDataFolder.h" #include "GeoDataLineStyle.h" #include "GeoDataMultiTrack.h" #include "GeoDataObject.h" #include "GeoDataPlacemark.h" #include "GeoDataLinearRing.h" #include "GeoDataMultiGeometry.h" #include "GeoDataPolygon.h" #include "GeoDataPolyStyle.h" #include "GeoDataStyle.h" #include "GeoDataIconStyle.h" #include "GeoDataStyleMap.h" #include "GeoDataTrack.h" #include "GeoDataTypes.h" #include "GeoDataFeature.h" #include "MarbleDebug.h" #include "GeoPainter.h" #include "ViewportParams.h" #include "RenderState.h" #include "GeoGraphicsScene.h" #include "GeoGraphicsItem.h" #include "GeoLineStringGraphicsItem.h" #include "GeoPolygonGraphicsItem.h" #include "GeoTrackGraphicsItem.h" #include "GeoDataPhotoOverlay.h" #include "GeoDataScreenOverlay.h" #include "GeoPhotoGraphicsItem.h" #include "ScreenOverlayGraphicsItem.h" #include "TileId.h" #include "MarbleGraphicsItem.h" #include "MarblePlacemarkModel.h" #include "GeoDataTreeModel.h" #include #include "StyleBuilder.h" +#include "AbstractGeoPolygonGraphicsItem.h" +#include "GeoLineStringGraphicsItem.h" // Qt #include #include #include namespace Marble { class GeometryLayerPrivate { public: typedef QVector OsmLineStringItems; struct PaintFragments { // Three lists for different z values // A z value of 0 is default and used by the majority of items, so sorting // can be avoided for it QVector negative; // subways QVector null; // areas and roads QVector positive; // buildings }; explicit GeometryLayerPrivate(const QAbstractItemModel *model, const StyleBuilder *styleBuilder); void createGraphicsItems( const GeoDataObject *object ); void createGraphicsItemFromGeometry(const GeoDataGeometry *object, const GeoDataPlacemark *placemark); void createGraphicsItemFromOverlay( const GeoDataOverlay *overlay ); void removeGraphicsItems( const GeoDataFeature *feature ); void updateTiledLineStrings(const GeoDataPlacemark *placemark, GeoLineStringGraphicsItem* lineStringItem); void updateTiledLineStrings(OsmLineStringItems &lineStringItems); const QAbstractItemModel *const m_model; const StyleBuilder *const m_styleBuilder; GeoGraphicsScene m_scene; QString m_runtimeTrace; QList m_items; QHash m_osmLineStringItems; int m_tileLevel; }; GeometryLayerPrivate::GeometryLayerPrivate(const QAbstractItemModel *model, const StyleBuilder *styleBuilder) : m_model(model), m_styleBuilder(styleBuilder), m_tileLevel(0) { } GeometryLayer::GeometryLayer(const QAbstractItemModel *model, const StyleBuilder *styleBuilder) : d(new GeometryLayerPrivate(model, styleBuilder)) { const GeoDataObject *object = static_cast( d->m_model->index( 0, 0, QModelIndex() ).internalPointer() ); if ( object && object->parent() ) d->createGraphicsItems( object->parent() ); connect( model, SIGNAL(dataChanged(QModelIndex,QModelIndex)), this, SLOT(resetCacheData()) ); connect( model, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(addPlacemarks(QModelIndex,int,int)) ); connect( model, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), this, SLOT(removePlacemarks(QModelIndex,int,int)) ); connect( model, SIGNAL(modelReset()), this, SLOT(resetCacheData()) ); connect( this, SIGNAL(highlightedPlacemarksChanged(QVector)), &d->m_scene, SLOT(applyHighlight(QVector)) ); connect( &d->m_scene, SIGNAL(repaintNeeded()), this, SIGNAL(repaintNeeded()) ); } GeometryLayer::~GeometryLayer() { delete d; } QStringList GeometryLayer::renderPosition() const { return QStringList(QStringLiteral("HOVERS_ABOVE_SURFACE")); } bool GeometryLayer::render( GeoPainter *painter, ViewportParams *viewport, const QString& renderPos, GeoSceneLayer * layer ) { Q_UNUSED( renderPos ) Q_UNUSED( layer ) painter->save(); const int maxZoomLevel = qMin(d->m_tileLevel, d->m_styleBuilder->maximumZoomLevel()); QList items = d->m_scene.items( viewport->viewLatLonAltBox(), maxZoomLevel ); typedef QPair LayerItem; QList defaultLayer; QHash paintedFragments; QSet const knownLayers = QSet::fromList(d->m_styleBuilder->renderOrder()); foreach( GeoGraphicsItem* item, items ) { QStringList paintLayers = item->paintLayers(); if (paintLayers.isEmpty()) { mDebug() << item << " provides no paint layers, so I force one onto it."; paintLayers << QString(); } foreach(const auto &layer, paintLayers) { if (knownLayers.contains(layer)) { GeometryLayerPrivate::PaintFragments & fragments = paintedFragments[layer]; double const zValue = item->zValue(); // assign subway stations if (zValue == 0.0) { fragments.null << item; // assign areas and streets } else if (zValue < 0.0) { fragments.negative << item; // assign buildings } else { fragments.positive << item; } } else { // assign symbols defaultLayer << LayerItem(layer, item); static QSet missingLayers; if (!missingLayers.contains(layer)) { mDebug() << "Missing layer " << layer << ", in render order, will render it on top"; missingLayers << layer; } } } } - // Sort each fragment by z-level and draw it foreach (const QString &layer, d->m_styleBuilder->renderOrder()) { GeometryLayerPrivate::PaintFragments & layerItems = paintedFragments[layer]; std::stable_sort(layerItems.negative.begin(), layerItems.negative.end(), GeoGraphicsItem::zValueLessThan); // The idea here is that layerItems.null has most items and needs not to be sorted => faster std::stable_sort(layerItems.positive.begin(), layerItems.positive.end(), GeoGraphicsItem::zValueLessThan); + AbstractGeoPolygonGraphicsItem::s_previousStyle = -1; + GeoLineStringGraphicsItem::s_previousStyle = -1; foreach(auto item, layerItems.negative) { item->paint(painter, viewport, layer, d->m_tileLevel); } foreach(auto item, layerItems.null) { item->paint(painter, viewport, layer, d->m_tileLevel); } foreach(auto item, layerItems.positive) { item->paint(painter, viewport, layer, d->m_tileLevel); } } + foreach(const auto & item, defaultLayer) { item.second->paint(painter, viewport, item.first, d->m_tileLevel); } painter->drawTextFragments(); painter->clearTextFragments(); foreach( ScreenOverlayGraphicsItem* item, d->m_items ) { item->paintEvent( painter, viewport ); } painter->restore(); d->m_runtimeTrace = QStringLiteral("Geometries: %1 Zoom: %2") .arg( items.size() ) .arg( d->m_tileLevel ); return true; } RenderState GeometryLayer::renderState() const { return RenderState(QStringLiteral("GeoGraphicsScene")); } QString GeometryLayer::runtimeTrace() const { return d->m_runtimeTrace; } void GeometryLayerPrivate::createGraphicsItems( const GeoDataObject *object ) { if ( const GeoDataPlacemark *placemark = dynamic_cast( object ) ) { createGraphicsItemFromGeometry(placemark->geometry(), placemark); } else if ( const GeoDataOverlay* overlay = dynamic_cast( object ) ) { createGraphicsItemFromOverlay( overlay ); } // parse all child objects of the container if ( const GeoDataContainer *container = dynamic_cast( object ) ) { int rowCount = container->size(); for ( int row = 0; row < rowCount; ++row ) { createGraphicsItems( container->child( row ) ); } } } void GeometryLayerPrivate::updateTiledLineStrings(const GeoDataPlacemark* placemark, GeoLineStringGraphicsItem* lineStringItem) { if (!placemark->hasOsmData()) { return; } qint64 const osmId = placemark->osmData().oid(); if (osmId <= 0) { return; } auto & lineStringItems = m_osmLineStringItems[osmId]; lineStringItems << lineStringItem; updateTiledLineStrings(lineStringItems); } void GeometryLayerPrivate::updateTiledLineStrings(OsmLineStringItems &lineStringItems) { GeoDataLineString merged; if (lineStringItems.size() > 1) { QVector lineStrings; for (auto item: lineStringItems) { lineStrings << item->lineString(); } merged = GeoLineStringGraphicsItem::merge(lineStrings); } // If merging failed, reset all. Otherwise only the first one // gets the merge result and becomes visible. bool visible = true; for (auto item: lineStringItems) { item->setVisible(visible); if (visible) { item->setMergedLineString(merged); visible = merged.isEmpty(); } } } void GeometryLayerPrivate::createGraphicsItemFromGeometry(const GeoDataGeometry* object, const GeoDataPlacemark *placemark) { if (!placemark->isGloballyVisible()) { return; // Reconsider this when visibility can be changed dynamically } GeoGraphicsItem *item = 0; if ( object->nodeType() == GeoDataTypes::GeoDataLineStringType ) { const GeoDataLineString* line = static_cast( object ); auto lineStringItem = new GeoLineStringGraphicsItem( placemark, line ); item = lineStringItem; updateTiledLineStrings(placemark, lineStringItem); } else if ( object->nodeType() == GeoDataTypes::GeoDataLinearRingType ) { const GeoDataLinearRing *ring = static_cast( object ); item = GeoPolygonGraphicsItem::createGraphicsItem(placemark, ring); } else if ( object->nodeType() == GeoDataTypes::GeoDataPolygonType ) { const GeoDataPolygon *poly = static_cast( object ); item = GeoPolygonGraphicsItem::createGraphicsItem(placemark, poly); if (item->zValue() == 0) { item->setZValue(poly->renderOrder()); } } else if ( object->nodeType() == GeoDataTypes::GeoDataMultiGeometryType ) { const GeoDataMultiGeometry *multigeo = static_cast( object ); int rowCount = multigeo->size(); for ( int row = 0; row < rowCount; ++row ) { createGraphicsItemFromGeometry(multigeo->child( row ), placemark); } } else if ( object->nodeType() == GeoDataTypes::GeoDataMultiTrackType ) { const GeoDataMultiTrack *multitrack = static_cast( object ); int rowCount = multitrack->size(); for ( int row = 0; row < rowCount; ++row ) { createGraphicsItemFromGeometry(multitrack->child( row ), placemark); } } else if ( object->nodeType() == GeoDataTypes::GeoDataTrackType ) { const GeoDataTrack *track = static_cast( object ); item = new GeoTrackGraphicsItem( placemark, track ); } if ( !item ) return; item->setStyleBuilder(m_styleBuilder); item->setVisible( item->visible() && placemark->isGloballyVisible() ); item->setMinZoomLevel(m_styleBuilder->minimumZoomLevel(*placemark)); m_scene.addItem( item ); } void GeometryLayerPrivate::createGraphicsItemFromOverlay( const GeoDataOverlay *overlay ) { if (!overlay->isGloballyVisible()) { return; // Reconsider this when visibility can be changed dynamically } GeoGraphicsItem* item = 0; if ( overlay->nodeType() == GeoDataTypes::GeoDataPhotoOverlayType ) { GeoDataPhotoOverlay const * photoOverlay = static_cast( overlay ); GeoPhotoGraphicsItem *photoItem = new GeoPhotoGraphicsItem( overlay ); photoItem->setPoint( photoOverlay->point() ); item = photoItem; } else if ( overlay->nodeType() == GeoDataTypes::GeoDataScreenOverlayType ) { GeoDataScreenOverlay const * screenOverlay = static_cast( overlay ); ScreenOverlayGraphicsItem *screenItem = new ScreenOverlayGraphicsItem ( screenOverlay ); m_items.push_back( screenItem ); } if ( item ) { item->setStyleBuilder(m_styleBuilder); item->setVisible( overlay->isGloballyVisible() ); m_scene.addItem( item ); } } void GeometryLayerPrivate::removeGraphicsItems( const GeoDataFeature *feature ) { if( feature->nodeType() == GeoDataTypes::GeoDataPlacemarkType ) { GeoDataPlacemark const * placemark = static_cast(feature); if (placemark->isGloballyVisible() && placemark->geometry()->nodeType() == GeoDataTypes::GeoDataLineStringType && placemark->hasOsmData() && placemark->osmData().oid() > 0) { auto & items = m_osmLineStringItems[placemark->osmData().oid()]; bool removed = false; for (auto item: items) { if (item->feature() == feature) { items.removeOne(item); removed = true; break; } } Q_ASSERT(removed); updateTiledLineStrings(items); } m_scene.removeItem( feature ); } else if( feature->nodeType() == GeoDataTypes::GeoDataFolderType || feature->nodeType() == GeoDataTypes::GeoDataDocumentType ) { const GeoDataContainer *container = static_cast( feature ); foreach( const GeoDataFeature *child, container->featureList() ) { removeGraphicsItems( child ); } } else if( feature->nodeType() == GeoDataTypes::GeoDataScreenOverlayType ) { foreach( ScreenOverlayGraphicsItem *item, m_items ) { if( item->screenOverlay() == feature ) { m_items.removeAll( item ); } } } } void GeometryLayer::addPlacemarks( const QModelIndex& parent, int first, int last ) { Q_ASSERT( first < d->m_model->rowCount( parent ) ); Q_ASSERT( last < d->m_model->rowCount( parent ) ); for( int i=first; i<=last; ++i ) { QModelIndex index = d->m_model->index( i, 0, parent ); Q_ASSERT( index.isValid() ); const GeoDataObject *object = qvariant_cast(index.data( MarblePlacemarkModel::ObjectPointerRole ) ); Q_ASSERT( object ); d->createGraphicsItems( object ); } emit repaintNeeded(); } void GeometryLayer::removePlacemarks( const QModelIndex& parent, int first, int last ) { Q_ASSERT( last < d->m_model->rowCount( parent ) ); bool isRepaintNeeded = false; for( int i=first; i<=last; ++i ) { QModelIndex index = d->m_model->index( i, 0, parent ); Q_ASSERT( index.isValid() ); const GeoDataObject *object = qvariant_cast(index.data( MarblePlacemarkModel::ObjectPointerRole ) ); const GeoDataFeature *feature = dynamic_cast( object ); if( feature != 0 ) { d->removeGraphicsItems( feature ); isRepaintNeeded = true; } } if( isRepaintNeeded ) { emit repaintNeeded(); } } void GeometryLayer::resetCacheData() { d->m_scene.clear(); qDeleteAll( d->m_items ); d->m_items.clear(); d->m_osmLineStringItems.clear(); const GeoDataObject *object = static_cast( d->m_model->index( 0, 0, QModelIndex() ).internalPointer() ); if ( object && object->parent() ) d->createGraphicsItems( object->parent() ); emit repaintNeeded(); } void GeometryLayer::setTileLevel(int tileLevel) { d->m_tileLevel = tileLevel; } QVector GeometryLayer::whichFeatureAt(const QPoint &curpos, const ViewportParams *viewport) { const int maxZoom = qMin(d->m_tileLevel, d->m_styleBuilder->maximumZoomLevel()); QVector result; foreach ( GeoGraphicsItem * item, d->m_scene.items( viewport->viewLatLonAltBox(), maxZoom ) ) { if ( item->feature()->nodeType() == GeoDataTypes::GeoDataPhotoOverlayType ) { GeoPhotoGraphicsItem* photoItem = dynamic_cast( item ); qreal x(0.0), y( 0.0 ); viewport->screenCoordinates( photoItem->point().coordinates(), x, y ); if ( photoItem->style() != 0 && !photoItem->style()->iconStyle().icon().isNull() ) { int halfIconWidth = photoItem->style()->iconStyle().icon().size().width() / 2; int halfIconHeight = photoItem->style()->iconStyle().icon().size().height() / 2; if ( x - halfIconWidth < curpos.x() && curpos.x() < x + halfIconWidth && y - halfIconHeight / 2 < curpos.y() && curpos.y() < y + halfIconHeight / 2 ) { result.push_back( item->feature() ); } } else if ( curpos.x() == x && curpos.y() == y ) { result.push_back( item->feature() ); } } } return result; } QVector GeometryLayer::whichBuildingAt(const QPoint &curpos, const ViewportParams *viewport) { QVector result; qreal lon, lat; if (!viewport->geoCoordinates(curpos.x(), curpos.y(), lon, lat, GeoDataCoordinates::Radian)) { return result; } GeoDataCoordinates const coordinates = GeoDataCoordinates(lon, lat); const int maxZoom = qMin(d->m_tileLevel, d->m_styleBuilder->maximumZoomLevel()); foreach ( GeoGraphicsItem * item, d->m_scene.items( viewport->viewLatLonAltBox(), maxZoom ) ) { if (item->feature()->nodeType() == GeoDataTypes::GeoDataPlacemarkType) { const GeoDataPlacemark* placemark = static_cast(item->feature()); if (placemark->visualCategory() != GeoDataPlacemark::Building) { continue; } if (placemark->geometry()->nodeType() == GeoDataTypes::GeoDataPolygonType) { const GeoDataPolygon *polygon = static_cast(placemark->geometry()); if (polygon->contains(coordinates)) { result << item->feature(); } } else if (placemark->geometry()->nodeType() == GeoDataTypes::GeoDataLinearRingType) { const GeoDataLinearRing *ring = static_cast(placemark->geometry()); if (ring->contains(coordinates)) { result << item->feature(); } } } } return result; } void GeometryLayer::handleHighlight( qreal lon, qreal lat, GeoDataCoordinates::Unit unit ) { GeoDataCoordinates clickedPoint( lon, lat, 0, unit ); QVector selectedPlacemarks; for ( int i = 0; i < d->m_model->rowCount(); ++i ) { QVariant const data = d->m_model->data ( d->m_model->index ( i, 0 ), MarblePlacemarkModel::ObjectPointerRole ); GeoDataObject *object = qvariant_cast ( data ); Q_ASSERT ( object ); if ( object->nodeType() == GeoDataTypes::GeoDataDocumentType ) { Q_ASSERT( dynamic_cast( object ) != 0 ); GeoDataDocument* doc = static_cast ( object ); bool isHighlight = false; foreach ( const GeoDataStyleMap &styleMap, doc->styleMaps() ) { if (styleMap.contains(QStringLiteral("highlight"))) { isHighlight = true; break; } } /* * If a document doesn't specify any highlight * styleId in its style maps then there is no need * to further check that document for placemarks * which have been clicked because we won't * highlight them. */ if ( isHighlight ) { QVector::Iterator iter = doc->begin(); QVector::Iterator const end = doc->end(); for ( ; iter != end; ++iter ) { if ( (*iter)->nodeType() == GeoDataTypes::GeoDataPlacemarkType ) { GeoDataPlacemark *placemark = static_cast( *iter ); GeoDataPolygon *polygon = dynamic_cast( placemark->geometry() ); GeoDataLineString *lineString = dynamic_cast( placemark->geometry() ); GeoDataMultiGeometry *multiGeometry = dynamic_cast(placemark->geometry() ); if ( polygon && polygon->contains( clickedPoint ) ) { selectedPlacemarks.push_back( placemark ); } if ( lineString && lineString->nodeType() == GeoDataTypes::GeoDataLinearRingType ) { GeoDataLinearRing *linearRing = static_cast( lineString ); if ( linearRing->contains( clickedPoint ) ) { selectedPlacemarks.push_back( placemark ); } } if ( multiGeometry ) { QVector::Iterator multiIter = multiGeometry->begin(); QVector::Iterator const multiEnd = multiGeometry->end(); for ( ; multiIter != multiEnd; ++multiIter ) { GeoDataPolygon *poly = dynamic_cast( *multiIter ); GeoDataLineString *linestring = dynamic_cast( *multiIter ); if ( poly && poly->contains( clickedPoint ) ) { selectedPlacemarks.push_back( placemark ); break; } if ( linestring && linestring->nodeType() == GeoDataTypes::GeoDataLinearRingType ) { GeoDataLinearRing *linearRing = static_cast( linestring ); if ( linearRing->contains( clickedPoint ) ) { selectedPlacemarks.push_back( placemark ); break; } } } } } } } } } emit highlightedPlacemarksChanged( selectedPlacemarks ); } } #include "moc_GeometryLayer.cpp"