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
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.
Result of MapCSS stylesheet evaluation for all layer selectors.
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
Bounding box, ie.
Definition datatypes.h:95
A set of nodes, ways and relations.
Definition datatypes.h:343
A reference to any of OSM::Node/OSMWay/OSMRelation.
Definition element.h:24
A key of an OSM tag.
Definition datatypes.h:179
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
bool contains(QByteArrayView bv) const const
bool isEmpty() const const
QString fromUtf8(QByteArrayView str)
bool isEmpty() const const
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-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:57:46 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.