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

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2009 Andrew Manson <g.real.ate@gmail.com>
0004 //
0005 
0006 #include "GeoLineStringGraphicsItem.h"
0007 
0008 #include <qmath.h>
0009 #include <QPainterPathStroker>
0010 
0011 #include <klocalizedstring.h>
0012 
0013 #include "GeoDataLineStyle.h"
0014 #include "GeoDataLabelStyle.h"
0015 #include "GeoDataPlacemark.h"
0016 #include "GeoDataPolyStyle.h"
0017 #include "GeoPainter.h"
0018 #include "StyleBuilder.h"
0019 #include "ViewportParams.h"
0020 #include "GeoDataStyle.h"
0021 #include "GeoDataColorStyle.h"
0022 #include "OsmPlacemarkData.h"
0023 
0024 #include "digikam_debug.h"
0025 
0026 namespace Marble
0027 {
0028 
0029 const GeoDataStyle *GeoLineStringGraphicsItem::s_previousStyle = nullptr;
0030 bool GeoLineStringGraphicsItem::s_paintInline = true;
0031 bool GeoLineStringGraphicsItem::s_paintOutline = true;
0032 
0033 GeoLineStringGraphicsItem::GeoLineStringGraphicsItem(const GeoDataPlacemark *placemark,
0034                                                      const GeoDataLineString *lineString) :
0035     GeoGraphicsItem(placemark),
0036     m_lineString(lineString),
0037     m_renderLineString(lineString),
0038     m_renderLabel(false),
0039     m_penWidth(0.0),
0040     m_name(placemark->name())
0041 {
0042     QString const category = StyleBuilder::visualCategoryName(placemark->visualCategory());
0043     QStringList paintLayers;
0044     paintLayers << QLatin1String("LineString/") + category + QLatin1String("/outline");
0045     paintLayers << QLatin1String("LineString/") + category + QLatin1String("/inline");
0046     if (!m_name.isEmpty()) {
0047         paintLayers << QLatin1String("LineString/") + category + QLatin1String("/label");
0048     }
0049     setPaintLayers(paintLayers);
0050 }
0051 
0052 GeoLineStringGraphicsItem::~GeoLineStringGraphicsItem()
0053 {
0054     qDeleteAll(m_cachedPolygons);
0055 }
0056 
0057 
0058 void GeoLineStringGraphicsItem::setLineString( const GeoDataLineString* lineString )
0059 {
0060     m_lineString = lineString;
0061     m_renderLineString = lineString;
0062 }
0063 
0064 const GeoDataLineString *GeoLineStringGraphicsItem::lineString() const
0065 {
0066     return m_lineString;
0067 }
0068 
0069 GeoDataLineString GeoLineStringGraphicsItem::merge(const QVector<const GeoDataLineString *> &lineStrings_)
0070 {
0071     if (lineStrings_.isEmpty()) {
0072         return GeoDataLineString();
0073     }
0074 
0075     Q_ASSERT(!lineStrings_.isEmpty());
0076     auto lineStrings = lineStrings_;
0077     GeoDataLineString result = *lineStrings.first();
0078     lineStrings.pop_front();
0079     for (bool matched = true; matched && !lineStrings.isEmpty();) {
0080         matched = false;
0081         for (auto lineString: lineStrings) {
0082             if (canMerge(result.first(), lineString->first())) {
0083                 result.remove(0);
0084                 result.reverse();
0085                 result << *lineString;
0086                 lineStrings.removeOne(lineString);
0087                 matched = true;
0088                 break;
0089             } else if (canMerge(result.last(), lineString->first())) {
0090                 result.remove(result.size()-1);
0091                 result << *lineString;
0092                 lineStrings.removeOne(lineString);
0093                 matched = true;
0094                 break;
0095             } else if (canMerge(result.first(), lineString->last())) {
0096                 GeoDataLineString behind = result;
0097                 result = *lineString;
0098                 behind.remove(0);
0099                 result << behind;
0100                 lineStrings.removeOne(lineString);
0101                 matched = true;
0102                 break;
0103             } else if (canMerge(result.last(), lineString->last())) {
0104                 GeoDataLineString behind = *lineString;
0105                 behind.reverse();
0106                 behind.remove(0);
0107                 result << behind;
0108                 lineStrings.removeOne(lineString);
0109                 matched = true;
0110                 break;
0111             }
0112         }
0113 
0114         if (!matched) {
0115             return GeoDataLineString();
0116         }
0117     }
0118     return lineStrings.isEmpty() ? result : GeoDataLineString();
0119 }
0120 
0121 void GeoLineStringGraphicsItem::setMergedLineString(const GeoDataLineString &mergedLineString)
0122 {
0123     m_mergedLineString = mergedLineString;
0124     m_renderLineString = mergedLineString.isEmpty() ? m_lineString : &m_mergedLineString;
0125 }
0126 
0127 const GeoDataLatLonAltBox& GeoLineStringGraphicsItem::latLonAltBox() const
0128 {
0129     return m_renderLineString->latLonAltBox();
0130 }
0131 
0132 void GeoLineStringGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport , const QString &layer, int tileLevel)
0133 {
0134     setRenderContext(RenderContext(tileLevel));
0135 
0136     if (layer.endsWith(QLatin1String("/outline"))) {
0137         qDeleteAll(m_cachedPolygons);
0138         m_cachedPolygons.clear();
0139         m_cachedRegion = QRegion();
0140         painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons);
0141         if (m_cachedPolygons.empty()) {
0142             return;
0143         }
0144         if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) {
0145             paintOutline(painter, viewport);
0146         }
0147     } else if (layer.endsWith(QLatin1String("/inline"))) {
0148         if (m_cachedPolygons.empty()) {
0149             return;
0150         }
0151         paintInline(painter, viewport);
0152     } else if (layer.endsWith(QLatin1String("/label"))) {
0153         if (!m_cachedPolygons.empty()) {
0154             if (m_renderLabel) {
0155                 paintLabel(painter, viewport);
0156             }
0157         }
0158     } else {
0159         qDeleteAll(m_cachedPolygons);
0160         m_cachedPolygons.clear();
0161         m_cachedRegion = QRegion();
0162         painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons);
0163         if (m_cachedPolygons.empty()) {
0164             return;
0165         }
0166         if (s_previousStyle != style().data()) {
0167             configurePainterForLine(painter, viewport, false);
0168         }
0169         s_previousStyle = style().data();
0170         for(const QPolygonF* itPolygon: m_cachedPolygons) {
0171             painter->drawPolyline(*itPolygon);
0172         }
0173     }
0174 }
0175 
0176 bool GeoLineStringGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *) const
0177 {
0178     if (m_penWidth <= 0.0) {
0179         return false;
0180     }
0181 
0182     if (m_cachedRegion.isNull()) {
0183         QPainterPath painterPath;
0184         for (auto polygon: m_cachedPolygons) {
0185             painterPath.addPolygon(*polygon);
0186         }
0187         QPainterPathStroker stroker;
0188         qreal const margin = 6.0;
0189         stroker.setWidth(m_penWidth + margin);
0190         QPainterPath strokePath = stroker.createStroke(painterPath);
0191         m_cachedRegion = QRegion(strokePath.toFillPolygon().toPolygon(), Qt::WindingFill);
0192     }
0193     return m_cachedRegion.contains(screenPosition);
0194 }
0195 
0196 void GeoLineStringGraphicsItem::handleRelationUpdate(const QVector<const GeoDataRelation *> &relations)
0197 {
0198     QHash<GeoDataRelation::RelationType, QStringList> names;
0199     for (auto relation: relations) {
0200         auto const ref = relation->osmData().tagValue(QStringLiteral("ref"));
0201         if (relation->isVisible() && !ref.isEmpty()) {
0202             names[relation->relationType()] << ref;
0203         }
0204     }
0205     if (names.isEmpty()) {
0206         m_name = feature()->name();
0207     } else {
0208         QStringList namesList;
0209         for (auto iter = names.begin(); iter != names.end(); ++iter) {
0210             QString value;
0211             switch (iter.key()) {
0212             case GeoDataRelation::UnknownType:
0213             case GeoDataRelation::RouteRoad: break;
0214             case GeoDataRelation::RouteDetour: value = i18n("Detour"); break;
0215             case GeoDataRelation::RouteFerry: value = i18n("Ferry Route"); break;
0216             case GeoDataRelation::RouteTrain: value = i18n("Train"); break;
0217             case GeoDataRelation::RouteSubway: value = i18n("Subway"); break;
0218             case GeoDataRelation::RouteTram: value = i18n("Tram"); break;
0219             case GeoDataRelation::RouteBus: value = i18n("Bus"); break;
0220             case GeoDataRelation::RouteTrolleyBus: value = i18n("Trolley Bus"); break;
0221             case GeoDataRelation::RouteBicycle: value = i18n("Bicycle Route"); break;
0222             case GeoDataRelation::RouteMountainbike: value = i18n("Mountainbike Route"); break;
0223             case GeoDataRelation::RouteFoot: value = i18n("Walking Route"); break;
0224             case GeoDataRelation::RouteHiking: value = i18n("Hiking Route"); break;
0225             case GeoDataRelation::RouteHorse: value = i18n("Bridleway"); break;
0226             case GeoDataRelation::RouteInlineSkates: value = i18n("Inline Skates Route"); break;
0227             case GeoDataRelation::RouteSkiDownhill: value = i18n("Downhill Piste"); break;
0228             case GeoDataRelation::RouteSkiNordic: value = i18n("Nordic Ski Trail"); break;
0229             case GeoDataRelation::RouteSkitour: value = i18n("Skitour"); break;
0230             case GeoDataRelation::RouteSled: value = i18n("Sled Trail"); break;
0231             }
0232 
0233             QStringList &references = iter.value();
0234             std::sort(references.begin(), references.end());
0235             auto const last = std::unique(references.begin(), references.end());
0236             references.erase(last, references.end());
0237             auto const routes = references.join(QLatin1String(", "));
0238             namesList << (value.isEmpty() ? routes : QStringLiteral("%1 %2").arg(value, routes));
0239         }
0240         auto const allRoutes = namesList.join(QStringLiteral("; "));
0241         if (feature()->name().isEmpty()) {
0242             m_name = allRoutes;
0243         } else {
0244             m_name = QStringLiteral("%1 (%2)").arg(feature()->name(), allRoutes);
0245         }
0246     }
0247 }
0248 
0249 void GeoLineStringGraphicsItem::paintInline(GeoPainter* painter, const ViewportParams* viewport)
0250 {
0251     if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) {
0252         return;
0253     }
0254 
0255     if (s_previousStyle != style().data()) {
0256         s_paintInline = configurePainterForLine(painter, viewport, false);
0257     }
0258     s_previousStyle = style().data();
0259 
0260     if (s_paintInline) {
0261       m_renderLabel = painter->pen().widthF() >= 6.0f;
0262       m_penWidth = painter->pen().widthF();
0263       for(const QPolygonF* itPolygon: m_cachedPolygons) {
0264           painter->drawPolyline(*itPolygon);
0265       }
0266     }
0267 }
0268 
0269 void GeoLineStringGraphicsItem::paintOutline(GeoPainter *painter, const ViewportParams *viewport) const
0270 {
0271     if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) {
0272         return;
0273     }
0274 
0275     if (s_previousStyle != style().data()) {
0276         s_paintOutline = configurePainterForLine(painter, viewport, true);
0277     }
0278     s_previousStyle = style().data();
0279 
0280     if (s_paintOutline) {
0281         for(const QPolygonF* itPolygon: m_cachedPolygons) {
0282             painter->drawPolyline(*itPolygon);
0283         }
0284     }
0285 
0286 }
0287 
0288 void GeoLineStringGraphicsItem::paintLabel(GeoPainter *painter, const ViewportParams *viewport) const
0289 {
0290     if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) {
0291         return;
0292     }
0293 
0294     LabelPositionFlags labelPositionFlags = NoLabel;
0295     bool isValid  = configurePainterForLabel(painter, viewport, labelPositionFlags);
0296 
0297     if (isValid) {
0298         GeoDataStyle::ConstPtr style = this->style();
0299 
0300         // Activate the lines below to paint a label background which
0301         // prevents antialiasing overpainting glitches, but leads to
0302         // other glitches.
0303         //QColor const color = style->polyStyle().paintedColor();
0304         //painter->setBackground(QBrush(color));
0305         //painter->setBackgroundMode(Qt::OpaqueMode);
0306 
0307         const GeoDataLabelStyle& labelStyle = style->labelStyle();
0308         painter->drawLabelsForPolygons(m_cachedPolygons, m_name, FollowLine,
0309                                labelStyle.paintedColor());
0310     }
0311 }
0312 
0313 
0314 bool  GeoLineStringGraphicsItem::configurePainterForLine(GeoPainter *painter, const ViewportParams *viewport, const bool isOutline) const
0315 {
0316     QPen currentPen = painter->pen();
0317     GeoDataStyle::ConstPtr style = this->style();
0318     if (!style) {
0319         painter->setPen( QPen() );
0320         painter->setBackground(QBrush(Qt::transparent));
0321         painter->setBackgroundMode(Qt::TransparentMode);
0322     }
0323     else {
0324         if (isOutline && !style->polyStyle().outline()) {
0325             return false;
0326         }
0327 
0328         const GeoDataLineStyle& lineStyle = style->lineStyle();
0329 
0330         // To save performance we avoid making changes to the painter's pen.
0331         // So we first take a copy of the actual painter pen, make changes to it
0332         // and only if the resulting pen is different from the actual pen
0333         // we replace the painter's pen with our new pen.
0334 
0335         // We want to avoid the mandatory detach in QPen::setColor(),
0336         // so we carefully check whether applying the setter is needed
0337         const QColor linePaintedColor = (!isOutline && (lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine))
0338                                         ? style->polyStyle().paintedColor()
0339                                         : lineStyle.paintedColor();
0340         if (currentPen.color() != linePaintedColor) {
0341             if (linePaintedColor.alpha() == 255) {
0342                 currentPen.setColor(linePaintedColor);
0343             }
0344             else {
0345                 if ( painter->mapQuality() != Marble::HighQuality
0346                         && painter->mapQuality() != Marble::PrintQuality ) {
0347                     QColor penColor = linePaintedColor;
0348                     if (penColor.alpha() != 0) {
0349                         penColor.setAlpha( 255 );
0350                     }
0351                     if (currentPen.color() != penColor) {
0352                         currentPen.setColor( penColor );
0353                     }
0354                 }
0355                 else {
0356                     currentPen.setColor(linePaintedColor);
0357                 }
0358             }
0359         }
0360 
0361         const float lineWidth = lineStyle.width();
0362         const float linePhysicalWidth = lineStyle.physicalWidth();
0363         float newLineWidth = lineWidth;
0364         if (linePhysicalWidth != 0.0) {
0365           const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth;
0366           newLineWidth = scaledLinePhysicalWidth > lineWidth
0367                          ? scaledLinePhysicalWidth
0368                          : lineWidth;
0369         }
0370 
0371         if (!isOutline && lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine) {
0372             if (newLineWidth > 2.5) {
0373               newLineWidth -= 2.0;
0374             }
0375         }
0376 
0377         const qreal lineDrawThreshold = isOutline ? 2.5 : 0.5;
0378 
0379         // We want to avoid the mandatory detach in QPen::setWidthF(),
0380         // so we carefully check whether applying the setter is needed
0381         if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) {
0382           if (newLineWidth < lineDrawThreshold) {
0383               if (painter->pen().style() != Qt::NoPen) {
0384                   painter->setPen(Qt::NoPen);
0385               }
0386               return false; // Don't draw any outline and abort painter configuration early
0387           }
0388           currentPen.setWidthF(newLineWidth);
0389         }
0390 
0391         // No need to avoid detaches inside QPen::setCapsStyle() since Qt does that for us
0392         const Qt::PenCapStyle lineCapStyle = lineStyle.capStyle();
0393         currentPen.setCapStyle(lineCapStyle);
0394 
0395         // No need to avoid detaches inside QPen::setStyle() since Qt does that for us
0396         if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) {
0397             const Qt::PenStyle linePenStyle = lineStyle.penStyle();
0398             currentPen.setStyle(linePenStyle);
0399 
0400             if (linePenStyle == Qt::CustomDashLine) {
0401                 // We want to avoid the mandatory detach in QPen::setDashPattern(),
0402                 // so we carefully check whether applying the setter is needed
0403                 if (currentPen.dashPattern() != lineStyle.dashPattern()) {
0404                   currentPen.setDashPattern(lineStyle.dashPattern());
0405                 }
0406             }
0407         } else {
0408             currentPen.setStyle(Qt::SolidLine);
0409         }
0410 
0411         if ( painter->pen() != currentPen ) {
0412             painter->setPen( currentPen );
0413         }
0414 
0415         // Set the background
0416 
0417         if (!isOutline) {
0418           if (lineStyle.background()) {
0419               QBrush brush = painter->background();
0420               brush.setColor(style->polyStyle().paintedColor());
0421               painter->setBackground( brush );
0422 
0423               painter->setBackgroundMode( Qt::OpaqueMode );
0424           }
0425           else {
0426               painter->setBackground(QBrush(Qt::transparent));
0427               painter->setBackgroundMode(Qt::TransparentMode);
0428           }
0429         }
0430         else {
0431             painter->setBackground(QBrush(Qt::transparent));
0432             painter->setBackgroundMode(Qt::TransparentMode);
0433         }
0434     }
0435 
0436     return true;
0437 }
0438 
0439 bool GeoLineStringGraphicsItem::configurePainterForLabel(GeoPainter *painter,  const ViewportParams *viewport, LabelPositionFlags &labelPositionFlags) const
0440 {
0441   QPen currentPen = painter->pen();
0442   GeoDataStyle::ConstPtr style = this->style();
0443   if (!style) {
0444       painter->setPen( QPen() );
0445   }
0446   else {
0447       const GeoDataLineStyle& lineStyle = style->lineStyle();
0448 
0449       // To save performance we avoid making changes to the painter's pen.
0450       // So we first take a copy of the actual painter pen, make changes to it
0451       // and only if the resulting pen is different from the actual pen
0452       // we replace the painter's pen with our new pen.
0453 
0454       // We want to avoid the mandatory detach in QPen::setColor(),
0455       // so we carefully check whether applying the setter is needed
0456 
0457       const float lineWidth = lineStyle.width();
0458       const float linePhysicalWidth = lineStyle.physicalWidth();
0459       float newLineWidth = lineWidth;
0460       if (linePhysicalWidth != 0.0) {
0461         const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth;
0462         newLineWidth = scaledLinePhysicalWidth > lineWidth
0463                        ? scaledLinePhysicalWidth
0464                        : lineWidth;
0465       }
0466 
0467       // We want to avoid the mandatory detach in QPen::setWidthF(),
0468       // so we carefully check whether applying the setter is needed
0469       if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) {
0470         if (newLineWidth < 6.0) {
0471             return false; // Don't draw any labels and abort painter configuration early
0472         }
0473         currentPen.setWidthF(newLineWidth);
0474       }
0475 
0476 
0477       if ( painter->pen() != currentPen ) {
0478           painter->setPen( currentPen );
0479       }
0480 //        else qCDebug(DIGIKAM_MARBLE_LOG) << "Detach and painter change successfully Avoided!" << Q_FUNC_INFO;
0481 
0482       if (painter->brush().color() != Qt::transparent) {
0483           painter->setBrush(QColor(Qt::transparent));
0484       }
0485       if (painter->backgroundMode() == Qt::OpaqueMode) {
0486           painter->setBackgroundMode(Qt::TransparentMode);
0487           painter->setBackground(QBrush(Qt::transparent));
0488       }
0489 
0490       // label styles
0491       const GeoDataLabelStyle& labelStyle = style->labelStyle();
0492       painter->setFont(labelStyle.font() );
0493       switch (labelStyle.alignment()) {
0494       case GeoDataLabelStyle::Corner:
0495       case GeoDataLabelStyle::Right:
0496           labelPositionFlags |= LineStart;
0497           break;
0498       case GeoDataLabelStyle::Center:
0499           labelPositionFlags |= LineCenter;
0500           break;
0501       }
0502   }
0503 
0504   return true;
0505 }
0506 
0507 bool GeoLineStringGraphicsItem::canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b)
0508 {
0509     return a.sphericalDistanceTo(b) * EARTH_RADIUS < 0.1;
0510 }
0511 
0512 }