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> &sections, 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> &sections, 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> &&sections)
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> &sections)
0262 {
0263     auto dist = std::numeric_limits<double>::lowest();
0264     for (const auto &section : 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> &sections, 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> &sections, 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"