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:346
const Way * way(Id id) const
Find a way by its id.
Definition datatypes.cpp:61
A reference to any of OSM::Node/OSM::Way/OSM::Relation.
Definition element.h:24
An OSM way.
Definition datatypes.h:232
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
qsizetype size() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:06:15 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.