KOSMIndoorMap

platformmodel.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 "platformmodel.h"
8#include "platformfinder_p.h"
9
10#include <QPointF>
11#include <QRegularExpression>
12
13#include <limits>
14
15using namespace KOSMIndoorMap;
16
17static constexpr auto TOP_PARENT = std::numeric_limits<quintptr>::max();
18
19PlatformModel::PlatformModel(QObject* parent) :
20 QAbstractItemModel(parent)
21{
22 m_matchTimer.setSingleShot(true);
23 m_matchTimer.setInterval(0);
24 connect(&m_matchTimer, &QTimer::timeout, this, &PlatformModel::matchPlatforms);
25
26 connect(this, &PlatformModel::mapDataChanged, &m_matchTimer, qOverload<>(&QTimer::start));
27 connect(this, &PlatformModel::arrivalPlatformChanged, &m_matchTimer, qOverload<>(&QTimer::start));
28 connect(this, &PlatformModel::departurePlatformChanged, &m_matchTimer, qOverload<>(&QTimer::start));
29}
30
31PlatformModel::~PlatformModel() = default;
32
33MapData PlatformModel::mapData() const
34{
35 return m_data;
36}
37
38void PlatformModel::setMapData(const MapData &data)
39{
40 if (m_data == data) {
41 return;
42 }
43
45 m_platforms.clear();
46 m_platformLabels.clear();
47 m_sectionsLabels.clear();
48 m_arrivalPlatformRow = -1;
49 m_departurePlatformRow = -1;
50
51 m_data = data;
52 if (!m_data.isEmpty()) {
53 PlatformFinder finder;
54 m_platforms = finder.find(m_data);
55
56 m_tagKeys.arrival = m_data.dataSet().makeTagKey("mx:arrival");
57 m_tagKeys.departure = m_data.dataSet().makeTagKey("mx:departure");
58 createLabels();
59 }
61 Q_EMIT mapDataChanged();
62 Q_EMIT platformIndexChanged();
63}
64
65bool PlatformModel::isEmpty() const
66{
67 return rowCount() == 0;
68}
69
70int PlatformModel::columnCount(const QModelIndex& parent) const
71{
72 Q_UNUSED(parent);
73 return 1;
74}
75
76int PlatformModel::rowCount(const QModelIndex &parent) const
77{
78 if (parent.isValid()) {
79 return parent.internalId() == TOP_PARENT ? m_platforms[parent.row()].sections().size() : 0;
80 }
81
82 return m_platforms.size();
83}
84
85QVariant PlatformModel::data(const QModelIndex &index, int role) const
86{
87 if (!index.isValid()) {
88 return {};
89 }
90
91 if (index.internalId() == TOP_PARENT) {
92 const auto &platform = m_platforms[index.row()];
93 switch (role) {
94 case Qt::DisplayRole:
95 return platform.name();
96 case CoordinateRole:
97 return QPointF(platform.position().lonF(), platform.position().latF());
98 case ElementRole:
99 return QVariant::fromValue(OSM::Element(m_platformLabels[index.row()]));
100 case LevelRole:
101 return platform.level();
102 case TransportModeRole:
103 return platform.mode();
104 case LinesRole:
105 return platform.lines();
106 case ArrivalPlatformRole:
107 return index.row() == m_arrivalPlatformRow;
108 case DeparturePlatformRole:
109 return index.row() == m_departurePlatformRow;
110 }
111 } else {
112 const auto &platform = m_platforms[index.internalId()];
113 const auto &section = platform.sections()[index.row()];
114 switch (role) {
115 case Qt::DisplayRole:
116 return section.name();
117 case CoordinateRole:
118 return QPointF(section.position().center().lonF(), section.position().center().latF());
119 case ElementRole:
120 return QVariant::fromValue(OSM::Element(m_sectionsLabels[index.internalId()][index.row()]));
121 case LevelRole:
122 return platform.level();
123 }
124 }
125
126 return {};
127}
128
129QModelIndex PlatformModel::index(int row, int column, const QModelIndex &parent) const
130{
131 if (!parent.isValid()) {
132 return createIndex(row, column, TOP_PARENT);
133 }
134 return createIndex(row, column, parent.row());
135}
136
138{
139 if (!child.isValid() || child.internalId() == TOP_PARENT) {
140 return {};
141 }
142 return createIndex(child.internalId(), 0, TOP_PARENT);
143}
144
145QHash<int, QByteArray> PlatformModel::roleNames() const
146{
148 n.insert(CoordinateRole, "coordinate");
149 n.insert(ElementRole, "osmElement");
150 n.insert(LevelRole, "level");
151 n.insert(TransportModeRole, "mode");
152 n.insert(LinesRole, "lines");
153 n.insert(ArrivalPlatformRole, "isArrivalPlatform");
154 n.insert(DeparturePlatformRole, "isDeparturePlatform");
155 return n;
156}
157
159{
160 return m_arrivalPlatform;
161}
162
163void PlatformModel::setArrivalPlatform(const Platform &platform)
164{
165 m_arrivalPlatform = platform;
166 Q_EMIT arrivalPlatformChanged();
167}
168
169void PlatformModel::setArrivalPlatform(const QString &name, Platform::Mode mode)
170{
171 m_arrivalPlatform.setName(name);
172 m_arrivalPlatform.setMode(mode);
173 Q_EMIT arrivalPlatformChanged();
174}
175
176Platform PlatformModel::departurePlatform() const
177{
178 return m_departurePlatform;
179}
180
181void PlatformModel::setDeparturePlatform(const Platform &platform)
182{
183 m_departurePlatform = platform;
184 Q_EMIT departurePlatformChanged();
185}
186
187void PlatformModel::setDeparturePlatform(const QString &name, Platform::Mode mode)
188{
189 m_departurePlatform.setName(name);
190 m_departurePlatform.setMode(mode);
191 Q_EMIT departurePlatformChanged();
192}
193
195{
196 return m_arrivalPlatformRow;
197}
198
199int PlatformModel::departurePlatformRow() const
200{
201 return m_departurePlatformRow;
202}
203
204void PlatformModel::matchPlatforms()
205{
206 setPlatformTag(m_arrivalPlatformRow, m_tagKeys.arrival, false);
207 applySectionSelection(m_arrivalPlatformRow, m_tagKeys.arrival, {});
208 m_arrivalPlatformRow = matchPlatform(m_arrivalPlatform);
209 setPlatformTag(m_arrivalPlatformRow, m_tagKeys.arrival, true);
210 setPlatformTag(m_departurePlatformRow, m_tagKeys.departure, false);
211 applySectionSelection(m_departurePlatformRow, m_tagKeys.departure, {});
212 m_departurePlatformRow = matchPlatform(m_departurePlatform);
213 setPlatformTag(m_departurePlatformRow, m_tagKeys.departure, true);
214 Q_EMIT platformIndexChanged();
215
216 if (m_arrivalPlatformRow >= 0) {
217 const auto idx = index(m_arrivalPlatformRow, 0);
218 Q_EMIT dataChanged(idx, idx);
219 applySectionSelection(m_arrivalPlatformRow, m_tagKeys.arrival, effectiveArrivalSections());
220 Q_EMIT dataChanged(index(0, 0, idx), index(rowCount(idx) - 1, 0, idx));
221 }
222 if (m_departurePlatformRow >= 0) {
223 const auto idx = index(m_departurePlatformRow, 0);
224 Q_EMIT dataChanged(idx, idx);
225 applySectionSelection(m_departurePlatformRow, m_tagKeys.departure, effectiveDepartureSections());
226 Q_EMIT dataChanged(index(0, 0, idx), index(rowCount(idx) - 1, 0, idx));
227 }
228}
229
230static bool isPossiblySamePlatformName(const QString &name, const QString &platform)
231{
232 // <platform>\w?<section(s)>
233 if (name.size() > platform.size()) {
234 QRegularExpression exp(QStringLiteral("(\\d+)\\s?[A-Z-]+"));
235 const auto match = exp.match(name);
236 return match.hasMatch() && match.captured(1) == platform;
237 }
238
239 return false;
240}
241
242int PlatformModel::matchPlatform(const Platform &platform) const
243{
244 if (!platform.ifopt().isEmpty()) { // try IFOPT first, if we have that
245 const auto it = std::find_if(m_platforms.begin(), m_platforms.end(), [platform](const auto &p) {
246 return p.ifopt() == platform.ifopt();
247 });
248 if (it != m_platforms.end()) {
249 return std::distance(m_platforms.begin(), it);
250 }
251 }
252
253 if (platform.name().isEmpty()) {
254 return -1;
255 }
256
257 // exact match
258 int i = 0;
259 for (const auto &p : m_platforms) {
260 if (p.name() == platform.name() && p.mode() == platform.mode()) {
261 return i;
262 }
263 ++i;
264 }
265
266 // fuzzy match
267 // TODO this likely will need to handle more scenarios
268 // TODO when we get section ranges here, we might want to use those as well?
269 i = 0;
270 for (const auto &p : m_platforms) {
271 if (p.mode() == platform.mode() && isPossiblySamePlatformName(platform.name(), p.name())) {
272 return i;
273 }
274 ++i;
275 }
276
277 return -1;
278}
279
280void PlatformModel::createLabels()
281{
282 const auto platformTag = m_data.dataSet().makeTagKey("mx:platform");
283 const auto sectionTag = m_data.dataSet().makeTagKey("mx:platform_section");
284
285 m_platformLabels.reserve(m_platforms.size());
286 m_sectionsLabels.resize(m_platforms.size());
287 for (std::size_t i = 0; i < m_platforms.size(); ++i) {
288 const auto &p = m_platforms[i];
289
290 // TODO using the full edge/track path here might be better for layouting
291 auto node = new OSM::Node;
292 node->id = m_data.dataSet().nextInternalId();
293 node->coordinate = p.position();
294 OSM::setTagValue(*node, platformTag, p.name().toUtf8());
295 m_platformLabels.push_back(OSM::UniqueElement(node));
296
297 m_sectionsLabels[i].reserve(p.sections().size());
298 for (const auto &sec : p.sections()) {
299 auto node = new OSM::Node;
300 node->id = m_data.dataSet().nextInternalId();
301 node->coordinate = sec.position().center();
302 OSM::setTagValue(*node, sectionTag, sec.name().toUtf8());
303 m_sectionsLabels[i].push_back(OSM::UniqueElement(node));
304 }
305 }
306}
307
308void PlatformModel::setPlatformTag(int idx, OSM::TagKey key, bool enabled)
309{
310 if (idx < 0) {
311 return;
312 }
313
314 m_platformLabels[idx].setTagValue(key, enabled ? "1" : "0");
315}
316
317static QStringView stripPlatform(QStringView p)
318{
319 while (!p.empty() && (p[0].isDigit() || p[0].isSpace())) {
320 p = p.mid(1);
321 }
322 return p;
323}
324
325QStringView PlatformModel::effectiveArrivalSections() const
326{
327 // TODO prefer explicit section selectors once implemented/when present
328 return stripPlatform(m_arrivalPlatform.name());
329}
330
331QStringView PlatformModel::effectiveDepartureSections() const
332{
333 // TODO prefer explicit section selectors once implemented/when present
334 return stripPlatform(m_departurePlatform.name());
335}
336
337static std::vector<QChar> parseSectionSet(QStringView sections)
338{
339 std::vector<QChar> result;
340 const auto ranges = sections.split(QLatin1Char(','));
341 for (const auto &r : ranges) {
342 if (r.size() == 1) {
343 result.push_back(r[0]);
344 continue;
345 }
346 if (r.size() == 3 && r[1] == QLatin1Char('-') && r[0] < r[2]) {
347 for (QChar c = r[0]; c <= r[2]; c = QChar(c.unicode() + 1)) {
348 result.push_back(c);
349 }
350 continue;
351 }
352 qDebug() << "failed to parse platform section expression:" << r;
353 }
354 return result;
355}
356
357void PlatformModel::applySectionSelection(int platformIdx, OSM::TagKey key, QStringView sections)
358{
359 if (platformIdx < 0) {
360 return;
361 }
362
363 const auto sectionSet = parseSectionSet(sections);
364
365 std::size_t totalSelected = 0;
366 for (std::size_t i = 0; i < m_platforms[platformIdx].sections().size(); ++i) {
367 if (std::any_of(sectionSet.begin(), sectionSet.end(), [this, i, platformIdx](const QChar s) {
368 return s == m_platforms[platformIdx].sections()[i].name();
369 })) {
370 m_sectionsLabels[platformIdx][i].setTagValue(key, "1");
371 ++totalSelected;
372 } else {
373 m_sectionsLabels[platformIdx][i].setTagValue(key, "0");
374 }
375 }
376
377 // if we enabled all sections, disable them again, highlighting adds no value then
378 if (totalSelected == m_sectionsLabels[platformIdx].size()) {
379 for (auto &s : m_sectionsLabels[platformIdx]) {
380 s.setTagValue(key, "0");
381 }
382 }
383}
384
385#include "moc_platformmodel.cpp"
Raw OSM map data, separated by levels.
Definition mapdata.h:60
int arrivalPlatformRow
Row indexes of the matched arrival/departure platforms, if found and/or set, otherwise -1.
KOSMIndoorMap::Platform arrivalPlatform
Platform search parameters (name/mode/ifopt) for matching arrival/departure platform against what we ...
A railway platform/track.
Definition platform.h:56
Mode
Transportation mode served by a platform.
Definition platform.h:109
Id nextInternalId() const
Create a unique id for internal use (ie.
TagKey makeTagKey(const char *keyName, StringMemory keyMemOpt=StringMemory::Transient)
Create a tag key for the given tag name.
Definition datatypes.cpp:17
A reference to any of OSM::Node/OSM::Way/OSM::Relation.
Definition element.h:24
An OSM node.
Definition datatypes.h:204
A key of an OSM tag.
Definition datatypes.h:179
A std::unique_ptr-like object for OSM element types.
Definition element.h:100
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
OSM-based multi-floor indoor maps for buildings.
void setTagValue(Elem &elem, TagKey key, QByteArray &&value)
Inserts a new tag, or updates an existing one.
Definition datatypes.h:484
QModelIndex createIndex(int row, int column, const void *ptr) const const
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
virtual QHash< int, QByteArray > roleNames() const const
quintptr internalId() const const
bool isValid() const const
int row() const const
Q_EMITQ_EMIT
QObject * parent() const const
bool isEmpty() const const
qsizetype size() const const
QStringView mid(qsizetype start, qsizetype length) const const
bool empty() const const
QList< QStringView > split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
DisplayRole
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void start()
void timeout()
QVariant fromValue(T &&value)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:20:03 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.