KOSMIndoorMap

platform.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "platform.h"
8 
9 #include <osm/geomath.h>
10 #include <osm/pathutil.h>
11 
12 #include <QRegularExpression>
13 
14 using namespace KOSMIndoorMap;
15 
17 {
18  return !name.isEmpty() && position;
19 }
20 
21 
22 Platform::Platform() = default;
23 Platform::~Platform() = default;
24 
25 bool Platform::isValid() const
26 {
27  return !m_name.isEmpty() && position().isValid() && m_mode != Unknown;
28 }
29 
31 {
32  return m_name;
33 }
34 
35 void Platform::setName(const QString &name)
36 {
37  m_name = name;
38 }
39 
40 int Platform::level() const
41 {
42  return hasLevel() ? m_level : 0;
43 }
44 
45 bool Platform::hasLevel() const
46 {
47  return m_level != std::numeric_limits<int>::min();
48 }
49 
50 void Platform::setLevel(int level)
51 {
52  m_level = level;
53 }
54 
56 {
57  return OSM::coalesce(m_stopPoint, m_area).center();
58 }
59 
60 OSM::Element Platform::stopPoint() const
61 {
62  return m_stopPoint;
63 }
64 
65 void Platform::setStopPoint(OSM::Element stop)
66 {
67  m_stopPoint = stop;
68 }
69 
71 {
72  return OSM::coalesce(m_edge, m_stopPoint);
73 }
74 
75 void Platform::setEdge(OSM::Element edge)
76 {
77  m_edge = edge;
78 }
79 
81 {
82  return OSM::coalesce(m_area, m_edge, m_stopPoint);
83 }
84 
85 void Platform::setArea(OSM::Element area)
86 {
87  m_area = area;
88 }
89 
90 const std::vector<OSM::Element>& Platform::track() const
91 {
92  return m_track;
93 }
94 
95 void Platform::setTrack(std::vector<OSM::Element> &&track)
96 {
97  m_track = std::move(track);
98 }
99 
100 std::vector<OSM::Element>&& Platform::takeTrack()
101 {
102  return std::move(m_track);
103 }
104 
105 const std::vector<PlatformSection>& Platform::sections() const
106 {
107  return m_sections;
108 }
109 
110 void Platform::setSections(std::vector<PlatformSection> &&sections)
111 {
112  m_sections = std::move(sections);
113 }
114 
115 std::vector<PlatformSection>&& Platform::takeSections()
116 {
117  return std::move(m_sections);
118 }
119 
120 Platform::Mode Platform::mode() const
121 {
122  return m_mode;
123 }
124 
125 void Platform::setMode(Platform::Mode mode)
126 {
127  m_mode = mode;
128 }
129 
130 static bool conflictIfPresent(OSM::Element lhs, OSM::Element rhs)
131 {
132  return lhs && rhs && lhs != rhs;
133 }
134 
135 static bool equalIfPresent(OSM::Element lhs, OSM::Element rhs)
136 {
137  return lhs && rhs && lhs == rhs;
138 }
139 
140 static bool isSubPath(const std::vector<const OSM::Node*> &path, const OSM::Way &way)
141 {
142  return std::all_of(way.nodes.begin(), way.nodes.end(), [&path](OSM::Id node) {
143  return std::find_if(path.begin(), path.end(), [node](auto n) { return n->id == node; }) != path.end();
144  });
145 }
146 
147 static constexpr const auto MAX_TRACK_TO_EDGE_DISTANCE = 4.5; // meters
148 static constexpr const auto MAX_SECTION_TO_EDGE_DISTANCE = 5.0;
149 
150 static double maxSectionDistance(const std::vector<const OSM::Node*> &path, const std::vector<PlatformSection> &sections)
151 {
152  auto dist = std::numeric_limits<double>::lowest();
153  for (const auto &section : sections) {
154  dist = std::max(dist, OSM::distance(path, section.position.center()));
155  }
156  return dist;
157 }
158 
159 double Platform::maxSectionDistance(const Platform &p, const std::vector<PlatformSection> &sections, const OSM::DataSet &dataSet)
160 {
161  if (auto elem = OSM::coalesce(p.m_edge, p.m_area)) {
162  return ::maxSectionDistance(elem.outerPath(dataSet), sections);
163  }
164  if (!p.m_track.empty()) {
165  std::vector<const OSM::Node*> path;
166  OSM::assemblePath(dataSet, p.m_track, path);
167  return ::maxSectionDistance(path, sections);
168  }
169  return std::numeric_limits<double>::lowest();
170 }
171 
172 static const OSM::Way* outerWay(OSM::Element &multiPolygon, const OSM::DataSet &dataSet)
173 {
174  // ### this assumes multi-polygons are structured in the way the Marble generator normalizes them!
175  for (const auto &mem : multiPolygon.relation()->members) {
176  if (mem.type() == OSM::Type::Way && (std::strcmp(mem.role().name(), "outer") == 0)) {
177  return dataSet.way(mem.id);
178  }
179  }
180  return nullptr;
181 }
182 
183 static bool isConnectedGeometry(OSM::Element lhs, OSM::Element rhs, const OSM::DataSet &dataSet)
184 {
185  if (lhs == rhs) { return false; }
186  const OSM::Way *lway = nullptr;
187  const OSM::Way *rway = nullptr;
188 
189  switch (lhs.type()) {
190  case OSM::Type::Null:
191  case OSM::Type::Node:
192  return false;
193  case OSM::Type::Way:
194  lway = lhs.way();
195  break;
196  case OSM::Type::Relation:
197  lway = outerWay(lhs, dataSet);
198  break;
199 
200  }
201  switch (rhs.type()) {
202  case OSM::Type::Null:
203  case OSM::Type::Node:
204  return false;
205  case OSM::Type::Way:
206  rway = rhs.way();
207  break;
208  case OSM::Type::Relation:
209  rway = outerWay(rhs, dataSet);
210  break;
211  }
212  if (!lway || !rway) {
213  return false;
214  }
215 
216  if (!lway->isClosed() && !rway->isClosed()) {
217  return lway->nodes.front() == rway->nodes.front()
218  || lway->nodes.back() == rway->nodes.front()
219  || lway->nodes.front() == rway->nodes.back()
220  || lway->nodes.back() == rway->nodes.back();
221  }
222  if (lway->isClosed() && lway->nodes.size() > 2 && rway->isClosed() && rway->nodes.size() > 2) {
223  // ### this assumes multi-polygons are structured in the way the Marble generator normalizes them!
224  bool found = false;
225  for (std::size_t i = 0; i < lway->nodes.size() && !found; ++i) {
226  const auto n1 = lway->nodes[i];
227  const auto n2 = lway->nodes[(i + 1) % lway->nodes.size()];
228  for (std::size_t j = 0; j < rway->nodes.size() && !found; ++j) {
229  found = ((n1 == rway->nodes[j]) && (n2 == rway->nodes[(j + 1) % rway->nodes.size()]))
230  || ((n2 == rway->nodes[j]) && (n1 == rway->nodes[(j + 1) % rway->nodes.size()]));
231  }
232  }
233  return found;
234  }
235 
236  return false;
237 }
238 
239 static bool isConnectedWay(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs, const OSM::DataSet &dataSet)
240 {
241  return std::any_of(lhs.begin(), lhs.end(), [&](auto lway) {
242  return std::any_of(rhs.begin(), rhs.end(), [&](auto rway) {
243  return isConnectedGeometry(lway, rway, dataSet);
244  });
245  });
246 }
247 
248 static bool isOverlappingWay(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs)
249 {
250  return std::any_of(lhs.begin(), lhs.end(), [&](auto lway) {
251  return std::any_of(rhs.begin(), rhs.end(), [&](auto rway) {
252  return lway == rway;
253  });
254  });
255 }
256 
257 bool Platform::isSame(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet)
258 {
259  const auto isConnectedEdge = isConnectedGeometry(lhs.m_edge, rhs.m_edge, dataSet);
260  const auto isConnectedTrack = isConnectedWay(lhs.m_track, rhs.m_track, dataSet);
261  const auto isOverlappingTrack = isOverlappingWay(lhs.m_track, rhs.m_track);
262  const auto isConnectedArea = isConnectedGeometry(lhs.m_area, rhs.m_area, dataSet);
263 
264  if ((conflictIfPresent(lhs.m_stopPoint, rhs.m_stopPoint) && lhs.m_track != rhs.m_track && !isConnectedTrack)
265  || (conflictIfPresent(lhs.m_edge, rhs.m_edge) && !isConnectedEdge)
266  || (conflictIfPresent(lhs.m_area, rhs.m_area) && !isConnectedArea)
267  || (!lhs.m_track.empty() && !rhs.m_track.empty() && !isOverlappingTrack && !isConnectedTrack)
268  || (lhs.hasLevel() && rhs.hasLevel() && lhs.level() != rhs.level())
269  || (lhs.m_mode != Unknown && rhs.m_mode != Unknown && lhs.m_mode != rhs.m_mode))
270  {
271  return false;
272  }
273 
274  // we can accept conflicting names if one of them is likely a station name instead of a platform name
275  if (!lhs.m_name.isEmpty() && !rhs.m_name.isEmpty() && lhs.m_name != rhs.m_name) {
276  if (isPlausibleName(lhs.name()) && isPlausibleName(rhs.name())) {
277  return false;
278  }
279  }
280 
281  // edge has to be part of area, but on its own that doesn't mean equallity
282  if (!isConnectedArea && !isConnectedEdge) {
283  if ((lhs.m_area && rhs.m_edge.type() == OSM::Type::Way && !isSubPath(lhs.m_area.outerPath(dataSet), *rhs.m_edge.way()))
284  || (rhs.m_area && lhs.m_edge.type() == OSM::Type::Way && !isSubPath(rhs.m_area.outerPath(dataSet), *lhs.m_edge.way()))) {
285  return false;
286  }
287  }
288 
289  // matching edge, point or track is good enough, matching area however isn't
290  if (equalIfPresent(lhs.m_stopPoint, rhs.m_stopPoint)
291  || equalIfPresent(lhs.m_edge, rhs.m_edge) || isConnectedEdge
292  || isOverlappingTrack)
293  {
294  return true;
295  }
296 
297  if (!isConnectedEdge) {
298  // track/stop and area/edge elements do not share nodes, so those we need to match by spatial distance
299  if (lhs.m_edge && rhs.m_stopPoint) {
300  return OSM::distance(lhs.m_edge.outerPath(dataSet), rhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
301  }
302  if (rhs.m_edge && lhs.m_stopPoint) {
303  return OSM::distance(rhs.m_edge.outerPath(dataSet), lhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
304  }
305  }
306 
307  if (!isConnectedArea) {
308  if (lhs.m_area && rhs.m_stopPoint) {
309  return OSM::distance(lhs.m_area.outerPath(dataSet), rhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
310  }
311  if (rhs.m_area && lhs.m_stopPoint) {
312  return OSM::distance(rhs.m_area.outerPath(dataSet), lhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
313  }
314  }
315 
316  // free-floating sections: edge, area or track is within a reasonable distance
317  if (!lhs.m_name.isEmpty() && lhs.m_name == rhs.m_name && !isConnectedArea && !isConnectedEdge) {
318  auto d = maxSectionDistance(lhs, rhs.sections(), dataSet);
319  if (d >= 0.0) {
320  return d < MAX_SECTION_TO_EDGE_DISTANCE;
321  }
322  d = maxSectionDistance(rhs, lhs.sections(), dataSet);
323  if (d >= 0.0) {
324  return d < MAX_SECTION_TO_EDGE_DISTANCE;
325  }
326  }
327 
328  return isConnectedArea || isConnectedEdge || isConnectedTrack;
329 }
330 
331 static bool compareSection(const PlatformSection &lhs, const PlatformSection &rhs)
332 {
333  if (lhs.name == rhs.name) {
334  return lhs.position < rhs.position;
335  }
336  return lhs.name < rhs.name;
337 }
338 
339 void Platform::appendSection(std::vector<PlatformSection> &sections, const Platform &p, PlatformSection &&sec, std::vector<const OSM::Node*> &edgePath, const OSM::DataSet &dataSet)
340 {
341  if (sections.empty() || sections.back().name != sec.name) {
342  sections.push_back(std::move(sec));
343  }
344 
345  // check which one is closer
346  if (edgePath.empty()) {
347  if (p.m_edge) {
348  edgePath = p.m_edge.outerPath(dataSet);
349  } else if (!p.m_track.empty()) {
350  OSM::assemblePath(dataSet, p.m_track, edgePath);
351  }
352  }
353  const auto dist1 = OSM::distance(edgePath, sections.back().position.center());
354  const auto dist2 = OSM::distance(edgePath, sec.position.center());
355  if (dist2 < dist1) {
356  sections.back() = std::move(sec);
357  }
358 }
359 
360 static std::vector<OSM::Element> mergeWays(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs)
361 {
362  std::vector<OSM::Element> w = lhs;
363  for (auto e : rhs) {
364  if (std::find(w.begin(), w.end(), e) == w.end()) {
365  w.push_back(e);
366  }
367  }
368  return w;
369 }
370 
371 Platform Platform::merge(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet)
372 {
373  Platform p;
374  p.m_name = preferredName(lhs.name(), rhs.name());
375  p.m_stopPoint = OSM::coalesce(lhs.m_stopPoint, rhs.m_stopPoint);
376  p.m_edge = OSM::coalesce(lhs.m_edge, rhs.m_edge);
377  p.m_area = OSM::coalesce(lhs.m_area, rhs.m_area);
378  p.m_track = mergeWays(lhs.m_track, rhs.m_track);
379  p.m_level = lhs.hasLevel() ? lhs.m_level : rhs.m_level;
380 
381  // TODO
382  p.m_mode = std::max(lhs.m_mode, rhs.m_mode);
383  p.lines = lhs.lines.isEmpty() ? std::move(rhs.lines) : std::move(lhs.lines);
384 
385  std::vector<const OSM::Node*> edgePath;
386  std::vector<PlatformSection> sections;
387  auto lsec = lhs.sections();
388  auto rsec = rhs.sections();
389  std::sort(lsec.begin(), lsec.end(), compareSection);
390  std::sort(rsec.begin(), rsec.end(), compareSection);
391  for (auto lit = lsec.begin(), rit = rsec.begin(); lit != lsec.end() || rit != rsec.end();) {
392  if (rit == rsec.end()) {
393  appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
394  continue;
395  }
396  if (lit == lsec.end()) {
397  appendSection(sections, p, std::move(*rit++), edgePath, dataSet);
398  continue;
399  }
400  if (compareSection(*lit, *rit)) {
401  appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
402  continue;
403  }
404  if (compareSection(*rit, *lit)) {
405  appendSection(sections, p, std::move(*rit++), edgePath, dataSet);
406  continue;
407  }
408 
409  // both are equal
410  if ((*lit).position == (*rit).position) {
411  appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
412  ++rit;
413  continue;
414  }
415 
416  // both are equal but differ in distance: will be handled in appendSection in the next iteration
417  appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
418  }
419  p.setSections(std::move(sections));
420 
421  return p;
422 }
423 
425 {
426  static QRegularExpression exp(QStringLiteral("^(\\d{1,3}[a-z]?|[A-Z])$"));
427  return exp.match(name).hasMatch();
428 }
429 
431 {
432  if (lhs.isEmpty()) {
433  return rhs;
434  }
435  if (rhs.isEmpty()) {
436  return lhs;
437  }
438 
439  if (isPlausibleName(lhs)) {
440  return lhs;
441  }
442  if (isPlausibleName(rhs)) {
443  return rhs;
444  }
445 
446  return lhs.size() <= rhs.size() ? lhs: rhs;
447 }
OSM-based multi-floor indoor maps for buildings.
QRegularExpressionMatch match(const QString &subject, int offset, QRegularExpression::MatchType matchType, QRegularExpression::MatchOptions matchOptions) const const
int level() const
Floor level.
Definition: platform.cpp:40
KOSM_EXPORT void assemblePath(const DataSet &dataSet, std::vector< const Way * > &&ways, std::vector< const Node * > &path)
Assemble a continuous path into path from the given ways.
Definition: pathutil.cpp:42
static bool isPlausibleName(const QString &name)
Checks if name is a plausible name for a platform.
Definition: platform.cpp:424
static bool isSame(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet)
Checks if two instances refer to the same platform.
Definition: platform.cpp:257
OSM::Element area() const
The platform area.
Definition: platform.cpp:80
int size() const const
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition: datatypes.h:37
constexpr Element coalesce(Element e)
Utility function similar to SQL COALESCE for OSM::Element, ie.
Definition: element.h:138
static Platform merge(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet)
Merge two platform objects.
Definition: platform.cpp:371
bool isValid() const
Platform section has enough data to work with.
Definition: platform.cpp:16
static QString preferredName(const QString &lhs, const QString &rhs)
Returns the preferred platform name given two possible alternatives.
Definition: platform.cpp:430
const std::vector< PlatformSection > & sections() const
Platform sections.
Definition: platform.cpp:105
OSM::Element edge() const
The platform edge path.
Definition: platform.cpp:70
A reference to any of OSM::Node/OSMWay/OSMRelation.
Definition: element.h:22
bool isEmpty() const const
bool isEmpty() const const
const std::vector< OSM::Element > & track() const
The (railway) track this platform is serving.
Definition: platform.cpp:90
bool hasMatch() const const
int64_t Id
OSM element identifier.
Definition: datatypes.h:27
A railway platform section.
Definition: platform.h:23
void push_back(QChar ch)
A railway platform/track.
Definition: platform.h:34
An OSM way.
Definition: datatypes.h:206
Mode
Transportation mode served by a platform.
Definition: platform.h:80
OSM::Coordinate position() const
A singular position for this platform (typically the stop position).
Definition: platform.cpp:55
QString name() const
User-visible name of the platform.
Definition: platform.cpp:30
std::vector< const Node * > outerPath(const DataSet &dataSet) const
Returns all nodes belonging to the outer path of this element.
Definition: element.cpp:166
A set of nodes, ways and relations.
Definition: datatypes.h:283
const Way * way(Id id) const
Find a way by its id.
Definition: datatypes.cpp:46
KOSM_EXPORT double distance(double lat1, double lon1, double lat2, double lon2)
Distance between two coordinates.
Definition: geomath.cpp:17
bool isValid() const
Platform has enough data to work with.
Definition: platform.cpp:25
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 23:04:00 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.