7#include "osmelementinformationmodel.h" 
    8#include "osmelementinformationmodel_data.cpp" 
   10#include "localization.h" 
   11#include "osmaddress.h" 
   13#include <wikidata/wikidataquery.h> 
   15#include <KLocalizedString> 
   24[[nodiscard]] 
static QString formatDistance(
int meter)
 
   27        return i18n(
"%1m", meter);
 
   30        return i18n(
"%1km", ((
int)meter/100)/10.0);
 
   32    return i18n(
"%1km", (
int)qRound(meter/1000.0));
 
   35bool OSMElementInformationModel::Info::operator<(OSMElementInformationModel::Info other)
 const 
   37    if (category == other.category) {
 
   38        return key < other.key;
 
   40    return category < other.category;
 
   43bool OSMElementInformationModel::Info::operator==(OSMElementInformationModel::Info other)
 const 
   45    return category == other.category && key == other.key;
 
   49OSMElementInformationModel::OSMElementInformationModel(
QObject *
parent)
 
   51    , m_langs(OSM::Languages::fromQLocale(QLocale()))
 
   53    m_wikidataMgr.setUserAgentEmailAddress(u
"kde-pim@kde.org"_s);
 
   56OSMElementInformationModel::~OSMElementInformationModel() = 
default;
 
   58OSMElement OSMElementInformationModel::element()
 const 
   60    return OSMElement(m_element);
 
   63void OSMElementInformationModel::setElement(
const OSMElement &element)
 
   65    if (m_element == element.element()) {
 
   70    m_element = element.element();
 
   72    if (m_element.type() != OSM::Type::Null) {
 
   79void OSMElementInformationModel::clear()
 
   81    if (m_element.type() == OSM::Type::Null) {
 
   91QString OSMElementInformationModel::name()
 const 
   93    return valueForKey(Info{m_nameKey, Header}).
toString();
 
   96QString OSMElementInformationModel::category()
 const 
   98    return valueForKey(Info{m_categoryKey, Header}).
toString();
 
  101int OSMElementInformationModel::rowCount(
const QModelIndex &parent)
 const 
  103    if (
parent.isValid() || m_element.type() == OSM::Type::Null) {
 
  106    return (
int)m_infos.size();
 
  109QVariant OSMElementInformationModel::data(
const QModelIndex &index, 
int role)
 const 
  111    if (!
index.isValid()) {
 
  115    const auto info = m_infos[
index.row()];
 
  123                case OperatorWikipedia:
 
  127                    return PostalAddress;
 
  129                    return OpeningHoursType;
 
  134                    return debugTagUrl(
index.row()).isValid() ? Link : String;
 
  141            if (info.key == DebugKey) {
 
  142                return debugTagKey(
index.row());
 
  144            return keyName(info.key);
 
  147                case DebugKey: 
return debugTagValue(
index.row());
 
  148                case Wikipedia: 
return i18n(
"Wikipedia");
 
  149                default: 
return valueForKey(info);
 
  152            if (info.key == DebugKey) {
 
  153                return debugTagUrl(
index.row());
 
  155            return urlify(valueForKey(info), info.key);
 
  157            return info.category;
 
  158        case CategoryLabelRole:
 
  159            return categoryLabel(info.category);
 
  165QHash<int, QByteArray> OSMElementInformationModel::roleNames()
 const 
  168    r.insert(KeyRole, 
"key");
 
  169    r.insert(KeyLabelRole, 
"keyLabel");
 
  170    r.insert(ValueRole, 
"value");
 
  171    r.insert(ValueUrlRole, 
"url");
 
  172    r.insert(CategoryRole, 
"category");
 
  173    r.insert(CategoryLabelRole, 
"categoryLabel");
 
  174    r.insert(TypeRole, 
"type");
 
  178#define M(name, key, category) { name, OSMElementInformationModel::key, OSMElementInformationModel::category } 
  179struct KeyCategoryMapEntry {
 
  181    OSMElementInformationModel::Key m_key;
 
  182    OSMElementInformationModel::KeyCategory m_category;
 
  184    [[nodiscard]] 
constexpr inline OSMElementInformationModel::Key key()
 const { 
return m_key; }
 
  185    [[nodiscard]] 
constexpr inline OSMElementInformationModel::KeyCategory 
category()
 const { 
return m_category; }
 
  188static constexpr const KeyCategoryMapEntry simple_key_map[] = {
 
  189    M(
"addr:city", Address, Contact),
 
  190    M(
"addr:street", Address, Contact),
 
  191    M(
"amenity", Category, Header),
 
  192    M(
"bicycle_parking", BicycleParking, Parking),
 
  193    M(
"brand", Name, Header),
 
  194    M(
"brand:wikidata", 
Image, Main),
 
  195    M(
"brand:wikipedia", Wikipedia, UnresolvedCategory),
 
  196    M(
"building", Category, Header),
 
  197    M(
"bus_lines", Routes, Main),
 
  198    M(
"bus_routes", Routes, Main),
 
  199    M(
"buses", Routes, Main),
 
  200    M(
"capacity", Capacity, UnresolvedCategory),
 
  201    M(
"capacity:charging", CapacityCharing, Parking),
 
  202    M(
"capacity:disabled", CapacityDisabled, Parking),
 
  203    M(
"capacity:parent", CapacityParent, Parking),
 
  204    M(
"capacity:women", CapacityWomen, Parking),
 
  205    M(
"centralkey", CentralKey, Accessibility),
 
  206    M(
"changing_table", DiaperChangingTable, UnresolvedCategory),
 
  207    M(
"charge", Fee, UnresolvedCategory),
 
  208    M(
"contact:city", Address, Contact),
 
  209    M(
"contact:email", Email, Contact),
 
  210    M(
"contact:phone", Phone, Contact),
 
  211    M(
"contact:street", Address, Contact),
 
  212    M(
"contact:website", Website, Contact),
 
  213    M(
"cuisine", Cuisine, Main),
 
  214    M(
"description", Description, Main),
 
  215    M(
"diaper", DiaperChangingTable, UnresolvedCategory),
 
  216    M(
"diplomatic", Category, Header),
 
  217    M(
"email", Email, Contact),
 
  218    M(
"fee", Fee, UnresolvedCategory),
 
  219    M(
"genus", Name, Header),
 
  220    M(
"genus:wikidata", 
Image, Main),
 
  221    M(
"historic", Category, Header),
 
  222    M(
"image", 
Image, Main),
 
  223    M(
"int_name", Name, Header),
 
  224    M(
"leisure", Category, Header),
 
  225    M(
"maxstay", MaxStay, Parking),
 
  226    M(
"memorial:text", Description, Main),
 
  227    M(
"mx:realtime_available", AvailableVehicles, Main),
 
  228    M(
"mx:remaining_range", RemainingRange, Main),
 
  229    M(
"mx:vehicle", Category, Header),
 
  230    M(
"network", Network, Operator),
 
  231    M(
"network:wikidata", 
Image, Main),
 
  232    M(
"network:wikipedia", OperatorWikipedia, Operator),
 
  233    M(
"office", Category, Header),
 
  234    M(
"old_name", OldName, UnresolvedCategory),
 
  235    M(
"opening_hours", OpeningHours, OpeningHoursCategory),
 
  236    M(
"operator", OperatorName, Operator),
 
  237    M(
"operator:email", Email, Contact),
 
  238    M(
"operator:phone", Phone, Contact),
 
  239    M(
"operator:website", Website, Contact),
 
  240    M(
"operator:wikidata", 
Image, Main),
 
  241    M(
"operator:wikipedia", OperatorWikipedia, Operator),
 
  242    M(
"parking:fee", Fee, Parking),
 
  243    M(
"payment:cash", PaymentCash, Payment),
 
  244    M(
"payment:coins", PaymentCash, Payment),
 
  245    M(
"payment:notes", PaymentCash, Payment),
 
  246    M(
"phone", Phone, Contact),
 
  247    M(
"room", Category, Header),
 
  248    M(
"route_ref", Routes, Main),
 
  249    M(
"shop", Category, Header),
 
  250    M(
"species:wikidata", 
Image, Main),
 
  251    M(
"tactile_writing", TactileWriting, Accessibility), 
 
  252    M(
"takeaway", Takeaway, Main),
 
  253    M(
"toilets:fee", Fee, Toilets),
 
  254    M(
"toilets:wheelchair", Wheelchair, Toilets),
 
  255    M(
"tourism", Category, Header),
 
  256    M(
"url", Website, Contact),
 
  257    M(
"vending", VendingMachineOffer, Header),
 
  258    M(
"website", Website, Contact),
 
  259    M(
"wheelchair", Wheelchair, Accessibility),
 
  260    M(
"wheelchair:lift", WheelchairLift, Accessibility),
 
  261    M(
"wikidata", 
Image, Main),
 
  262    M(
"wikimedia_commons", 
Image, Main),
 
  264static_assert(isSortedLookupTable(simple_key_map), 
"key map is not sorted!");
 
  266static constexpr const KeyCategoryMapEntry localized_key_map[] = {
 
  267    M(
"name", Name, Header),
 
  268    M(
"loc_name", Name, Header),
 
  269    M(
"species", Name, Header),
 
  270    M(
"species:wikipedia", Wikipedia, UnresolvedCategory),
 
  271    M(
"speech_output", SpeechOutput, Accessibility),
 
  272    M(
"wikipedia", Wikipedia, UnresolvedCategory),
 
  276template <
typename KeyMapEntry, std::
size_t N>
 
  277void OSMElementInformationModel::addEntryForKey(
const char *keyName, 
const KeyMapEntry(&map)[N])
 
  279    const auto it = std::lower_bound(std::begin(map), std::end(map), keyName, [](
const auto &lhs, 
auto rhs) {
 
  280        return std::strcmp(lhs.keyName, rhs) < 0;
 
  282    if (it != std::end(map) && std::strcmp((*it).keyName, keyName) == 0) {
 
  283        m_infos.push_back(Info{(*it).key(), (*it).category()});
 
  287template <
typename KeyMapEntry, std::
size_t N>
 
  288void OSMElementInformationModel::addEntryForLocalizedKey(
const char *keyName, 
const KeyMapEntry(&map)[N])
 
  290    for (
const auto &entry : map) {
 
  291        const auto mapKeyLen = std::strlen(entry.keyName);
 
  292        if (std::strncmp(keyName, entry.keyName, mapKeyLen) != 0) {
 
  295        const auto keyNameLen = std::strlen(keyName);
 
  296        if (keyNameLen == mapKeyLen || (keyNameLen == mapKeyLen + 3 && keyName[mapKeyLen] == 
':')) {
 
  297            m_infos.push_back(Info{entry.key(), entry.category()});
 
  303void OSMElementInformationModel::reload()
 
  306    m_categoryKey = NoKey;
 
  308    const bool isRoom = m_element.tagValue(
"indoor") == 
"room";
 
  309    for (
auto it = m_element.tagsBegin(); it != m_element.tagsEnd(); ++it) {
 
  310        addEntryForLocalizedKey((*it).key.name(), localized_key_map);
 
  311        addEntryForKey((*it).key.name(), simple_key_map);
 
  312        addEntryForKey((*it).key.name(), payment_generic_type_map);
 
  313        addEntryForKey((*it).key.name(), payment_type_map);
 
  314        addEntryForKey((*it).key.name(), diet_type_map);
 
  315        addEntryForKey((*it).key.name(), socket_type_map);
 
  316        addEntryForKey((*it).key.name(), authentication_type_map);
 
  317        addEntryForLocalizedKey((*it).key.name(), tactile_writing_map);
 
  319        if (isRoom && std::strcmp((*it).key.name(), 
"ref") == 0) {
 
  320            m_infos.push_back(Info{Name, Header});
 
  324         m_infos.emplace_back(Gender, UnresolvedCategory);
 
  327    std::sort(m_infos.begin(), m_infos.end());
 
  328    m_infos.erase(std::unique(m_infos.begin(), m_infos.end()), m_infos.end());
 
  329    resolveOnlineContent();
 
  334    for (
auto cat : {Parking, Toilets}) {
 
  335        if (promoteMainCategory(cat)) {
 
  341    for (
auto &info : m_infos) {
 
  342        if (info.category == UnresolvedCategory) {
 
  343            info.category = Main;
 
  346    std::sort(m_infos.begin(), m_infos.end());
 
  347    m_infos.erase(std::unique(m_infos.begin(), m_infos.end()), m_infos.end());
 
  350        m_infos.push_back(Info{ DebugLink, DebugCategory });
 
  351        const auto count = std::distance(m_element.tagsBegin(), m_element.tagsEnd());
 
  352        std::fill_n(std::back_inserter(m_infos), count, Info{ DebugKey, DebugCategory });
 
  356void OSMElementInformationModel::resolveOnlineContent()
 
  358    if (!m_allowOnlineContent) {
 
  359        m_infos.erase(std::remove_if(m_infos.begin(), m_infos.end(), [](
const auto &info) {
 
  360            return info.key == Image || info.key == Logo;
 
  365    const auto commons = m_element.tagValue(
"wikimedia_commons");
 
  366    const auto hasValidCommons = commons.startsWith(
"File:");
 
  367    const auto image = m_element.tagValue(
"image");
 
  368    const auto hasValidImage = image.contains(
"://commons.wikimedia.org/");
 
  369    const auto wdId = m_element.tagValue(
"wikidata", 
"species:wikidata", 
"genus:wikidata", 
"subject:wikidata", 
"operator:wikidata", 
"network:wikidata", 
"brand:wikidata");
 
  372    if (!hasValidCommons && !hasValidImage && !wdId.isEmpty()) {
 
  373        auto query = 
new Wikidata::EntitiesQuery(
this);
 
  374        query->setItems({Wikidata::Q{wdId}});
 
  375        connect(query, &Wikidata::EntitiesQuery::finished, 
this, [query, 
this]() {
 
  376            query->deleteLater();
 
  377            auto res = 
query->takeResult();
 
  378            for (
const auto &item : res) {
 
  379                std::vector<Wikidata::P> props({Wikidata::P::image, Wikidata::P::imageOfInterior, Wikidata::P::aerialView, Wikidata::P::view, Wikidata::P::modelImage});
 
  382                    props.insert(props.begin(), Wikidata::P::nighttimeView);
 
  384                    props.emplace_back(Wikidata::P::nighttimeView);
 
  388                    props.insert(props.begin(), Wikidata::P::winterView);
 
  390                    props.emplace_back(Wikidata::P::nighttimeView);
 
  394                if (item.id() == Wikidata::Q(m_element.tagValue(
"operator:wikidata", 
"network:wikidata", 
"brand:wikidata"))) {
 
  395                    props = {Wikidata::P::logoImage};
 
  398                    props.emplace_back(Wikidata::P::logoImage);
 
  401                for (
const auto p : props) {
 
  402                    const auto img = item.value<QString>(p);
 
  406                    m_wikidataImageMap.insert(item.id(), img);
 
  407                    const auto it = std::find_if(m_infos.begin(), m_infos.end(), [](
const auto &info) { return info.key == Image; });
 
  411                    const auto idx = 
index((
int)std::distance(m_infos.begin(), it), 0);
 
  417        m_wikidataMgr.execute(query);
 
  420    if (!hasValidCommons && !hasValidImage && wdId.isEmpty()) {
 
  421        m_infos.erase(std::remove_if(m_infos.begin(), m_infos.end(), [](
const auto &info) { return info.key == Image; }), m_infos.end());
 
  425void OSMElementInformationModel::resolveCategories()
 
  427    if (m_infos.empty() || m_infos[0].category != UnresolvedCategory) {
 
  430    for (
auto &info : m_infos) {
 
  431        if (info.category != UnresolvedCategory) {
 
  436                if (m_element.tagValue(
"parking:fee").isEmpty() && (!m_element.tagValue(
"parking").isEmpty()
 
  437                    || m_element.tagValue(
"amenity") == 
"parking" || m_element.tagValue(
"amenity") == 
"bicycle_parking"))
 
  439                    info.category = Parking;
 
  440                } 
else if (m_element.tagValue(
"toilets:fee").isEmpty() && (m_element.tagValue(
"toilets") == 
"yes" || m_element.tagValue(
"amenity") == 
"toilets")) {
 
  441                    info.category = Toilets;
 
  443                    info.category = Main;
 
  447                if (m_element.tagValue(
"amenity").endsWith(
"rental")) {
 
  448                    info.category = Main;
 
  450                    info.category = Parking;
 
  456                const auto amenity = m_element.tagValue(
"amenity");
 
  457                if ((amenity != 
"parking" && amenity != 
"toilets")
 
  458                    || !m_element.tagValue(
"office").isEmpty()
 
  459                    || (!m_element.tagValue(
"room").isEmpty() && m_element.tagValue(
"room") != 
"toilets")
 
  460                    || !m_element.tagValue(
"shop").isEmpty()
 
  461                    || !m_element.tagValue(
"tourism").isEmpty()) {
 
  462                    info.category = Main;
 
  468    std::sort(m_infos.begin(), m_infos.end());
 
  471void OSMElementInformationModel::resolveHeaders()
 
  474    for (
auto key : { Name, VendingMachineOffer, Network, OperatorName, Category }) {
 
  475        if (m_nameKey != NoKey) {
 
  479        const auto it = std::find_if(m_infos.begin(), m_infos.end(), [key](Info info) {
 
  480            return info.key == key;
 
  482        if (it == m_infos.end()) {
 
  486        m_nameKey = (*it).key;
 
  492    for (
auto key : { VendingMachineOffer, Category }) {
 
  493        const auto it = std::find_if(m_infos.begin(), m_infos.end(), [key](Info info) {
 
  494            return info.key == key;
 
  496        if (it == m_infos.end()) {
 
  500        if (m_categoryKey == NoKey && m_nameKey != key) {
 
  501            m_categoryKey = (*it).key;
 
  508bool OSMElementInformationModel::promoteMainCategory(OSMElementInformationModel::KeyCategory cat)
 
  510    const auto hasMain = std::any_of(m_infos.begin(), m_infos.end(), [](
const auto &info) {
 
  511        return info.category == Main;
 
  518    bool didPromote = 
false;
 
  519    for (
auto &info : m_infos) {
 
  520        if (info.category == cat) {
 
  521            info.category = (info.key == Wheelchair ? Accessibility : Main);
 
  527        std::sort(m_infos.begin(), m_infos.end());
 
  532QString OSMElementInformationModel::categoryLabel(OSMElementInformationModel::KeyCategory cat)
 const 
  535        case UnresolvedCategory:
 
  537        case Main:          
return {};
 
  538        case OpeningHoursCategory: 
return i18n(
"Opening Hours");
 
  539        case Contact:       
return i18n(
"Contact");
 
  540        case Payment:       
return i18n(
"Payment");
 
  541        case Toilets:       
return i18n(
"Toilets");
 
  542        case Accessibility: 
return i18n(
"Accessibility");
 
  543        case Parking:       
return i18n(
"Parking");
 
  544        case Operator:      
return i18n(
"Operator");
 
  545        case DebugCategory: 
return QStringLiteral(
"Debug");
 
  550QString OSMElementInformationModel::debugTagKey(
int row)
 const 
  552    const auto tagCount = std::distance(m_element.tagsBegin(), m_element.tagsEnd());
 
  553    const auto tagIdx = row - (rowCount() - tagCount);
 
  557QString OSMElementInformationModel::debugTagValue(
int row)
 const 
  559    const auto tagCount = std::distance(m_element.tagsBegin(), m_element.tagsEnd());
 
  560    const auto tagIdx = row - (rowCount() - tagCount);
 
  564QUrl OSMElementInformationModel::debugTagUrl(
int row)
 const 
  566    const auto tagCount = std::distance(m_element.tagsBegin(), m_element.tagsEnd());
 
  567    const auto tagIdx = row - (rowCount() - tagCount);
 
  568    const auto key = QByteArrayView((*(m_element.tagsBegin() + tagIdx)).key.name());
 
  569    const auto value = (*(m_element.tagsBegin() + tagIdx)).value;
 
  570    if (key.endsWith(
":wikipedia") || key == 
"wikipedia") {
 
  571        return wikipediaUrl(value);
 
  573    if (key.endsWith(
":wikidata") || key == 
"wikidata") {
 
  576    if (value.startsWith(
"http"_L1)) {
 
  582QString OSMElementInformationModel::keyName(OSMElementInformationModel::Key key)
 const 
  588        case VendingMachineOffer:
 
  592        case OldName: 
return i18n(
"Formerly");
 
  593        case Description: 
return i18n(
"Description");
 
  594        case Routes: 
return i18n(
"Routes");
 
  595        case Cuisine: 
return i18n(
"Cuisine");
 
  596        case Diet: 
return i18n(
"Diet");
 
  597        case Takeaway: 
return i18n(
"Takeaway");
 
  598        case Socket: 
return i18nc(
"electrical power socket", 
"Socket");
 
  599        case OpeningHours: 
return {};
 
  600        case AvailableVehicles: 
return i18n(
"Available vehicles");
 
  601        case Fee: 
return i18n(
"Fee");
 
  602        case Authentication: 
return i18n(
"Authentication");
 
  603        case BicycleParking: 
return i18n(
"Bicycle parking");
 
  604        case Capacity: 
return i18n(
"Capacity");
 
  605        case CapacityDisabled: 
return i18n(
"Disabled parking spaces");
 
  606        case CapacityWomen: 
return i18n(
"Women parking spaces");
 
  607        case CapacityParent: 
return i18n(
"Parent parking spaces");
 
  608        case CapacityCharing: 
return i18n(
"Parking spaces for charging");
 
  609        case MaxStay: 
return i18n(
"Maximum stay");
 
  610        case DiaperChangingTable: 
return i18n(
"Diaper changing table");
 
  611        case Gender: 
return i18n(
"Gender");
 
  612        case Wikipedia: 
return {};
 
  613        case Address: 
return i18n(
"Address");
 
  614        case Phone: 
return i18n(
"Phone");
 
  615        case Email: 
return i18n(
"Email");
 
  616        case Website: 
return i18n(
"Website");
 
  617        case PaymentCash: 
return i18n(
"Cash");
 
  618        case PaymentDigital: 
return i18n(
"Digital");
 
  619        case PaymentDebitCard: 
return i18n(
"Debit cards");
 
  620        case PaymentCreditCard: 
return i18n(
"Credit cards");
 
  621        case PaymentStoredValueCard: 
return i18n(
"Stored value cards");
 
  622        case Wheelchair: 
return i18n(
"Wheelchair access");
 
  623        case WheelchairLift: 
return i18n(
"Wheelchair lift");
 
  624        case CentralKey: 
return i18n(
"Central key");
 
  625        case SpeechOutput: 
return i18n(
"Speech output");
 
  626        case TactileWriting: 
return i18n(
"Tactile writing");
 
  627        case OperatorName: 
return {};
 
  628        case Network: 
return i18nc(
"transport network", 
"Network");
 
  629        case OperatorWikipedia: 
return {};
 
  630        case RemainingRange: 
return i18nc(
"remaining travel range of a battery powered vehicle", 
"Remaining range");
 
  631        case DebugLink: 
return QStringLiteral(
"OSM");
 
  632        case DebugKey: 
return {};
 
  637static void appendNonEmpty(
const QByteArray &tagValue, QList<QByteArray> &l)
 
  643    for (
const auto &s : split) {
 
  650[[nodiscard]] 
static QChar::Script scriptForString(QStringView s)
 
  660} 
static constexpr const script_map[] = {
 
  667    return std::find_if(std::begin(script_map), std::end(script_map), [ls, cs](
const auto &m) { 
return m.localeScript == ls && m.charScript == cs; }) != std::end(script_map);
 
  670[[nodiscard]] 
static QUrl wikimediaCommondRedirect(
const QString &fileName)
 
  678    redirectUrl.
setHost(u
"commons.wikimedia.org"_s);
 
  679    redirectUrl.
setPath(u
"/wiki/Special:Redirect/file"_s);
 
  681    query.addQueryItem(u
"wptype"_s, u
"file"_s);
 
  682    query.addQueryItem(u
"wpvalue"_s, fileName);
 
  683    query.addQueryItem(u
"width"_s, u
"512"_s);
 
  688QVariant OSMElementInformationModel::valueForKey(Info info)
 const 
  691        case NoKey: 
return {};
 
  693            const auto n = 
QString::fromUtf8(m_element.tagValue(m_langs, 
"name", 
"loc_name", 
"int_name", 
"brand", 
"ref", 
"species", 
"genus"));
 
  694            const auto script = scriptForString(n);
 
  696                const auto transliterated = 
QString::fromUtf8(m_element.tagValue(m_langs, 
"int_name"));
 
  697                if (transliterated.isEmpty() || transliterated == n) {
 
  700                return i18nc(
"local name (transliterated name)", 
"%1 (%2)", n, transliterated);
 
  707            appendNonEmpty(m_element.tagValue(
"amenity"), l);
 
  708            appendNonEmpty(m_element.tagValue(
"shop"), l);
 
  709            appendNonEmpty(m_element.tagValue(
"tourism"), l);
 
  711            const auto diplomatic = m_element.tagValue(
"diplomatic");
 
  712            appendNonEmpty(diplomatic, l);
 
  713            if (diplomatic.isEmpty()) {
 
  714                appendNonEmpty(m_element.tagValue(
"office"), l);
 
  716            appendNonEmpty(m_element.tagValue(
"leisure"), l);
 
  717            appendNonEmpty(m_element.tagValue(
"historic"), l);
 
  718            appendNonEmpty(m_element.tagValue(
"mx:vehicle"), l);
 
  720                appendNonEmpty(m_element.tagValue(
"room"), l);
 
  728            for (
auto it = l.
begin(); it != l.
end();++it) {
 
  729                if ((*it).isEmpty() || (*it) == 
"yes" || (*it) == 
"no" || (*it) == 
"building") {
 
  736                appendNonEmpty(m_element.tagValue(
"building"), l);
 
  737                for (
const auto &key : l) {
 
  747            return QLocale().createSeparatedList(out);
 
  752            const auto commons = m_element.tagValue(
"wikimedia_commons");
 
  753            if (commons.startsWith(
"File:")) {
 
  754                return wikimediaCommondRedirect(
QString::fromUtf8(QByteArrayView(commons).mid(5)));
 
  757            if (url.host() == 
"commons.wikimedia.org"_L1) {
 
  758                return wikimediaCommondRedirect(url.fileName());
 
  760            const auto wdId = m_element.tagValue(
"wikidata", 
"species:wikidata", 
"genus:wikidata", 
"subject:wikidata", 
"operator:wikidata", 
"network:wikidata", 
"brand:wikidata");
 
  761            return wikimediaCommondRedirect(m_wikidataImageMap.value(Wikidata::Q{wdId}));
 
  766            return l.
join(QLatin1String(
", "));
 
  769            return m_element.tagValue(m_langs, 
"description", 
"memorial:text");
 
  776            return QLocale().createSeparatedList(l);
 
  779        case VendingMachineOffer:
 
  784            for (
const auto &d : diet_type_map) {
 
  785                const auto v = m_element.tagValue(d.keyName);
 
  786                const auto label = d.label.
toString();
 
  789                } 
else if (v == 
"only") {
 
  791                } 
else if (v == 
"no") {
 
  795            return l.
join(QLatin1String(
", "));
 
  797        case Takeaway: 
return translatedBoolValue(m_element.tagValue(
"takeaway")); 
 
  801            for (
const auto &socket : socket_type_map) {
 
  802                const auto value = m_element.tagValue(socket.keyName);
 
  803                if (value.isEmpty() || value == 
"no") {
 
  807                auto s = socket.label.toString();
 
  810                if (value != 
"yes") {
 
  814                const auto current = m_element.tagValue(QByteArray(socket.keyName + QByteArray(
":current")).constData());
 
  815                if (!current.isEmpty()) {
 
  816                    if (std::all_of(current.begin(), current.end(), [](
unsigned char c) { return std::isdigit(c); })) {
 
  822                const auto output = m_element.tagValue(QByteArray(socket.keyName + QByteArray(
":output")).constData());
 
  823                if (!output.isEmpty()) {
 
  824                    if (std::all_of(output.begin(), output.end(), [](
unsigned char c) { return std::isdigit(c); })) {
 
  831                if (!details.
empty()) {
 
  832                    s += QLatin1String(
" (") + details.
join(QLatin1String(
", ")) + QLatin1Char(
')');
 
  836            return QLocale().createSeparatedList(l);
 
  838        case OpeningHours: 
return QString::fromUtf8(m_element.tagValue(
"opening_hours"));
 
  839        case AvailableVehicles:
 
  841            const auto total = m_element.tagValue(
"mx:realtime_available").toInt();
 
  843            for (
const auto &v : available_vehicles_map) {
 
  844                const auto b = m_element.tagValue(v.keyName);
 
  848                types.
push_back(v.label.subs(b.toInt()).toString());
 
  852                return QLocale().toString(total);
 
  853            } 
else if (types.
size() == 1) {
 
  856                return i18n(
"%1 (%2)", total, QLocale().createSeparatedList(types));
 
  862            switch (info.category) {
 
  863                case Parking: fee = m_element.tagValue(
"parking:fee", 
"fee"); 
break;
 
  864                case Toilets: fee = m_element.tagValue(
"toilets:fee", 
"fee"); 
break;
 
  865                default: fee = m_element.tagValue(
"fee");
 
  872            if (!charge.isEmpty()) {
 
  873                s += QLatin1String(
" (") + charge + QLatin1Char(
')');
 
  880            for (
const auto &auth : authentication_type_map) {
 
  881                const auto v = m_element.tagValue(auth.keyName);
 
  882                if (v.isEmpty() || v == 
"no") {
 
  887            return QLocale().createSeparatedList(l);
 
  889        case BicycleParking: 
return translateValues(m_element.tagValue(
"bicycle_parking"), bicycle_parking_map);
 
  891        case CapacityDisabled: 
return capacitryValue(
"capacity:disabled");
 
  892        case CapacityWomen: 
return capacitryValue(
"capacity:women");
 
  893        case CapacityParent: 
return capacitryValue(
"capacity:parent");
 
  894        case CapacityCharing: 
return capacitryValue(
"capacity:charging");
 
  896        case DiaperChangingTable:
 
  898            return translatedBoolValue(m_element.tagValue(
"changing_table", 
"diaper"));
 
  901        case Wikipedia: 
return wikipediaUrl(m_element.tagValue(m_langs, 
"wikipedia", 
"brand:wikipedia", 
"species:wikipedia"));
 
  903        case Phone: 
return QString::fromUtf8(m_element.tagValue(
"contact:phone", 
"phone", 
"telephone", 
"operator:phone"));
 
  904        case Email: 
return QString::fromUtf8(m_element.tagValue(
"contact:email", 
"email", 
"operator:email"));
 
  905        case Website: 
return QString::fromUtf8(m_element.tagValue(
"website", 
"contact:website", 
"url", 
"operator:website"));
 
  909            const auto coins = m_element.tagValue(
"payment:coins");
 
  910            const auto notes = m_element.tagValue(
"payment:notes");
 
  911            if (coins.isEmpty() && notes.isEmpty()) {
 
  912                return translatedBoolValue(m_element.tagValue(
"payment:cash"));
 
  914            if (!coins.isEmpty() && !notes.isEmpty() && coins != 
"no" && notes != 
"no") {
 
  917            if (!coins.isEmpty() && coins != 
"no" && (notes == 
"no" || notes.isEmpty())) {
 
  918                return i18nc(
"payment option", 
"coins only");
 
  920            if (!notes.isEmpty() && notes != 
"no" && (coins == 
"no" || coins.isEmpty())) {
 
  921                return i18nc(
"payment option", 
"notes only");
 
  926        case PaymentDebitCard:
 
  927        case PaymentCreditCard:
 
  928        case PaymentStoredValueCard:
 
  929            return paymentMethodValue(info.key);
 
  932            QByteArray wheelchair;
 
  933            if (info.category == Toilets) {
 
  934                wheelchair = m_element.tagValue(
"toilets:wheelchair", 
"wheelchair");
 
  936                wheelchair = m_element.tagValue(
"wheelchair");
 
  938            const auto a = translateValue(wheelchair.
constData(), wheelchair_map);
 
  939            const auto d = 
QString::fromUtf8(m_element.tagValue(m_langs, 
"wheelchair:description"));
 
  941                return QString(a + QLatin1String(
" (") + d + QLatin1Char(
')'));
 
  946            return translatedBoolValue(m_element.tagValue(
"wheelchair:lift"));
 
  952            return translatedBoolValue(m_element.tagValue(m_langs, 
"speech_output"));
 
  957            bool explicitNo = 
false;
 
  958            for (
const auto &writing : tactile_writing_map) {
 
  959                const auto v = m_element.tagValue(m_langs, writing.keyName);
 
  970                return QLocale().createSeparatedList(l);
 
  972            const auto v = m_element.tagValue(m_langs, 
"tactile_writing");
 
  973            if (explicitNo && v.isEmpty()) {
 
  976            return translatedBoolValue(v);
 
  980        case OperatorWikipedia: 
return wikipediaUrl(m_element.tagValue(m_langs, 
"operator:wikipedia", 
"network:wikipedia"));
 
  983            const auto range = m_element.tagValue(
"mx:remaining_range").toInt();
 
  984            return formatDistance(range);
 
  986        case DebugLink: 
return m_element.url();
 
  987        case DebugKey: 
return {};
 
  992QVariant OSMElementInformationModel::urlify(
const QVariant& v, OSMElementInformationModel::Key key)
 const 
 1003            if (
const auto commons = m_element.tagValue(
"wikimedia_commons"); commons.startsWith(
"File:")) {
 
 1004                return QUrl(u
"https://commons.wikimedia.org/wiki/"_s + 
QString::fromUtf8(commons));
 
 1006            if (
const QUrl url(
QString::fromUtf8(m_element.tagValue(
"image"))); url.host() == 
"commons.wikimedia.org"_L1) {
 
 1007                return wikimediaCommondRedirect(url.fileName());
 
 1009            if (
const auto wdId = m_element.tagValue(
"wikidata", 
"species:wikidata", 
"genus:wikidata", 
"subject:wikidata", 
"operator:wikidata", 
"network:wikidata", 
"brand:wikidata"); !wdId.isEmpty()) {
 
 1015            if (!s.startsWith(QLatin1String(
"mailto:"))) {
 
 1016                return QString(QLatin1String(
"mailto:") + s);
 
 1021            if (s.startsWith(QLatin1String(
"tel:"))) {
 
 1024            QString e = QLatin1String(
"tel:") + s;
 
 1025            e.
remove(QLatin1Char(
' '));
 
 1030            if (s.startsWith(QLatin1String(
"http"))) {
 
 1033            return QString(QLatin1String(
"https://") + s);
 
 1041QString OSMElementInformationModel::paymentMethodList(OSMElementInformationModel::Key key)
 const 
 1044    for (
const auto &payment : payment_type_map) {
 
 1045        if (payment.key() != key) {
 
 1048        if (m_element.tagValue(payment.keyName) == 
"yes") {
 
 1053    return QLocale().createSeparatedList(l);
 
 1056QString OSMElementInformationModel::paymentMethodValue(OSMElementInformationModel::Key key)
 const 
 1058    const auto s = paymentMethodList(key);
 
 1063    for (
const auto &payment : payment_generic_type_map) {
 
 1064        if (payment.key() != key) {
 
 1067        const auto s = m_element.tagValue(payment.keyName);
 
 1075QUrl OSMElementInformationModel::wikipediaUrl(
const QByteArray &wp)
 const 
 1082    const auto idx = s.indexOf(QLatin1Char(
':'));
 
 1089    url.
setHost(QStringView(s).
left(idx) + QLatin1String(
".wikipedia.org"));
 
 1090    url.
setPath(QLatin1String(
"/wiki/") + QStringView(s).mid(idx + 1));
 
 1094QString OSMElementInformationModel::capacitryValue(
const char *prop)
 const 
 1096    const auto v = m_element.tagValue(prop);
 
 1097    return translatedBoolValue(v);
 
 1100QString OSMElementInformationModel::translatedBoolValue(
const QByteArray &value)
 const 
 1102    if (value == 
"yes") {
 
 1105    if (value == 
"no") {
 
 1111#include "moc_osmelementinformationmodel.cpp" 
QML wrapper around an OSM element.
 
QString i18nc(const char *context, const char *text, const TYPE &arg...)
 
QString i18n(const char *text, const TYPE &arg...)
 
char * toString(const EngineQuery &query)
 
KSERVICE_EXPORT KService::List query(FilterFunc filterFunc)
 
QString cuisineTypes(const QByteArray &value, Localization::TranslationOption opt=Localization::ReturnUnknownKey)
Translated values of the cuisine tag (does list splitting).
 
QString genderSegregation(OSM::Element element)
Translated gender segregation information e.g.
 
bool hasGenderSegregrationKey(OSM::Element element)
Checks whether element contains a known key for gender segregation information.
 
QString amenityTypes(const QByteArray &value, Localization::TranslationOption opt=Localization::ReturnUnknownKey)
Translated list of amenity tag values (including list splitting).
 
QString amenityType(const char *value, Localization::TranslationOption opt=Localization::ReturnUnknownKey)
Translated name for an amenity tag value (after list splitting).
 
OSM-based multi-floor indoor maps for buildings.
 
Category category(StandardShortcut id)
 
QByteArray tagValue(const Elem &elem, TagKey key)
Returns the tag value for key of elem.
 
void dataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QList< int > &roles)
 
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const=0
 
virtual QModelIndex parent(const QModelIndex &index) const const=0
 
virtual QHash< int, QByteArray > roleNames() const const
 
QAbstractListModel(QObject *parent)
 
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const const override
 
const char * constData() const const
 
bool isEmpty() const const
 
QList< QByteArray > split(char sep) const const
 
const_reference at(qsizetype i) const const
 
iterator erase(const_iterator begin, const_iterator end)
 
bool isEmpty() const const
 
void push_back(parameter_type value)
 
void reserve(qsizetype size)
 
qsizetype size() const const
 
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
 
QString fromUtf8(QByteArrayView str)
 
bool isEmpty() const const
 
QString & remove(QChar ch, Qt::CaseSensitivity cs)
 
QStringList split(QChar sep, Qt::SplitBehavior behavior, Qt::CaseSensitivity cs) const const
 
QString join(QChar separator) const const
 
const_iterator begin() const const
 
const_iterator end() const const
 
QTextStream & left(QTextStream &stream)
 
void setHost(const QString &host, ParsingMode mode)
 
void setPath(const QString &path, ParsingMode mode)
 
void setQuery(const QString &query, ParsingMode mode)
 
void setScheme(const QString &scheme)
 
QVariant fromValue(T &&value)
 
QString toString() const const
 
int userType() const const