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/mapcssstate_p.h"
20#include <KOSMIndoorMap/MapData>
21#include <KOSMIndoorMap/MapCSSResult>
22#include <KOSMIndoorMap/MapCSSStyle>
23#include <KOSMIndoorMap/OverlaySource>
24#include <KOSMIndoorMap/SceneGraph>
25#include <KOSMIndoorMap/View>
27#include <osm/element.h>
28#include <osm/datatypes.h>
31#include <QElapsedTimer>
32#include <QGuiApplication>
36class SceneControllerPrivate
41 const View *m_view =
nullptr;
42 std::vector<QPointer<AbstractOverlaySource>> m_overlaySources;
43 mutable std::vector<OSM::Element> m_hiddenElements;
50 TextureCache m_textureCache;
51 IconLoader m_iconLoader;
52 OpeningHoursCache m_openingHours;
53 PoleOfInaccessibilityFinder m_piaFinder;
60 bool m_overlay =
false;
66SceneController::SceneController() : d(new SceneControllerPrivate)
70SceneController::~SceneController() =
default;
72void SceneController::setMapData(
const MapData &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);
82 d->m_openingHours.setMapData(
MapData());
87void SceneController::setStyleSheet(
const MapCSSStyle *styleSheet)
89 d->m_styleSheet = styleSheet;
93void SceneController::setView(
const View *view)
96 QObject::connect(view, &View::timeChanged, view, [
this]() { d->m_dirty =
true; });
102 d->m_overlaySources = std::move(overlays);
115 sgUpdateTimer.
start();
118 if (!d->m_view || !d->m_styleSheet) {
123 if (sg.zoomLevel() == (
int)d->m_view->zoomLevel() && sg.currentFloorLevel() == d->m_view->level() && !d->m_dirty) {
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());
134 if (d->m_data.isEmpty()) {
140 auto it = d->m_data.levelMap().find(
MapLevel(d->m_view->level()));
141 if (it == d->m_data.levelMap().end()) {
146 if (beginIt != d->m_data.levelMap().begin()) {
149 }
while (!(*beginIt).first.isFullLevel() && beginIt != d->m_data.levelMap().begin());
154 for (++endIt; endIt != d->m_data.levelMap().end(); ++endIt) {
155 if ((*endIt).first.isFullLevel()) {
161 d->m_hiddenElements.clear();
162 for (
const auto &overlaySource : d->m_overlaySources) {
163 overlaySource->hiddenElements(d->m_hiddenElements);
165 std::sort(d->m_hiddenElements.begin(), d->m_hiddenElements.end());
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);
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);
186 d->m_overlay =
false;
191 qCDebug(RenderLog) <<
"updated scenegraph took" << sgUpdateTimer.
elapsed() <<
"ms";
194void SceneController::updateCanvas(
SceneGraph &sg)
const
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());
210 d->m_defaultTextColor = decl->colorValue();
222 state.zoomLevel = d->m_view->zoomLevel();
223 state.floorLevel = d->m_view->level();
224 state.openingHours = &d->m_openingHours;
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);
232[[nodiscard]]
static bool canWordWrap(
const QString &s)
234 return std::any_of(s.
begin(), s.
end(), [](
QChar c) { return !c.isLetter(); });
241 std::unique_ptr<SceneGraphItemPayload> baseItem;
242 if (e.type() == OSM::Type::Relation && e.tagValue(d->m_typeTag) ==
"multipolygon") {
246 i->path = createPath(e, d->m_labelPlacementPath);
248 SceneGeometry::outerPolygonFromPath(i->path, d->m_labelPlacementPath);
253 auto i =
static_cast<PolygonItem*
>(baseItem.get());
255 i->polygon = createPolygon(e);
257 d->m_labelPlacementPath = i->polygon;
261 double lineOpacity = 1.0;
262 double casingOpacity = 1.0;
263 double fillOpacity = 1.0;
264 bool hasTexture =
false;
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());
278 fillOpacity = decl->doubleValue();
281 item->textureBrush.
setTextureImage(d->m_textureCache.image(decl->stringValue()));
288 finalizePen(item->pen, lineOpacity);
289 finalizePen(item->casingPen, casingOpacity);
291 auto c = item->fillBrush.
color();
298 if (hasTexture && item->textureBrush.
style() !=
Qt::NoBrush && fillOpacity > 0.0) {
299 auto c = item->textureBrush.
color();
306 addItem(sg, e, level, result, std::move(baseItem));
311 item->path = createPolygon(e);
314 double lineOpacity = 1.0;
315 double casingOpacity = 1.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);
324 finalizePen(item->pen, lineOpacity);
325 finalizePen(item->casingPen, casingOpacity);
327 d->m_labelPlacementPath = item->path;
328 addItem(sg, e, level, result, std::move(baseItem));
339 if (!textDecl->keyValue().isEmpty()) {
340 text =
QString::fromUtf8(e.tagValue(d->m_langs, textDecl->keyValue().constData()));
342 text = textDecl->stringValue();
348 if (!text.
isEmpty() || iconDecl) {
350 auto item =
static_cast<LabelItem*
>(baseItem.get());
352 item->textIsSet = !text.
isEmpty();
353 item->textOutputSizeCache = {};
354 item->font = d->m_defaultFont;
355 item->color = d->m_defaultTextColor;
357 item->textOffset = 0;
360 double textOpacity = 1.0;
361 double shieldOpacity = 1.0;
362 bool forceCenterPosition =
false;
363 bool forceLinePosition =
false;
365 for (
auto decl : result.declarations()) {
366 applyGenericStyle(decl, item);
367 applyFontStyle(decl, item->font);
368 switch (decl->property()) {
370 item->color = decl->colorValue();
373 textOpacity = decl->doubleValue();
376 item->casingColor = decl->colorValue();
379 item->casingWidth = decl->doubleValue();
382 item->shieldColor = decl->colorValue();
385 shieldOpacity = decl->doubleValue();
388 item->frameColor = decl->colorValue();
391 item->frameWidth = decl->doubleValue();
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);
401 case MapCSSDeclaration::Position::Center:
402 forceCenterPosition =
true;
404 case MapCSSDeclaration::Position::NoPostion:
409 item->textOffset = decl->doubleValue();
414 if (canWordWrap(text)) {
419 if (!decl->keyValue().isEmpty()) {
422 iconData.name = decl->stringValue();
426 item->iconSize.
setHeight(PenWidthUtil::penWidth(e, decl, item->iconHeightUnit));
429 item->iconSize.
setWidth(PenWidthUtil::penWidth(e, decl, item->iconWidthUnit));
433 const auto alpha = iconData.color.alphaF();
434 iconData.color = decl->colorValue().rgb();
435 iconData.color.setAlphaF(alpha);
439 iconData.color.setAlphaF(decl->doubleValue());
442 item->haloColor = decl->colorValue();
445 item->haloRadius = decl->doubleValue();
448 item->allowIconOverlap = decl->boolValue();
451 item->allowTextOverlap = decl->boolValue();
461 if (d->m_labelPlacementPath.size() > 6) {
462 item->pos = d->m_piaFinder.find(d->m_labelPlacementPath);
464 item->pos = SceneGeometry::polygonCentroid(d->m_labelPlacementPath);
467 item->pos = SceneGeometry::polylineMidPoint(d->m_labelPlacementPath);
470 item->pos = d->m_view->mapGeoToScene(e.center());
474 if (item->color.
isValid() && textOpacity < 1.0) {
475 auto c = item->color;
479 if (item->shieldColor.
isValid() && shieldOpacity < 1.0) {
480 auto c = item->shieldColor;
482 item->shieldColor = c;
484 if (!iconData.name.isEmpty() && iconData.color.alphaF() > 0.0) {
485 if (!iconData.color.isValid()) {
486 iconData.color = d->m_defaultTextColor;
488 item->icon = d->m_iconLoader.loadIcon(iconData);
489 item->iconOpacity = iconData.color.alphaF();
491 if (!item->icon.
isNull()) {
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) {
498 }
else if (item->iconSize.
height() <= 0.0) {
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()) {
528 if (!item->icon.
isNull() && item->textOffset == 0.0) {
529 item->textOffset = item->iconSize.
height();
534 addItem(sg, e, level, result, std::move(baseItem));
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())) {
564 poly = poly.
isEmpty() ? std::move(subPoly) : poly.united(subPoly);
572 assert(e.type() == OSM::Type::Relation);
573 outerPath = createPolygon(e);
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)) {
583 if (
auto way = d->m_data.dataSet().way(mem.id)) {
588 path.addPolygon(subPoly);
596void SceneController::applyGenericStyle(
const MapCSSDeclaration *decl,
SceneGraphItemPayload *item)
const
598 if (decl->property() == MapCSSProperty::ZIndex) {
599 item->z = decl->intValue();
603void SceneController::applyPenStyle(
OSM::Element e,
const MapCSSDeclaration *decl,
QPen &pen,
double &opacity,
Unit &unit)
const
605 switch (decl->property()) {
610 pen.
setWidthF(PenWidthUtil::penWidth(e, decl, unit));
622 opacity = decl->doubleValue();
629void SceneController::applyCasingPenStyle(
OSM::Element e,
const MapCSSDeclaration *decl,
QPen &pen,
double &opacity,
Unit &unit)
const
631 switch (decl->property()) {
636 pen.
setWidthF(PenWidthUtil::penWidth(e, decl, unit));
648 opacity = decl->doubleValue();
655void SceneController::applyFontStyle(
const MapCSSDeclaration *decl,
QFont &font)
const
657 switch (decl->property()) {
662 if (decl->unit() == MapCSSDeclaration::Pixels) {
669 font.
setBold(decl->isBoldStyle());
688void SceneController::initializePen(
QPen &pen)
const
699void SceneController::finalizePen(
QPen &pen,
double opacity)
const
702 auto c = pen.
color();
714 std::for_each(dashes.begin(), dashes.end(), [pen](
double &d) { d /= pen.widthF(); });
725 item.payload = std::move(payload);
729 auto layerStr = result.
tagValue(d->m_layerTag);
730 if (layerStr.isNull()) {
731 layerStr = e.tagValue(d->m_layerTag);
733 if (!layerStr.isEmpty()) {
734 bool success =
false;
735 const auto layer = layerStr.
toInt(&success);
747 if (level != layer * 10) {
751 qCWarning(
Log) <<
"Invalid layer:" << e.url() << layerStr;
755 item.layer = std::numeric_limits<int>::max();
758 sg.addItem(std::move(item));
763 return d->m_hoverElement;
766void SceneController::setHoveredElement(
OSM::Element element)
768 if (d->m_hoverElement == element) {
771 d->m_hoverElement = element;
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.
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
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)
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
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
qsizetype size() const const
void setAlignment(Qt::Alignment alignment)
void setWrapMode(WrapMode mode)