File indexing completed on 2024-11-24 04:15:36
0001 /* 0002 SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "platform.h" 0008 0009 #include <osm/element.h> 0010 #include <osm/geomath.h> 0011 #include <osm/pathutil.h> 0012 0013 #include <QRegularExpression> 0014 0015 #include <limits> 0016 0017 using namespace KOSMIndoorMap; 0018 0019 namespace KOSMIndoorMap { 0020 class PlatformSectionPrivate : public QSharedData 0021 { 0022 public: 0023 QString name; 0024 OSM::Element position; 0025 }; 0026 } 0027 0028 PlatformSection::PlatformSection() 0029 : d(new PlatformSectionPrivate) 0030 { 0031 } 0032 0033 PlatformSection::PlatformSection(const PlatformSection&) = default; 0034 PlatformSection::PlatformSection(PlatformSection&&) = default; 0035 PlatformSection::~PlatformSection() = default; 0036 PlatformSection& PlatformSection::operator=(const PlatformSection&) = default; 0037 PlatformSection& PlatformSection::operator=(PlatformSection&&) = default; 0038 0039 QString PlatformSection::name() const 0040 { 0041 return d->name; 0042 } 0043 0044 void PlatformSection::setName(const QString &name) 0045 { 0046 d.detach(); 0047 d->name = name; 0048 } 0049 0050 OSM::Element PlatformSection::position() const 0051 { 0052 return d->position; 0053 } 0054 0055 void PlatformSection::setPosition(const OSM::Element &position) 0056 { 0057 d.detach(); 0058 d->position = position; 0059 } 0060 0061 bool PlatformSection::isValid() const 0062 { 0063 return !d->name.isEmpty() && d->position; 0064 } 0065 0066 0067 namespace KOSMIndoorMap { 0068 class PlatformPrivate : public QSharedData 0069 { 0070 public: 0071 QString m_name; 0072 OSM::Element m_stopPoint; 0073 OSM::Element m_edge; 0074 OSM::Element m_area; 0075 std::vector<OSM::Element> m_track; 0076 Platform::Mode m_mode = Platform::Rail; // TODO should eventually be "Unknown" 0077 int m_level = std::numeric_limits<int>::min(); // INT_MIN indicates not set, needed for merging 0078 std::vector<PlatformSection> m_sections; 0079 QString m_ifopt; 0080 QStringList m_lines; 0081 0082 static void appendSection(std::vector<PlatformSection> §ions, const Platform &p, PlatformSection &&sec, std::vector<const OSM::Node*> &edgePath, const OSM::DataSet &dataSet); 0083 static double maxSectionDistance(const Platform &p, const std::vector<PlatformSection> §ions, const OSM::DataSet &dataSet); 0084 }; 0085 } 0086 0087 Platform::Platform() 0088 : d(new PlatformPrivate) 0089 { 0090 } 0091 0092 Platform::Platform(const Platform&) = default; 0093 Platform::Platform(Platform&&) = default; 0094 Platform::~Platform() = default; 0095 Platform& Platform::operator=(const Platform&) = default; 0096 Platform& Platform::operator=(Platform&&) = default; 0097 0098 bool Platform::isValid() const 0099 { 0100 return !d->m_name.isEmpty() && position().isValid() && d->m_mode != Unknown; 0101 } 0102 0103 QString Platform::name() const 0104 { 0105 return d->m_name; 0106 } 0107 0108 void Platform::setName(const QString &name) 0109 { 0110 d.detach(); 0111 d->m_name = name; 0112 } 0113 0114 int Platform::level() const 0115 { 0116 return hasLevel() ? d->m_level : 0; 0117 } 0118 0119 bool Platform::hasLevel() const 0120 { 0121 return d->m_level != std::numeric_limits<int>::min(); 0122 } 0123 0124 void Platform::setLevel(int level) 0125 { 0126 d.detach(); 0127 d->m_level = level; 0128 } 0129 0130 OSM::Coordinate Platform::position() const 0131 { 0132 return OSM::coalesce(d->m_stopPoint, d->m_area).center(); 0133 } 0134 0135 OSM::Element Platform::stopPoint() const 0136 { 0137 return d->m_stopPoint; 0138 } 0139 0140 void Platform::setStopPoint(OSM::Element stop) 0141 { 0142 d.detach(); 0143 d->m_stopPoint = stop; 0144 } 0145 0146 OSM::Element Platform::edge() const 0147 { 0148 return OSM::coalesce(d->m_edge, d->m_stopPoint); 0149 } 0150 0151 void Platform::setEdge(OSM::Element edge) 0152 { 0153 d.detach(); 0154 d->m_edge = edge; 0155 } 0156 0157 OSM::Element Platform::area() const 0158 { 0159 return OSM::coalesce(d->m_area, d->m_edge, d->m_stopPoint); 0160 } 0161 0162 void Platform::setArea(OSM::Element area) 0163 { 0164 d.detach(); 0165 d->m_area = area; 0166 } 0167 0168 const std::vector<OSM::Element>& Platform::track() const 0169 { 0170 return d->m_track; 0171 } 0172 0173 void Platform::setTrack(std::vector<OSM::Element> &&track) 0174 { 0175 d.detach(); 0176 d->m_track = std::move(track); 0177 } 0178 0179 std::vector<OSM::Element>&& Platform::takeTrack() 0180 { 0181 d.detach(); 0182 return std::move(d->m_track); 0183 } 0184 0185 const std::vector<PlatformSection>& Platform::sections() const 0186 { 0187 return d->m_sections; 0188 } 0189 0190 void Platform::setSections(std::vector<PlatformSection> &§ions) 0191 { 0192 d.detach(); 0193 d->m_sections = std::move(sections); 0194 } 0195 0196 std::vector<PlatformSection>&& Platform::takeSections() 0197 { 0198 d.detach(); 0199 return std::move(d->m_sections); 0200 } 0201 0202 Platform::Mode Platform::mode() const 0203 { 0204 return d->m_mode; 0205 } 0206 0207 void Platform::setMode(Platform::Mode mode) 0208 { 0209 d.detach(); 0210 d->m_mode = mode; 0211 } 0212 0213 QString Platform::ifopt() const 0214 { 0215 return d->m_ifopt; 0216 } 0217 0218 void Platform::setIfopt(const QString &ifopt) 0219 { 0220 d.detach(); 0221 d->m_ifopt = ifopt; 0222 } 0223 0224 QStringList Platform::lines() const 0225 { 0226 return d->m_lines; 0227 } 0228 0229 void Platform::setLines(QStringList &&lines) 0230 { 0231 d.detach(); 0232 d->m_lines = std::move(lines); 0233 } 0234 0235 QStringList&& Platform::takeLines() 0236 { 0237 d.detach(); 0238 return std::move(d->m_lines); 0239 } 0240 0241 static bool conflictIfPresent(OSM::Element lhs, OSM::Element rhs) 0242 { 0243 return lhs && rhs && lhs != rhs; 0244 } 0245 0246 static bool equalIfPresent(OSM::Element lhs, OSM::Element rhs) 0247 { 0248 return lhs && rhs && lhs == rhs; 0249 } 0250 0251 static bool isSubPath(const std::vector<const OSM::Node*> &path, const OSM::Way &way) 0252 { 0253 return std::all_of(way.nodes.begin(), way.nodes.end(), [&path](OSM::Id node) { 0254 return std::find_if(path.begin(), path.end(), [node](auto n) { return n->id == node; }) != path.end(); 0255 }); 0256 } 0257 0258 static constexpr const auto MAX_TRACK_TO_EDGE_DISTANCE = 4.5; // meters 0259 static constexpr const auto MAX_SECTION_TO_EDGE_DISTANCE = 5.0; 0260 0261 static double maxSectionDistance(const std::vector<const OSM::Node*> &path, const std::vector<PlatformSection> §ions) 0262 { 0263 auto dist = std::numeric_limits<double>::lowest(); 0264 for (const auto §ion : sections) { 0265 dist = std::max(dist, OSM::distance(path, section.position().center())); 0266 } 0267 return dist; 0268 } 0269 0270 double PlatformPrivate::maxSectionDistance(const Platform &p, const std::vector<PlatformSection> §ions, const OSM::DataSet &dataSet) 0271 { 0272 if (auto elem = OSM::coalesce(p.d->m_edge, p.d->m_area)) { 0273 return ::maxSectionDistance(elem.outerPath(dataSet), sections); 0274 } 0275 if (!p.d->m_track.empty()) { 0276 std::vector<const OSM::Node*> path; 0277 OSM::assemblePath(dataSet, p.d->m_track, path); 0278 return ::maxSectionDistance(path, sections); 0279 } 0280 return std::numeric_limits<double>::lowest(); 0281 } 0282 0283 static const OSM::Way* outerWay(OSM::Element &multiPolygon, const OSM::DataSet &dataSet) 0284 { 0285 // ### this assumes multi-polygons are structured in the way the Marble generator normalizes them! 0286 for (const auto &mem : multiPolygon.relation()->members) { 0287 if (mem.type() == OSM::Type::Way && (std::strcmp(mem.role().name(), "outer") == 0)) { 0288 return dataSet.way(mem.id); 0289 } 0290 } 0291 return nullptr; 0292 } 0293 0294 static bool isConnectedGeometry(OSM::Element lhs, OSM::Element rhs, const OSM::DataSet &dataSet) 0295 { 0296 if (lhs == rhs) { return false; } 0297 const OSM::Way *lway = nullptr; 0298 const OSM::Way *rway = nullptr; 0299 0300 switch (lhs.type()) { 0301 case OSM::Type::Null: 0302 case OSM::Type::Node: 0303 return false; 0304 case OSM::Type::Way: 0305 lway = lhs.way(); 0306 break; 0307 case OSM::Type::Relation: 0308 lway = outerWay(lhs, dataSet); 0309 break; 0310 0311 } 0312 switch (rhs.type()) { 0313 case OSM::Type::Null: 0314 case OSM::Type::Node: 0315 return false; 0316 case OSM::Type::Way: 0317 rway = rhs.way(); 0318 break; 0319 case OSM::Type::Relation: 0320 rway = outerWay(rhs, dataSet); 0321 break; 0322 } 0323 if (!lway || !rway) { 0324 return false; 0325 } 0326 0327 if (!lway->isClosed() && !rway->isClosed()) { 0328 return lway->nodes.front() == rway->nodes.front() 0329 || lway->nodes.back() == rway->nodes.front() 0330 || lway->nodes.front() == rway->nodes.back() 0331 || lway->nodes.back() == rway->nodes.back(); 0332 } 0333 if (lway->isClosed() && lway->nodes.size() > 2 && rway->isClosed() && rway->nodes.size() > 2) { 0334 // ### this assumes multi-polygons are structured in the way the Marble generator normalizes them! 0335 bool found = false; 0336 for (std::size_t i = 0; i < lway->nodes.size() && !found; ++i) { 0337 const auto n1 = lway->nodes[i]; 0338 const auto n2 = lway->nodes[(i + 1) % lway->nodes.size()]; 0339 for (std::size_t j = 0; j < rway->nodes.size() && !found; ++j) { 0340 found = ((n1 == rway->nodes[j]) && (n2 == rway->nodes[(j + 1) % rway->nodes.size()])) 0341 || ((n2 == rway->nodes[j]) && (n1 == rway->nodes[(j + 1) % rway->nodes.size()])); 0342 } 0343 } 0344 return found; 0345 } 0346 0347 return false; 0348 } 0349 0350 static bool isConnectedWay(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs, const OSM::DataSet &dataSet) 0351 { 0352 return std::any_of(lhs.begin(), lhs.end(), [&](auto lway) { 0353 return std::any_of(rhs.begin(), rhs.end(), [&](auto rway) { 0354 return isConnectedGeometry(lway, rway, dataSet); 0355 }); 0356 }); 0357 } 0358 0359 static bool isOverlappingWay(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs) 0360 { 0361 return std::any_of(lhs.begin(), lhs.end(), [&](auto lway) { 0362 return std::any_of(rhs.begin(), rhs.end(), [&](auto rway) { 0363 return lway == rway; 0364 }); 0365 }); 0366 } 0367 0368 bool Platform::isSame(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet) 0369 { 0370 if (!lhs.ifopt().isEmpty() && !rhs.ifopt().isEmpty()) { 0371 return lhs.ifopt() == rhs.ifopt(); 0372 } 0373 0374 const auto isConnectedEdge = isConnectedGeometry(lhs.d->m_edge, rhs.d->m_edge, dataSet); 0375 const auto isConnectedTrack = isConnectedWay(lhs.d->m_track, rhs.d->m_track, dataSet); 0376 const auto isOverlappingTrack = isOverlappingWay(lhs.d->m_track, rhs.d->m_track); 0377 const auto isConnectedArea = isConnectedGeometry(lhs.d->m_area, rhs.d->m_area, dataSet); 0378 0379 if ((conflictIfPresent(lhs.d->m_stopPoint, rhs.d->m_stopPoint) && lhs.d->m_track != rhs.d->m_track && !isConnectedTrack) 0380 || (conflictIfPresent(lhs.d->m_edge, rhs.d->m_edge) && !isConnectedEdge) 0381 || (conflictIfPresent(lhs.d->m_area, rhs.d->m_area) && !isConnectedArea) 0382 || (!lhs.d->m_track.empty() && !rhs.d->m_track.empty() && !isOverlappingTrack && !isConnectedTrack) 0383 || (lhs.hasLevel() && rhs.hasLevel() && lhs.level() != rhs.level()) 0384 || (lhs.d->m_mode != Unknown && rhs.d->m_mode != Unknown && lhs.d->m_mode != rhs.d->m_mode)) 0385 { 0386 return false; 0387 } 0388 0389 // we can accept conflicting names if one of them is likely a station name instead of a platform name 0390 if (!lhs.d->m_name.isEmpty() && !rhs.d->m_name.isEmpty() && lhs.d->m_name != rhs.d->m_name) { 0391 if (isPlausibleName(lhs.name()) && isPlausibleName(rhs.name())) { 0392 return false; 0393 } 0394 } 0395 0396 // edge has to be part of area, but on its own that doesn't mean equallity 0397 if (!isConnectedArea && !isConnectedEdge) { 0398 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())) 0399 || (rhs.d->m_area && lhs.d->m_edge.type() == OSM::Type::Way && !isSubPath(rhs.d->m_area.outerPath(dataSet), *lhs.d->m_edge.way()))) { 0400 return false; 0401 } 0402 } 0403 0404 // matching edge, point or track is good enough, matching area however isn't 0405 if (equalIfPresent(lhs.d->m_stopPoint, rhs.d->m_stopPoint) 0406 || equalIfPresent(lhs.d->m_edge, rhs.d->m_edge) || isConnectedEdge 0407 || isOverlappingTrack) 0408 { 0409 return true; 0410 } 0411 0412 if (!isConnectedEdge) { 0413 // track/stop and area/edge elements do not share nodes, so those we need to match by spatial distance 0414 if (lhs.d->m_edge && rhs.d->m_stopPoint) { 0415 return OSM::distance(lhs.d->m_edge.outerPath(dataSet), rhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE; 0416 } 0417 if (rhs.d->m_edge && lhs.d->m_stopPoint) { 0418 return OSM::distance(rhs.d->m_edge.outerPath(dataSet), lhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE; 0419 } 0420 } 0421 0422 if (!isConnectedArea) { 0423 if (lhs.d->m_area && rhs.d->m_stopPoint) { 0424 return OSM::distance(lhs.d->m_area.outerPath(dataSet), rhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE; 0425 } 0426 if (rhs.d->m_area && lhs.d->m_stopPoint) { 0427 return OSM::distance(rhs.d->m_area.outerPath(dataSet), lhs.position()) < MAX_TRACK_TO_EDGE_DISTANCE; 0428 } 0429 } 0430 0431 // free-floating sections: edge, area or track is within a reasonable distance 0432 if (((lhs.d->m_name.isEmpty() ^ rhs.d->m_name.isEmpty()) || lhs.d->m_name == rhs.d->m_name) && !isConnectedArea && !isConnectedEdge) { 0433 auto d = PlatformPrivate::maxSectionDistance(lhs, rhs.sections(), dataSet); 0434 if (d >= 0.0) { 0435 return d < MAX_SECTION_TO_EDGE_DISTANCE; 0436 } 0437 d = PlatformPrivate::maxSectionDistance(rhs, lhs.sections(), dataSet); 0438 if (d >= 0.0) { 0439 return d < MAX_SECTION_TO_EDGE_DISTANCE; 0440 } 0441 } 0442 0443 return isConnectedArea || isConnectedEdge || isConnectedTrack; 0444 } 0445 0446 static bool compareSection(const PlatformSection &lhs, const PlatformSection &rhs) 0447 { 0448 if (lhs.name() == rhs.name()) { 0449 return lhs.position() < rhs.position(); 0450 } 0451 return lhs.name() < rhs.name(); 0452 } 0453 0454 void PlatformPrivate::appendSection(std::vector<PlatformSection> §ions, const Platform &p, PlatformSection &&sec, std::vector<const OSM::Node*> &edgePath, const OSM::DataSet &dataSet) 0455 { 0456 if (sections.empty() || sections.back().name() != sec.name()) { 0457 sections.push_back(std::move(sec)); 0458 return; 0459 } 0460 0461 // check which one is closer 0462 if (edgePath.empty()) { 0463 if (p.d->m_edge) { 0464 edgePath = p.d->m_edge.outerPath(dataSet); 0465 } else if (!p.d->m_track.empty()) { 0466 OSM::assemblePath(dataSet, p.d->m_track, edgePath); 0467 } 0468 } 0469 const auto dist1 = OSM::distance(edgePath, sections.back().position().center()); 0470 const auto dist2 = OSM::distance(edgePath, sec.position().center()); 0471 if (dist2 < dist1) { 0472 sections.back() = std::move(sec); 0473 } 0474 } 0475 0476 static std::vector<OSM::Element> mergeWays(const std::vector<OSM::Element> &lhs, const std::vector<OSM::Element> &rhs) 0477 { 0478 std::vector<OSM::Element> w = lhs; 0479 for (auto e : rhs) { 0480 if (std::find(w.begin(), w.end(), e) == w.end()) { 0481 w.push_back(e); 0482 } 0483 } 0484 return w; 0485 } 0486 0487 Platform Platform::merge(const Platform &lhs, const Platform &rhs, const OSM::DataSet &dataSet) 0488 { 0489 Platform p; 0490 p.d->m_name = preferredName(lhs.name(), rhs.name()); 0491 p.d->m_stopPoint = OSM::coalesce(lhs.d->m_stopPoint, rhs.d->m_stopPoint); 0492 p.d->m_edge = OSM::coalesce(lhs.d->m_edge, rhs.d->m_edge); 0493 p.d->m_area = OSM::coalesce(lhs.d->m_area, rhs.d->m_area); 0494 p.d->m_track = mergeWays(lhs.d->m_track, rhs.d->m_track); 0495 p.d->m_level = lhs.hasLevel() ? lhs.d->m_level : rhs.d->m_level; 0496 p.d->m_ifopt = lhs.ifopt().isEmpty() ? rhs.ifopt() : lhs.ifopt(); 0497 0498 // TODO 0499 p.d->m_mode = std::max(lhs.d->m_mode, rhs.d->m_mode); 0500 p.d->m_lines = lhs.d->m_lines.isEmpty() ? std::move(rhs.d->m_lines) : std::move(lhs.d->m_lines); 0501 0502 std::vector<const OSM::Node*> edgePath; 0503 std::vector<PlatformSection> sections; 0504 auto lsec = lhs.sections(); 0505 auto rsec = rhs.sections(); 0506 std::sort(lsec.begin(), lsec.end(), compareSection); 0507 std::sort(rsec.begin(), rsec.end(), compareSection); 0508 for (auto lit = lsec.begin(), rit = rsec.begin(); lit != lsec.end() || rit != rsec.end();) { 0509 if (rit == rsec.end()) { 0510 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet); 0511 continue; 0512 } 0513 if (lit == lsec.end()) { 0514 PlatformPrivate::appendSection(sections, p, std::move(*rit++), edgePath, dataSet); 0515 continue; 0516 } 0517 if (compareSection(*lit, *rit)) { 0518 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet); 0519 continue; 0520 } 0521 if (compareSection(*rit, *lit)) { 0522 PlatformPrivate::appendSection(sections, p, std::move(*rit++), edgePath, dataSet); 0523 continue; 0524 } 0525 0526 // both are equal 0527 if ((*lit).position() == (*rit).position()) { 0528 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet); 0529 ++rit; 0530 continue; 0531 } 0532 0533 // both are equal but differ in distance: will be handled in appendSection in the next iteration 0534 PlatformPrivate::appendSection(sections, p, std::move(*lit++), edgePath, dataSet); 0535 } 0536 p.setSections(std::move(sections)); 0537 0538 return p; 0539 } 0540 0541 bool Platform::isPlausibleName(const QString &name) 0542 { 0543 static QRegularExpression exp(QStringLiteral("^(\\d{1,3}[a-z]?|[A-Z])$")); 0544 return exp.match(name).hasMatch(); 0545 } 0546 0547 QString Platform::preferredName(const QString &lhs, const QString &rhs) 0548 { 0549 if (lhs.isEmpty()) { 0550 return rhs; 0551 } 0552 if (rhs.isEmpty()) { 0553 return lhs; 0554 } 0555 0556 if (isPlausibleName(lhs)) { 0557 return lhs; 0558 } 0559 if (isPlausibleName(rhs)) { 0560 return rhs; 0561 } 0562 0563 return lhs.size() <= rhs.size() ? lhs: rhs; 0564 } 0565 0566 #include "moc_platform.cpp"