KOSMIndoorMap

realtimeequipmentmodel.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 "realtimeequipmentmodel.h"
8
9#include <KPublicTransport/Equipment>
10#include <KPublicTransport/Location>
11#include <KPublicTransport/LocationQueryModel>
12
13#include <QAbstractItemModel>
14
15using namespace KOSMIndoorMap;
16
17constexpr inline const float EquipmentMatchDistance = 2.0; // meters
18
19RealtimeEquipmentModel::RealtimeEquipmentModel(QObject *parent)
20 : EquipmentModel(parent)
21{
22}
23
24RealtimeEquipmentModel::~RealtimeEquipmentModel() = default;
25
26QObject* RealtimeEquipmentModel::realtimeModel() const
27{
28 return m_realtimeModel;
29}
30
31void RealtimeEquipmentModel::setRealtimeModel(QObject *model)
32{
33 if (m_realtimeModel == model) {
34 return;
35 }
36
37 m_realtimeModel = qobject_cast<QAbstractItemModel*>(model);
38 Q_EMIT realtimeModelChanged();
39
40 if (m_realtimeModel) {
41 connect(m_realtimeModel, &QAbstractItemModel::modelReset, this, &RealtimeEquipmentModel::updateRealtimeState);
42 connect(m_realtimeModel, &QAbstractItemModel::rowsInserted, this, [this](const auto &parent, auto first, auto last) {
43 if (parent.isValid() || m_pendingRealtimeUpdate) {
44 return;
45 }
46 for (auto i = first; i <= last; ++i) {
47 const auto idx = m_realtimeModel->index(i, 0);
48 const auto loc = idx.data(KPublicTransport::LocationQueryModel::LocationRole).template value<KPublicTransport::Location>();
49 if (loc.type() == KPublicTransport::Location::Equipment) {
50 m_pendingRealtimeUpdate = true;
51 QMetaObject::invokeMethod(this, &RealtimeEquipmentModel::updateRealtimeState, Qt::QueuedConnection);
52 return;
53 }
54 }
55 });
56 connect(m_realtimeModel, &QAbstractItemModel::rowsRemoved, this, &RealtimeEquipmentModel::updateRealtimeState);
57 connect(m_realtimeModel, &QAbstractItemModel::dataChanged, this, [this](const auto &fromIdx, const auto &toIdx) {
58 if (m_pendingRealtimeUpdate) {
59 return;
60 }
61 for (auto i = fromIdx.row(); i <= toIdx.row(); ++i) {
62 const auto idx = m_realtimeModel->index(i, 0);
63 const auto loc = idx.data(KPublicTransport::LocationQueryModel::LocationRole).template value<KPublicTransport::Location>();
64 if (loc.type() == KPublicTransport::Location::Equipment) {
65 m_pendingRealtimeUpdate = true;
66 QMetaObject::invokeMethod(this, &RealtimeEquipmentModel::updateRealtimeState, Qt::QueuedConnection);
67 return;
68 }
69 }
70 });
71
72 if (m_realtimeModel->rowCount() > 0) {
73 updateRealtimeState();
74 }
75 }
76}
77
78static bool isSameEquipmentType(Equipment::Type lhs, KPublicTransport::Equipment::Type rhs)
79{
80 return (lhs == Equipment::Elevator && rhs == KPublicTransport::Equipment::Elevator)
81 || (lhs == Equipment::Escalator && rhs == KPublicTransport::Equipment::Escalator);
82}
83
84void RealtimeEquipmentModel::updateEquipment(Equipment &eq, const KPublicTransport::Equipment &rtEq) const
85{
86 createSyntheticElement(eq);
87 eq.syntheticElement.setTagValue(m_tagKeys.realtimeStatus, rtEq.disruptionEffect() == KPublicTransport::Disruption::NoService ? "0" : "1");
88}
89
90static int matchCount(const std::vector<std::vector<int>> &matches, int idx)
91{
92 return std::accumulate(matches.begin(), matches.end(), 0, [idx](int count, const auto &indexes) {
93 return count + std::count(indexes.begin(), indexes.end(), idx);
94 });
95}
96
97static int findOtherMatch(const std::vector<std::vector<int>> &matches, int value, std::size_t current)
98{
99 for (std::size_t i = 0; i < matches.size(); ++i) {
100 if (i == current) {
101 continue;
102 }
103 if (std::find(matches[i].begin(), matches[i].end(), value) != matches[i].end()) {
104 return i;
105 }
106 }
107 Q_UNREACHABLE();
108 return -1;
109}
110
111void RealtimeEquipmentModel::updateRealtimeState()
112{
113 m_pendingRealtimeUpdate = false;
114 if (!m_realtimeModel) {
115 return;
116 }
117
118 // clear previous data
119 for (auto &eq : m_equipment) {
120 if (!eq.syntheticElement) {
121 continue;
122 }
123 eq.syntheticElement.removeTag(m_tagKeys.realtimeStatus);
124 }
125
126 // find candidates by distance
127 std::vector<std::vector<int>> matches;
128 matches.resize(m_equipment.size());
129 for (auto i = 0; i < m_realtimeModel->rowCount(); ++i) {
130 const auto idx = m_realtimeModel->index(i, 0);
131 const auto loc = idx.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>();
132 if (loc.type() != KPublicTransport::Location::Equipment) {
133 continue;
134 }
135
136 const auto rtEq = loc.equipment();
137 for (std::size_t j = 0; j < m_equipment.size(); ++j) {
138 const auto &eq = m_equipment[j];
139 if (!isSameEquipmentType(eq.type, rtEq.type())) {
140 continue;
141 }
142 if (eq.distanceTo(m_data.dataSet(), loc.latitude(), loc.longitude()) < EquipmentMatchDistance) {
143 matches[j].push_back(i);
144 }
145 }
146 }
147
148 // apply realtime status
149 // we accept 3 different cases:
150 // - a single 1:1 match
151 // - a 1/2 or a 2/2 match for horizontally adjacent elements if there is a distance difference
152 for (std::size_t i = 0; i < m_equipment.size(); ++i) {
153 if (matches[i].size() == 1) {
154 const auto mcount = matchCount(matches, matches[i][0]);
155 if (mcount == 1) {
156 const auto idx = m_realtimeModel->index(matches[i][0], 0);
157 const auto rtEq = idx.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>().equipment();
158 updateEquipment(m_equipment[i], rtEq);
159 }
160 else if (mcount == 2) {
161 const auto other = findOtherMatch(matches, matches[i][0], i);
162 if (matches[other].size() == 2) {
163 const auto otherRow = matches[other][0] == matches[i][0] ? matches[other][1] : matches[other][0];
164 if (matchCount(matches, otherRow) == 1) {
165 resolveEquipmentPair(i, other, matches[other][0], matches[other][1]);
166 }
167 }
168 }
169 }
170
171 if (matches[i].size() == 2) {
172 if (matchCount(matches, matches[i][0]) == 2 && matchCount(matches, matches[i][1]) == 2) {
173 const auto it = std::find(std::next(matches.begin() + i), matches.end(), matches[i]);
174 if (it != matches.end()) {
175 resolveEquipmentPair(i, std::distance(matches.begin(), it), matches[i][0], matches[i][1]);
176 }
177 }
178 }
179 }
180
181 Q_EMIT update();
182}
183void RealtimeEquipmentModel::resolveEquipmentPair(int eqRow1, int eqRow2, int rtRow1, int rtRow2)
184{
185 // check if the equipment pair is horizontally adjacent
186 if (m_equipment[eqRow1].levels != m_equipment[eqRow2].levels) {
187 return;
188 }
189
190 // match best combination
191 const auto rtIdx1 = m_realtimeModel->index(rtRow1, 0);
192 const auto rtIdx2 = m_realtimeModel->index(rtRow2, 0);
193 const auto rtEq1 = rtIdx1.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>();
194 const auto rtEq2 = rtIdx2.data(KPublicTransport::LocationQueryModel::LocationRole).value<KPublicTransport::Location>();
195
196 const auto d11 = m_equipment[eqRow1].distanceTo(m_data.dataSet(), rtEq1.latitude(), rtEq1.longitude());
197 const auto d12 = m_equipment[eqRow1].distanceTo(m_data.dataSet(), rtEq2.latitude(), rtEq2.longitude());
198 const auto d21 = m_equipment[eqRow2].distanceTo(m_data.dataSet(), rtEq1.latitude(), rtEq1.longitude());
199 const auto d22 = m_equipment[eqRow2].distanceTo(m_data.dataSet(), rtEq2.latitude(), rtEq2.longitude());
200
201 const auto swap1 = d11 >= d12;
202 const auto swap2 = d21 < d22;
203
204 if (swap1 != swap2) {
205 return;
206 }
207
208 if (swap1) {
209 if (d12 < EquipmentMatchDistance && d21 < EquipmentMatchDistance) {
210 updateEquipment(m_equipment[eqRow1], rtEq2.equipment());
211 updateEquipment(m_equipment[eqRow2], rtEq1.equipment());
212 }
213 } else {
214 if (d11 < EquipmentMatchDistance && d22 < EquipmentMatchDistance) {
215 updateEquipment(m_equipment[eqRow1], rtEq1.equipment());
216 updateEquipment(m_equipment[eqRow2], rtEq2.equipment());
217 }
218 }
219}
220
221#include "moc_realtimeequipmentmodel.cpp"
void update()
Trigger map re-rendering when the source changes.
Overlay source for elevators and escalators.
Elevator or escalator element.
KPublicTransport::Disruption::Effect disruptionEffect
KPublicTransport::Equipment equipment
QAction * end(const QObject *recvr, const char *slot, QObject *parent)
OSM-based multi-floor indoor maps for buildings.
const QList< QKeySequence > & begin()
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
void rowsInserted(const QModelIndex &parent, int first, int last)
void rowsRemoved(const QModelIndex &parent, int first, int last)
bool invokeMethod(QObject *context, Functor &&function, FunctorReturnType *ret)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
T qobject_cast(QObject *object)
QueuedConnection
T value() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 29 2024 11:55:43 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.