File indexing completed on 2024-05-12 04:42:08

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "realtimeequipmentmodel.h"
0008 
0009 #include <KPublicTransport/Equipment>
0010 #include <KPublicTransport/Location>
0011 #include <KPublicTransport/LocationQueryModel>
0012 
0013 #include <QAbstractItemModel>
0014 
0015 using namespace KOSMIndoorMap;
0016 
0017 constexpr inline const float EquipmentMatchDistance = 2.0; // meters
0018 
0019 RealtimeEquipmentModel::RealtimeEquipmentModel(QObject *parent)
0020     : EquipmentModel(parent)
0021 {
0022 }
0023 
0024 RealtimeEquipmentModel::~RealtimeEquipmentModel() = default;
0025 
0026 QObject* RealtimeEquipmentModel::realtimeModel() const
0027 {
0028     return m_realtimeModel;
0029 }
0030 
0031 void RealtimeEquipmentModel::setRealtimeModel(QObject *model)
0032 {
0033     if (m_realtimeModel == model) {
0034         return;
0035     }
0036 
0037     m_realtimeModel = qobject_cast<QAbstractItemModel*>(model);
0038     Q_EMIT realtimeModelChanged();
0039 
0040     if (m_realtimeModel) {
0041         connect(m_realtimeModel, &QAbstractItemModel::modelReset, this, &RealtimeEquipmentModel::updateRealtimeState);
0042         connect(m_realtimeModel, &QAbstractItemModel::rowsInserted, this, [this](const auto &parent, auto first, auto last) {
0043             if (parent.isValid() || m_pendingRealtimeUpdate) {
0044                 return;
0045             }
0046             for (auto i = first; i <= last; ++i) {
0047                 const auto idx = m_realtimeModel->index(i, 0);
0048                 const auto loc = idx.data(KPublicTransport::LocationQueryModel::LocationRole).template value<KPublicTransport::Location>();
0049                 if (loc.type() == KPublicTransport::Location::Equipment) {
0050                     m_pendingRealtimeUpdate = true;
0051                     QMetaObject::invokeMethod(this, &RealtimeEquipmentModel::updateRealtimeState, Qt::QueuedConnection);
0052                     return;
0053                 }
0054             }
0055         });
0056         connect(m_realtimeModel, &QAbstractItemModel::rowsRemoved, this, &RealtimeEquipmentModel::updateRealtimeState);
0057         connect(m_realtimeModel, &QAbstractItemModel::dataChanged, this, [this](const auto &fromIdx, const auto &toIdx) {
0058             if (m_pendingRealtimeUpdate) {
0059                 return;
0060             }
0061             for (auto i = fromIdx.row(); i <= toIdx.row(); ++i) {
0062                 const auto idx = m_realtimeModel->index(i, 0);
0063                 const auto loc = idx.data(KPublicTransport::LocationQueryModel::LocationRole).template value<KPublicTransport::Location>();
0064                 if (loc.type() == KPublicTransport::Location::Equipment) {
0065                     m_pendingRealtimeUpdate = true;
0066                     QMetaObject::invokeMethod(this, &RealtimeEquipmentModel::updateRealtimeState, Qt::QueuedConnection);
0067                     return;
0068                 }
0069             }
0070         });
0071 
0072         if (m_realtimeModel->rowCount() > 0) {
0073             updateRealtimeState();
0074         }
0075     }
0076 }
0077 
0078 static bool isSameEquipmentType(Equipment::Type lhs, KPublicTransport::Equipment::Type rhs)
0079 {
0080     return (lhs == Equipment::Elevator && rhs == KPublicTransport::Equipment::Elevator)
0081         || (lhs == Equipment::Escalator && rhs == KPublicTransport::Equipment::Escalator);
0082 }
0083 
0084 void RealtimeEquipmentModel::updateEquipment(Equipment &eq, const KPublicTransport::Equipment &rtEq) const
0085 {
0086     createSyntheticElement(eq);
0087     eq.syntheticElement.setTagValue(m_tagKeys.realtimeStatus, rtEq.disruptionEffect() == KPublicTransport::Disruption::NoService ? "0" : "1");
0088 }
0089 
0090 static int matchCount(const std::vector<std::vector<int>> &matches, int idx)
0091 {
0092     return std::accumulate(matches.begin(), matches.end(), 0, [idx](int count, const auto &indexes) {
0093         return count + std::count(indexes.begin(), indexes.end(), idx);
0094     });
0095 }
0096 
0097 static int findOtherMatch(const std::vector<std::vector<int>> &matches, int value, std::size_t current)
0098 {
0099     for (std::size_t i = 0; i < matches.size(); ++i) {
0100         if (i == current) {
0101             continue;
0102         }
0103         if (std::find(matches[i].begin(), matches[i].end(), value) != matches[i].end()) {
0104             return i;
0105         }
0106     }
0107     Q_UNREACHABLE();
0108     return -1;
0109 }
0110 
0111 void RealtimeEquipmentModel::updateRealtimeState()
0112 {
0113     m_pendingRealtimeUpdate = false;
0114     if (!m_realtimeModel) {
0115         return;
0116     }
0117 
0118     // clear previous data
0119     for (auto &eq : m_equipment) {
0120         if (!eq.syntheticElement) {
0121             continue;
0122         }
0123         eq.syntheticElement.removeTag(m_tagKeys.realtimeStatus);
0124     }
0125 
0126     // find candidates by distance
0127     std::vector<std::vector<int>> matches;
0128     matches.resize(m_equipment.size());
0129     for (auto i = 0; i < m_realtimeModel->rowCount(); ++i) {
0130         const auto idx = m_realtimeModel->index(i, 0);
0131         const auto loc = idx.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>();
0132         if (loc.type() != KPublicTransport::Location::Equipment) {
0133             continue;
0134         }
0135 
0136         const auto rtEq = loc.equipment();
0137         for (std::size_t j = 0; j < m_equipment.size(); ++j) {
0138             const auto &eq = m_equipment[j];
0139             if (!isSameEquipmentType(eq.type, rtEq.type())) {
0140                 continue;
0141             }
0142             if (eq.distanceTo(m_data.dataSet(), loc.latitude(), loc.longitude()) < EquipmentMatchDistance) {
0143                 matches[j].push_back(i);
0144             }
0145         }
0146     }
0147 
0148     // apply realtime status
0149     // we accept 3 different cases:
0150     // - a single 1:1 match
0151     // - a 1/2 or a 2/2 match for horizontally adjacent elements if there is a distance difference
0152     for (std::size_t i = 0; i < m_equipment.size(); ++i) {
0153         if (matches[i].size() == 1) {
0154             const auto mcount = matchCount(matches, matches[i][0]);
0155             if (mcount == 1) {
0156                 const auto idx =  m_realtimeModel->index(matches[i][0], 0);
0157                 const auto rtEq = idx.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>().equipment();
0158                 updateEquipment(m_equipment[i], rtEq);
0159             }
0160             else if (mcount == 2) {
0161                 const auto other = findOtherMatch(matches, matches[i][0], i);
0162                 if (matches[other].size() == 2) {
0163                     const auto otherRow = matches[other][0] == matches[i][0] ? matches[other][1] : matches[other][0];
0164                     if (matchCount(matches, otherRow) == 1) {
0165                         resolveEquipmentPair(i, other, matches[other][0], matches[other][1]);
0166                     }
0167                 }
0168             }
0169         }
0170 
0171         if (matches[i].size() == 2) {
0172             if (matchCount(matches, matches[i][0]) == 2 && matchCount(matches, matches[i][1]) == 2) {
0173                 const auto it = std::find(std::next(matches.begin() + i), matches.end(), matches[i]);
0174                 if (it != matches.end()) {
0175                     resolveEquipmentPair(i, std::distance(matches.begin(), it), matches[i][0], matches[i][1]);
0176                 }
0177             }
0178         }
0179     }
0180 
0181     Q_EMIT update();
0182 }
0183 void RealtimeEquipmentModel::resolveEquipmentPair(int eqRow1, int eqRow2, int rtRow1, int rtRow2)
0184 {
0185     // check if the equipment pair is horizontally adjacent
0186     if (m_equipment[eqRow1].levels != m_equipment[eqRow2].levels) {
0187         return;
0188     }
0189 
0190     // match best combination
0191     const auto rtIdx1 = m_realtimeModel->index(rtRow1, 0);
0192     const auto rtIdx2 = m_realtimeModel->index(rtRow2, 0);
0193     const auto rtEq1 = rtIdx1.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>();
0194     const auto rtEq2 = rtIdx2.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>();
0195 
0196     const auto d11 = m_equipment[eqRow1].distanceTo(m_data.dataSet(), rtEq1.latitude(), rtEq1.longitude());
0197     const auto d12 = m_equipment[eqRow1].distanceTo(m_data.dataSet(), rtEq2.latitude(), rtEq2.longitude());
0198     const auto d21 = m_equipment[eqRow2].distanceTo(m_data.dataSet(), rtEq1.latitude(), rtEq1.longitude());
0199     const auto d22 = m_equipment[eqRow2].distanceTo(m_data.dataSet(), rtEq2.latitude(), rtEq2.longitude());
0200 
0201     const auto swap1 = d11 >= d12;
0202     const auto swap2 = d21 < d22;
0203 
0204     if (swap1 != swap2) {
0205         return;
0206     }
0207 
0208     if (swap1) {
0209         if (d12 < EquipmentMatchDistance && d21 < EquipmentMatchDistance) {
0210             updateEquipment(m_equipment[eqRow1], rtEq2.equipment());
0211             updateEquipment(m_equipment[eqRow2], rtEq1.equipment());
0212         }
0213     } else {
0214         if (d11 < EquipmentMatchDistance && d22 < EquipmentMatchDistance) {
0215             updateEquipment(m_equipment[eqRow1], rtEq1.equipment());
0216             updateEquipment(m_equipment[eqRow2], rtEq2.equipment());
0217         }
0218     }
0219 }
0220 
0221 #include "moc_realtimeequipmentmodel.cpp"