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
46 const View *m_view =
nullptr;
47 std::vector<QPointer<AbstractOverlaySource>> m_overlaySources;
48 mutable std::vector<OSM::Element> m_hiddenElements;
55 TextureCache m_textureCache;
56 IconLoader m_iconLoader;
57 OpeningHoursCache m_openingHours;
58 PoleOfInaccessibilityFinder m_piaFinder;
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; });
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();
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(); });
251 std::unique_ptr<SceneGraphItemPayload> baseItem;
252 if (state.element.type() == OSM::Type::Relation && state.element.tagValue(d->m_typeTag) ==
"multipolygon") {
255 if (i->path.isEmpty()) {
256 i->path = createPath(state.element, d->m_labelPlacementPath);
258 SceneGeometry::outerPolygonFromPath(i->path, d->m_labelPlacementPath);
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));
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) {
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();
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));
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);
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)) {
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.
Result of MapCSS stylesheet evaluation for all layer selectors.
A parsed MapCSS style sheet.
Raw OSM map data, separated by levels.
Multi-polygon item, used for polygons with "holes" in them.
Base item for filled polygons.
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.
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/OSM::Way/OSM::Relation.
std::vector< const Node * > outerPath(const DataSet &dataSet) const
Returns all nodes belonging to the outer path of this element.
Languages in preference order to consider when looking up translated tag values.
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)