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 }