KOSMIndoorMap

roommodel.cpp
1/*
2 SPDX-FileCopyrightText: 2024 Volker Krause <vkrause@kde.org>
3 SPDX-License-Identifier: LGPL-2.0-or-later
4*/
5
6#include "roommodel.h"
7#include "localization.h"
8#include "logging.h"
9#include "osmelement.h"
10
11#include <style/mapcssdeclaration_p.h>
12#include <style/mapcssstate_p.h>
13
14#include <KOSMIndoorMap/MapCSSParser>
15#include <KOSMIndoorMap/MapCSSResult>
16
17#include <KLocalizedString>
18
19#include <QDebug>
20#include <QFile>
21#include <QPointF>
22
23#include <limits>
24
25using namespace KOSMIndoorMap;
26
27RoomModel::RoomModel(QObject *parent)
28 : QAbstractListModel(parent)
29 , m_langs(OSM::Languages::fromQLocale(QLocale()))
30{
31}
32
33RoomModel::~RoomModel() = default;
34
35MapData RoomModel::mapData() const
36{
37 return m_data;
38}
39
40void RoomModel::setMapData(const MapData &data)
41{
42 if (m_data == data) {
43 return;
44 }
45
46 if (m_style.isEmpty()) {
48 m_style = p.parse(QStringLiteral(":/org.kde.kosmindoormap/assets/quick/room-model.mapcss"));
49 if (p.hasError()) {
50 qWarning() << p.errorMessage();
51 return;
52 }
53 }
54
56 m_buildings.clear();
57 m_rooms.clear();
58 m_data = data;
59 if (!m_data.isEmpty()) {
60 m_style.compile(m_data.dataSet());
61 }
63 Q_EMIT mapDataChanged();
64}
65
66int RoomModel::rowCount(const QModelIndex &parent) const
67{
68 if (parent.isValid()) {
69 return 0;
70 }
71
72 if (m_rooms.empty() && !m_data.isEmpty()) {
73 // we assume that this is expensive but almost never will result in an empty result
74 // and if it does nevertheless, it's a sparsely populated tile where this is cheap
75 const_cast<RoomModel*>(this)->populateModel();
76 }
77
78 return (int)m_rooms.size();
79}
80
81QVariant RoomModel::data(const QModelIndex &index, int role) const
82{
83 if (!checkIndex(index)) {
84 return {};
85 }
86
87 const auto &room = m_rooms[index.row()];
88 switch (role) {
89 case NameRole:
90 // TODO better name/number handling - separate roles?
91 return QString::fromUtf8(room.element.tagValue(m_langs, "name"));
92 case NumberRole:
93 return QString::fromUtf8(room.element.tagValue("ref"));
94 case TypeNameRole:
95 {
96 const auto types = room.element.tagValue("room", "amenity").split(';');
98 for (const auto &type : types) {
99 if (type == "yes") {
100 continue;
101 }
102 auto s = Localization::amenityType(type.trimmed().constData(), Localization::ReturnEmptyOnUnknownKey);
103 if (!s.isEmpty()) {
104 l.push_back(std::move(s));
105 }
106 }
107 return QLocale().createSeparatedList(l);
108 }
109 case CoordinateRole:
110 {
111 const auto center = room.element.center();
112 return QPointF(center.lonF(), center.latF());
113 }
114 case LevelRole:
115 return room.level;
116 case ElementRole:
117 return QVariant::fromValue(OSMElement(room.element));
118 case BuildingNameRole:
119 return QString::fromUtf8(room.buildingElement.tagValue(m_langs, "name", "local_ref", "ref"));
121 {
122 auto s = QString::fromUtf8(room.levelElement.tagValue(m_langs, "name", "level:ref"));
123 if (!s.isEmpty()) {
124 return s;
125 }
126
127 if ((room.level / 10) == 0) {
128 return i18n("Ground floor");
129 }
130 return i18n("Floor %1", room.level / 10); // TODO this isn't properly localized...
131 }
133 {
134 auto s = QString::fromUtf8(room.levelElement.tagValue(m_langs, "level:ref"));
135 if (!s.isEmpty()) {
136 return s;
137 }
138 return QString::number(room.level/ 10); // TODO this could use localized floor level abbrevations
139 }
140
141 }
142
143 return {};
144}
145
146QHash<int, QByteArray> RoomModel::roleNames() const
147{
149 r.insert(NameRole, "name");
150 r.insert(NumberRole, "number");
151 r.insert(TypeNameRole, "typeName");
152 r.insert(CoordinateRole, "coordinate");
153 r.insert(LevelRole, "level");
154 r.insert(ElementRole, "element");
155 r.insert(BuildingNameRole, "buildingName");
156 r.insert(LevelLongNameRole, "levelLongName");
157 r.insert(LevelShortNameRole, "levelShortName");
158 return r;
159}
160
161int RoomModel::buildingCount() const
162{
163 return (int)m_buildings.size();
164}
165
166void RoomModel::populateModel()
167{
168 // find all buildings
169 const auto buildingKey = m_data.dataSet().tagKey("building");
170 const auto nameKey = m_data.dataSet().tagKey("name");
171 const auto refKey = m_data.dataSet().tagKey("ref");
172
173 for (auto it = m_data.levelMap().begin(); it != m_data.levelMap().end(); ++it) {
174 for (const auto &e : (*it).second) {
175 if (e.type() == OSM::Type::Node || !OSM::contains(m_data.boundingBox(), e.center())) {
176 continue;
177 }
178 if (e.hasTag(buildingKey) && (e.hasTag(nameKey) || e.hasTag(refKey))) {
179 Building building;
180 building.element = e;
181 // building.outerPath = e.outerPath(m_data.dataSet()); TODO needed?
182 m_buildings.push_back(std::move(building));
183 }
184 }
185 }
186
187 // find floor levels for each building
188 const auto indoorKey = m_data.dataSet().tagKey("indoor");
189 for (auto it = m_data.levelMap().begin(); it != m_data.levelMap().end(); ++it) {
190 for (const auto &e : (*it).second) {
191 if (e.type() == OSM::Type::Node || !OSM::contains(m_data.boundingBox(), e.center())) {
192 continue;
193 }
194 if (e.tagValue(indoorKey) == "level") {
195 Level level;
196 level.element = e;
197 level.level = (*it).first.numericLevel();
198
199 // find building this level belongs to
200 for (auto &building : m_buildings) {
201 // TODO this is likely not precise enough?
202 if (OSM::intersects(e.boundingBox(), building.element.boundingBox())) {
203 building.levels.push_back(level);
204 break;
205 }
206 }
207 }
208 }
209 }
210
211 // find all rooms
212 MapCSSResult filterResult;
213 for (auto it = m_data.levelMap().begin(); it != m_data.levelMap().end(); ++it) {
214 for (const auto &e : (*it).second) {
215 if (e.type() == OSM::Type::Node || !OSM::contains(m_data.boundingBox(), e.center())) {
216 continue;
217 }
218
219 MapCSSState filterState;
220 filterState.element = e;
221 m_style.initializeState(filterState);
222 m_style.evaluate(filterState, filterResult);
223
224 const auto &res = filterResult[{}];
225 if (auto prop = res.declaration(MapCSSProperty::Opacity); !prop || prop->doubleValue() < 1.0) {
226 continue; // hidden element
227 }
228
229 Room room;
230 room.element = e;
231 room.level = (*it).first.numericLevel(); // TODO we only need one entry, not one per level!
232
233 // find the building this room is in
234 for (auto &building :m_buildings) {
235 // TODO this is likely not precise enough?
236 if (OSM::intersects(e.boundingBox(), building.element.boundingBox())) {
237 room.buildingElement = building.element;
238 ++building.roomCount;
239
240 // find level meta-data if available
241 for (const auto &level : building.levels) {
242 if (level.level == room.level) {
243 room.levelElement = level.element;
244 break;
245 }
246 }
247
248 break;
249 }
250 }
251
252 m_rooms.push_back(std::move(room));
253 }
254 }
255
256 // TODO we could accumulate the covered levels and show all of them?
257 // de-duplicate multi-level entries
258 // we could also just iterate over the non-level-split data, but
259 // then we need to reparse the level data here...
260 std::sort(m_rooms.begin(), m_rooms.end(), [](const auto &lhs, const auto &rhs) {
261 if (lhs.element == rhs.element) {
262 return std::abs(lhs.level) < std::abs(rhs.level);
263 }
264 return lhs.element < rhs.element;
265 });
266 m_rooms.erase(std::unique(m_rooms.begin(), m_rooms.end(), [](const auto &lhs, const auto &rhs) {
267 return lhs.element == rhs.element;
268 }), m_rooms.end());
269
270 // de-duplicate multi-level rooms that consist of multiple OSM elements (e.g. due to varying sizes per floor)
271 // TODO
272
273 // sort by building
274 std::sort(m_rooms.begin(), m_rooms.end(), [](const auto &lhs, const auto &rhs) {
275 return lhs.buildingElement < rhs.buildingElement;
276 });
277
278 // remove buildings without rooms
279 m_buildings.erase(std::remove_if(m_buildings.begin(), m_buildings.end(), [](const auto &b) { return b.roomCount == 0; }), m_buildings.end());
280
281 qCDebug(Log) << m_buildings.size() << "buildings found";
282 qCDebug(Log) << m_rooms.size() << "rooms found";
283 Q_EMIT populated();
284}
285
286#include "moc_roommodel.cpp"
Result of MapCSS stylesheet evaluation for all layer selectors.
void compile(const OSM::DataSet &dataSet)
Optimizes style sheet rules for application against dataSet.
void evaluate(const MapCSSState &state, MapCSSResult &result) const
Evaluates the style sheet for a given state state (OSM element, view state, element state,...
bool isEmpty() const
Returns true if this is a default-constructed or otherwise empty/invalud style.
void initializeState(MapCSSState &state) const
Initializes the evaluation state.
Raw OSM map data, separated by levels.
Definition mapdata.h:60
QML wrapper around an OSM element.
Definition osmelement.h:19
List all rooms of buildings in a given data set.
Definition roommodel.h:21
int buildingCount
Number of buildings found in the model data.
Definition roommodel.h:25
@ TypeNameRole
Type of the room as translated human readable text, if set.
Definition roommodel.h:39
@ NumberRole
room number, if set
Definition roommodel.h:36
@ LevelRole
numeric level for positioning rather than for display
Definition roommodel.h:37
@ ElementRole
OSM element for this room.
Definition roommodel.h:38
@ LevelShortNameRole
Name of the floor the room is on (short form, if available)
Definition roommodel.h:42
@ NameRole
room name, if set
Definition roommodel.h:34
@ LevelLongNameRole
Name of the floor the room is on (long form, if available)
Definition roommodel.h:41
@ BuildingNameRole
Name of the building the room is in.
Definition roommodel.h:40
TagKey tagKey(const char *keyName) const
Look up a tag key for the given tag name, if it exists.
Definition datatypes.cpp:38
QString i18n(const char *text, const TYPE &arg...)
QString amenityType(const char *value, Localization::TranslationOption opt=Localization::ReturnUnknownKey)
Translated name for an amenity tag value (after list splitting).
OSM-based multi-floor indoor maps for buildings.
QStringView level(QStringView ifopt)
Low-level types and functions to work with raw OSM data as efficiently as possible.
bool checkIndex(const QModelIndex &index, CheckIndexOptions options) const const
virtual QHash< int, QByteArray > roleNames() const const
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
void push_back(parameter_type value)
QString createSeparatedList(const QStringList &list) const const
int row() const const
Q_EMITQ_EMIT
QObject * parent() const const
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
QChar first() const const
QTextStream & center(QTextStream &stream)
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 17 2024 11:53:52 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.