KOSMIndoorMap

platform.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 "platform.h"
8
9#include <osm/element.h>
10#include <osm/geomath.h>
11#include <osm/pathutil.h>
12
13#include <QRegularExpression>
14
15#include <limits>
16
17using namespace KOSMIndoorMap;
18
19namespace KOSMIndoorMap {
20class PlatformSectionPrivate : public QSharedData
21{
22public:
23 QString name;
24 OSM::Element position;
25};
26}
27
28PlatformSection::PlatformSection()
29 : d(new PlatformSectionPrivate)
30{
31}
32
33PlatformSection::PlatformSection(const PlatformSection&) = default;
34PlatformSection::PlatformSection(PlatformSection&&) = default;
35PlatformSection::~PlatformSection() = default;
36PlatformSection& PlatformSection::operator=(const PlatformSection&) = default;
37PlatformSection& PlatformSection::operator=(PlatformSection&&) = default;
38
40{
41 return d->name;
42}
43
44void PlatformSection::setName(const QString &name)
45{
46 d.detach();
47 d->name = name;
48}
49
51{
52 return d->position;
53}
54
55void PlatformSection::setPosition(const OSM::Element &position)
56{
57 d.detach();
58 d->position = position;
59}
60
62{
63 return !d->name.isEmpty() && d->position;
64}
65
66
67namespace KOSMIndoorMap {
68class PlatformPrivate : public QSharedData
69{
70public:
71 QString m_name;
72 OSM::Element m_stopPoint;
73 OSM::Element m_edge;
74 OSM::Element m_area;
75 std::vector<OSM::Element> m_track;
76 Platform::Mode m_mode = Platform::Rail; // TODO should eventually be "Unknown"
77 int m_level = std::numeric_limits<int>::min(); // INT_MIN indicates not set, needed for merging
78 std::vector<PlatformSection> m_sections;
79 QString m_ifopt;
80 QStringList m_lines;
81
82 static void appendSection(std::vector<PlatformSection> &sections, const Platform &p, PlatformSection &&sec, std::vector<const OSM::Node*> &edgePath, const OSM::DataSet &dataSet);
83 static double maxSectionDistance(const Platform &p, const std::vector<PlatformSection> &sections, const OSM::DataSet &dataSet);
84};
85}
86
87Platform::Platform()
88 : d(new PlatformPrivate)
89{
90}
91
92Platform::Platform(const Platform&) = default;
93Platform::Platform(Platform&&) = default;
94Platform::~Platform() = default;
95Platform& Platform::operator=(const Platform&) = default;
96Platform& Platform::operator=(Platform&&) = default;
97
99{
100 return !d->m_name.isEmpty() && position().isValid() && d->m_mode != Unknown;
101}
102
103QString Platform::name() const
104{
105 return d->m_name;
106}
107
108void Platform::setName(const QString &name)
109{
110 d.detach();
111 d->m_name = name;
112}
113
115{
116 return hasLevel() ? d->m_level : 0;
117}
118
119bool Platform::hasLevel() const
120{
121 return d->m_level != std::numeric_limits<int>::min();
122}
123
124void Platform::setLevel(int level)
125{
126 d.detach();
127 d->m_level = level;
128}
129
131{
132 return OSM::coalesce(d->m_stopPoint, d->m_area).center();
133}
134
135OSM::Element Platform::stopPoint() const
136{
137 return d->m_stopPoint;
138}
139
140void Platform::setStopPoint(OSM::Element stop)
141{
142 d.detach();
143 d->m_stopPoint = stop;
144}
145
147{
148 return OSM::coalesce(d->m_edge, d->m_stopPoint);
149}
150
151void Platform::setEdge(OSM::Element edge)
152{
153 d.detach();
154 d->m_edge = edge;
155}
156
158{
159 return OSM::coalesce(d->m_area, d->m_edge, d->m_stopPoint);
160}
161
162void Platform::setArea(OSM::Element area)
163{
164 d.detach();
165 d->m_area = area;
166}
167
168const std::vector<OSM::Element>& Platform::track() const
169{
170 return d->m_track;
171}
172
173void Platform::setTrack(std::vector<OSM::Element> &&track)
174{
175 d.detach();
176 d->m_track = std::move(track);
177}
178
179std::vector<OSM::Element>&& Platform::takeTrack()
180{
181 d.detach();
182 return std::move(d->m_track);
183}
184
185const std::vector<PlatformSection>& Platform::sections() const
186{
187 return d->m_sections;
188}
189
190void Platform::setSections(std::vector<PlatformSection> &&sections)
191{
192 d.detach();
193 d->m_sections = std::move(sections);
194}
195
196std::vector<PlatformSection>&& Platform::takeSections()
197{
198 d.detach();
199 return std::move(d->m_sections);
200}
201
202Platform::Mode Platform::mode() const
203{
204 return d->m_mode;
205}
206
207void Platform::setMode(Platform::Mode mode)
208{
209 d.detach();
210 d->m_mode = mode;
211}
212
213QString Platform::ifopt() const
214{
215 return d->m_ifopt;
216}
217
218void Platform::setIfopt(const QString &ifopt)
219{
220 d.detach();
221 d->m_ifopt = ifopt;
222}
223
225{
226 return d->m_lines;
227}
228
229void Platform::setLines(QStringList &&lines)
230{
231 d.detach();
232 d->m_lines = std::move(lines);
233}
234
235QStringList&& Platform::takeLines()
236{
237 d.detach();
238 return std::move(d->m_lines);
239}
240
241static bool conflictIfPresent(OSM::Element lhs, OSM::Element rhs)
242{
243 return lhs && rhs && lhs != rhs;
244}
245
246static bool equalIfPresent(OSM::Element lhs, OSM::Element rhs)
247{
248 return lhs && rhs && lhs == rhs;
249}
250
251static bool isSubPath(const std::vector<const OSM::Node*> &path, const OSM::Way &way)
252{
253 return std::all_of(way.nodes.begin(), way.nodes.end(), [&path](OSM::Id node) {
254 return std::find_if(path.begin(), path.end(), [node](auto n) { return n->id == node; }) != path.end();
255 });
256}
257
258static constexpr const auto MAX_TRACK_TO_EDGE_DISTANCE = 4.5; // meters
259static constexpr const auto MAX_SECTION_TO_EDGE_DISTANCE = 5.0;
260
261static double maxSectionDistance(const std::vector<const OSM::Node*> &path, const std::vector<PlatformSection> &sections)
262{
263 auto dist = std::numeric_limits<double>::lowest();
264 for (const auto &section : sections) {
265 dist = std::max(dist, OSM::distance(path, section.position().center()));
266 }
267 return dist;
268}
269
270double PlatformPrivate::maxSectionDistance(const Platform &p, const std::vector<PlatformSection> &sections, const OSM::DataSet &dataSet)
271{
272 if (auto elem = OSM::coalesce(p.d->m_edge, p.d->m_area)) {
273 return ::maxSectionDistance(elem.outerPath(dataSet), sections);
274 }
275 if (!p.d->m_track.empty()) {
276 std::vector<const OSM::Node*> path;
277 OSM::assemblePath(dataSet, p.d->m_track, path);
278 return ::maxSectionDistance(path, sections);
279 }
280 return std::numeric_limits<double>::lowest();
281}
282
283static const OSM::Way* outerWay(OSM::Element &multiPolygon, const OSM::DataSet &dataSet)
284{
285 // ### this assumes multi-polygons are structured in the way the Marble generator normalizes them!
286 for (const auto &mem : multiPolygon.relation()->members) {
287 if (mem.type() == OSM::Type::Way && (std::strcmp(mem.role().name(), "outer") == 0)) {
288 return dataSet.way(mem.id);
289 }
290 }
291 return nullptr;
292}
293
294static bool isConnectedGeometry(OSM::Element lhs, OSM::Element rhs, const OSM::DataSet &dataSet)
295{
296 if (lhs == rhs) { return false; }
297 const OSM::Way *lway = nullptr;
298 const OSM::Way *rway = nullptr;
299
300 switch (lhs.type()) {
301 case OSM::Type::Null:
302 case OSM::Type::Node:
303 return false;
304 case OSM::Type::Way:
305 lway = lhs.way();
306 break;
307 case OSM::Type::Relation:
308 lway = outerWay(lhs, dataSet);
309 break;
310
311 }
312 switch (rhs.type()) {
313 case OSM::Type::Null:
314 case OSM::Type::Node:
315 return false;
316 case OSM::Type::Way:
317 rway = rhs.way();
318 break;
319 case OSM::Type::Relation:
320 rway = outerWay(rhs, dataSet);
321 break;
322 }
323 if (!lway || !rway) {
324 return false;
325 }
326
327 if (!lway->isClosed() && !rway->isClosed()) {
328 return lway->nodes.front() == rway->nodes.front()
329 || lway->nodes.back() == rway->nodes.front()
330 || lway->nodes.front() == rway->nodes.back()
331 || lway->nodes.back() == rway->nodes.back();
332 }
333 if (lway->isClosed() && lway->nodes.size() > 2 && rway->isClosed() && rway->nodes.size() > 2) {
334 // ### this assumes multi-polygons are structured in the way the Marble generator normalizes them!
335 bool found = false;
336 for (std::size_t i = 0; i < lway->nodes.size() && !found; ++i) {
337 const auto n1 = lway->nodes[i];
338 const auto n2 = lway->nodes[(i + 1) % lway->nodes.size()];
339 for (std::size_t j = 0; j < rway->nodes.size() && !found; ++j) {
340 found = ((n1 == rway->nodes[j]) && (n2 == rway->nodes[(j + 1) % rway->nodes.size()]))
341 || ((n2 == rway->nodes[j]) && (n1 == rway->nodes[(j + 1) % rway->nodes.size()]));
342 }
343 }
344 return found;
345 }
346
347 return false;
348}
349
350static bool isConnectedWay(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs, const OSM::DataSet &dataSet)
351{
352 return std::any_of(lhs.begin(), lhs.end(), [&](auto lway) {
353 return std::any_of(rhs.begin(), rhs.end(), [&](auto rway) {
354 return isConnectedGeometry(lway, rway, dataSet);
355 });
356 });
357}
358
359static bool isOverlappingWay(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs)
360{
361 return std::any_of(lhs.begin(), lhs.end(), [&](auto lway) {
362 return std::any_of(rhs.begin(), rhs.end(), [&](auto rway) {
363 return lway == rway;
364 });
365 });
366}
367
368bool Platform::isSame(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet)
369{
370 if (!lhs.ifopt().isEmpty() && !rhs.ifopt().isEmpty()) {
371 return lhs.ifopt() == rhs.ifopt();
372 }
373
374 const auto isConnectedEdge = isConnectedGeometry(lhs.d->m_edge, rhs.d->m_edge, dataSet);
375 const auto isConnectedTrack = isConnectedWay(lhs.d->m_track, rhs.d->m_track, dataSet);
376 const auto isOverlappingTrack = isOverlappingWay(lhs.d->m_track, rhs.d->m_track);
377 const auto isConnectedArea = isConnectedGeometry(lhs.d->m_area, rhs.d->m_area, dataSet);
378
379 if ((conflictIfPresent(lhs.d->m_stopPoint, rhs.d->m_stopPoint) && lhs.d->m_track != rhs.d->m_track && !isConnectedTrack)
380 || (conflictIfPresent(lhs.d->m_edge, rhs.d->m_edge) && !isConnectedEdge)
381 || (conflictIfPresent(lhs.d->m_area, rhs.d->m_area) && !isConnectedArea)
382 || (!lhs.d->m_track.empty() && !rhs.d->m_track.empty() && !isOverlappingTrack && !isConnectedTrack)
383 || (lhs.hasLevel() && rhs.hasLevel() && lhs.level() != rhs.level())
384 || (lhs.d->m_mode != Unknown && rhs.d->m_mode != Unknown && lhs.d->m_mode != rhs.d->m_mode))
385 {
386 return false;
387 }
388
389 // we can accept conflicting names if one of them is likely a station name instead of a platform name
390 if (!lhs.d->m_name.isEmpty() && !rhs.d->m_name.isEmpty() && lhs.d->m_name != rhs.d->m_name) {
391 if (isPlausibleName(lhs.name()) && isPlausibleName(rhs.name())) {
392 return false;
393 }
394 }
395
396 // edge has to be part of area, but on its own that doesn't mean equallity
397 if (!isConnectedArea && !isConnectedEdge) {
398 if ((lhs.d->m_area && rhs.d->m_edge.type() == OSM::Type::Way && !isSubPath(lhs.d->m_area.outerPath(dataSet), *rhs.d->m_edge.way()))
399 || (rhs.d->m_area && lhs.d->m_edge.type() == OSM::Type::Way && !isSubPath(rhs.d->m_area.outerPath(dataSet), *lhs.d->m_edge.way()))) {
400 return false;
401 }
402 }
403
404 // matching edge, point or track is good enough, matching area however isn't
405 if (equalIfPresent(lhs.d->m_stopPoint, rhs.d->m_stopPoint)
406 || equalIfPresent(lhs.d->m_edge, rhs.d->m_edge) || isConnectedEdge
407 || isOverlappingTrack)
408 {
409 return true;
410 }
411
412 if (!isConnectedEdge) {
413 // track/stop and area/edge elements do not share nodes, so those we need to match by spatial distance
414 if (lhs.d->m_edge && rhs.d->m_stopPoint) {
415 return OSM::distance(lhs.d->m_edge.outerPath(dataSet), rhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
416 }
417 if (rhs.d->m_edge && lhs.d->m_stopPoint) {
418 return OSM::distance(rhs.d->m_edge.outerPath(dataSet), lhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
419 }
420 }
421
422 if (!isConnectedArea) {
423 if (lhs.d->m_area && rhs.d->m_stopPoint) {
424 return OSM::distance(lhs.d->m_area.outerPath(dataSet), rhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
425 }
426 if (rhs.d->m_area && lhs.d->m_stopPoint) {
427 return OSM::distance(rhs.d->m_area.outerPath(dataSet), lhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE;
428 }
429 }
430
431 // free-floating sections: edge, area or track is within a reasonable distance
432 if (((lhs.d->m_name.isEmpty() ^ rhs.d->m_name.isEmpty()) || lhs.d->m_name == rhs.d->m_name) && !isConnectedArea && !isConnectedEdge) {
433 auto d = PlatformPrivate::maxSectionDistance(lhs, rhs.sections(), dataSet);
434 if (d >= 0.0) {
435 return d < MAX_SECTION_TO_EDGE_DISTANCE;
436 }
437 d = PlatformPrivate::maxSectionDistance(rhs, lhs.sections(), dataSet);
438 if (d >= 0.0) {
439 return d < MAX_SECTION_TO_EDGE_DISTANCE;
440 }
441 }
442
443 return isConnectedArea || isConnectedEdge || isConnectedTrack;
444}
445
446static bool compareSection(const PlatformSection &lhs, const PlatformSection &rhs)
447{
448 if (lhs.name() == rhs.name()) {
449 return lhs.position() < rhs.position();
450 }
451 return lhs.name() < rhs.name();
452}
453
454void PlatformPrivate::appendSection(std::vector<PlatformSection> &sections, const Platform &p, PlatformSection &&sec, std::vector<const OSM::Node*> &edgePath, const OSM::DataSet &dataSet)
455{
456 if (sections.empty() || sections.back().name() != sec.name()) {
457 sections.push_back(std::move(sec));
458 return;
459 }
460
461 // check which one is closer
462 if (edgePath.empty()) {
463 if (p.d->m_edge) {
464 edgePath = p.d->m_edge.outerPath(dataSet);
465 } else if (!p.d->m_track.empty()) {
466 OSM::assemblePath(dataSet, p.d->m_track, edgePath);
467 }
468 }
469 const auto dist1 = OSM::distance(edgePath, sections.back().position().center());
470 const auto dist2 = OSM::distance(edgePath, sec.position().center());
471 if (dist2 < dist1) {
472 sections.back() = std::move(sec);
473 }
474}
475
476static std::vector<OSM::Element> mergeWays(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs)
477{
478 std::vector<OSM::Element> w = lhs;
479 for (auto e : rhs) {
480 if (std::find(w.begin(), w.end(), e) == w.end()) {
481 w.push_back(e);
482 }
483 }
484 return w;
485}
486
487Platform Platform::merge(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet)
488{
489 Platform p;
490 p.d->m_name = preferredName(lhs.name(), rhs.name());
491 p.d->m_stopPoint = OSM::coalesce(lhs.d->m_stopPoint, rhs.d->m_stopPoint);
492 p.d->m_edge = OSM::coalesce(lhs.d->m_edge, rhs.d->m_edge);
493 p.d->m_area = OSM::coalesce(lhs.d->m_area, rhs.d->m_area);
494 p.d->m_track = mergeWays(lhs.d->m_track, rhs.d->m_track);
495 p.d->m_level = lhs.hasLevel() ? lhs.d->m_level : rhs.d->m_level;
496 p.d->m_ifopt = lhs.ifopt().isEmpty() ? rhs.ifopt() : lhs.ifopt();
497
498 // TODO
499 p.d->m_mode = std::max(lhs.d->m_mode, rhs.d->m_mode);
500 p.d->m_lines = lhs.d->m_lines.isEmpty() ? std::move(rhs.d->m_lines) : std::move(lhs.d->m_lines);
501
502 std::vector<const OSM::Node*> edgePath;
503 std::vector<PlatformSection> sections;
504 auto lsec = lhs.sections();
505 auto rsec = rhs.sections();
506 std::sort(lsec.begin(), lsec.end(), compareSection);
507 std::sort(rsec.begin(), rsec.end(), compareSection);
508 for (auto lit = lsec.begin(), rit = rsec.begin(); lit != lsec.end() || rit != rsec.end();) {
509 if (rit == rsec.end()) {
510 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
511 continue;
512 }
513 if (lit == lsec.end()) {
514 PlatformPrivate::appendSection(sections, p, std::move(*rit++), edgePath, dataSet);
515 continue;
516 }
517 if (compareSection(*lit, *rit)) {
518 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
519 continue;
520 }
521 if (compareSection(*rit, *lit)) {
522 PlatformPrivate::appendSection(sections, p, std::move(*rit++), edgePath, dataSet);
523 continue;
524 }
525
526 // both are equal
527 if ((*lit).position() == (*rit).position()) {
528 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
529 ++rit;
530 continue;
531 }
532
533 // both are equal but differ in distance: will be handled in appendSection in the next iteration
534 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet);
535 }
536 p.setSections(std::move(sections));
537
538 return p;
539}
540
542{
543 static QRegularExpression exp(QStringLiteral("^(\\d{1,3}[a-z]?|[A-Z])$"));
544 return exp.match(name).hasMatch();
545}
546
548{
549 if (lhs.isEmpty()) {
550 return rhs;
551 }
552 if (rhs.isEmpty()) {
553 return lhs;
554 }
555
556 if (isPlausibleName(lhs)) {
557 return lhs;
558 }
559 if (isPlausibleName(rhs)) {
560 return rhs;
561 }
562
563 return lhs.size() <= rhs.size() ? lhs: rhs;
564}
565
566#include "moc_platform.cpp"
A railway platform section.
Definition platform.h:30
bool isValid() const
Platform section has enough data to work with.
Definition platform.cpp:61
OSM::Element position() const
Platform section position.
Definition platform.cpp:50
QString name() const
Platform section name.
Definition platform.cpp:39
A railway platform/track.
Definition platform.h:56
OSM::Element area() const
The platform area.
Definition platform.cpp:157
OSM::Element edge() const
The platform edge path.
Definition platform.cpp:146
static Platform merge(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet)
Merge two platform objects.
Definition platform.cpp:487
static QString preferredName(const QString &lhs, const QString &rhs)
Returns the preferred platform name given two possible alternatives.
Definition platform.cpp:547
QStringList lines() const
Names of public transport lines stopping at this platform.
Definition platform.cpp:224
const std::vector< OSM::Element > & track() const
The (railway) track this platform is serving.
Definition platform.cpp:168
static bool isPlausibleName(const QString &name)
Checks if name is a plausible name for a platform.
Definition platform.cpp:541
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:368
int level() const
Floor level.
Definition platform.cpp:114
Mode
Transportation mode served by a platform.
Definition platform.h:109
OSM::Coordinate position() const
A singular position for this platform (typically the stop position).
Definition platform.cpp:130
const std::vector< PlatformSection > & sections() const
Platform sections.
Definition platform.cpp:185
bool isValid() const
Platform has enough data to work with.
Definition platform.cpp:98
Coordinate, stored as 1e7 * degree to avoid floating point precision issues, and offset to unsigned v...
Definition datatypes.h:37
A set of nodes, ways and relations.
Definition datatypes.h:340
const Way * way(Id id) const
Find a way by its id.
Definition datatypes.cpp:46
A reference to any of OSM::Node/OSM::Way/OSM::Relation.
Definition element.h:24
An OSM way.
Definition datatypes.h:231
void stop(Ekos::AlignState mode)
QString path(const QString &relativePath)
OSM-based multi-floor indoor maps for buildings.
KOSM_EXPORT double distance(double lat1, double lon1, double lat2, double lon2)
Distance between two coordinates.
Definition geomath.cpp:16
constexpr Element coalesce(Element e)
Utility function similar to SQL COALESCE for OSM::Element, ie.
Definition element.h:141
int64_t Id
OSM element identifier.
Definition datatypes.h:30
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
QRegularExpressionMatch match(QStringView subjectView, qsizetype offset, MatchType matchType, MatchOptions matchOptions) const const
bool hasMatch() const const
iterator end()
bool isEmpty() const const
void push_back(QChar ch)
qsizetype size() const const
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.