KOSMIndoorMap

osmelementinformationmodel.cpp
1 /*
2  SPDX-FileCopyrightText: 2020 Volker Krause <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "osmelementinformationmodel.h"
8 #include "osmelementinformationmodel_p.h"
9 #include "osmelementinformationmodel_data.cpp"
10 #include "osmaddress.h"
11 
12 #include <KLocalizedString>
13 
14 #include <cctype>
15 
16 using namespace KOSMIndoorMap;
17 
18 static QString formatDistance(int meter)
19 {
20  if (meter < 1000) {
21  return i18n("%1m", meter);
22  }
23  if (meter < 10000) {
24  return i18n("%1km", ((int)meter/100)/10.0);
25  }
26  return i18n("%1km", (int)qRound(meter/1000.0));
27 }
28 
29 bool OSMElementInformationModel::Info::operator<(OSMElementInformationModel::Info other) const
30 {
31  if (category == other.category) {
32  return key < other.key;
33  }
34  return category < other.category;
35 }
36 
37 bool OSMElementInformationModel::Info::operator==(OSMElementInformationModel::Info other) const
38 {
39  return category == other.category && key == other.key;
40 }
41 
42 
43 OSMElementInformationModel::OSMElementInformationModel(QObject *parent)
44  : QAbstractListModel(parent)
45 {
46 }
47 
48 OSMElementInformationModel::~OSMElementInformationModel() = default;
49 
50 OSMElement OSMElementInformationModel::element() const
51 {
52  return OSMElement(m_element);
53 }
54 
55 void OSMElementInformationModel::setElement(const OSMElement &element)
56 {
57  if (m_element == element.element()) {
58  return;
59  }
60 
61  beginResetModel();
62  m_element = element.element();
63  m_infos.clear();
64  if (m_element.type() != OSM::Type::Null) {
65  reload();
66  }
67  endResetModel();
68  Q_EMIT elementChanged();
69 }
70 
71 void OSMElementInformationModel::clear()
72 {
73  if (m_element.type() == OSM::Type::Null) {
74  return;
75  }
76  beginResetModel();
77  m_infos.clear();
78  m_element = {};
79  endResetModel();
80  Q_EMIT elementChanged();
81 }
82 
83 QString OSMElementInformationModel::name() const
84 {
85  return valueForKey(Info{m_nameKey, Header}).toString();
86 }
87 
88 QString OSMElementInformationModel::category() const
89 {
90  return valueForKey(Info{m_categoryKey, Header}).toString();
91 }
92 
93 int OSMElementInformationModel::rowCount(const QModelIndex &parent) const
94 {
95  if (parent.isValid() || m_element.type() == OSM::Type::Null) {
96  return 0;
97  }
98  return m_infos.size();
99 }
100 
101 QVariant OSMElementInformationModel::data(const QModelIndex &index, int role) const
102 {
103  if (!index.isValid()) {
104  return {};
105  }
106 
107  const auto info = m_infos[index.row()];
108  switch (role) {
109  case TypeRole:
110  switch (info.key) {
111  case Wikipedia:
112  case Phone:
113  case Email:
114  case Website:
115  case OperatorWikipedia:
116  case DebugLink:
117  return Link;
118  case Address:
119  return PostalAddress;
120  case OpeningHours:
121  return OpeningHoursType;
122  default:
123  return String;
124  }
125  case KeyRole:
126  return info.key;
127  case KeyLabelRole:
128  if (info.key == DebugKey) {
129  return debugTagKey(index.row());
130  }
131  return keyName(info.key);
132  case ValueRole:
133  switch (info.key) {
134  case DebugKey: return debugTagValue(index.row());
135  case Wikipedia: return i18n("Wikipedia");
136  default: return valueForKey(info);
137  }
138  case ValueUrlRole:
139  return urlify(valueForKey(info), info.key);
140  case CategoryRole:
141  return info.category;
142  case CategoryLabelRole:
143  return categoryLabel(info.category);
144  }
145 
146  return {};
147 }
148 
149 QHash<int, QByteArray> OSMElementInformationModel::roleNames() const
150 {
152  r.insert(KeyRole, "key");
153  r.insert(KeyLabelRole, "keyLabel");
154  r.insert(ValueRole, "value");
155  r.insert(ValueUrlRole, "url");
156  r.insert(CategoryRole, "category");
157  r.insert(CategoryLabelRole, "categoryLabel");
158  r.insert(TypeRole, "type");
159  return r;
160 }
161 
162 #define M(name, key, category) { name, OSMElementInformationModel::key, OSMElementInformationModel::category }
163 struct {
164  const char *keyName;
165  OSMElementInformationModel::Key m_key;
166  OSMElementInformationModel::KeyCategory m_category;
167 
168  constexpr inline OSMElementInformationModel::Key key() const { return m_key; }
169  constexpr inline OSMElementInformationModel::KeyCategory category() const { return m_category; }
170 } static constexpr const simple_key_map[] = {
171  M("addr:city", Address, Contact),
172  M("addr:street", Address, Contact),
173  M("amenity", Category, Header),
174  M("bicycle_parking", BicycleParking, Parking),
175  M("brand", Name, Header),
176  M("brand:wikipedia", Wikipedia, UnresolvedCategory),
177  M("bus_routes", Routes, Main),
178  M("buses", Routes, Main),
179  M("capacity", Capacity, UnresolvedCategory),
180  M("capacity:charging", CapacityCharing, Parking),
181  M("capacity:disabled", CapacityDisabled, Parking),
182  M("capacity:parent", CapacityParent, Parking),
183  M("capacity:women", CapacityWomen, Parking),
184  M("centralkey", CentralKey, Accessibility),
185  M("changing_table", DiaperChangingTable, UnresolvedCategory),
186  M("charge", Fee, UnresolvedCategory),
187  M("contact:city", Address, Contact),
188  M("contact:email", Email, Contact),
189  M("contact:phone", Phone, Contact),
190  M("contact:street", Address, Contact),
191  M("contact:website", Website, Contact),
192  M("cuisine", Cuisine, Main),
193  M("diaper", DiaperChangingTable, UnresolvedCategory),
194  M("email", Email, Contact),
195  M("fee", Fee, UnresolvedCategory),
196  M("maxstay", MaxStay, Parking),
197  M("mx:realtime_available", AvailableVehicles, Main),
198  M("mx:remaining_range", RemainingRange, Main),
199  M("mx:vehicle", Category, Header),
200  M("network", Network, Operator),
201  M("office", Category, Header),
202  M("old_name", OldName, UnresolvedCategory),
203  M("opening_hours", OpeningHours, OpeningHoursCategory),
204  M("operator", OperatorName, Operator),
205  M("operator:email", Email, Contact),
206  M("operator:phone", Phone, Contact),
207  M("operator:website", Website, Contact),
208  M("operator:wikipedia", OperatorWikipedia, Operator),
209  M("parking:fee", Fee, Parking),
210  M("payment:cash", PaymentCash, Payment),
211  M("payment:coins", PaymentCash, Payment),
212  M("payment:notes", PaymentCash, Payment),
213  M("phone", Phone, Contact),
214  M("room", Category, Header),
215  M("route_ref", Routes, Main),
216  M("shop", Category, Header),
217  M("takeaway", Takeaway, Main),
218  M("toilets:fee", Fee, Toilets),
219  M("toilets:wheelchair", Wheelchair, Toilets),
220  M("tourism", Category, Header),
221  M("url", Website, Contact),
222  M("website", Website, Contact),
223  M("wheelchair", Wheelchair, Accessibility),
224 };
225 #undef M
226 static_assert(isSortedLookupTable(simple_key_map), "key map is not sorted!");
227 
228 template <typename KeyMapEntry, std::size_t N>
229 void OSMElementInformationModel::addEntryForKey(const char *keyName, const KeyMapEntry(&map)[N])
230 {
231  const auto it = std::lower_bound(std::begin(map), std::end(map), keyName, [](const auto &lhs, auto rhs) {
232  return std::strcmp(lhs.keyName, rhs) < 0;
233  });
234  if (it != std::end(map) && std::strcmp((*it).keyName, keyName) == 0) {
235  m_infos.push_back(Info{(*it).key(), (*it).category()});
236  }
237 }
238 
239 void OSMElementInformationModel::reload()
240 {
241  m_nameKey = NoKey;
242  m_categoryKey = NoKey;
243 
244  for (auto it = m_element.tagsBegin(); it != m_element.tagsEnd(); ++it) {
245  if (std::strncmp((*it).key.name(), "name", 4) == 0) {
246  m_infos.push_back(Info{Name, Header});
247  continue;
248  }
249  if (std::strncmp((*it).key.name(), "wikipedia", 9) == 0) {
250  m_infos.push_back(Info{Wikipedia, UnresolvedCategory});
251  continue;
252  }
253  addEntryForKey((*it).key.name(), simple_key_map);
254  addEntryForKey((*it).key.name(), payment_generic_type_map);
255  addEntryForKey((*it).key.name(), payment_type_map);
256  addEntryForKey((*it).key.name(), diet_type_map);
257  addEntryForKey((*it).key.name(), socket_type_map);
258  addEntryForKey((*it).key.name(), authentication_type_map);
259  }
260 
261  std::sort(m_infos.begin(), m_infos.end());
262  m_infos.erase(std::unique(m_infos.begin(), m_infos.end()), m_infos.end());
263  resolveCategories();
264  resolveHeaders();
265 
266  // if we don't have a primary group, promote a suitable secondary one
267  for (auto cat : {Parking, Toilets}) {
268  if (promoteMainCategory(cat)) {
269  break;
270  }
271  }
272 
273  // resolve all remaining unresolved elements to the primary category
274  for (auto &info : m_infos) {
275  if (info.category == UnresolvedCategory) {
276  info.category = Main;
277  }
278  }
279  std::sort(m_infos.begin(), m_infos.end());
280  m_infos.erase(std::unique(m_infos.begin(), m_infos.end()), m_infos.end());
281 
282  if (m_debug) {
283  m_infos.push_back(Info{ DebugLink, DebugCategory });
284  const auto count = std::distance(m_element.tagsBegin(), m_element.tagsEnd());
285  std::fill_n(std::back_inserter(m_infos), count, Info{ DebugKey, DebugCategory });
286  }
287 }
288 
289 void OSMElementInformationModel::resolveCategories()
290 {
291  if (m_infos.empty() || m_infos[0].category != UnresolvedCategory) {
292  return;
293  }
294  for (auto &info : m_infos) {
295  if (info.category != UnresolvedCategory) {
296  break;
297  }
298  switch (info.key) {
299  case Fee:
300  if (m_element.tagValue("parking:fee").isEmpty() && (!m_element.tagValue("parking").isEmpty()
301  || m_element.tagValue("amenity") == "parking" || m_element.tagValue("amenity") == "bicycle_parking"))
302  {
303  info.category = Parking;
304  } else if (m_element.tagValue("toilets:fee").isEmpty() && (m_element.tagValue("toilets") == "yes" || m_element.tagValue("amenity") == "toilets")) {
305  info.category = Toilets;
306  } else {
307  info.category = Main;
308  }
309  break;
310  case Capacity:
311  if (m_element.tagValue("amenity").endsWith("rental")) {
312  info.category = Main;
313  } else {
314  info.category = Parking;
315  }
316  break;
317  default:
318  {
319  // for anything else: if it's not clearly something we have a secondary group for, resolve it to Main
320  const auto amenity = m_element.tagValue("amenity");
321  if ((amenity != "parking" && amenity != "toilets")
322  || !m_element.tagValue("office").isEmpty()
323  || (!m_element.tagValue("room").isEmpty() && m_element.tagValue("room") != "toilets")
324  || !m_element.tagValue("shop").isEmpty()
325  || !m_element.tagValue("tourism").isEmpty()) {
326  info.category = Main;
327  }
328  break;
329  }
330  }
331  }
332  std::sort(m_infos.begin(), m_infos.end());
333 }
334 
335 void OSMElementInformationModel::resolveHeaders()
336 {
337  for (auto key : { Name, Network, OperatorName, Category }) {
338  if (m_nameKey != NoKey) {
339  break;
340  }
341 
342  const auto it = std::find_if(m_infos.begin(), m_infos.end(), [key](Info info) {
343  return info.key == key;
344  });
345  if (it == m_infos.end()) {
346  continue;
347  }
348 
349  m_nameKey = (*it).key;
350  m_infos.erase(it);
351  break;
352  }
353 
354  // we use the categories as header if there is no name, so don't duplicate that
355  const auto it = std::find_if(m_infos.begin(), m_infos.end(), [](Info info) {
356  return info.key == Category;
357  });
358  if (it == m_infos.end() || m_nameKey == Category) {
359  return;
360  }
361 
362  m_infos.erase(it);
363  m_categoryKey = Category;
364 }
365 
366 bool OSMElementInformationModel::promoteMainCategory(OSMElementInformationModel::KeyCategory cat)
367 {
368  const auto hasMain = std::any_of(m_infos.begin(), m_infos.end(), [](const auto &info) {
369  return info.category == Main;
370  });
371 
372  if (hasMain) {
373  return true;
374  }
375 
376  bool didPromote = false;
377  for (auto &info : m_infos) {
378  if (info.category == cat) {
379  info.category = (info.key == Wheelchair ? Accessibility : Main);
380  didPromote = true;
381  }
382  }
383 
384  if (didPromote) {
385  std::sort(m_infos.begin(), m_infos.end());
386  }
387  return didPromote;
388 }
389 
390 QString OSMElementInformationModel::categoryLabel(OSMElementInformationModel::KeyCategory cat) const
391 {
392  switch (cat) {
393  case UnresolvedCategory:
394  case Header:
395  case Main: return {};
396  case OpeningHoursCategory: return i18n("Opening Hours");
397  case Contact: return i18n("Contact");
398  case Payment: return i18n("Payment");
399  case Toilets: return i18n("Toilets");
400  case Accessibility: return i18n("Accessibility");
401  case Parking: return i18n("Parking");
402  case Operator: return i18n("Operator");
403  case DebugCategory: return QStringLiteral("Debug");
404  }
405  return {};
406 }
407 
408 QString OSMElementInformationModel::debugTagKey(int row) const
409 {
410  const auto tagCount = std::distance(m_element.tagsBegin(), m_element.tagsEnd());
411  const auto tagIdx = row - (rowCount() - tagCount);
412  return QString::fromUtf8((*(m_element.tagsBegin() + tagIdx)).key.name());
413 }
414 
415 QString OSMElementInformationModel::debugTagValue(int row) const
416 {
417  const auto tagCount = std::distance(m_element.tagsBegin(), m_element.tagsEnd());
418  const auto tagIdx = row - (rowCount() - tagCount);
419  return QString::fromUtf8((*(m_element.tagsBegin() + tagIdx)).value);
420 }
421 
422 QString OSMElementInformationModel::keyName(OSMElementInformationModel::Key key) const
423 {
424  switch (key) {
425  case NoKey:
426  case Name:
427  case Category: return {};
428  case OldName: return i18n("Formerly");
429  case Routes: return i18n("Routes");
430  case Cuisine: return i18n("Cuisine");
431  case Diet: return i18n("Diet");
432  case Takeaway: return i18n("Takeaway");
433  case Socket: return i18nc("electrical power socket", "Socket");
434  case OpeningHours: return {};
435  case AvailableVehicles: return i18n("Available vehicles");
436  case Fee: return i18n("Fee");
437  case Authentication: return i18n("Authentication");
438  case BicycleParking: return i18n("Bicycle parking");
439  case Capacity: return i18n("Capacity");
440  case CapacityDisabled: return i18n("Disabled parking spaces");
441  case CapacityWomen: return i18n("Women parking spaces");
442  case CapacityParent: return i18n("Parent parking spaces");
443  case CapacityCharing: return i18n("Parking spaces for charging");
444  case MaxStay: return i18n("Maximum stay");
445  case DiaperChangingTable: return i18n("Diaper changing table");
446  case Wikipedia: return {};
447  case Address: return i18n("Address");
448  case Phone: return i18n("Phone");
449  case Email: return i18n("Email");
450  case Website: return i18n("Website");
451  case PaymentCash: return i18n("Cash");
452  case PaymentDigital: return i18n("Digital");
453  case PaymentDebitCard: return i18n("Debit cards");
454  case PaymentCreditCard: return i18n("Credit cards");
455  case PaymentStoredValueCard: return i18n("Stored value cards");
456  case Wheelchair: return i18n("Wheelchair access");
457  case CentralKey: return i18n("Central key");
458  case OperatorName: return {};
459  case Network: return i18nc("transport network", "Network");
460  case OperatorWikipedia: return {};
461  case RemainingRange: return i18nc("remaining travel range of a battery powered vehicle", "Remaining range");
462  case DebugLink: return QStringLiteral("OSM");
463  case DebugKey: return {};
464  }
465  return {};
466 }
467 
468 QVariant OSMElementInformationModel::valueForKey(Info info) const
469 {
470  switch (info.key) {
471  case NoKey: return {};
472  case Name: return QString::fromUtf8(m_element.tagValue("name", "brand", QLocale()));
473  case Category:
474  {
476  l += m_element.tagValue("amenity").split(';');
477  l += m_element.tagValue("shop").split(';');
478  l += m_element.tagValue("tourism").split(';');
479  l += m_element.tagValue("vending").split(';');
480  l += m_element.tagValue("office").split(';');
481  l += m_element.tagValue("mx:vehicle");
482  if (l.isEmpty()) {
483  l += m_element.tagValue("room").split(';');
484  }
485  QStringList out;
486  out.reserve(l.size());
487 
488  // TODO drop general categories if specific ones are available (e.g. restaurant vs fast_food)
489 
490  for (auto it = l.begin(); it != l.end();++it) {
491  (*it) = (*it).trimmed();
492  if ((*it).isEmpty() || (*it) == "yes" || (*it) == "vending_machine") {
493  continue;
494  }
495  out.push_back(translateValue((*it).constData(), amenity_map, "OSM::amenity/shop"));
496  }
497 
498  std::sort(out.begin(), out.end());
499  out.erase(std::unique(out.begin(), out.end()), out.end());
500  return QLocale().createSeparatedList(out);
501  }
502  case OldName:
503  {
504  const auto l = QString::fromUtf8(m_element.tagValue("old_name")).split(QLatin1Char(';'));
505  return l.join(QLatin1String(", "));
506  }
507  case Routes:
508  {
509  auto l = QString::fromUtf8(m_element.tagValue("route_ref", "bus_routes", "buses")).split(QLatin1Char(';'), Qt::SkipEmptyParts);
510  for (auto &s : l) {
511  s = s.trimmed();
512  }
513  return QLocale().createSeparatedList(l);
514  }
515  case Cuisine: return translateValues(m_element.tagValue("cuisine"), cuisine_map, "OSM::cuisine");
516  case Diet:
517  {
518  QStringList l;
519  for (const auto &d : diet_type_map) {
520  const auto v = m_element.tagValue(d.keyName);
521  const auto label = i18nc("OSM::diet_type", d.label);
522  if (v == "yes") {
523  l.push_back(label);
524  } else if (v == "only") {
525  l.push_back(i18n("only %1", label));
526  } else if (v == "no") {
527  l.push_back(i18n("no %1", label));
528  }
529  }
530  return l.join(QLatin1String(", "));
531  }
532  case Takeaway: return QString::fromUtf8(m_element.tagValue("takeaway")); // TODO decode (yes/only/no) and translate
533  case Socket:
534  {
535  QStringList l;
536  for (const auto &socket : socket_type_map) {
537  const auto value = m_element.tagValue(socket.keyName);
538  if (value.isEmpty() || value == "no") {
539  continue;
540  }
541 
542  auto s = i18nc("OSM::charging_station_socket", socket.label);
543 
544  QStringList details;
545  if (value != "yes") {
546  details.push_back(QString::fromUtf8(value));
547  }
548 
549  const auto current = m_element.tagValue(QByteArray(socket.keyName + QByteArray(":current")).constData());
550  if (!current.isEmpty()) {
551  if (std::all_of(current.begin(), current.end(), [](char c) { return std::isdigit(c); })) {
552  details.push_back(i18nc("electrical current/Ampere value", "%1 A", QString::fromUtf8(current)));
553  } else {
554  details.push_back(QString::fromUtf8(current));
555  }
556  }
557  const auto output = m_element.tagValue(QByteArray(socket.keyName + QByteArray(":output")).constData());
558  if (!output.isEmpty()) {
559  if (std::all_of(output.begin(), output.end(), [](char c) { return std::isdigit(c); })) {
560  details.push_back(i18nc("electrical power/kilowatt value", "%1 kW", QString::fromUtf8(output)));
561  } else {
562  details.push_back(QString::fromUtf8(output));
563  }
564  }
565 
566  if (!details.empty()) {
567  s += QLatin1String(" (") + details.join(QLatin1String(", ")) + QLatin1Char(')');
568  }
569  l.push_back(s);
570  }
571  return QLocale().createSeparatedList(l);
572  }
573  case OpeningHours: return QString::fromUtf8(m_element.tagValue("opening_hours"));
574  case AvailableVehicles:
575  {
576  const auto total = m_element.tagValue("mx:realtime_available").toInt();
578  // there's no I18N_NOOP plural variants, so we have to use this more expensive approach
579  struct {
580  const char *tagName;
581  KLocalizedString msg;
582  } static const available_vehicles_map[] = {
583  { "mx:realtime_available:bike", ki18ncp("available rental vehicles", "%1 bike", "%1 bikes") },
584  { "mx:realtime_available:pedelec", ki18ncp("available rental vehicles", "%1 pedelec", "%1 pedelecs") },
585  { "mx:realtime_available:scooter", ki18ncp("available rental vehicles", "%1 kick scooter", "%1 kick scooters") },
586  { "mx:realtime_available:motorcycle", ki18ncp("available rental vehicles", "%1 moped", "%1 mopeds") },
587  { "mx:realtime_available:car", ki18ncp("available rental vehicles", "%1 car", "%1 cars") },
588  };
589  for (const auto &v : available_vehicles_map) {
590  const auto b = m_element.tagValue(v.tagName);
591  if (b.isEmpty()) {
592  continue;
593  }
594  types.push_back(v.msg.subs(b.toInt()).toString());
595  }
596 
597  if (types.isEmpty()) {
598  return QString::number(total);
599  } else if (types.size() == 1) {
600  return types.at(0);
601  } else {
602  return i18n("%1 (%2)", QString::number(total), QLocale().createSeparatedList(types));
603  }
604  }
605  case Fee:
606  {
607  QByteArray fee;
608  switch (info.category) {
609  case Parking: fee = m_element.tagValue("parking:fee", "fee"); break;
610  case Toilets: fee = m_element.tagValue("toilets:fee", "fee"); break;
611  default: fee = m_element.tagValue("fee");
612  }
613  auto s = QString::fromUtf8(fee);
614  const auto charge = QString::fromUtf8(m_element.tagValue("charge"));
615  if (s.isEmpty()) {
616  return charge;
617  }
618  if (!charge.isEmpty()) {
619  s += QLatin1String(" (") + charge + QLatin1Char(')');
620  }
621  return s;
622  }
623  case Authentication:
624  {
625  QStringList l;
626  for (const auto &auth : authentication_type_map) {
627  const auto v = m_element.tagValue(auth.keyName);
628  if (v.isEmpty() || v == "no") {
629  continue;
630  }
631  l.push_back(i18nc("OSM::charging_station_authentication", auth.label));
632  }
633  return QLocale().createSeparatedList(l);
634  }
635  case BicycleParking: return translateValues(m_element.tagValue("bicycle_parking"), bicycle_parking_map, "OSM::bicycle_parking");
636  case Capacity: return QString::fromUtf8(m_element.tagValue("capacity"));
637  case CapacityDisabled: return capacitryValue("capacity:disabled");
638  case CapacityWomen: return capacitryValue("capacity:women");
639  case CapacityParent: return capacitryValue("capacity:parent");
640  case CapacityCharing: return capacitryValue("capacity:charging");
641  case MaxStay: return QString::fromUtf8(m_element.tagValue("maxstay"));
642  case DiaperChangingTable:
643  // TODO bool value translation
644  // TODO look for changing_table:location too
645  return QString::fromUtf8(m_element.tagValue("changing_table", "diaper"));
646  case Wikipedia: return wikipediaUrl(m_element.tagValue("wikipedia", "brand:wikipedia", QLocale()));
647  case Address: return QVariant::fromValue(OSMAddress(m_element));
648  case Phone: return QString::fromUtf8(m_element.tagValue("contact:phone", "phone", "telephone", "operator:phone"));
649  case Email: return QString::fromUtf8(m_element.tagValue("contact:email", "email", "operator:email"));
650  case Website: return QString::fromUtf8(m_element.tagValue("website", "contact:website", "url", "operator:website"));
651  case PaymentCash:
652  {
653  const auto coins = m_element.tagValue("payment:coins");
654  const auto notes = m_element.tagValue("payment:notes");
655  if (coins.isEmpty() && notes.isEmpty()) {
656  return m_element.tagValue("payment:cash"); // TODO decode bool
657  }
658  if (coins == "yes" && notes == "yes") {
659  return i18n("yes");
660  }
661  if (coins == "yes") {
662  return i18nc("payment option", "coins only");
663  }
664  if (notes == "yes") {
665  return i18nc("payment option", "notes only");
666  }
667  return i18n("no");
668  }
669  case PaymentDigital:
670  case PaymentDebitCard:
671  case PaymentCreditCard:
672  case PaymentStoredValueCard:
673  return paymentMethodValue(info.key);
674  case Wheelchair:
675  {
676  QByteArray wheelchair;
677  if (info.category == Toilets) {
678  wheelchair = m_element.tagValue("toilets:wheelchair", "wheelchair");
679  } else {
680  wheelchair = m_element.tagValue("wheelchair");
681  }
682  const auto a = translateValue(wheelchair.constData(), wheelchair_map, "OSM::wheelchair_access");
683  const auto d = QString::fromUtf8(m_element.tagValue("wheelchair:description", QLocale()));
684  if (!d.isEmpty()) {
685  return QString(a + QLatin1String(" (") + d + QLatin1Char(')'));
686  }
687  return a;
688  }
689  case CentralKey:
690  // translate enum values
691  return QString::fromUtf8(m_element.tagValue("centralkey"));
692  case OperatorName: return QString::fromUtf8(m_element.tagValue("operator"));
693  case Network: return QString::fromUtf8(m_element.tagValue("network"));
694  case OperatorWikipedia: return wikipediaUrl(m_element.tagValue("operator:wikipedia", QLocale()));
695  case RemainingRange:
696  {
697  const auto range = m_element.tagValue("mx:remaining_range").toInt();
698  return formatDistance(range);
699  }
700  case DebugLink: return m_element.url();
701  case DebugKey: return {};
702  }
703  return {};
704 }
705 
706 QVariant OSMElementInformationModel::urlify(const QVariant& v, OSMElementInformationModel::Key key) const
707 {
708  if (v.type() != QVariant::String) {
709  return v;
710  }
711  const auto s = v.toString();
712 
713  switch (key) {
714  case Email:
715  if (!s.startsWith(QLatin1String("mailto:"))) {
716  return QString(QLatin1String("mailto:") + s);
717  }
718  return s;
719  case Phone:
720  {
721  if (s.startsWith(QLatin1String("tel:"))) {
722  return s;
723  }
724  QString e = QLatin1String("tel:") + s;
725  e.remove(QLatin1Char(' '));
726  return e;
727  }
728  case Website:
729  case DebugLink:
730  if (s.startsWith(QLatin1String("http"))) {
731  return s;
732  }
733  return QString(QLatin1String("https://") + s);
734  default:
735  return {};
736  }
737 
738  return {};
739 }
740 
741 QString OSMElementInformationModel::paymentMethodList(OSMElementInformationModel::Key key) const
742 {
743  QStringList l;
744  for (const auto &payment : payment_type_map) {
745  if (payment.key() != key) {
746  continue;
747  }
748  if (m_element.tagValue(payment.keyName) == "yes") {
749  l.push_back(i18nc("OSM::payment_method", payment.label));
750  }
751  }
752  std::sort(l.begin(), l.end());
753  return QLocale().createSeparatedList(l);
754 }
755 
756 QString OSMElementInformationModel::paymentMethodValue(OSMElementInformationModel::Key key) const
757 {
758  const auto s = paymentMethodList(key);
759  if (!s.isEmpty()) {
760  return s;
761  }
762 
763  for (const auto &payment : payment_generic_type_map) {
764  if (payment.key() != key) {
765  continue;
766  }
767  const auto s = m_element.tagValue(payment.keyName);
768  if (!s.isEmpty()) {
769  return QString::fromUtf8(s);
770  }
771  }
772  return {};
773 }
774 
775 QUrl OSMElementInformationModel::wikipediaUrl(const QByteArray &wp) const
776 {
777  if (wp.isEmpty()) {
778  return {};
779  }
780 
781  const auto s = QString::fromUtf8(wp);
782  const auto idx = s.indexOf(QLatin1Char(':'));
783  if (idx < 0) {
784  return {};
785  }
786 
787  QUrl url;
788  url.setScheme(QStringLiteral("https"));
789  url.setHost(QStringView(s).left(idx) + QLatin1String(".wikipedia.org"));
790  url.setPath(QLatin1String("/wiki/") + QStringView(s).mid(idx + 1));
791  return url;
792 }
793 
794 QString OSMElementInformationModel::capacitryValue(const char *prop) const
795 {
796  const auto v = m_element.tagValue(prop);
797  if (v == "yes") {
798  return i18n("yes");
799  }
800  if (v == "no") {
801  return i18n("no");
802  }
803  return QString::fromUtf8(v);
804 }
OSM-based multi-floor indoor maps for buildings.
AKONADI_MIME_EXPORT const char Header[]
QML wrapper around an OSM element.
Definition: osmelement.h:18
int toInt(bool *ok, int base) const const
QList< QByteArray > split(char sep) const const
void push_back(const T &value)
void reserve(int alloc)
virtual QHash< int, QByteArray > roleNames() const const
const T & at(int i) const const
bool isEmpty() const const
QList::iterator erase(QList::iterator pos)
QString join(const QString &separator) const const
QString & remove(int position, int n)
int size() const const
const QList< QKeySequence > & reload()
QTextStream & left(QTextStream &stream)
void setPath(const QString &path, QUrl::ParsingMode mode)
bool isValid() const const
QString number(int n, int base)
QString fromUtf8(const char *str, int size)
bool empty() const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
bool isEmpty() const const
QString trimmed() const const
const char * constData() const const
int row() const const
void setScheme(const QString &scheme)
Postal address from OSM data.
Definition: osmaddress.h:15
QString createSeparatedList(const QStringList &list) const const
QList::iterator end()
SkipEmptyParts
Email
QVariant fromValue(const T &value)
QString i18n(const char *text, const TYPE &arg...)
Category category(StandardShortcut id)
char * toString(const T &value)
KLocalizedString KI18N_EXPORT ki18ncp(const char *context, const char *singular, const char *plural)
void setHost(const QString &host, QUrl::ParsingMode mode)
QVariant::Type type() const const
QString toString() const const
QList::iterator begin()
bool endsWith(const QByteArray &ba) const const
Types types(const QStringList &mimeTypes)
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Oct 25 2021 23:04:00 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.