KOSMIndoorMap

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

KDE's Doxygen guidelines are available online.