KOSMIndoorMap

scenecontroller.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "scenecontroller.h"
8#include "logging.h"
9#include "render-logging.h"
10
11#include "iconloader_p.h"
12#include "penwidthutil_p.h"
13#include "poleofinaccessibilityfinder_p.h"
14#include "scenegeometry_p.h"
15#include "openinghourscache_p.h"
16#include "texturecache_p.h"
17#include "../style/mapcssdeclaration_p.h"
18#include "../style/mapcssstate_p.h"
19
20#include <KOSMIndoorMap/MapData>
21#include <KOSMIndoorMap/MapCSSResult>
22#include <KOSMIndoorMap/MapCSSStyle>
23#include <KOSMIndoorMap/OverlaySource>
24#include <KOSMIndoorMap/SceneGraph>
25#include <KOSMIndoorMap/View>
26
27#include <osm/element.h>
28#include <osm/datatypes.h>
29
30#include <QDebug>
31#include <QElapsedTimer>
32#include <QGuiApplication>
33#include <QPalette>
34
35namespace KOSMIndoorMap {
36class SceneControllerPrivate
37{
38public:
39 MapData m_data;
40 const MapCSSStyle *m_styleSheet = nullptr;
41 const View *m_view = nullptr;
42 std::vector<QPointer<AbstractOverlaySource>> m_overlaySources;
43 mutable std::vector<OSM::Element> m_hiddenElements;
44 OSM::Element m_hoverElement;
45
46 MapCSSResult m_styleResult;
47 QColor m_defaultTextColor;
48 QFont m_defaultFont;
49 QPolygonF m_labelPlacementPath;
50 TextureCache m_textureCache;
51 IconLoader m_iconLoader;
52 OpeningHoursCache m_openingHours;
53 PoleOfInaccessibilityFinder m_piaFinder;
54
55 OSM::TagKey m_layerTag;
56 OSM::TagKey m_typeTag;
57 OSM::Languages m_langs;
58
59 bool m_dirty = true;
60 bool m_overlay = false;
61};
62}
63
64using namespace KOSMIndoorMap;
65
66SceneController::SceneController() : d(new SceneControllerPrivate)
67{
69}
70SceneController::~SceneController() = default;
71
72void SceneController::setMapData(const MapData &data)
73{
74 d->m_data = data;
75 if (!d->m_data.isEmpty()) {
76 d->m_layerTag = data.dataSet().tagKey("layer");
77 d->m_typeTag = data.dataSet().tagKey("type");
78 d->m_openingHours.setMapData(data);
79 } else {
80 d->m_layerTag = {};
81 d->m_typeTag = {};
82 d->m_openingHours.setMapData(MapData());
83 }
84 d->m_dirty = true;
85}
86
87void SceneController::setStyleSheet(const MapCSSStyle *styleSheet)
88{
89 d->m_styleSheet = styleSheet;
90 d->m_dirty = true;
91}
92
93void SceneController::setView(const View *view)
94{
95 d->m_view = view;
96 QObject::connect(view, &View::timeChanged, view, [this]() { d->m_dirty = true; });
97 d->m_dirty = true;
98}
99
100void SceneController::setOverlaySources(std::vector<QPointer<AbstractOverlaySource>> &&overlays)
101{
102 d->m_overlaySources = std::move(overlays);
103 d->m_dirty = true;
104}
105
107{
108 // TODO we could potentially do this more fine-grained?
109 d->m_dirty = true;
110}
111
113{
114 QElapsedTimer sgUpdateTimer;
115 sgUpdateTimer.start();
116
117 // check if we are set up completely yet (we can't rely on a defined order with QML)
118 if (!d->m_view || !d->m_styleSheet) {
119 return;
120 }
121
122 // check if the scene is dirty at all
123 if (sg.zoomLevel() == (int)d->m_view->zoomLevel() && sg.currentFloorLevel() == d->m_view->level() && !d->m_dirty) {
124 return;
125 }
126 sg.setZoomLevel(d->m_view->zoomLevel());
127 sg.setCurrentFloorLevel(d->m_view->level());
128 d->m_openingHours.setTimeRange(d->m_view->beginTime(), d->m_view->endTime());
129 d->m_dirty = false;
130
131 sg.beginSwap();
132 updateCanvas(sg);
133
134 if (d->m_data.isEmpty()) { // if we don't have map data yet, we just need to get canvas styling here
135 sg.endSwap();
136 return;
137 }
138
139 // find all intermediate levels below or above the currently selected "full" level
140 auto it = d->m_data.levelMap().find(MapLevel(d->m_view->level()));
141 if (it == d->m_data.levelMap().end()) {
142 return;
143 }
144
145 auto beginIt = it;
146 if (beginIt != d->m_data.levelMap().begin()) {
147 do {
148 --beginIt;
149 } while (!(*beginIt).first.isFullLevel() && beginIt != d->m_data.levelMap().begin());
150 ++beginIt;
151 }
152
153 auto endIt = it;
154 for (++endIt; endIt != d->m_data.levelMap().end(); ++endIt) {
155 if ((*endIt).first.isFullLevel()) {
156 break;
157 }
158 }
159
160 // collect elements that the overlay want to hide
161 d->m_hiddenElements.clear();
162 for (const auto &overlaySource : d->m_overlaySources) {
163 overlaySource->hiddenElements(d->m_hiddenElements);
164 }
165 std::sort(d->m_hiddenElements.begin(), d->m_hiddenElements.end());
166
167 // for each level, update or create scene graph elements, after a some basic bounding box check
168 const auto geoBbox = d->m_view->mapSceneToGeo(d->m_view->sceneBoundingBox());
169 for (auto it = beginIt; it != endIt; ++it) {
170 for (auto e : (*it).second) {
171 if (OSM::intersects(geoBbox, e.boundingBox()) && !std::binary_search(d->m_hiddenElements.begin(), d->m_hiddenElements.end(), e)) {
172 updateElement(e, (*it).first.numericLevel(), sg);
173 }
174 }
175 }
176
177 // update overlay elements
178 d->m_overlay = true;
179 for (const auto &overlaySource : d->m_overlaySources) {
180 overlaySource->forEach(d->m_view->level(), [this, &geoBbox, &sg](OSM::Element e, int floorLevel) {
181 if (OSM::intersects(geoBbox, e.boundingBox()) && e.type() != OSM::Type::Null) {
182 updateElement(e, floorLevel, sg);
183 }
184 });
185 }
186 d->m_overlay = false;
187
188 sg.zSort();
189 sg.endSwap();
190
191 qCDebug(RenderLog) << "updated scenegraph took" << sgUpdateTimer.elapsed() << "ms";
192}
193
194void SceneController::updateCanvas(SceneGraph &sg) const
195{
196 sg.setBackgroundColor(QGuiApplication::palette().color(QPalette::Base));
197 d->m_defaultTextColor = QGuiApplication::palette().color(QPalette::Text);
198 d->m_defaultFont = QGuiApplication::font();
199
200 MapCSSState state;
201 state.zoomLevel = d->m_view->zoomLevel();
202 state.floorLevel = d->m_view->level();
203 d->m_styleSheet->evaluateCanvas(state, d->m_styleResult);
204 for (auto decl : d->m_styleResult[{}].declarations()) {
205 switch (decl->property()) {
207 sg.setBackgroundColor(decl->colorValue());
208 break;
210 d->m_defaultTextColor = decl->colorValue();
211 break;
212 default:
213 break;
214 }
215 }
216}
217
218void SceneController::updateElement(OSM::Element e, int level, SceneGraph &sg) const
219{
220 MapCSSState state;
221 state.element = e;
222 state.zoomLevel = d->m_view->zoomLevel();
223 state.floorLevel = d->m_view->level();
224 state.openingHours = &d->m_openingHours;
225 state.state = d->m_hoverElement == e ? MapCSSElementState::Hovered : MapCSSElementState::NoState;
226 d->m_styleSheet->evaluate(std::move(state), d->m_styleResult);
227 for (const auto &result : d->m_styleResult.results()) {
228 updateElement(e, level, sg, result);
229 }
230}
231
232[[nodiscard]] static bool canWordWrap(const QString &s)
233{
234 return std::any_of(s.begin(), s.end(), [](QChar c) { return !c.isLetter(); });
235}
236
237void SceneController::updateElement(OSM::Element e, int level, SceneGraph &sg, const MapCSSResultLayer &result) const
238{
239 if (result.hasAreaProperties()) {
240 PolygonBaseItem *item = nullptr;
241 std::unique_ptr<SceneGraphItemPayload> baseItem;
242 if (e.type() == OSM::Type::Relation && e.tagValue(d->m_typeTag) == "multipolygon") {
243 baseItem = sg.findOrCreatePayload<MultiPolygonItem>(e, level, result.layerSelector());
244 auto i = static_cast<MultiPolygonItem*>(baseItem.get());
245 if (i->path.isEmpty()) {
246 i->path = createPath(e, d->m_labelPlacementPath);
247 } else if (result.hasLabelProperties()) {
248 SceneGeometry::outerPolygonFromPath(i->path, d->m_labelPlacementPath);
249 }
250 item = i;
251 } else {
252 baseItem = sg.findOrCreatePayload<PolygonItem>(e, level, result.layerSelector());
253 auto i = static_cast<PolygonItem*>(baseItem.get());
254 if (i->polygon.isEmpty()) {
255 i->polygon = createPolygon(e);
256 }
257 d->m_labelPlacementPath = i->polygon;
258 item = i;
259 }
260
261 double lineOpacity = 1.0;
262 double casingOpacity = 1.0;
263 double fillOpacity = 1.0;
264 bool hasTexture = false;
265 item->z = 0;
266 initializePen(item->pen);
267 initializePen(item->casingPen);
268 for (auto decl : result.declarations()) {
269 applyGenericStyle(decl, item);
270 applyPenStyle(e, decl, item->pen, lineOpacity, item->penWidthUnit);
271 applyCasingPenStyle(e, decl, item->casingPen, casingOpacity, item->casingPenWidthUnit);
272 switch (decl->property()) {
274 item->fillBrush.setColor(decl->colorValue());
275 item->fillBrush.setStyle(Qt::SolidPattern);
276 break;
278 fillOpacity = decl->doubleValue();
279 break;
281 item->textureBrush.setTextureImage(d->m_textureCache.image(decl->stringValue()));
282 hasTexture = true;
283 break;
284 default:
285 break;
286 }
287 }
288 finalizePen(item->pen, lineOpacity);
289 finalizePen(item->casingPen, casingOpacity);
290 if (item->fillBrush.style() == Qt::SolidPattern && item->textureBrush.style() == Qt::NoBrush && fillOpacity < 1.0) {
291 auto c = item->fillBrush.color();
292 c.setAlphaF(c.alphaF() * fillOpacity);
293 item->fillBrush.setColor(c);
294 }
295 if (item->fillBrush.color().alphaF() == 0.0) {
296 item->fillBrush.setStyle(Qt::NoBrush);
297 }
298 if (hasTexture && item->textureBrush.style() != Qt::NoBrush && fillOpacity > 0.0) {
299 auto c = item->textureBrush.color();
300 c.setAlphaF(fillOpacity);
301 item->textureBrush.setColor(c);
302 } else {
303 item->textureBrush.setStyle(Qt::NoBrush);
304 }
305
306 addItem(sg, e, level, result, std::move(baseItem));
307 } else if (result.hasLineProperties()) {
308 auto baseItem = sg.findOrCreatePayload<PolylineItem>(e, level, result.layerSelector());
309 auto item = static_cast<PolylineItem*>(baseItem.get());
310 if (item->path.isEmpty()) {
311 item->path = createPolygon(e);
312 }
313
314 double lineOpacity = 1.0;
315 double casingOpacity = 1.0;
316 item->z = 0;
317 initializePen(item->pen);
318 initializePen(item->casingPen);
319 for (auto decl : result.declarations()) {
320 applyGenericStyle(decl, item);
321 applyPenStyle(e, decl, item->pen, lineOpacity, item->penWidthUnit);
322 applyCasingPenStyle(e, decl, item->casingPen, casingOpacity, item->casingPenWidthUnit);
323 }
324 finalizePen(item->pen, lineOpacity);
325 finalizePen(item->casingPen, casingOpacity);
326
327 d->m_labelPlacementPath = item->path;
328 addItem(sg, e, level, result, std::move(baseItem));
329 }
330
331 if (result.hasLabelProperties()) {
332 QString text;
333 auto textDecl = result.declaration(MapCSSProperty::Text);
334 if (!textDecl) {
335 textDecl = result.declaration(MapCSSProperty::ShieldText);
336 }
337
338 if (textDecl) {
339 if (!textDecl->keyValue().isEmpty()) {
340 text = QString::fromUtf8(e.tagValue(d->m_langs, textDecl->keyValue().constData()));
341 } else {
342 text = textDecl->stringValue();
343 }
344 }
345
346 const auto iconDecl = result.declaration(MapCSSProperty::IconImage);
347
348 if (!text.isEmpty() || iconDecl) {
349 auto baseItem = sg.findOrCreatePayload<LabelItem>(e, level, result.layerSelector());
350 auto item = static_cast<LabelItem*>(baseItem.get());
351 item->text.setText(text);
352 item->textIsSet = !text.isEmpty();
353 item->textOutputSizeCache = {};
354 item->font = d->m_defaultFont;
355 item->color = d->m_defaultTextColor;
356 item->iconSize = {};
357 item->textOffset = 0;
358 item->z = 0;
359
360 double textOpacity = 1.0;
361 double shieldOpacity = 1.0;
362 bool forceCenterPosition = false;
363 bool forceLinePosition = false;
364 IconData iconData;
365 for (auto decl : result.declarations()) {
366 applyGenericStyle(decl, item);
367 applyFontStyle(decl, item->font);
368 switch (decl->property()) {
370 item->color = decl->colorValue();
371 break;
373 textOpacity = decl->doubleValue();
374 break;
376 item->casingColor = decl->colorValue();
377 break;
379 item->casingWidth = decl->doubleValue();
380 break;
382 item->shieldColor = decl->colorValue();
383 break;
385 shieldOpacity = decl->doubleValue();
386 break;
388 item->frameColor = decl->colorValue();
389 break;
391 item->frameWidth = decl->doubleValue();
392 break;
394 switch (decl->textPosition()) {
395 case MapCSSDeclaration::Position::Line:
396 forceLinePosition = true;
397 if (d->m_labelPlacementPath.size() > 1) {
398 item->angle = SceneGeometry::polylineMidPointAngle(d->m_labelPlacementPath);
399 }
400 break;
401 case MapCSSDeclaration::Position::Center:
402 forceCenterPosition = true;
403 break;
404 case MapCSSDeclaration::Position::NoPostion:
405 break;
406 }
407 break;
409 item->textOffset = decl->doubleValue();
410 break;
412 // work around for QStaticText misbehaving when we have a max width but can't actually word-wrap
413 // far from perfect but covers the most common cases
414 if (canWordWrap(text)) {
415 item->text.setTextWidth(decl->intValue());
416 }
417 break;
419 if (!decl->keyValue().isEmpty()) {
420 iconData.name = QString::fromUtf8(e.tagValue(decl->keyValue().constData()));
421 } else {
422 iconData.name = decl->stringValue();
423 }
424 break;
426 item->iconSize.setHeight(PenWidthUtil::penWidth(e, decl, item->iconHeightUnit));
427 break;
429 item->iconSize.setWidth(PenWidthUtil::penWidth(e, decl, item->iconWidthUnit));
430 break;
432 {
433 const auto alpha = iconData.color.alphaF();
434 iconData.color = decl->colorValue().rgb();
435 iconData.color.setAlphaF(alpha);
436 break;
437 }
439 iconData.color.setAlphaF(decl->doubleValue());
440 break;
442 item->haloColor = decl->colorValue();
443 break;
445 item->haloRadius = decl->doubleValue();
446 break;
448 item->allowIconOverlap = decl->boolValue();
449 break;
451 item->allowTextOverlap = decl->boolValue();
452 break;
453 default:
454 break;
455 }
456 }
457
458 if (item->pos.isNull()) {
459 if ((result.hasAreaProperties() || forceCenterPosition) && !forceLinePosition) {
460 // for simple enough shapes we can use the faster centroid rather than the expensive PIA
461 if (d->m_labelPlacementPath.size() > 6) {
462 item->pos = d->m_piaFinder.find(d->m_labelPlacementPath);
463 } else {
464 item->pos = SceneGeometry::polygonCentroid(d->m_labelPlacementPath);
465 }
466 } else if (result.hasLineProperties() || forceLinePosition) {
467 item->pos = SceneGeometry::polylineMidPoint(d->m_labelPlacementPath);
468 }
469 if (item->pos.isNull()) {
470 item->pos = d->m_view->mapGeoToScene(e.center()); // node or something failed above
471 }
472 }
473
474 if (item->color.isValid() && textOpacity < 1.0) {
475 auto c = item->color;
476 c.setAlphaF(c.alphaF() * textOpacity);
477 item->color = c;
478 }
479 if (item->shieldColor.isValid() && shieldOpacity < 1.0) {
480 auto c = item->shieldColor;
481 c.setAlphaF(c.alphaF() * shieldOpacity);
482 item->shieldColor = c;
483 }
484 if (!iconData.name.isEmpty() && iconData.color.alphaF() > 0.0) {
485 if (!iconData.color.isValid()) {
486 iconData.color = d->m_defaultTextColor;
487 }
488 item->icon = d->m_iconLoader.loadIcon(iconData);
489 item->iconOpacity = iconData.color.alphaF();
490 }
491 if (!item->icon.isNull()) {
492 const auto iconSourceSize = item->icon.availableSizes().at(0);
493 const auto aspectRatio = (double)iconSourceSize.width() / (double)iconSourceSize.height();
494 if (item->iconSize.width() <= 0.0 && item->iconSize.height() <= 0.0) {
495 item->iconSize = iconSourceSize;
496 } else if (item->iconSize.width() <= 0.0) {
497 item->iconSize.setWidth(item->iconSize.height() * aspectRatio);
498 } else if (item->iconSize.height() <= 0.0) {
499 item->iconSize.setHeight(item->iconSize.width() / aspectRatio);
500 }
501 }
502
503 if (!item->text.text().isEmpty()) {
504 QTextOption opt;
507 item->text.setTextOption(opt);
508 // do not use QStaticText::prepare here:
509 // the vast majority of text items will likely not be shown at all for being overlapped or out of view
510 // and pre-computing them is too expensive. Instead this will happen as needed on first use, for only
511 // a smaller amounts at a time.
512 // item->text.prepare({}, item->font);
513
514 // discard labels that are longer than the line they are aligned with
515 if (result.hasLineProperties() && d->m_labelPlacementPath.size() > 1 && item->angle != 0.0) {
516 const auto sceneLen = SceneGeometry::polylineLength(d->m_labelPlacementPath);
517 const auto sceneP1 = d->m_view->viewport().topLeft();
518 const auto sceneP2 = QPointF(sceneP1.x() + sceneLen, sceneP1.y());
519 const auto screenP1 = d->m_view->mapSceneToScreen(sceneP1);
520 const auto screenP2 = d->m_view->mapSceneToScreen(sceneP2);
521 const auto screenLen = screenP2.x() - screenP1.x();
522 if (screenLen < item->text.size().width()) {
523 item->text = {};
524 }
525 }
526
527 // put texts below icons by default
528 if (!item->icon.isNull() && item->textOffset == 0.0) {
529 item->textOffset = item->iconSize.height(); // ### what about heights in meters?
530 }
531 }
532
533 if (!item->icon.isNull() || !item->text.text().isEmpty()) {
534 addItem(sg, e, level, result, std::move(baseItem));
535 }
536 }
537 }
538}
539
540QPolygonF SceneController::createPolygon(OSM::Element e) const
541{
542 const auto path = e.outerPath(d->m_data.dataSet());
543 if (path.empty()) {
544 return {};
545 }
546
547 QPolygonF poly;
548 // Element::outerPath takes care of re-assembling broken up line segments
549 // the below takes care of properly merging broken up polygons
550 for (auto it = path.begin(); it != path.end();) {
551 QPolygonF subPoly;
552 subPoly.reserve(path.size());
553 OSM::Id pathBegin = (*it)->id;
554
555 auto subIt = it;
556 for (; subIt != path.end(); ++subIt) {
557 subPoly.push_back(d->m_view->mapGeoToScene((*subIt)->coordinate));
558 if ((*subIt)->id == pathBegin && subIt != it && subIt != std::prev(path.end())) {
559 ++subIt;
560 break;
561 }
562 }
563 it = subIt;
564 poly = poly.isEmpty() ? std::move(subPoly) : poly.united(subPoly);
565 }
566 return poly;
567}
568
569// @see https://wiki.openstreetmap.org/wiki/Relation:multipolygon
570QPainterPath SceneController::createPath(const OSM::Element e, QPolygonF &outerPath) const
571{
572 assert(e.type() == OSM::Type::Relation);
573 outerPath = createPolygon(e); // TODO this is actually not correct for the multiple outer polygon case
575 path.setFillRule(Qt::OddEvenFill);
576
577 for (const auto &mem : e.relation()->members) {
578 const bool isInner = std::strcmp(mem.role().name(), "inner") == 0;
579 const bool isOuter = std::strcmp(mem.role().name(), "outer") == 0;
580 if (mem.type() != OSM::Type::Way || (!isInner && !isOuter)) {
581 continue;
582 }
583 if (auto way = d->m_data.dataSet().way(mem.id)) {
584 const auto subPoly = createPolygon(OSM::Element(way));
585 if (subPoly.isEmpty()) {
586 continue;
587 }
588 path.addPolygon(subPoly);
589 path.closeSubpath();
590 }
591 }
592
593 return path;
594}
595
596void SceneController::applyGenericStyle(const MapCSSDeclaration *decl, SceneGraphItemPayload *item) const
597{
598 if (decl->property() == MapCSSProperty::ZIndex) {
599 item->z = decl->intValue();
600 }
601}
602
603void SceneController::applyPenStyle(OSM::Element e, const MapCSSDeclaration *decl, QPen &pen, double &opacity, Unit &unit) const
604{
605 switch (decl->property()) {
607 pen.setColor(decl->colorValue());
608 break;
610 pen.setWidthF(PenWidthUtil::penWidth(e, decl, unit));
611 break;
613 pen.setDashPattern(decl->dashesValue());
614 break;
616 pen.setCapStyle(decl->capStyle());
617 break;
619 pen.setJoinStyle(decl->joinStyle());
620 break;
622 opacity = decl->doubleValue();
623 break;
624 default:
625 break;
626 }
627}
628
629void SceneController::applyCasingPenStyle(OSM::Element e, const MapCSSDeclaration *decl, QPen &pen, double &opacity, Unit &unit) const
630{
631 switch (decl->property()) {
633 pen.setColor(decl->colorValue());
634 break;
636 pen.setWidthF(PenWidthUtil::penWidth(e, decl, unit));
637 break;
639 pen.setDashPattern(decl->dashesValue());
640 break;
642 pen.setCapStyle(decl->capStyle());
643 break;
645 pen.setJoinStyle(decl->joinStyle());
646 break;
648 opacity = decl->doubleValue();
649 break;
650 default:
651 break;
652 }
653}
654
655void SceneController::applyFontStyle(const MapCSSDeclaration *decl, QFont &font) const
656{
657 switch (decl->property()) {
659 font.setFamily(decl->stringValue());
660 break;
662 if (decl->unit() == MapCSSDeclaration::Pixels) {
663 font.setPixelSize(decl->doubleValue());
664 } else {
665 font.setPointSizeF(decl->doubleValue());
666 }
667 break;
669 font.setBold(decl->isBoldStyle());
670 break;
672 font.setItalic(decl->isItalicStyle());
673 break;
675 font.setCapitalization(decl->capitalizationStyle());
676 break;
678 font.setUnderline(decl->isUnderlineStyle());
679 break;
681 font.setCapitalization(decl->capitalizationStyle());
682 break;
683 default:
684 break;
685 }
686}
687
688void SceneController::initializePen(QPen &pen) const
689{
691 pen.setWidthF(0.0);
692
693 // default according to spec
697}
698
699void SceneController::finalizePen(QPen &pen, double opacity) const
700{
701 if (pen.color().isValid() && opacity < 1.0) {
702 auto c = pen.color();
703 c.setAlphaF(c.alphaF() * opacity);
704 pen.setColor(c);
705 }
706
707 if (pen.color().alphaF() == 0.0 || pen.widthF() == 0.0) {
708 pen.setStyle(Qt::NoPen); // so the renderer can skip this entirely
709 }
710
711 // normalize dash pattern, as QPainter scales that with the line width
712 if (pen.widthF() > 0.0 && !pen.dashPattern().isEmpty()) {
713 auto dashes = pen.dashPattern();
714 std::for_each(dashes.begin(), dashes.end(), [pen](double &d) { d /= pen.widthF(); });
715 pen.setDashPattern(std::move(dashes));
716 }
717}
718
719void SceneController::addItem(SceneGraph &sg, OSM::Element e, int level, const MapCSSResultLayer &result, std::unique_ptr<SceneGraphItemPayload> &&payload) const
720{
721 SceneGraphItem item;
722 item.element = e;
723 item.layerSelector = result.layerSelector();
724 item.level = level;
725 item.payload = std::move(payload);
726
727 // get the OSM layer, if set
728 if (!d->m_overlay) {
729 auto layerStr = result.tagValue(d->m_layerTag);
730 if (layerStr.isNull()) {
731 layerStr = e.tagValue(d->m_layerTag);
732 }
733 if (!layerStr.isEmpty()) {
734 bool success = false;
735 const auto layer = layerStr.toInt(&success);
736 if (success) {
737
738 // ### Ignore layer information when it matches the level
739 // This is very wrong according to the specification, however it looks that in many places
740 // layer and level tags aren't correctly filled, possibly a side-effect of layer pre-dating
741 // level and layers not having been properly updated when retrofitting level information
742 // Strictly following the MapCSS rendering order yields sub-optimal results in that case, with
743 // relevant elements being hidden.
744 //
745 // Ideally we find a way to detect the presence of that problem, and only then enabling this
746 // workaround, but until we have this, this seems to produce better results in all tests.
747 if (level != layer * 10) {
748 item.layer = layer;
749 }
750 } else {
751 qCWarning(Log) << "Invalid layer:" << e.url() << layerStr;
752 }
753 }
754 } else {
755 item.layer = std::numeric_limits<int>::max();
756 }
757
758 sg.addItem(std::move(item));
759}
760
762{
763 return d->m_hoverElement;
764}
765
766void SceneController::setHoveredElement(OSM::Element element)
767{
768 if (d->m_hoverElement == element) {
769 return;
770 }
771 d->m_hoverElement = element;
772 d->m_dirty = true;
773}
A text or icon label.
Result of MapCSS stylesheet evaluation for a single layer selector.
QByteArray tagValue(OSM::TagKey key) const
Tag lookup for tags overridden by the style sheet.
LayerSelectorKey layerSelector() const
The layer selector for this result.
bool hasLineProperties() const
Returns true if a way/line needs to be drawn.
bool hasLabelProperties() const
Returns true if a label needs to be drawn.
const MapCSSDeclaration * declaration(MapCSSProperty prop) const
Returns the declaration for property @prop, or nullptr is this property isn't set.
bool hasAreaProperties() const
Returns true if an area/polygon needs to be drawn.
Result of MapCSS stylesheet evaluation for all layer selectors.
A parsed MapCSS style sheet.
Definition mapcssstyle.h:33
Raw OSM map data, separated by levels.
Definition mapdata.h:60
A floor level.
Definition mapdata.h:28
Multi-polygon item, used for polygons with "holes" in them.
Base item for filled polygons.
A single filled polygon.
A path/way/line item in the scenegraph.
OSM::Element hoveredElement() const
Set currently hovered element.
void overlaySourceUpdated()
Overlay dirty state tracking.
void updateScene(SceneGraph &sg) const
Creates or updates sg based on the currently set style and view settings.
Payload base class for scene graph items.
Scene graph item description and handle for its content.
OSM::Element element
The OSM::Element this item refers to.
Scene graph of the currently displayed level.
Definition scenegraph.h:29
TagKey tagKey(const char *keyName) const
Look up a tag key for the given tag name, if it exists.
Definition datatypes.cpp:27
A reference to any of OSM::Node/OSM::Way/OSM::Relation.
Definition element.h:24
std::vector< const Node * > outerPath(const DataSet &dataSet) const
Returns all nodes belonging to the outer path of this element.
Definition element.cpp:166
Languages in preference order to consider when looking up translated tag values.
Definition languages.h:25
static KOSM_EXPORT Languages fromQLocale(const QLocale &locale)
Convert QLocale::uiLanguages() into an OSM::Languages set.
Definition languages.cpp:40
A key of an OSM tag.
Definition datatypes.h:179
QString path(const QString &relativePath)
OSM-based multi-floor indoor maps for buildings.
@ Hovered
element is selected
Unit
Unit for geometry sizes.
@ LineJoin
line end cap style: none (default), round, square
@ CasingDashes
line casing opacity
@ IconWidth
URL to the icon image.
@ FontStyle
font weight: bold or normal (default)
@ ShieldText
shield casing width
@ CasingOpacity
line casing color
@ ShieldFrameWidth
shield frame color
@ TextOpacity
text color used for the label
@ CasingColor
line casing width
@ FillImage
area fill opacity
@ FillColor
line casing join style
@ ShieldFrameColor
shield opacity
@ LineCap
fill image for the line
@ CasingLineCap
line casing dash pattern
@ IconAllowIconOverlap
the equivalent to CartoCSS's allow-overlap, non-standard extension
@ FontVariant
italic or normal (default)
@ CasingLineJoin
line casing end cap
@ ShieldCasingWidth
shield casing color
@ Text
maximum width before wrapping
@ TextTransform
none (default) or underline
@ TextColor
none (default), uppercase, lowercase or capitalize
@ IconAllowTextOverlap
for colorized SVGs, non-standard extension
@ ShieldColor
text halo radius
@ TextHaloRadius
text halo color
@ ShieldCasingColor
shield frame width (0 to disable)
@ CasingWidth
line join style: round (default), miter, bevel
@ TextDecoration
small-caps or normal (default)
@ IconImage
image to fill the area with
@ MaxWidth
vertical offset from the center of the way or point
@ FontFamily
the equivalent to CartoCSS's ignore-placement, non-standard extension
@ FillOpacity
area fill color
QStringView level(QStringView ifopt)
int64_t Id
OSM element identifier.
Definition datatypes.h:30
const QColor & color() const const
void setColor(Qt::GlobalColor color)
void setStyle(Qt::BrushStyle style)
void setTextureImage(const QImage &image)
Qt::BrushStyle style() const const
int toInt(bool *ok, int base) const const
float alphaF() const const
bool isValid() const const
void setAlphaF(float alpha)
qint64 elapsed() const const
void setBold(bool enable)
void setCapitalization(Capitalization caps)
void setFamily(const QString &family)
void setItalic(bool enable)
void setPixelSize(int pixelSize)
void setPointSizeF(qreal pointSize)
void setUnderline(bool enable)
QPalette palette()
QList< QSize > availableSizes(Mode mode, State state) const const
bool isNull() const const
bool isEmpty() const const
void push_back(parameter_type value)
void reserve(qsizetype size)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool isEmpty() const const
const QColor & color(ColorGroup group, ColorRole role) const const
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 setJoinStyle(Qt::PenJoinStyle style)
void setStyle(Qt::PenStyle style)
void setWidthF(qreal width)
qreal widthF() const const
bool isNull() const const
qreal height() const const
void setHeight(qreal height)
void setWidth(qreal width)
qreal width() const const
void setText(const QString &text)
void setTextOption(const QTextOption &textOption)
void setTextWidth(qreal textWidth)
QString text() const const
qreal textWidth() const const
iterator begin()
iterator end()
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
AlignHCenter
SolidPattern
OddEvenFill
transparent
RoundJoin
SolidLine
void setAlignment(Qt::Alignment alignment)
void setWrapMode(WrapMode mode)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.