Marble

GeoLineStringGraphicsItem.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2009 Andrew Manson <[email protected]>
4 //
5 
6 #include "GeoLineStringGraphicsItem.h"
7 
8 #include "GeoDataLineStyle.h"
9 #include "GeoDataLabelStyle.h"
10 #include "GeoDataPlacemark.h"
11 #include "GeoDataPolyStyle.h"
12 #include "GeoPainter.h"
13 #include "StyleBuilder.h"
14 #include "ViewportParams.h"
15 #include "GeoDataStyle.h"
16 #include "GeoDataColorStyle.h"
17 #include "MarbleDebug.h"
18 #include "OsmPlacemarkData.h"
19 
20 #include <qmath.h>
21 #include <QPainterPathStroker>
22 
23 namespace Marble
24 {
25 
26 const GeoDataStyle *GeoLineStringGraphicsItem::s_previousStyle = nullptr;
27 bool GeoLineStringGraphicsItem::s_paintInline = true;
28 bool GeoLineStringGraphicsItem::s_paintOutline = true;
29 
30 GeoLineStringGraphicsItem::GeoLineStringGraphicsItem(const GeoDataPlacemark *placemark,
31  const GeoDataLineString *lineString) :
32  GeoGraphicsItem(placemark),
33  m_lineString(lineString),
34  m_renderLineString(lineString),
35  m_renderLabel(false),
36  m_penWidth(0.0),
37  m_name(placemark->name())
38 {
39  QString const category = StyleBuilder::visualCategoryName(placemark->visualCategory());
40  QStringList paintLayers;
41  paintLayers << QLatin1String("LineString/") + category + QLatin1String("/outline");
42  paintLayers << QLatin1String("LineString/") + category + QLatin1String("/inline");
43  if (!m_name.isEmpty()) {
44  paintLayers << QLatin1String("LineString/") + category + QLatin1String("/label");
45  }
46  setPaintLayers(paintLayers);
47 }
48 
49 GeoLineStringGraphicsItem::~GeoLineStringGraphicsItem()
50 {
51  qDeleteAll(m_cachedPolygons);
52 }
53 
54 
55 void GeoLineStringGraphicsItem::setLineString( const GeoDataLineString* lineString )
56 {
57  m_lineString = lineString;
58  m_renderLineString = lineString;
59 }
60 
61 const GeoDataLineString *GeoLineStringGraphicsItem::lineString() const
62 {
63  return m_lineString;
64 }
65 
66 GeoDataLineString GeoLineStringGraphicsItem::merge(const QVector<const GeoDataLineString *> &lineStrings_)
67 {
68  if (lineStrings_.isEmpty()) {
69  return GeoDataLineString();
70  }
71 
72  Q_ASSERT(!lineStrings_.isEmpty());
73  auto lineStrings = lineStrings_;
74  GeoDataLineString result = *lineStrings.first();
75  lineStrings.pop_front();
76  for (bool matched = true; matched && !lineStrings.isEmpty();) {
77  matched = false;
78  for (auto lineString: lineStrings) {
79  if (canMerge(result.first(), lineString->first())) {
80  result.remove(0);
81  result.reverse();
82  result << *lineString;
83  lineStrings.removeOne(lineString);
84  matched = true;
85  break;
86  } else if (canMerge(result.last(), lineString->first())) {
87  result.remove(result.size()-1);
88  result << *lineString;
89  lineStrings.removeOne(lineString);
90  matched = true;
91  break;
92  } else if (canMerge(result.first(), lineString->last())) {
93  GeoDataLineString behind = result;
94  result = *lineString;
95  behind.remove(0);
96  result << behind;
97  lineStrings.removeOne(lineString);
98  matched = true;
99  break;
100  } else if (canMerge(result.last(), lineString->last())) {
101  GeoDataLineString behind = *lineString;
102  behind.reverse();
103  behind.remove(0);
104  result << behind;
105  lineStrings.removeOne(lineString);
106  matched = true;
107  break;
108  }
109  }
110 
111  if (!matched) {
112  return GeoDataLineString();
113  }
114  }
115  return lineStrings.isEmpty() ? result : GeoDataLineString();
116 }
117 
118 void GeoLineStringGraphicsItem::setMergedLineString(const GeoDataLineString &mergedLineString)
119 {
120  m_mergedLineString = mergedLineString;
121  m_renderLineString = mergedLineString.isEmpty() ? m_lineString : &m_mergedLineString;
122 }
123 
124 const GeoDataLatLonAltBox& GeoLineStringGraphicsItem::latLonAltBox() const
125 {
126  return m_renderLineString->latLonAltBox();
127 }
128 
129 void GeoLineStringGraphicsItem::paint(GeoPainter* painter, const ViewportParams* viewport , const QString &layer, int tileLevel)
130 {
131  setRenderContext(RenderContext(tileLevel));
132 
133  if (layer.endsWith(QLatin1String("/outline"))) {
134  qDeleteAll(m_cachedPolygons);
135  m_cachedPolygons.clear();
136  m_cachedRegion = QRegion();
137  painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons);
138  if (m_cachedPolygons.empty()) {
139  return;
140  }
141  if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) {
142  paintOutline(painter, viewport);
143  }
144  } else if (layer.endsWith(QLatin1String("/inline"))) {
145  if (m_cachedPolygons.empty()) {
146  return;
147  }
148  paintInline(painter, viewport);
149  } else if (layer.endsWith(QLatin1String("/label"))) {
150  if (!m_cachedPolygons.empty()) {
151  if (m_renderLabel) {
152  paintLabel(painter, viewport);
153  }
154  }
155  } else {
156  qDeleteAll(m_cachedPolygons);
157  m_cachedPolygons.clear();
158  m_cachedRegion = QRegion();
159  painter->polygonsFromLineString(*m_renderLineString, m_cachedPolygons);
160  if (m_cachedPolygons.empty()) {
161  return;
162  }
163  if (s_previousStyle != style().data()) {
164  configurePainterForLine(painter, viewport, false);
165  }
166  s_previousStyle = style().data();
167  for(const QPolygonF* itPolygon: m_cachedPolygons) {
168  painter->drawPolyline(*itPolygon);
169  }
170  }
171 }
172 
173 bool GeoLineStringGraphicsItem::contains(const QPoint &screenPosition, const ViewportParams *) const
174 {
175  if (m_penWidth <= 0.0) {
176  return false;
177  }
178 
179  if (m_cachedRegion.isNull()) {
180  QPainterPath painterPath;
181  for (auto polygon: m_cachedPolygons) {
182  painterPath.addPolygon(*polygon);
183  }
184  QPainterPathStroker stroker;
185  qreal const margin = 6.0;
186  stroker.setWidth(m_penWidth + margin);
187  QPainterPath strokePath = stroker.createStroke(painterPath);
188  m_cachedRegion = QRegion(strokePath.toFillPolygon().toPolygon(), Qt::WindingFill);
189  }
190  return m_cachedRegion.contains(screenPosition);
191 }
192 
193 void GeoLineStringGraphicsItem::handleRelationUpdate(const QVector<const GeoDataRelation *> &relations)
194 {
196  for (auto relation: relations) {
197  auto const ref = relation->osmData().tagValue(QStringLiteral("ref"));
198  if (relation->isVisible() && !ref.isEmpty()) {
199  names[relation->relationType()] << ref;
200  }
201  }
202  if (names.isEmpty()) {
203  m_name = feature()->name();
204  } else {
205  QStringList namesList;
206  for (auto iter = names.begin(); iter != names.end(); ++iter) {
207  QString value;
208  switch (iter.key()) {
209  case GeoDataRelation::UnknownType:
210  case GeoDataRelation::RouteRoad: break;
211  case GeoDataRelation::RouteDetour: value = tr("Detour"); break;
212  case GeoDataRelation::RouteFerry: value = tr("Ferry Route"); break;
213  case GeoDataRelation::RouteTrain: value = tr("Train"); break;
214  case GeoDataRelation::RouteSubway: value = tr("Subway"); break;
215  case GeoDataRelation::RouteTram: value = tr("Tram"); break;
216  case GeoDataRelation::RouteBus: value = tr("Bus"); break;
217  case GeoDataRelation::RouteTrolleyBus: value = tr("Trolley Bus"); break;
218  case GeoDataRelation::RouteBicycle: value = tr("Bicycle Route"); break;
219  case GeoDataRelation::RouteMountainbike: value = tr("Mountainbike Route"); break;
220  case GeoDataRelation::RouteFoot: value = tr("Walking Route"); break;
221  case GeoDataRelation::RouteHiking: value = tr("Hiking Route"); break;
222  case GeoDataRelation::RouteHorse: value = tr("Bridleway"); break;
223  case GeoDataRelation::RouteInlineSkates: value = tr("Inline Skates Route"); break;
224  case GeoDataRelation::RouteSkiDownhill: value = tr("Downhill Piste"); break;
225  case GeoDataRelation::RouteSkiNordic: value = tr("Nordic Ski Trail"); break;
226  case GeoDataRelation::RouteSkitour: value = tr("Skitour"); break;
227  case GeoDataRelation::RouteSled: value = tr("Sled Trail"); break;
228  }
229 
230  QStringList &references = iter.value();
231  std::sort(references.begin(), references.end());
232  auto const last = std::unique(references.begin(), references.end());
233  references.erase(last, references.end());
234  auto const routes = references.join(", ");
235  namesList << (value.isEmpty() ? routes : QStringLiteral("%1 %2").arg(value, routes));
236  }
237  auto const allRoutes = namesList.join(QStringLiteral("; "));
238  if (feature()->name().isEmpty()) {
239  m_name = allRoutes;
240  } else {
241  m_name = QStringLiteral("%1 (%2)").arg(feature()->name(), allRoutes);
242  }
243  }
244 }
245 
246 void GeoLineStringGraphicsItem::paintInline(GeoPainter* painter, const ViewportParams* viewport)
247 {
248  if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) {
249  return;
250  }
251 
252  if (s_previousStyle != style().data()) {
253  s_paintInline = configurePainterForLine(painter, viewport, false);
254  }
255  s_previousStyle = style().data();
256 
257  if (s_paintInline) {
258  m_renderLabel = painter->pen().widthF() >= 6.0f;
259  m_penWidth = painter->pen().widthF();
260  for(const QPolygonF* itPolygon: m_cachedPolygons) {
261  painter->drawPolyline(*itPolygon);
262  }
263  }
264 }
265 
266 void GeoLineStringGraphicsItem::paintOutline(GeoPainter *painter, const ViewportParams *viewport) const
267 {
268  if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) {
269  return;
270  }
271 
272  if (s_previousStyle != style().data()) {
273  s_paintOutline = configurePainterForLine(painter, viewport, true);
274  }
275  s_previousStyle = style().data();
276 
277  if (s_paintOutline) {
278  for(const QPolygonF* itPolygon: m_cachedPolygons) {
279  painter->drawPolyline(*itPolygon);
280  }
281  }
282 
283 }
284 
285 void GeoLineStringGraphicsItem::paintLabel(GeoPainter *painter, const ViewportParams *viewport) const
286 {
287  if ( ( !viewport->resolves( m_renderLineString->latLonAltBox(), 2) ) ) {
288  return;
289  }
290 
291  LabelPositionFlags labelPositionFlags = NoLabel;
292  bool isValid = configurePainterForLabel(painter, viewport, labelPositionFlags);
293 
294  if (isValid) {
295  GeoDataStyle::ConstPtr style = this->style();
296 
297  // Activate the lines below to paint a label background which
298  // prevents antialiasing overpainting glitches, but leads to
299  // other glitches.
300  //QColor const color = style->polyStyle().paintedColor();
301  //painter->setBackground(QBrush(color));
302  //painter->setBackgroundMode(Qt::OpaqueMode);
303 
304  const GeoDataLabelStyle& labelStyle = style->labelStyle();
305  painter->drawLabelsForPolygons(m_cachedPolygons, m_name, FollowLine,
306  labelStyle.paintedColor());
307  }
308 }
309 
310 
311 bool GeoLineStringGraphicsItem::configurePainterForLine(GeoPainter *painter, const ViewportParams *viewport, const bool isOutline) const
312 {
313  QPen currentPen = painter->pen();
314  GeoDataStyle::ConstPtr style = this->style();
315  if (!style) {
316  painter->setPen( QPen() );
317  painter->setBackground(QBrush(Qt::transparent));
318  painter->setBackgroundMode(Qt::TransparentMode);
319  }
320  else {
321  if (isOutline && !style->polyStyle().outline()) {
322  return false;
323  }
324 
325  const GeoDataLineStyle& lineStyle = style->lineStyle();
326 
327  // To save performance we avoid making changes to the painter's pen.
328  // So we first take a copy of the actual painter pen, make changes to it
329  // and only if the resulting pen is different from the actual pen
330  // we replace the painter's pen with our new pen.
331 
332  // We want to avoid the mandatory detach in QPen::setColor(),
333  // so we carefully check whether applying the setter is needed
334  const QColor linePaintedColor = (!isOutline && (lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine))
335  ? style->polyStyle().paintedColor()
336  : lineStyle.paintedColor();
337  if (currentPen.color() != linePaintedColor) {
338  if (linePaintedColor.alpha() == 255) {
339  currentPen.setColor(linePaintedColor);
340  }
341  else {
342  if ( painter->mapQuality() != Marble::HighQuality
343  && painter->mapQuality() != Marble::PrintQuality ) {
344  QColor penColor = linePaintedColor;
345  if (penColor.alpha() != 0) {
346  penColor.setAlpha( 255 );
347  }
348  if (currentPen.color() != penColor) {
349  currentPen.setColor( penColor );
350  }
351  }
352  else {
353  currentPen.setColor(linePaintedColor);
354  }
355  }
356  }
357 
358  const float lineWidth = lineStyle.width();
359  const float linePhysicalWidth = lineStyle.physicalWidth();
360  float newLineWidth = lineWidth;
361  if (linePhysicalWidth != 0.0) {
362  const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth;
363  newLineWidth = scaledLinePhysicalWidth > lineWidth
364  ? scaledLinePhysicalWidth
365  : lineWidth;
366  }
367 
368  if (!isOutline && lineStyle.cosmeticOutline() && lineStyle.penStyle() == Qt::SolidLine) {
369  if (newLineWidth > 2.5) {
370  newLineWidth -= 2.0;
371  }
372  }
373 
374  const qreal lineDrawThreshold = isOutline ? 2.5 : 0.5;
375 
376  // We want to avoid the mandatory detach in QPen::setWidthF(),
377  // so we carefully check whether applying the setter is needed
378  if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) {
379  if (newLineWidth < lineDrawThreshold) {
380  if (painter->pen().style() != Qt::NoPen) {
381  painter->setPen(Qt::NoPen);
382  }
383  return false; // Don't draw any outline and abort painter configuration early
384  }
385  currentPen.setWidthF(newLineWidth);
386  }
387 
388  // No need to avoid detaches inside QPen::setCapsStyle() since Qt does that for us
389  const Qt::PenCapStyle lineCapStyle = lineStyle.capStyle();
390  currentPen.setCapStyle(lineCapStyle);
391 
392  // No need to avoid detaches inside QPen::setStyle() since Qt does that for us
393  if (painter->mapQuality() == HighQuality || painter->mapQuality() == PrintQuality) {
394  const Qt::PenStyle linePenStyle = lineStyle.penStyle();
395  currentPen.setStyle(linePenStyle);
396 
397  if (linePenStyle == Qt::CustomDashLine) {
398  // We want to avoid the mandatory detach in QPen::setDashPattern(),
399  // so we carefully check whether applying the setter is needed
400  if (currentPen.dashPattern() != lineStyle.dashPattern()) {
401  currentPen.setDashPattern(lineStyle.dashPattern());
402  }
403  }
404  } else {
405  currentPen.setStyle(Qt::SolidLine);
406  }
407 
408  if ( painter->pen() != currentPen ) {
409  painter->setPen( currentPen );
410  }
411 
412  // Set the background
413 
414  if (!isOutline) {
415  if (lineStyle.background()) {
416  QBrush brush = painter->background();
417  brush.setColor(style->polyStyle().paintedColor());
418  painter->setBackground( brush );
419 
420  painter->setBackgroundMode( Qt::OpaqueMode );
421  }
422  else {
423  painter->setBackground(QBrush(Qt::transparent));
424  painter->setBackgroundMode(Qt::TransparentMode);
425  }
426  }
427  else {
428  painter->setBackground(QBrush(Qt::transparent));
429  painter->setBackgroundMode(Qt::TransparentMode);
430  }
431  }
432 
433  return true;
434 }
435 
436 bool GeoLineStringGraphicsItem::configurePainterForLabel(GeoPainter *painter, const ViewportParams *viewport, LabelPositionFlags &labelPositionFlags) const
437 {
438  QPen currentPen = painter->pen();
439  GeoDataStyle::ConstPtr style = this->style();
440  if (!style) {
441  painter->setPen( QPen() );
442  }
443  else {
444  const GeoDataLineStyle& lineStyle = style->lineStyle();
445 
446  // To save performance we avoid making changes to the painter's pen.
447  // So we first take a copy of the actual painter pen, make changes to it
448  // and only if the resulting pen is different from the actual pen
449  // we replace the painter's pen with our new pen.
450 
451  // We want to avoid the mandatory detach in QPen::setColor(),
452  // so we carefully check whether applying the setter is needed
453 
454  const float lineWidth = lineStyle.width();
455  const float linePhysicalWidth = lineStyle.physicalWidth();
456  float newLineWidth = lineWidth;
457  if (linePhysicalWidth != 0.0) {
458  const float scaledLinePhysicalWidth = float(viewport->radius()) / EARTH_RADIUS * linePhysicalWidth;
459  newLineWidth = scaledLinePhysicalWidth > lineWidth
460  ? scaledLinePhysicalWidth
461  : lineWidth;
462  }
463 
464  // We want to avoid the mandatory detach in QPen::setWidthF(),
465  // so we carefully check whether applying the setter is needed
466  if (currentPen.widthF() != newLineWidth && newLineWidth != 0.0) {
467  if (newLineWidth < 6.0) {
468  return false; // Don't draw any labels and abort painter configuration early
469  }
470  currentPen.setWidthF(newLineWidth);
471  }
472 
473 
474  if ( painter->pen() != currentPen ) {
475  painter->setPen( currentPen );
476  }
477 // else qDebug() << "Detach and painter change successfully Avoided!" << Q_FUNC_INFO;
478 
479  if (painter->brush().color() != Qt::transparent) {
480  painter->setBrush(QColor(Qt::transparent));
481  }
482  if (painter->backgroundMode() == Qt::OpaqueMode) {
483  painter->setBackgroundMode(Qt::TransparentMode);
484  painter->setBackground(QBrush(Qt::transparent));
485  }
486 
487  // label styles
488  const GeoDataLabelStyle& labelStyle = style->labelStyle();
489  painter->setFont(labelStyle.font() );
490  switch (labelStyle.alignment()) {
491  case GeoDataLabelStyle::Corner:
492  case GeoDataLabelStyle::Right:
493  labelPositionFlags |= LineStart;
494  break;
495  case GeoDataLabelStyle::Center:
496  labelPositionFlags |= LineCenter;
497  break;
498  }
499  }
500 
501  return true;
502 }
503 
504 bool GeoLineStringGraphicsItem::canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b)
505 {
506  return a.sphericalDistanceTo(b) * EARTH_RADIUS < 0.1;
507 }
508 
509 }
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qreal widthF() const const
void setWidth(qreal width)
void addPolygon(const QPolygonF &polygon)
TransparentMode
void ref()
QHash::iterator begin()
T & first()
QPolygonF toFillPolygon(const QMatrix &matrix) const const
void setWidthF(qreal width)
void setColor(const QColor &color)
WindingFill
void setDashPattern(const QVector< qreal > &pattern)
PenCapStyle
bool isEmpty() const const
QPainterPath createStroke(const QPainterPath &path) const const
void setAlpha(int alpha)
int alpha() const const
Binds a QML item to a specific geodetic location in screen coordinates.
QString join(const QString &separator) const const
@ HighQuality
High quality (e.g. antialiasing for lines)
Definition: MarbleGlobal.h:78
QList::iterator erase(QList::iterator pos)
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
bool isValid(QStringView ifopt)
QVector< qreal > dashPattern() const const
QString name(StandardShortcut id)
bool isEmpty() const const
void setColor(const QColor &color)
QColor color() const const
QList::iterator begin()
@ PrintQuality
Print quality.
Definition: MarbleGlobal.h:79
void setStyle(Qt::PenStyle style)
SolidLine
QList::iterator end()
QPolygon toPolygon() const const
Category category(StandardShortcut id)
void setCapStyle(Qt::PenCapStyle style)
T value(int i) const const
transparent
QHash::iterator end()
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Sep 25 2023 03:50:19 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.