Marble

GeoLineStringGraphicsItem.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2009 Andrew Manson <g.real.ate@gmail.com>
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
23namespace Marble
24{
25
26const GeoDataStyle *GeoLineStringGraphicsItem::s_previousStyle = nullptr;
27bool GeoLineStringGraphicsItem::s_paintInline = true;
28bool GeoLineStringGraphicsItem::s_paintOutline = true;
29
30GeoLineStringGraphicsItem::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
49GeoLineStringGraphicsItem::~GeoLineStringGraphicsItem()
50{
51 qDeleteAll(m_cachedPolygons);
52}
53
54
55void GeoLineStringGraphicsItem::setLineString( const GeoDataLineString* lineString )
56{
57 m_lineString = lineString;
58 m_renderLineString = lineString;
59}
60
61const GeoDataLineString *GeoLineStringGraphicsItem::lineString() const
62{
63 return m_lineString;
64}
65
66GeoDataLineString 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
118void GeoLineStringGraphicsItem::setMergedLineString(const GeoDataLineString &mergedLineString)
119{
120 m_mergedLineString = mergedLineString;
121 m_renderLineString = mergedLineString.isEmpty() ? m_lineString : &m_mergedLineString;
122}
123
124const GeoDataLatLonAltBox& GeoLineStringGraphicsItem::latLonAltBox() const
125{
126 return m_renderLineString->latLonAltBox();
127}
128
129void 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
173bool 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
193void 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
246void 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
266void 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
285void 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
311bool 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
436bool 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!";
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
504bool GeoLineStringGraphicsItem::canMerge(const GeoDataCoordinates &a, const GeoDataCoordinates &b)
505{
506 return a.sphericalDistanceTo(b) * EARTH_RADIUS < 0.1;
507}
508
509}
This file contains the headers for ViewportParams.
bool isValid(QStringView ifopt)
Category category(StandardShortcut id)
QString name(StandardShortcut id)
Binds a QML item to a specific geodetic location in screen coordinates.
@ HighQuality
High quality (e.g. antialiasing for lines)
@ PrintQuality
Print quality.
void setColor(Qt::GlobalColor color)
int alpha() const const
void setAlpha(int alpha)
iterator begin()
iterator end()
bool isEmpty() const const
iterator begin()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
T & first()
bool isEmpty() const const
T value(qsizetype i) const const
void addPolygon(const QPolygonF &polygon)
QPolygonF toFillPolygon(const QTransform &matrix) const const
QPainterPath createStroke(const QPainterPath &path) const const
void setWidth(qreal width)
QColor color() const const
QList< qreal > dashPattern() const const
void setCapStyle(Qt::PenCapStyle style)
void setColor(const QColor &color)
void setDashPattern(const QList< qreal > &pattern)
void setStyle(Qt::PenStyle style)
void setWidthF(qreal width)
qreal widthF() const const
QPolygon toPolygon() const const
QString arg(Args &&... args) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
QString join(QChar separator) const const
TransparentMode
WindingFill
transparent
PenCapStyle
SolidLine
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:49:04 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.