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"