File indexing completed on 2024-05-19 03:51:40

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