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/mapcssexpressioncontext_p.h"
19#include "../style/mapcssstate_p.h"
20#include "../style/mapcssvalue_p.h"
21
22#include <KOSMIndoorMap/MapData>
23#include <KOSMIndoorMap/MapCSSResult>
24#include <KOSMIndoorMap/MapCSSStyle>
25#include <KOSMIndoorMap/OverlaySource>
26#include <KOSMIndoorMap/SceneGraph>
27#include <KOSMIndoorMap/View>
28
29#include <osm/element.h>
30#include <osm/datatypes.h>
31
32#include <QDebug>
33#include <QElapsedTimer>
34#include <QGuiApplication>
35#include <QPalette>
36#include <QScopedValueRollback>
37
38using namespace Qt::Literals::StringLiterals;
39
40namespace KOSMIndoorMap {
41class SceneControllerPrivate
42{
43public:
44 MapData m_data;
45 const MapCSSStyle *m_styleSheet = nullptr;
46 const View *m_view = nullptr;
47 std::vector<QPointer<AbstractOverlaySource>> m_overlaySources;
48 mutable std::vector<OSM::Element> m_hiddenElements;
49 OSM::Element m_hoverElement;
50
51 MapCSSResult m_styleResult;
52 QColor m_defaultTextColor;
53 QFont m_defaultFont;
54 QPolygonF m_labelPlacementPath;
55 TextureCache m_textureCache;
56 IconLoader m_iconLoader;
57 OpeningHoursCache m_openingHours;
58 PoleOfInaccessibilityFinder m_piaFinder;
59
60 OSM::TagKey m_layerTag;
61 OSM::TagKey m_typeTag;
62 OSM::Languages m_langs;
63
64 bool m_dirty = true;
65 bool m_overlay = false;
66};
67}
68
69using namespace KOSMIndoorMap;
70
71SceneController::SceneController() : d(new SceneControllerPrivate)
72{
74}
75SceneController::~SceneController() = default;
76
77void SceneController::setMapData(const MapData &data)
78{
79 d->m_data = data;
80 if (!d->m_data.isEmpty()) {
81 d->m_layerTag = data.dataSet().tagKey("layer");
82 d->m_typeTag = data.dataSet().tagKey("type");
83 d->m_openingHours.setMapData(data);
84 } else {
85 d->m_layerTag = {};
86 d->m_typeTag = {};
87 d->m_openingHours.setMapData(MapData());
88 }
89 d->m_dirty = true;
90}
91
92void SceneController::setStyleSheet(const MapCSSStyle *styleSheet)
93{
94 d->m_styleSheet = styleSheet;
95 d->m_dirty = true;
96}
97
98void SceneController::setView(const View *view)
99{
100 d->m_view = view;
101 QObject::connect(view, &View::timeChanged, view, [this]() { d->m_dirty = true; });
102 d->m_dirty = true;
103}
104
105void SceneController::setOverlaySources(std::vector<QPointer<AbstractOverlaySource>> &&overlays)
106{
107 d->m_overlaySources = std::move(overlays);
108 d->m_dirty = true;
109}
110
112{
113 // TODO we could potentially do this more fine-grained?
114 d->m_dirty = true;
115}
116
118{
119 QElapsedTimer sgUpdateTimer;
120 sgUpdateTimer.start();
121
122 // check if we are set up completely yet (we can't rely on a defined order with QML)
123 if (!d->m_view || !d->m_styleSheet) {
124 return;
125 }
126
127 // check if the scene is dirty at all
128 if (sg.zoomLevel() == (int)d->m_view->zoomLevel() && sg.currentFloorLevel() == d->m_view->level() && !d->m_dirty) {
129 return;
130 }
131 sg.setZoomLevel(d->m_view->zoomLevel());
132 sg.setCurrentFloorLevel(d->m_view->level());
133 d->m_openingHours.setTimeRange(d->m_view->beginTime(), d->m_view->endTime());
134 d->m_dirty = false;
135
136 sg.beginSwap();
137 std::for_each(d->m_overlaySources.begin(), d->m_overlaySources.end(), std::mem_fn(&AbstractOverlaySource::beginSwap));
138 updateCanvas(sg);
139
140 if (d->m_data.isEmpty()) { // if we don't have map data yet, we just need to get canvas styling here
141 sg.endSwap();
142 return;
143 }
144
145 // find all intermediate levels below or above the currently selected "full" level
146 auto it = d->m_data.levelMap().find(MapLevel(d->m_view->level()));
147 if (it == d->m_data.levelMap().end()) {
148 return;
149 }
150
151 auto beginIt = it;
152 if (beginIt != d->m_data.levelMap().begin()) {
153 do {
154 --beginIt;
155 } while (!(*beginIt).first.isFullLevel() && beginIt != d->m_data.levelMap().begin());
156 ++beginIt;
157 }
158
159 auto endIt = it;
160 for (++endIt; endIt != d->m_data.levelMap().end(); ++endIt) {
161 if ((*endIt).first.isFullLevel()) {
162 break;
163 }
164 }
165
166 // collect elements that the overlay want to hide
167 d->m_hiddenElements.clear();
168 for (const auto &overlaySource : d->m_overlaySources) {
169 overlaySource->hiddenElements(d->m_hiddenElements);
170 }
171 std::sort(d->m_hiddenElements.begin(), d->m_hiddenElements.end());
172
173 // for each level, update or create scene graph elements, after a some basic bounding box check
174 const auto geoBbox = d->m_view->mapSceneToGeo(d->m_view->sceneBoundingBox());
175 for (auto it = beginIt; it != endIt; ++it) {
176 for (auto e : (*it).second) {
177 if (OSM::intersects(geoBbox, e.boundingBox()) && !std::binary_search(d->m_hiddenElements.begin(), d->m_hiddenElements.end(), e)) {
178 updateElement(e, (*it).first.numericLevel(), sg);
179 }
180 }
181 }
182
183 // update overlay elements
184 d->m_overlay = true;
185 for (const auto &overlaySource : d->m_overlaySources) {
186 QScopedValueRollback tranientNodes(d->m_data.dataSet().transientNodes, overlaySource->transientNodes());
187 overlaySource->forEach(d->m_view->level(), [this, &geoBbox, &sg](OSM::Element e, int floorLevel) {
188 if (OSM::intersects(geoBbox, e.boundingBox()) && e.type() != OSM::Type::Null) {
189 updateElement(e, floorLevel, sg);
190 }
191 });
192 d->m_data.dataSet().transientNodes = nullptr;
193 }
194 d->m_overlay = false;
195
196 sg.zSort();
197 sg.endSwap();
198 std::for_each(d->m_overlaySources.begin(), d->m_overlaySources.end(), std::mem_fn(&AbstractOverlaySource::endSwap));
199
200 qCDebug(RenderLog) << "updated scenegraph took" << sgUpdateTimer.elapsed() << "ms";
201}
202
203void SceneController::updateCanvas(SceneGraph &sg) const
204{
205 sg.setBackgroundColor(QGuiApplication::palette().color(QPalette::Base));
206 d->m_defaultTextColor = QGuiApplication::palette().color(QPalette::Text);
207 d->m_defaultFont = QGuiApplication::font();
208
209 MapCSSState state;
210 state.zoomLevel = d->m_view->zoomLevel();
211 state.floorLevel = d->m_view->level();
212 d->m_styleSheet->evaluateCanvas(state, d->m_styleResult);
213 for (auto decl : d->m_styleResult[{}].declarations()) {
214 switch (decl->property()) {
216 sg.setBackgroundColor(decl->colorValue());
217 break;
219 d->m_defaultTextColor = decl->colorValue();
220 break;
221 default:
222 break;
223 }
224 }
225}
226
227void SceneController::updateElement(OSM::Element e, int level, SceneGraph &sg) const
228{
229 MapCSSState state;
230 state.element = e;
231 state.zoomLevel = d->m_view->zoomLevel();
232 state.floorLevel = d->m_view->level();
233 state.openingHours = &d->m_openingHours;
234 state.state = d->m_hoverElement == e ? MapCSSElementState::Hovered : MapCSSElementState::NoState;
235 d->m_styleSheet->initializeState(state);
236 d->m_styleSheet->evaluate(state, d->m_styleResult);
237 for (const auto &result : d->m_styleResult.results()) {
238 updateElement(state, level, sg, result);
239 }
240}
241
242[[nodiscard]] static bool canWordWrap(const QString &s)
243{
244 return std::any_of(s.begin(), s.end(), [](QChar c) { return !c.isLetter(); });
245}
246
247void SceneController::updateElement(const MapCSSState &state, int level, SceneGraph &sg, const MapCSSResultLayer &result) const
248{
249 if (result.hasAreaProperties()) {
250 PolygonBaseItem *item = nullptr;
251 std::unique_ptr<SceneGraphItemPayload> baseItem;
252 if (state.element.type() == OSM::Type::Relation && state.element.tagValue(d->m_typeTag) == "multipolygon") {
253 baseItem = sg.findOrCreatePayload<MultiPolygonItem>(state.element, level, result.layerSelector());
254 auto i = static_cast<MultiPolygonItem*>(baseItem.get());
255 if (i->path.isEmpty()) {
256 i->path = createPath(state.element, d->m_labelPlacementPath);
257 } else if (result.hasLabelProperties()) {
258 SceneGeometry::outerPolygonFromPath(i->path, d->m_labelPlacementPath);
259 }
260 item = i;
261 } else {
262 baseItem = sg.findOrCreatePayload<PolygonItem>(state.element, level, result.layerSelector());
263 auto i = static_cast<PolygonItem*>(baseItem.get());
264 if (i->polygon.isEmpty()) {
265 i->polygon = createPolygon(state.element);
266 }
267 d->m_labelPlacementPath = i->polygon;
268 item = i;
269 }
270
271 double lineOpacity = 1.0;
272 double casingOpacity = 1.0;
273 double fillOpacity = 1.0;
274 bool hasTexture = false;
275 item->z = 0;
276 initializePen(item->pen);
277 initializePen(item->casingPen);
278 for (auto decl : result.declarations()) {
279 applyGenericStyle(decl, item);
280 applyPenStyle(state.element, decl, item->pen, lineOpacity, item->penWidthUnit);
281 applyCasingPenStyle(state.element, decl, item->casingPen, casingOpacity, item->casingPenWidthUnit);
282 switch (decl->property()) {
284 item->fillBrush.setColor(decl->colorValue());
285 item->fillBrush.setStyle(Qt::SolidPattern);
286 break;
288 fillOpacity = decl->doubleValue();
289 break;
291 item->textureBrush.setTextureImage(d->m_textureCache.image(decl->stringValue()));
292 hasTexture = true;
293 break;
294 default:
295 break;
296 }
297 }
298 finalizePen(item->pen, lineOpacity);
299 finalizePen(item->casingPen, casingOpacity);
300 if (item->fillBrush.style() == Qt::SolidPattern && item->textureBrush.style() == Qt::NoBrush && fillOpacity < 1.0) {
301 auto c = item->fillBrush.color();
302 c.setAlphaF(c.alphaF() * fillOpacity);
303 item->fillBrush.setColor(c);
304 }
305 if (item->fillBrush.color().alphaF() == 0.0) {
306 item->fillBrush.setStyle(Qt::NoBrush);
307 }
308 if (hasTexture && item->textureBrush.style() != Qt::NoBrush && fillOpacity > 0.0) {
309 auto c = item->textureBrush.color();
310 c.setAlphaF(fillOpacity);
311 item->textureBrush.setColor(c);
312 } else {
313 item->textureBrush.setStyle(Qt::NoBrush);
314 }
315
316 addItem(sg, state, level, result, std::move(baseItem));
317 } else if (result.hasLineProperties()) {
318 auto baseItem = sg.findOrCreatePayload<PolylineItem>(state.element, level, result.layerSelector());
319 auto item = static_cast<PolylineItem*>(baseItem.get());
320 if (item->path.isEmpty()) {
321 item->path = createPolygon(state.element);
322 }
323
324 double lineOpacity = 1.0;
325 double casingOpacity = 1.0;
326 item->z = 0;
327 initializePen(item->pen);
328 initializePen(item->casingPen);
329 for (auto decl : result.declarations()) {
330 applyGenericStyle(decl, item);
331 applyPenStyle(state.element, decl, item->pen, lineOpacity, item->penWidthUnit);
332 applyCasingPenStyle(state.element, decl, item->casingPen, casingOpacity, item->casingPenWidthUnit);
333 }
334 finalizePen(item->pen, lineOpacity);
335 finalizePen(item->casingPen, casingOpacity);
336
337 d->m_labelPlacementPath = item->path;
338 addItem(sg, state, level, result, std::move(baseItem));
339 }
340
341 if (result.hasLabelProperties()) {
342 QString text;
343 auto textDecl = result.declaration(MapCSSProperty::Text);
344 if (!textDecl) {
345 textDecl = result.declaration(MapCSSProperty::ShieldText);
346 }
347
348 if (textDecl) {
349 if (textDecl->hasExpression()) {
350 text = QString::fromUtf8(textDecl->evaluateExpression({state, result}).asString());
351 } else if (!textDecl->keyValue().isEmpty()) {
352 text = QString::fromUtf8(state.element.tagValue(d->m_langs, textDecl->keyValue().constData()));
353 } else {
354 text = textDecl->stringValue();
355 }
356 }
357
358 const auto iconDecl = result.declaration(MapCSSProperty::IconImage);
359
360 if (!text.isEmpty() || iconDecl) {
361 auto baseItem = sg.findOrCreatePayload<LabelItem>(state.element, level, result.layerSelector());
362 auto item = static_cast<LabelItem*>(baseItem.get());
363 item->text.setText(text);
364 item->textIsSet = !text.isEmpty();
365 item->textOutputSizeCache = {};
366 item->font = d->m_defaultFont;
367 item->color = d->m_defaultTextColor;
368 item->iconSize = {};
369 item->textOffset = 0;
370 item->z = 0;
371
372 double textOpacity = 1.0;
373 double shieldOpacity = 1.0;
374 bool forceCenterPosition = false;
375 bool forceLinePosition = false;
376 IconData iconData;
377 for (auto decl : result.declarations()) {
378 applyGenericStyle(decl, item);
379 applyFontStyle(decl, item->font);
380 switch (decl->property()) {
382 item->color = decl->colorValue();
383 break;
385 textOpacity = decl->doubleValue();
386 break;
388 item->casingColor = decl->colorValue();
389 break;
391 item->casingWidth = decl->doubleValue();
392 break;
394 item->shieldColor = decl->colorValue();
395 break;
397 shieldOpacity = decl->doubleValue();
398 break;
400 item->frameColor = decl->colorValue();
401 break;
403 item->frameWidth = decl->doubleValue();
404 break;
406 switch (decl->textPosition()) {
407 case MapCSSDeclaration::Position::Line:
408 forceLinePosition = true;
409 if (d->m_labelPlacementPath.size() > 1) {
410 item->angle = SceneGeometry::polylineMidPointAngle(d->m_labelPlacementPath);
411 }
412 break;
413 case MapCSSDeclaration::Position::Center:
414 forceCenterPosition = true;
415 break;
416 case MapCSSDeclaration::Position::NoPostion:
417 break;
418 }
419 break;
421 item->textOffset = decl->doubleValue();
422 break;
424 // work around for QStaticText misbehaving when we have a max width but can't actually word-wrap
425 // far from perfect but covers the most common cases
426 if (canWordWrap(text)) {
427 item->text.setTextWidth(decl->intValue());
428 }
429 break;
431 if (!decl->keyValue().isEmpty()) {
432 iconData.name = QString::fromUtf8(state.element.tagValue(decl->keyValue().constData()));
433 } else {
434 iconData.name = decl->stringValue();
435 }
436 break;
438 item->iconSize.setHeight(PenWidthUtil::penWidth(state.element, decl, item->iconHeightUnit));
439 break;
441 item->iconSize.setWidth(PenWidthUtil::penWidth(state.element, decl, item->iconWidthUnit));
442 break;
444 {
445 const auto alpha = iconData.color.alphaF();
446 iconData.color = decl->colorValue().rgb();
447 iconData.color.setAlphaF(alpha);
448 break;
449 }
451 iconData.color.setAlphaF(decl->doubleValue());
452 break;
454 item->haloColor = decl->colorValue();
455 break;
457 item->haloRadius = decl->doubleValue();
458 break;
460 item->allowIconOverlap = decl->boolValue();
461 break;
463 item->allowTextOverlap = decl->boolValue();
464 break;
465 default:
466 break;
467 }
468 }
469
470 if (item->pos.isNull()) {
471 if ((result.hasAreaProperties() || forceCenterPosition) && !forceLinePosition) {
472 // for simple enough shapes we can use the faster centroid rather than the expensive PIA
473 if (d->m_labelPlacementPath.size() > 6) {
474 item->pos = d->m_piaFinder.find(d->m_labelPlacementPath);
475 } else {
476 item->pos = SceneGeometry::polygonCentroid(d->m_labelPlacementPath);
477 }
478 } else if (result.hasLineProperties() || forceLinePosition) {
479 item->pos = SceneGeometry::polylineMidPoint(d->m_labelPlacementPath);
480 }
481 if (item->pos.isNull()) {
482 item->pos = d->m_view->mapGeoToScene(state.element.center()); // node or something failed above
483 }
484 }
485
486 if (item->color.isValid() && textOpacity < 1.0) {
487 auto c = item->color;
488 c.setAlphaF(c.alphaF() * textOpacity);
489 item->color = c;
490 }
491 if (item->shieldColor.isValid() && shieldOpacity < 1.0) {
492 auto c = item->shieldColor;
493 c.setAlphaF(c.alphaF() * shieldOpacity);
494 item->shieldColor = c;
495 }
496 if (!iconData.name.isEmpty() && iconData.color.alphaF() > 0.0) {
497 if (!iconData.color.isValid()) {
498 iconData.color = d->m_defaultTextColor;
499 }
500 item->icon = d->m_iconLoader.loadIcon(iconData);
501 item->iconOpacity = iconData.color.alphaF();
502 }
503 if (!item->icon.isNull()) {
504 const auto iconSourceSize = item->icon.availableSizes().at(0);
505 const auto aspectRatio = (double)iconSourceSize.width() / (double)iconSourceSize.height();
506 if (item->iconSize.width() <= 0.0 && item->iconSize.height() <= 0.0) {
507 item->iconSize = iconSourceSize;
508 } else if (item->iconSize.width() <= 0.0) {
509 item->iconSize.setWidth(item->iconSize.height() * aspectRatio);
510 } else if (item->iconSize.height() <= 0.0) {
511 item->iconSize.setHeight(item->iconSize.width() / aspectRatio);
512 }
513 }
514
515 if (!item->text.text().isEmpty()) {
516 QTextOption opt;
519 item->text.setTextOption(opt);
520
521 if (item->text.text().contains('\n'_L1) || item->text.textWidth() > 0) {
522 item->isComplexText = true;
523 }
524
525 // do not use QStaticText::prepare here:
526 // the vast majority of text items will likely not be shown at all for being overlapped or out of view
527 // and pre-computing them is too expensive. Instead this will happen as needed on first use, for only
528 // a smaller amounts at a time.
529 // item->text.prepare({}, item->font);
530
531 // discard labels that are longer than the line they are aligned with
532 if (result.hasLineProperties() && d->m_labelPlacementPath.size() > 1 && item->angle != 0.0) {
533 const auto sceneLen = SceneGeometry::polylineLength(d->m_labelPlacementPath);
534 const auto sceneP1 = d->m_view->viewport().topLeft();
535 const auto sceneP2 = QPointF(sceneP1.x() + sceneLen, sceneP1.y());
536 const auto screenP1 = d->m_view->mapSceneToScreen(sceneP1);
537 const auto screenP2 = d->m_view->mapSceneToScreen(sceneP2);
538 const auto screenLen = screenP2.x() - screenP1.x();
539 if (screenLen < item->text.size().width()) {
540 item->text = {};
541 }
542 }
543
544 // put texts below icons by default
545 if (!item->icon.isNull() && item->textOffset == 0.0) {
546 item->textOffset = item->iconSize.height(); // ### what about heights in meters?
547 }
548 }
549
550 if (!item->icon.isNull() || !item->text.text().isEmpty()) {
551 addItem(sg, state, level, result, std::move(baseItem));
552 }
553 }
554 }
555}
556
557QPolygonF SceneController::createPolygon(OSM::Element e) const
558{
559 const auto path = e.outerPath(d->m_data.dataSet());
560 if (path.empty()) {
561 return {};
562 }
563
564 QPolygonF poly;
565 // Element::outerPath takes care of re-assembling broken up line segments
566 // the below takes care of properly merging broken up polygons
567 for (auto it = path.begin(); it != path.end();) {
568 QPolygonF subPoly;
569 subPoly.reserve(path.size());
570 OSM::Id pathBegin = (*it)->id;
571
572 auto subIt = it;
573 for (; subIt != path.end(); ++subIt) {
574 subPoly.push_back(d->m_view->mapGeoToScene((*subIt)->coordinate));
575 if ((*subIt)->id == pathBegin && subIt != it && subIt != std::prev(path.end())) {
576 ++subIt;
577 break;
578 }
579 }
580 it = subIt;
581 poly = poly.isEmpty() ? std::move(subPoly) : poly.united(subPoly);
582 }
583 return poly;
584}
585
586// @see https://wiki.openstreetmap.org/wiki/Relation:multipolygon
587QPainterPath SceneController::createPath(const OSM::Element e, QPolygonF &outerPath) const
588{
589 assert(e.type() == OSM::Type::Relation);
590 outerPath = createPolygon(e); // TODO this is actually not correct for the multiple outer polygon case
592 path.setFillRule(Qt::OddEvenFill);
593
594 for (const auto &mem : e.relation()->members) {
595 const bool isInner = std::strcmp(mem.role().name(), "inner") == 0;
596 const bool isOuter = std::strcmp(mem.role().name(), "outer") == 0;
597 if (mem.type() != OSM::Type::Way || (!isInner && !isOuter)) {
598 continue;
599 }
600 if (auto way = d->m_data.dataSet().way(mem.id)) {
601 const auto subPoly = createPolygon(OSM::Element(way));
602 if (subPoly.isEmpty()) {
603 continue;
604 }
605 path.addPolygon(subPoly);
606 path.closeSubpath();
607 }
608 }
609
610 return path;
611}
612
613void SceneController::applyGenericStyle(const MapCSSDeclaration *decl, SceneGraphItemPayload *item) const
614{
615 if (decl->property() == MapCSSProperty::ZIndex) {
616 item->z = decl->intValue();
617 }
618}
619
620void SceneController::applyPenStyle(OSM::Element e, const MapCSSDeclaration *decl, QPen &pen, double &opacity, Unit &unit) const
621{
622 switch (decl->property()) {
624 pen.setColor(decl->colorValue());
625 break;
627 pen.setWidthF(PenWidthUtil::penWidth(e, decl, unit));
628 break;
630 pen.setDashPattern(decl->dashesValue());
631 break;
633 pen.setCapStyle(decl->capStyle());
634 break;
636 pen.setJoinStyle(decl->joinStyle());
637 break;
639 opacity = decl->doubleValue();
640 break;
641 default:
642 break;
643 }
644}
645
646void SceneController::applyCasingPenStyle(OSM::Element e, const MapCSSDeclaration *decl, QPen &pen, double &opacity, Unit &unit) const
647{
648 switch (decl->property()) {
650 pen.setColor(decl->colorValue());
651 break;
653 pen.setWidthF(PenWidthUtil::penWidth(e, decl, unit));
654 break;
656 pen.setDashPattern(decl->dashesValue());
657 break;
659 pen.setCapStyle(decl->capStyle());
660 break;
662 pen.setJoinStyle(decl->joinStyle());
663 break;
665 opacity = decl->doubleValue();
666 break;
667 default:
668 break;
669 }
670}
671
672void SceneController::applyFontStyle(const MapCSSDeclaration *decl, QFont &font) const
673{
674 switch (decl->property()) {
676 font.setFamily(decl->stringValue());
677 break;
679 if (decl->unit() == MapCSSDeclaration::Pixels) {
680 font.setPixelSize(decl->doubleValue());
681 } else {
682 font.setPointSizeF(decl->doubleValue());
683 }
684 break;
686 font.setBold(decl->isBoldStyle());
687 break;
689 font.setItalic(decl->isItalicStyle());
690 break;
692 font.setCapitalization(decl->capitalizationStyle());
693 break;
695 font.setUnderline(decl->isUnderlineStyle());
696 break;
698 font.setCapitalization(decl->capitalizationStyle());
699 break;
700 default:
701 break;
702 }
703}
704
705void SceneController::initializePen(QPen &pen) const
706{
708 pen.setWidthF(0.0);
709
710 // default according to spec
714}
715
716void SceneController::finalizePen(QPen &pen, double opacity) const
717{
718 if (pen.color().isValid() && opacity < 1.0) {
719 auto c = pen.color();
720 c.setAlphaF(c.alphaF() * opacity);
721 pen.setColor(c);
722 }
723
724 if (pen.color().alphaF() == 0.0 || pen.widthF() == 0.0) {
725 pen.setStyle(Qt::NoPen); // so the renderer can skip this entirely
726 }
727
728 // normalize dash pattern, as QPainter scales that with the line width
729 if (pen.widthF() > 0.0 && !pen.dashPattern().isEmpty()) {
730 auto dashes = pen.dashPattern();
731 std::for_each(dashes.begin(), dashes.end(), [pen](double &d) { d /= pen.widthF(); });
732 pen.setDashPattern(std::move(dashes));
733 }
734}
735
736void SceneController::addItem(SceneGraph &sg, const MapCSSState &state, int level, const MapCSSResultLayer &result, std::unique_ptr<SceneGraphItemPayload> &&payload) const
737{
738 SceneGraphItem item;
739 item.element = state.element;
740 item.layerSelector = result.layerSelector();
741 item.level = level;
742 item.payload = std::move(payload);
743
744 // get the OSM layer, if set
745 if (!d->m_overlay) {
746 const auto layerStr = result.resolvedTagValue(d->m_layerTag, state);
747 if (layerStr && !(*layerStr).isEmpty()) {
748 bool success = false;
749 const auto layer = (*layerStr).toInt(&success);
750 if (success) {
751
752 // ### Ignore layer information when it matches the level
753 // This is very wrong according to the specification, however it looks that in many places
754 // layer and level tags aren't correctly filled, possibly a side-effect of layer pre-dating
755 // level and layers not having been properly updated when retrofitting level information
756 // Strictly following the MapCSS rendering order yields sub-optimal results in that case, with
757 // relevant elements being hidden.
758 //
759 // Ideally we find a way to detect the presence of that problem, and only then enabling this
760 // workaround, but until we have this, this seems to produce better results in all tests.
761 if (level != layer * 10) {
762 item.layer = layer;
763 }
764 } else {
765 qCWarning(Log) << "Invalid layer:" << state.element.url() << *layerStr;
766 }
767 }
768 } else {
769 item.layer = std::numeric_limits<int>::max();
770 }
771
772 sg.addItem(std::move(item));
773}
774
776{
777 return d->m_hoverElement;
778}
779
780void SceneController::setHoveredElement(OSM::Element element)
781{
782 if (d->m_hoverElement == element) {
783 return;
784 }
785 d->m_hoverElement = element;
786 d->m_dirty = true;
787}
virtual void endSwap()
Indicates the end of a scene graph update.
virtual void beginSwap()
Indicates the being of a scene graph update.
A text or icon label.
Result of MapCSS stylesheet evaluation for a single layer selector.
LayerSelectorKey layerSelector() const
The layer selector for this result.
bool hasLineProperties() const
Returns true if a way/line needs to be drawn.
std::optional< QByteArray > resolvedTagValue(OSM::TagKey key, const MapCSSState &state) const
Returns the tag value set by preceding declarations, via MapCSS expressions or in the source data.
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:38
A reference to any of OSM::Node/OSMWay/OSMRelation.
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
float alphaF() const const
bool isValid() const const
void setAlphaF(float alpha)
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()
bool contains(QChar ch, Qt::CaseSensitivity cs) const const
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 Fri Jul 26 2024 11:57:46 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.