KOSMIndoorMap

mapdata.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include <config-kosmindoormap.h>
8#include "mapdata.h"
9#include "levelparser_p.h"
10
11#if !BUILD_TOOLS_ONLY
12#include "style/mapcssdeclaration_p.h"
13#include "style/mapcssresult.h"
14#include "style/mapcssstate_p.h"
15
16#include <KOSMIndoorMap/MapCSSParser>
17#include <KOSMIndoorMap/MapCSSProperty>
18#include <KOSMIndoorMap/MapCSSStyle>
19#endif
20
21#include <osm/geomath.h>
22
23#include <QPointF>
24#include <QTimeZone>
25
26using namespace KOSMIndoorMap;
27
28MapLevel::MapLevel(int level)
29 : m_level(level)
30{
31}
32
33MapLevel::~MapLevel() = default;
34
35bool MapLevel::operator<(const MapLevel &other) const
36{
37 return m_level > other.m_level;
38}
39
40bool MapLevel::operator==(const MapLevel &other) const
41{
42 return m_level == other.m_level;
43}
44
45bool MapLevel::hasName() const
46{
47 return !m_levelName.isEmpty();
48}
49
50QString MapLevel::name() const
51{
52 if (m_levelName.isEmpty()) {
53 return QString::number(m_level / 10);
54 }
55 return m_levelName;
56}
57
58void MapLevel::setName(const QString &name)
59{
60 m_levelName = name;
61}
62
63bool MapLevel::isFullLevel() const
64{
65 return m_level % 10 == 0;
66}
67
69{
70 return m_level < 0 ? (m_level - (10 + m_level % 10)) : (m_level - m_level % 10);
71}
72
73int MapLevel::fullLevelAbove() const
74{
75 return m_level < 0 ? (m_level - m_level % 10) : (m_level + (10 - m_level % 10));
76}
77
78int MapLevel::numericLevel() const
79{
80 return m_level;
81}
82
83namespace KOSMIndoorMap {
84class MapDataPrivate {
85public:
86 OSM::DataSet m_dataSet;
87 OSM::BoundingBox m_bbox;
88
89 OSM::TagKey m_levelRefTag;
90 OSM::TagKey m_nameTag;
91
92 std::map<MapLevel, std::vector<OSM::Element>> m_levelMap;
93 std::map<MapLevel, std::size_t> m_dependentElementCounts;
94
95 QString m_regionCode;
96 QTimeZone m_timeZone;
97};
98}
99
100MapData::MapData()
101 : d(std::make_shared<MapDataPrivate>())
102{
103}
104
105MapData::MapData(const MapData&) = default;
106MapData::MapData(MapData&&) = default;
107MapData::~MapData() = default;
108
109MapData& MapData::operator=(const MapData&) = default;
110MapData& MapData::operator=(MapData&&) = default;
111
112const OSM::DataSet& MapData::dataSet() const
113{
114 return d->m_dataSet;
115}
116
117bool MapData::isEmpty() const
118{
119 return !d || d->m_levelMap.empty();
120}
121
122bool MapData::operator==(const MapData &other) const
123{
124 return d == other.d;
125}
126
127OSM::DataSet& MapData::dataSet()
128{
129 return d->m_dataSet;
130}
131
132void MapData::setDataSet(OSM::DataSet &&dataSet)
133{
134 d->m_dataSet = std::move(dataSet);
135
136 d->m_levelRefTag = d->m_dataSet.tagKey("level:ref");
137 d->m_nameTag = d->m_dataSet.tagKey("name");
138
139 d->m_levelMap.clear();
140 d->m_bbox = {};
141
142 processElements();
143 filterLevels();
144}
145
146OSM::BoundingBox MapData::boundingBox() const
147{
148 return d->m_bbox;
149}
150
151void MapData::setBoundingBox(OSM::BoundingBox bbox)
152{
153 d->m_bbox = bbox;
154}
155
156const std::map<MapLevel, std::vector<OSM::Element>>& MapData::levelMap() const
157{
158 return d->m_levelMap;
159}
160
161void MapData::processElements()
162{
163 const auto levelTag = d->m_dataSet.tagKey("level");
164 const auto repeatOnTag = d->m_dataSet.tagKey("repeat_on");
165 const auto buildingLevelsTag = d->m_dataSet.tagKey("building:levels");
166 const auto buildingMinLevelTag = d->m_dataSet.tagKey("building:min_level");
167 const auto buildingLevelsUndergroundTag = d->m_dataSet.tagKey("building:levels:underground");
168 const auto maxLevelTag = d->m_dataSet.tagKey("max_level");
169 const auto minLevelTag = d->m_dataSet.tagKey("min_level");
170 const auto countryTag = d->m_dataSet.tagKey("addr:country");
171
172#if !BUILD_TOOLS_ONLY
173 MapCSSParser p;
174 auto filter = p.parse(QStringLiteral(":/org.kde.kosmindoormap/assets/css/input-filter.mapcss"));
175 if (p.hasError()) {
176 qWarning() << p.errorMessage();
177 }
178 filter.compile(d->m_dataSet);
179 MapCSSResult filterResult;
180#endif
181
182 OSM::for_each(d->m_dataSet, [&](auto e) {
183 // discard everything here that is tag-less (and thus likely part of a higher-level geometry)
184 if (!e.hasTags()) {
185 return;
186 }
187
188 // attempt to detect the country we are in
189 if (d->m_regionCode.isEmpty()) {
190 const auto countryCode = e.tagValue(countryTag);
191 if (countryCode.size() == 2 && std::isupper(static_cast<unsigned char>(countryCode[0])) && std::isupper(static_cast<unsigned char>(countryCode[1]))) {
192 d->m_regionCode = QString::fromUtf8(countryCode);
193 }
194 }
195
196 // apply the input filter, anything that explicitly got opacity 0 will be discarded
197 bool isDependentElement = false;
198#if !BUILD_TOOLS_ONLY
199 MapCSSState filterState;
200 filterState.element = e;
201 filter.initializeState(filterState);
202 filter.evaluate(filterState, filterResult);
203 if (auto prop = filterResult[{}].declaration(MapCSSProperty::Opacity)) {
204 if (prop->doubleValue() == 0.0) {
205 qDebug() << "input filter dropped" << e.url();
206 return;
207 }
208 // anything that doesn't work on its own is a "dependent element"
209 // we discard levels only containing dependent elements, but we retain all of them if the
210 // level contains an element we are sure about that we can display it
211 if (prop->doubleValue() < 1.0) {
212 isDependentElement = true;
213 }
214 }
215#endif
216
217 // bbox computation
218 e.recomputeBoundingBox(d->m_dataSet);
219 d->m_bbox = OSM::unite(e.boundingBox(), d->m_bbox);
220
221 // multi-level building element
222 // we handle this first, before level=, as level is often used instead
223 // of building:min_level in combination with building:level
224 const auto buildingLevels = e.tagValue(buildingLevelsTag, maxLevelTag).toInt();
225 if (buildingLevels > 0) {
226 const auto startLevel = e.tagValue(buildingMinLevelTag, levelTag, minLevelTag).toInt();
227 //qDebug() << startLevel << buildingLevels << e.url();
228 for (auto i = startLevel; i < startLevel + buildingLevels; ++i) {
229 addElement(i * 10, e, true);
230 }
231 }
232 const auto undergroundLevels = e.tagValue(buildingLevelsUndergroundTag).toUInt();
233 for (auto i = undergroundLevels; i > 0; --i) {
234 addElement(-i * 10, e, true);
235 }
236 if (buildingLevels > 0 || undergroundLevels > 0) {
237 return;
238 }
239
240 // element with explicit level specified
241 auto level = e.tagValue(levelTag);
242 auto repeatOn = e.tagValue(repeatOnTag);
243 if (level.isEmpty() && repeatOn.isEmpty()) {
244 // no level information available
245 d->m_levelMap[MapLevel{}].push_back(e);
246 if (isDependentElement) {
247 d->m_dependentElementCounts[MapLevel{}]++;
248 }
249 } else {
250 LevelParser::parse(std::move(level), e, [this, isDependentElement](int level, OSM::Element e) {
251 addElement(level, e, isDependentElement);
252 });
253 LevelParser::parse(std::move(repeatOn), e, [this, isDependentElement](int level, OSM::Element e) {
254 addElement(level, e, isDependentElement);
255 });
256 }
257 });
258}
259
260void MapData::addElement(int level, OSM::Element e, bool isDependentElement)
261{
262 MapLevel l(level);
263 auto it = d->m_levelMap.find(l);
264 if (it == d->m_levelMap.end()) {
265 l.setName(levelName(e));
266 d->m_levelMap[l] = {e};
267 } else {
268 if (!(*it).first.hasName()) {
269 // name does not influence op< behavior, so modifying the key here is safe
270 const_cast<MapLevel&>((*it).first).setName(levelName(e));
271 }
272 (*it).second.push_back(e);
273 }
274 if (isDependentElement) {
275 d->m_dependentElementCounts[l]++;
276 }
277}
278
279static bool isPlausibleLevelName(const QByteArray &s)
280{
281 return !s.isEmpty() && !s.contains(';');
282}
283
284QString MapData::levelName(OSM::Element e)
285{
286 const auto n = e.tagValue(d->m_levelRefTag);
287 if (isPlausibleLevelName(n)) {
288 return QString::fromUtf8(n);
289 }
290
291 if (e.type() == OSM::Type::Relation) {
292 const auto isLevelRel = std::all_of(e.relation()->members.begin(), e.relation()->members.end(), [](const auto &mem) {
293 return std::strcmp(mem.role().name(), "shell") == 0 || std::strcmp(mem.role().name(), "buildingpart") == 0;
294 });
295 if (isLevelRel) {
296 const auto n = e.tagValue(d->m_nameTag);
297 if (isPlausibleLevelName(n)) {
298 return QString::fromUtf8(n);
299 }
300 }
301 }
302
303 return {};
304}
305
306void MapData::filterLevels()
307{
308 // remove all levels that don't contain something we are sure would make a meaningful output
309 // always retain the base level though
310 for (auto it = d->m_levelMap.begin(); it != d->m_levelMap.end();) {
311 if ((*it).first.numericLevel() != 0 && d->m_dependentElementCounts[(*it).first] == (*it).second.size()) {
312 it = d->m_levelMap.erase(it);
313 } else {
314 ++it;
315 }
316 }
317 d->m_dependentElementCounts.clear();
318}
319
320QPointF MapData::center() const
321{
322 return QPointF(d->m_bbox.center().lonF(), d->m_bbox.center().latF());
323}
324
325float MapData::radius() const
326{
327 return std::max(OSM::distance(d->m_bbox.center(), d->m_bbox.min), OSM::distance(d->m_bbox.center(), d->m_bbox.max));
328}
329
330QString MapData::regionCode() const
331{
332 return d->m_regionCode;
333}
334
335void MapData::setRegionCode(const QString &regionCode)
336{
337 d->m_regionCode = regionCode;
338}
339
340QTimeZone MapData::timeZone() const
341{
342 return d->m_timeZone;
343}
344
345void MapData::setTimeZone(const QTimeZone &tz)
346{
347 d->m_timeZone = tz;
348}
349
350QString MapData::timeZoneId() const
351{
352 return QString::fromUtf8(d->m_timeZone.id());
353}
354
355#include "moc_mapdata.cpp"
bool hasError() const
Returns true if an error occured during parsing and the returned style is invalid.
Raw OSM map data, separated by levels.
Definition mapdata.h:60
float radius
Radius from the bounding box center encompassing the entire bounding box, in meters.
Definition mapdata.h:67
QPointF center
Center position of the bounding box for QML usage (longitude/latitude, in degree).
Definition mapdata.h:63
A floor level.
Definition mapdata.h:28
int fullLevelBelow() const
In case this is not a full level, this returns the numeric values of the full levels above/below.
Definition mapdata.cpp:68
OSM-based multi-floor indoor maps for buildings.
QStringView level(QStringView ifopt)
KOSM_EXPORT double distance(double lat1, double lon1, double lat2, double lon2)
Distance between two coordinates.
Definition geomath.cpp:16
void clear()
bool contains(QByteArrayView bv) const const
bool isEmpty() const const
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
bool isEmpty() const const
QFuture< void > filter(QThreadPool *pool, Sequence &sequence, KeepFunctor &&filterFunction)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Jan 24 2025 11:54:42 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.