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;
378 applyGenericStyle(decl, item);
379 applyFontStyle(decl, item->font);
380 switch (decl->property()) {
382 item->color = decl->colorValue();
385 textOpacity = decl->doubleValue();
388 item->casingColor = decl->colorValue();
391 item->casingWidth = decl->doubleValue();
394 item->shieldColor = decl->colorValue();
397 shieldOpacity = decl->doubleValue();
400 item->frameColor = decl->colorValue();
403 item->frameWidth = decl->doubleValue();
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);
413 case MapCSSDeclaration::Position::Center:
414 forceCenterPosition =
true;
416 case MapCSSDeclaration::Position::NoPostion:
421 item->textOffset = decl->doubleValue();
426 if (canWordWrap(text)) {
427 item->text.setTextWidth(decl->intValue());
431 if (!decl->keyValue().isEmpty()) {
432 iconData.name =
QString::fromUtf8(state.element.tagValue(decl->keyValue().constData()));
434 iconData.name = decl->stringValue();
438 item->iconSize.setHeight(PenWidthUtil::penWidth(state.element, decl, item->iconHeightUnit));
441 item->iconSize.setWidth(PenWidthUtil::penWidth(state.element, decl, item->iconWidthUnit));
445 const auto alpha = iconData.color.alphaF();
446 iconData.color = decl->colorValue().rgb();
447 iconData.color.setAlphaF(alpha);
451 iconData.color.setAlphaF(decl->doubleValue());
454 item->haloColor = decl->colorValue();
457 item->haloRadius = decl->doubleValue();
460 item->allowIconOverlap = decl->boolValue();
463 item->allowTextOverlap = decl->boolValue();
470 if (item->pos.isNull()) {
473 if (d->m_labelPlacementPath.size() > 6) {
474 item->pos = d->m_piaFinder.find(d->m_labelPlacementPath);
476 item->pos = SceneGeometry::polygonCentroid(d->m_labelPlacementPath);
479 item->pos = SceneGeometry::polylineMidPoint(d->m_labelPlacementPath);
481 if (item->pos.isNull()) {
482 item->pos = d->m_view->mapGeoToScene(state.element.center());
486 if (item->color.isValid() && textOpacity < 1.0) {
487 auto c = item->color;
488 c.setAlphaF(c.alphaF() * textOpacity);
491 if (item->shieldColor.isValid() && shieldOpacity < 1.0) {
492 auto c = item->shieldColor;
493 c.setAlphaF(c.alphaF() * shieldOpacity);
494 item->shieldColor = c;
496 if (!iconData.name.isEmpty() && iconData.color.alphaF() > 0.0) {
497 if (!iconData.color.isValid()) {
498 iconData.color = d->m_defaultTextColor;
500 item->icon = d->m_iconLoader.loadIcon(iconData);
501 item->iconOpacity = iconData.color.alphaF();
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);
515 if (!item->text.text().isEmpty()) {
519 item->text.setTextOption(opt);
521 if (item->text.text().contains(
'\n'_L1) || item->text.textWidth() > 0) {
522 item->isComplexText =
true;
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()) {
545 if (!item->icon.isNull() && item->textOffset == 0.0) {
546 item->textOffset = item->iconSize.height();
550 if (!item->icon.isNull() || !item->text.text().isEmpty()) {
551 addItem(sg, state, level, result, std::move(baseItem));
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())) {
581 poly = poly.
isEmpty() ? std::move(subPoly) : poly.united(subPoly);
589 assert(e.type() == OSM::Type::Relation);
590 outerPath = createPolygon(e);
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)) {
600 if (
auto way = d->m_data.dataSet().way(mem.id)) {
605 path.addPolygon(subPoly);
613void SceneController::applyGenericStyle(
const MapCSSDeclaration *decl,
SceneGraphItemPayload *item)
const
615 if (decl->property() == MapCSSProperty::ZIndex) {
616 item->z = decl->intValue();
620void SceneController::applyPenStyle(
OSM::Element e,
const MapCSSDeclaration *decl,
QPen &pen,
double &opacity,
Unit &unit)
const
622 switch (decl->property()) {
627 pen.
setWidthF(PenWidthUtil::penWidth(e, decl, unit));
639 opacity = decl->doubleValue();
646void SceneController::applyCasingPenStyle(
OSM::Element e,
const MapCSSDeclaration *decl,
QPen &pen,
double &opacity,
Unit &unit)
const
648 switch (decl->property()) {
653 pen.
setWidthF(PenWidthUtil::penWidth(e, decl, unit));
665 opacity = decl->doubleValue();
672void SceneController::applyFontStyle(
const MapCSSDeclaration *decl,
QFont &font)
const
674 switch (decl->property()) {
679 if (decl->unit() == MapCSSDeclaration::Pixels) {
686 font.
setBold(decl->isBoldStyle());
705void SceneController::initializePen(
QPen &pen)
const
716void SceneController::finalizePen(
QPen &pen,
double opacity)
const
719 auto c = pen.
color();
731 std::for_each(dashes.begin(), dashes.end(), [pen](
double &d) { d /= pen.widthF(); });
736void SceneController::addItem(
SceneGraph &sg,
const MapCSSState &state,
int level,
const MapCSSResultLayer &result, std::unique_ptr<SceneGraphItemPayload> &&payload)
const
742 item.payload = std::move(payload);
747 if (layerStr && !(*layerStr).isEmpty()) {
748 bool success =
false;
749 const auto layer = (*layerStr).toInt(&success);
761 if (level != layer * 10) {
765 qCWarning(
Log) <<
"Invalid layer:" << state.element.url() << *layerStr;
769 item.layer = std::numeric_limits<int>::max();
772 sg.addItem(std::move(item));
777 return d->m_hoverElement;
780void SceneController::setHoveredElement(
OSM::Element element)
782 if (d->m_hoverElement == element) {
785 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
@ 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 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
@ 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
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)
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
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
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
void setAlignment(Qt::Alignment alignment)
void setWrapMode(WrapMode mode)