7#include "scenecontroller.h"
9#include "render-logging.h"
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"
22#include <KOSMIndoorMap/MapData>
23#include <KOSMIndoorMap/MapCSSResult>
24#include <KOSMIndoorMap/MapCSSStyle>
25#include <KOSMIndoorMap/OverlaySource>
26#include <KOSMIndoorMap/SceneGraph>
27#include <KOSMIndoorMap/View>
29#include <osm/element.h>
30#include <osm/datatypes.h>
33#include <QElapsedTimer>
34#include <QGuiApplication>
36#include <QScopedValueRollback>
41class SceneControllerPrivate
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;
51 MapCSSResult m_styleResult;
52 QColor m_defaultTextColor;
54 QPolygonF m_labelPlacementPath;
55 TextureCache m_textureCache;
56 IconLoader m_iconLoader;
57 OpeningHoursCache m_openingHours;
58 PoleOfInaccessibilityFinder m_piaFinder;
60 OSM::TagKey m_layerTag;
61 OSM::TagKey m_typeTag;
62 OSM::Languages m_langs;
65 bool m_overlay =
false;
71SceneController::SceneController() : d(new SceneControllerPrivate)
75SceneController::~SceneController() =
default;
77void SceneController::setMapData(
const MapData &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);
87 d->m_openingHours.setMapData(MapData());
92void SceneController::setStyleSheet(
const MapCSSStyle *styleSheet)
94 d->m_styleSheet = styleSheet;
98void SceneController::setView(
const View *view)
101 QObject::connect(view, &View::timeChanged, view, [
this]() { d->m_dirty =
true; });
105void SceneController::setOverlaySources(std::vector<QPointer<AbstractOverlaySource>> &&overlays)
107 d->m_overlaySources = std::move(overlays);
120 sgUpdateTimer.
start();
123 if (!d->m_view || !d->m_styleSheet) {
128 if (sg.zoomLevel() == (
int)d->m_view->zoomLevel() && sg.currentFloorLevel() == d->m_view->level() && !d->m_dirty) {
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());
140 if (d->m_data.isEmpty()) {
146 auto it = d->m_data.levelMap().find(
MapLevel(d->m_view->level()));
147 if (it == d->m_data.levelMap().end()) {
152 if (beginIt != d->m_data.levelMap().begin()) {
155 }
while (!(*beginIt).first.isFullLevel() && beginIt != d->m_data.levelMap().begin());
160 for (++endIt; endIt != d->m_data.levelMap().end(); ++endIt) {
161 if ((*endIt).first.isFullLevel()) {
167 d->m_hiddenElements.clear();
168 for (
const auto &overlaySource : d->m_overlaySources) {
169 overlaySource->hiddenElements(d->m_hiddenElements);
171 std::sort(d->m_hiddenElements.begin(), d->m_hiddenElements.end());
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);
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);
192 d->m_data.dataSet().transientNodes =
nullptr;
194 d->m_overlay =
false;
200 qCDebug(RenderLog) <<
"updated scenegraph took" << sgUpdateTimer.elapsed() <<
"ms";
203void SceneController::updateCanvas(
SceneGraph &sg)
const
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());
219 d->m_defaultTextColor = decl->colorValue();
227void SceneController::updateElement(OSM::Element e,
int level,
SceneGraph &sg)
const
231 state.zoomLevel = d->m_view->zoomLevel();
232 state.floorLevel = d->m_view->level();
233 state.openingHours = &d->m_openingHours;
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);
242[[nodiscard]]
static bool canWordWrap(
const QString &s)
244 return std::any_of(s.
begin(), s.
end(), [](QChar c) { return !c.isLetter(); });
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);
258 SceneGeometry::outerPolygonFromPath(i->path, d->m_labelPlacementPath);
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);
267 d->m_labelPlacementPath = i->polygon;
271 double lineOpacity = 1.0;
272 double casingOpacity = 1.0;
273 double fillOpacity = 1.0;
274 bool hasTexture =
false;
276 initializePen(item->pen);
277 initializePen(item->casingPen);
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());
288 fillOpacity = decl->doubleValue();
291 item->textureBrush.
setTextureImage(d->m_textureCache.image(decl->stringValue()));
298 finalizePen(item->pen, lineOpacity);
299 finalizePen(item->casingPen, casingOpacity);
301 auto c = item->fillBrush.
color();
308 if (hasTexture && item->textureBrush.
style() !=
Qt::NoBrush && fillOpacity > 0.0) {
309 auto c = item->textureBrush.
color();
316 addItem(sg, state, level, result, std::move(baseItem));
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);
324 double lineOpacity = 1.0;
325 double casingOpacity = 1.0;
327 initializePen(item->pen);
328 initializePen(item->casingPen);
330 applyGenericStyle(decl, item);
331 applyPenStyle(state.element, decl, item->pen, lineOpacity, item->penWidthUnit);
332 applyCasingPenStyle(state.element, decl, item->casingPen, casingOpacity, item->casingPenWidthUnit);
334 finalizePen(item->pen, lineOpacity);
335 finalizePen(item->casingPen, casingOpacity);
337 d->m_labelPlacementPath = item->path;
338 addItem(sg, state, level, result, std::move(baseItem));
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()));
354 text = textDecl->stringValue();
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;
369 item->textOffset = 0;
372 double textOpacity = 1.0;
373 double shieldOpacity = 1.0;
374 bool forceCenterPosition =
false;
375 bool forceLinePosition =
false;
376 bool textRequireFit =
false;
379 applyGenericStyle(decl, item);
380 applyFontStyle(decl, item->font);
381 switch (decl->property()) {
383 item->color = decl->colorValue();
386 textOpacity = decl->doubleValue();
389 item->casingColor = decl->colorValue();
392 item->casingWidth = decl->doubleValue();
395 item->shieldColor = decl->colorValue();
398 shieldOpacity = decl->doubleValue();
401 item->frameColor = decl->colorValue();
404 item->frameWidth = decl->doubleValue();
407 switch (decl->textPosition()) {
408 case MapCSSDeclaration::Position::Line:
409 forceLinePosition =
true;
410 if (d->m_labelPlacementPath.size() > 1) {
411 item->angle = SceneGeometry::polylineMidPointAngle(d->m_labelPlacementPath);
414 case MapCSSDeclaration::Position::Center:
415 forceCenterPosition =
true;
417 case MapCSSDeclaration::Position::NoPostion:
422 item->textOffset = decl->doubleValue();
427 if (canWordWrap(text)) {
428 item->text.setTextWidth(decl->intValue());
432 if (!decl->keyValue().isEmpty()) {
433 iconData.name =
QString::fromUtf8(state.element.tagValue(decl->keyValue().constData()));
435 iconData.name = decl->stringValue();
439 item->iconSize.setHeight(PenWidthUtil::penWidth(state.element, decl, item->iconHeightUnit));
442 item->iconSize.setWidth(PenWidthUtil::penWidth(state.element, decl, item->iconWidthUnit));
446 const auto alpha = iconData.color.alphaF();
447 iconData.color = decl->colorValue().rgb();
448 iconData.color.setAlphaF(alpha);
452 iconData.color.setAlphaF(decl->doubleValue());
455 item->haloColor = decl->colorValue();
458 item->haloRadius = decl->doubleValue();
461 item->allowIconOverlap = decl->boolValue();
464 item->allowTextOverlap = decl->boolValue();
467 textRequireFit =
true;
474 if (item->pos.isNull()) {
477 if (d->m_labelPlacementPath.size() > 6) {
478 item->pos = d->m_piaFinder.find(d->m_labelPlacementPath);
480 item->pos = SceneGeometry::polygonCentroid(d->m_labelPlacementPath);
483 item->pos = SceneGeometry::polylineMidPoint(d->m_labelPlacementPath);
485 if (item->pos.isNull()) {
486 item->pos = d->m_view->mapGeoToScene(state.element.center());
490 if (item->color.isValid() && textOpacity < 1.0) {
491 auto c = item->color;
492 c.setAlphaF(c.alphaF() * textOpacity);
495 if (item->shieldColor.isValid() && shieldOpacity < 1.0) {
496 auto c = item->shieldColor;
497 c.setAlphaF(c.alphaF() * shieldOpacity);
498 item->shieldColor = c;
500 if (!iconData.name.isEmpty() && iconData.color.alphaF() > 0.0) {
501 if (!iconData.color.isValid()) {
502 iconData.color = d->m_defaultTextColor;
504 item->icon = d->m_iconLoader.loadIcon(iconData);
505 item->iconOpacity = iconData.color.alphaF();
507 if (!item->icon.isNull()) {
508 const auto iconSourceSize = item->icon.availableSizes().at(0);
509 const auto aspectRatio = (double)iconSourceSize.width() / (double)iconSourceSize.height();
510 if (item->iconSize.width() <= 0.0 && item->iconSize.height() <= 0.0) {
511 item->iconSize = iconSourceSize;
512 }
else if (item->iconSize.width() <= 0.0) {
513 item->iconSize.setWidth(item->iconSize.height() * aspectRatio);
514 }
else if (item->iconSize.height() <= 0.0) {
515 item->iconSize.setHeight(item->iconSize.width() / aspectRatio);
519 if (!item->text.text().isEmpty()) {
523 item->text.setTextOption(opt);
525 if (item->text.text().contains(
'\n'_L1) || item->text.textWidth() > 0) {
526 item->isComplexText =
true;
536 if (result.
hasLineProperties() && d->m_labelPlacementPath.size() > 1 && item->angle != 0.0) {
537 const auto sceneLen = SceneGeometry::polylineLength(d->m_labelPlacementPath);
538 const auto sceneP1 = d->m_view->viewport().topLeft();
539 const auto sceneP2 = QPointF(sceneP1.x() + sceneLen, sceneP1.y());
540 const auto screenP1 = d->m_view->mapSceneToScreen(sceneP1);
541 const auto screenP2 = d->m_view->mapSceneToScreen(sceneP2);
542 const auto screenLen = screenP2.x() - screenP1.x();
543 if (screenLen < item->text.
size().width()) {
546 }
else if (result.
hasAreaProperties() && textRequireFit && d->m_labelPlacementPath.size() >= 5 && item->angle == 0.0) {
547 const auto textSize = item->textOutputSize();
548 QRectF sceneTextRect;
549 sceneTextRect.
setWidth(d->m_view->mapScreenDistanceToSceneDistance(textSize.width()));
550 sceneTextRect.
setHeight(d->m_view->mapScreenDistanceToSceneDistance(textSize.height()));
552 if (!SceneGeometry::polygonContainsRect(d->m_labelPlacementPath, sceneTextRect)) {
558 if (!item->icon.isNull() && item->textOffset == 0.0) {
559 item->textOffset = item->iconSize.height();
563 if (!item->icon.isNull() || !item->text.text().isEmpty()) {
564 addItem(sg, state, level, result, std::move(baseItem));
570QPolygonF SceneController::createPolygon(OSM::Element e)
const
586 for (; subIt !=
path.
end(); ++subIt) {
587 subPoly.
push_back(d->m_view->mapGeoToScene((*subIt)->coordinate));
588 if ((*subIt)->id == pathBegin && subIt != it && subIt != std::prev(
path.
end())) {
594 poly = poly.
isEmpty() ? std::move(subPoly) : poly.united(subPoly);
600QPainterPath SceneController::createPath(
const OSM::Element e, QPolygonF &outerPath)
const
602 assert(e.type() == OSM::Type::Relation);
603 outerPath = createPolygon(e);
607 for (
const auto &mem : e.relation()->members) {
608 const bool isInner = std::strcmp(mem.role().name(),
"inner") == 0;
609 const bool isOuter = std::strcmp(mem.role().name(),
"outer") == 0;
610 if (mem.type() != OSM::Type::Way || (!isInner && !isOuter)) {
613 if (
auto way = d->m_data.dataSet().way(mem.id)) {
614 const auto subPoly = createPolygon(OSM::Element(way));
618 path.addPolygon(subPoly);
626void SceneController::applyGenericStyle(
const MapCSSDeclaration *decl,
SceneGraphItemPayload *item)
const
628 if (decl->property() == MapCSSProperty::ZIndex) {
629 item->z = decl->intValue();
633void SceneController::applyPenStyle(OSM::Element e,
const MapCSSDeclaration *decl, QPen &pen,
double &opacity,
Unit &unit)
const
635 switch (decl->property()) {
640 pen.
setWidthF(PenWidthUtil::penWidth(e, decl, unit));
652 opacity = decl->doubleValue();
655 pen.
setBrush(d->m_textureCache.image(decl->stringValue()));
663void SceneController::applyCasingPenStyle(OSM::Element e,
const MapCSSDeclaration *decl, QPen &pen,
double &opacity,
Unit &unit)
const
665 switch (decl->property()) {
670 pen.
setWidthF(PenWidthUtil::penWidth(e, decl, unit));
682 opacity = decl->doubleValue();
689void SceneController::applyFontStyle(
const MapCSSDeclaration *decl, QFont &font)
const
691 switch (decl->property()) {
696 if (decl->unit() == MapCSSDeclaration::Pixels) {
703 font.
setBold(decl->isBoldStyle());
722void SceneController::initializePen(QPen &pen)
const
733void SceneController::finalizePen(QPen &pen,
double opacity)
const
736 auto c = pen.
color();
752 std::for_each(dashes.begin(), dashes.end(), [pen](
double &d) { d /= pen.widthF(); });
757void SceneController::addItem(
SceneGraph &sg,
const MapCSSState &state,
int level,
const MapCSSResultLayer &result, std::unique_ptr<SceneGraphItemPayload> &&payload)
const
763 item.payload = std::move(payload);
768 if (layerStr && !(*layerStr).isEmpty()) {
769 bool success =
false;
770 const auto layer = (*layerStr).toInt(&success);
782 if (level != layer * 10) {
786 qCWarning(Log) <<
"Invalid layer:" << state.element.url() << *layerStr;
790 item.layer = std::numeric_limits<int>::max();
793 sg.addItem(std::move(item));
798 return d->m_hoverElement;
801void SceneController::setHoveredElement(
OSM::Element element)
803 if (d->m_hoverElement == element) {
806 d->m_hoverElement = element;
virtual void endSwap()
Indicates the end of a scene graph update.
virtual void beginSwap()
Indicates the being of a scene graph update.
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.
const std::vector< const MapCSSDeclaration * > & declarations() const
The active declarations for the queried element.
bool hasAreaProperties() const
Returns true if an area/polygon needs to be drawn.
A parsed MapCSS style sheet.
Raw OSM map data, separated by levels.
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.
OSM::Element element
The OSM::Element this item refers to.
Scene graph of the currently displayed level.
View transformations and transformation manipulation.
TagKey tagKey(const char *keyName) const
Look up a tag key for the given tag name, if it exists.
A reference to any of OSM::Node/OSMWay/OSMRelation.
std::vector< const Node * > outerPath(const DataSet &dataSet) const
Returns all nodes belonging to the outer path of this element.
static KOSM_EXPORT Languages fromQLocale(const QLocale &locale)
Convert QLocale::uiLanguages() into an OSM::Languages set.
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
@ TextRequireFit
text halo radius
@ FillImage
area fill opacity
@ FillColor
line casing join style
@ ShieldFrameColor
shield opacity
@ LineCap
fill image for the line
@ CasingLineCap
line casing dash pattern
@ TextHaloColor
label content
@ IconAllowIconOverlap
the equivalent to CartoCSS's allow-overlap, non-standard extension
@ ShieldOpacity
shield color
@ 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 has to fit into its associated geometry (custom extension)
@ 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
@ TextPosition
text opacity
@ TextOffset
line or center
@ 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.
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
QImage textureImage() 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)
qreal devicePixelRatio() 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)
const QColor & color(ColorGroup group, ColorRole role) const const
QBrush brush() const const
QColor color() const const
QList< qreal > dashPattern() const const
void setBrush(const QBrush &brush)
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
void moveCenter(const QPointF &position)
void setHeight(qreal height)
void setWidth(qreal width)
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
void setAlignment(Qt::Alignment alignment)
void setWrapMode(WrapMode mode)