KOSMIndoorMap

realtimeequipmentmodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
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 
15 using namespace KOSMIndoorMap;
16 
17 constexpr inline const float EquipmentMatchDistance = 2.0; // meters
18 
19 RealtimeEquipmentModel::RealtimeEquipmentModel(QObject *parent)
20  : EquipmentModel(parent)
21 {
22 }
23 
24 RealtimeEquipmentModel::~RealtimeEquipmentModel() = default;
25 
26 QObject* RealtimeEquipmentModel::realtimeModel() const
27 {
28  return m_realtimeModel;
29 }
30 
31 void 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 
78 static 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 
84 void 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 
90 static 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 
97 static 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 
111 void 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 }
183 void 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 }
OSM-based multi-floor indoor maps for buildings.
T value() const const
const QList< QKeySequence > & begin()
KPublicTransport::Disruption::Effect disruptionEffect
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector< int > &roles)
Elevator or escalator element.
KPublicTransport::Equipment equipment
void rowsRemoved(const QModelIndex &parent, int first, int last)
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
const QList< QKeySequence > & end()
Overlay source for elevators and escalators.
void update(Part *part, const QByteArray &data, qint64 dataSize)
void rowsInserted(const QModelIndex &parent, int first, int last)
QueuedConnection
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
Q_EMITQ_EMIT
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 23 2021 23:03:45 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.