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.evaluate(std::move(filterState), filterResult);
202 if (auto prop = filterResult[{}].declaration(MapCSSProperty::Opacity)) {
203 if (prop->doubleValue() == 0.0) {
204 qDebug() << "input filter dropped" << e.url();
205 return;
206 }
207 // anything that doesn't work on its own is a "dependent element"
208 // we discard levels only containing dependent elements, but we retain all of them if the
209 // level contains an element we are sure about that we can display it
210 if (prop->doubleValue() < 1.0) {
211 isDependentElement = true;
212 }
213 }
214#endif
215
216 // bbox computation
217 e.recomputeBoundingBox(d->m_dataSet);
218 d->m_bbox = OSM::unite(e.boundingBox(), d->m_bbox);
219
220 // multi-level building element
221 // we handle this first, before level=, as level is often used instead
222 // of building:min_level in combination with building:level
223 const auto buildingLevels = e.tagValue(buildingLevelsTag, maxLevelTag).toInt();
224 if (buildingLevels > 0) {
225 const auto startLevel = e.tagValue(buildingMinLevelTag, levelTag, minLevelTag).toInt();
226 //qDebug() << startLevel << buildingLevels << e.url();
227 for (auto i = startLevel; i < startLevel + buildingLevels; ++i) {
228 addElement(i * 10, e, true);
229 }
230 }
231 const auto undergroundLevels = e.tagValue(buildingLevelsUndergroundTag).toUInt();
232 for (auto i = undergroundLevels; i > 0; --i) {
233 addElement(-i * 10, e, true);
234 }
235 if (buildingLevels > 0 || undergroundLevels > 0) {
236 return;
237 }
238
239 // element with explicit level specified
240 auto level = e.tagValue(levelTag);
241 if (level.isEmpty()) {
242 level = e.tagValue(repeatOnTag);
243 }
244 if (!level.isEmpty()) { // level-less -> outdoor
245 LevelParser::parse(std::move(level), e, [this, isDependentElement](int level, OSM::Element e) {
246 addElement(level, e, isDependentElement);
247 });
248 return;
249 }
250
251 // no level information available
252 d->m_levelMap[MapLevel{}].push_back(e);
253 if (isDependentElement) {
254 d->m_dependentElementCounts[MapLevel{}]++;
255 }
256 });
257}
258
259void MapData::addElement(int level, OSM::Element e, bool isDependentElement)
260{
261 MapLevel l(level);
262 auto it = d->m_levelMap.find(l);
263 if (it == d->m_levelMap.end()) {
264 l.setName(levelName(e));
265 d->m_levelMap[l] = {e};
266 } else {
267 if (!(*it).first.hasName()) {
268 // name does not influence op< behavior, so modifying the key here is safe
269 const_cast<MapLevel&>((*it).first).setName(levelName(e));
270 }
271 (*it).second.push_back(e);
272 }
273 if (isDependentElement) {
274 d->m_dependentElementCounts[l]++;
275 }
276}
277
278static bool isPlausibleLevelName(const QByteArray &s)
279{
280 return !s.isEmpty() && !s.contains(';');
281}
282
283QString MapData::levelName(OSM::Element e)
284{
285 const auto n = e.tagValue(d->m_levelRefTag);
286 if (isPlausibleLevelName(n)) {
287 return QString::fromUtf8(n);
288 }
289
290 if (e.type() == OSM::Type::Relation) {
291 const auto isLevelRel = std::all_of(e.relation()->members.begin(), e.relation()->members.end(), [](const auto &mem) {
292 return std::strcmp(mem.role().name(), "shell") == 0 || std::strcmp(mem.role().name(), "buildingpart") == 0;
293 });
294 if (isLevelRel) {
295 const auto n = e.tagValue(d->m_nameTag);
296 if (isPlausibleLevelName(n)) {
297 return QString::fromUtf8(n);
298 }
299 }
300 }
301
302 return {};
303}
304
305void MapData::filterLevels()
306{
307 // remove all levels that don't contain something we are sure would make a meaningful output
308 // always retain the base level though
309 for (auto it = d->m_levelMap.begin(); it != d->m_levelMap.end();) {
310 if ((*it).first.numericLevel() != 0 && d->m_dependentElementCounts[(*it).first] == (*it).second.size()) {
311 it = d->m_levelMap.erase(it);
312 } else {
313 ++it;
314 }
315 }
316 d->m_dependentElementCounts.clear();
317}
318
320{
321 return QPointF(d->m_bbox.center().lonF(), d->m_bbox.center().latF());
322}
323
324float MapData::radius() const
325{
326 return std::max(OSM::distance(d->m_bbox.center(), d->m_bbox.min), OSM::distance(d->m_bbox.center(), d->m_bbox.max));
327}
328
329QString MapData::regionCode() const
330{
331 return d->m_regionCode;
332}
333
334void MapData::setRegionCode(const QString &regionCode)
335{
336 d->m_regionCode = regionCode;
337}
338
339QTimeZone MapData::timeZone() const
340{
341 return d->m_timeZone;
342}
343
344void MapData::setTimeZone(const QTimeZone &tz)
345{
346 d->m_timeZone = tz;
347}
348
349QString MapData::timeZoneId() const
350{
351 return QString::fromUtf8(d->m_timeZone.id());
352}
353
354#include "moc_mapdata.cpp"
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:340
A reference to any of OSM::Node/OSM::Way/OSM::Relation.
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 Tue Mar 26 2024 11:20:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.