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 }