File indexing completed on 2024-05-12 04:42:08

0001 /*
0002     SPDX-FileCopyrightText: 2020 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "locationqueryoverlayproxymodel.h"
0008 
0009 #include <KPublicTransport/Location>
0010 #include <KPublicTransport/LocationQueryModel>
0011 #include <KPublicTransport/RentalVehicle>
0012 
0013 #include <osm/element.h>
0014 #include <osm/geomath.h>
0015 
0016 using namespace KOSMIndoorMap;
0017 using namespace KPublicTransport;
0018 
0019 struct vehicle_type {
0020     const char *tagName;
0021     RentalVehicle::VehicleType vehicleType;
0022 };
0023 static constexpr const vehicle_type vehicle_type_map[] = {
0024     { "mx:realtime_available:bike", RentalVehicle::Bicycle },
0025     { "mx:realtime_available:pedelec", RentalVehicle::Pedelec },
0026     { "mx:realtime_available:scooter", RentalVehicle::ElectricKickScooter },
0027     { "mx:realtime_available:motorcycle", RentalVehicle::ElectricMoped },
0028     { "mx:realtime_available:car", RentalVehicle::Car },
0029 };
0030 
0031 LocationQueryOverlayProxyModel::LocationQueryOverlayProxyModel(QObject *parent)
0032     : QAbstractListModel(parent)
0033 {
0034     static_assert((sizeof(vehicle_type_map) / sizeof(vehicle_type)) == (sizeof(LocationQueryOverlayProxyModel::m_realtimeAvailableTagKeys) / sizeof(OSM::TagKey)));
0035 }
0036 
0037 LocationQueryOverlayProxyModel::~LocationQueryOverlayProxyModel() = default;
0038 
0039 MapData LocationQueryOverlayProxyModel::mapData() const
0040 {
0041     return m_data;
0042 }
0043 
0044 void LocationQueryOverlayProxyModel::setMapData(const MapData &data)
0045 {
0046     if (m_data == data) {
0047         return;
0048     }
0049 
0050     beginResetModel();
0051     m_data = data;
0052 
0053     if (!m_data.isEmpty()) {
0054         m_tagKeys.name = m_data.dataSet().makeTagKey("name");
0055         m_tagKeys.amenity = m_data.dataSet().makeTagKey("amenity");
0056         m_tagKeys.capacity = m_data.dataSet().makeTagKey("capacity");
0057         m_tagKeys.realtimeAvailable = m_data.dataSet().makeTagKey("mx:realtime_available");
0058         m_tagKeys.network = m_data.dataSet().makeTagKey("network");
0059         m_tagKeys.mxoid = m_data.dataSet().makeTagKey("mx:oid");
0060         m_tagKeys.remainingRange = m_data.dataSet().makeTagKey("mx:remaining_range");
0061         m_tagKeys.vehicle = m_data.dataSet().makeTagKey("mx:vehicle");
0062         m_tagKeys.addr_street = m_data.dataSet().makeTagKey("addr:street");
0063         m_tagKeys.addr_city = m_data.dataSet().makeTagKey("addr:city");
0064         m_tagKeys.addr_postcode = m_data.dataSet().makeTagKey("addr:postcode");
0065     }
0066 
0067     int i = 0;
0068     for (const auto &v : vehicle_type_map) {
0069         m_realtimeAvailableTagKeys[i++] = m_data.dataSet().makeTagKey(v.tagName);
0070     }
0071 
0072     initialize();
0073     endResetModel();
0074     Q_EMIT mapDataChanged();
0075 }
0076 
0077 QObject* LocationQueryOverlayProxyModel::sourceModel() const
0078 {
0079     return m_sourceModel;
0080 }
0081 
0082 void LocationQueryOverlayProxyModel::setSourceModel(QObject *sourceModel)
0083 {
0084     if (m_sourceModel == sourceModel) {
0085         return;
0086     }
0087     beginResetModel();
0088     m_sourceModel = qobject_cast<QAbstractItemModel*>(sourceModel);
0089     initialize();
0090     endResetModel();
0091 
0092     connect(m_sourceModel, &QAbstractItemModel::modelReset, this, [this]() {
0093         beginResetModel();
0094         initialize();
0095         endResetModel();
0096     });
0097     connect(m_sourceModel, &QAbstractItemModel::rowsInserted, this, [this](const QModelIndex &parent, int first, int last) {
0098         if (parent.isValid() || m_data.isEmpty()) {
0099             return;
0100         }
0101         beginInsertRows({}, first, last);
0102         for (int i = first; i <= last; ++i) {
0103             m_nodes.insert(m_nodes.begin() + i, nodeForRow(i));
0104         }
0105         endInsertRows();
0106     });
0107     connect(m_sourceModel, &QAbstractItemModel::rowsRemoved, this, [this](const QModelIndex &parent, int first, int last) {
0108         if (parent.isValid() || m_data.isEmpty()) {
0109             return;
0110         }
0111         beginRemoveRows({}, first, last);
0112         m_nodes.erase(m_nodes.begin() + first, m_nodes.begin() + last);
0113         endRemoveRows();
0114     });
0115     connect(m_sourceModel, &QAbstractItemModel::dataChanged, this, [this](const QModelIndex &first, const QModelIndex &last) {
0116         if (first.parent().isValid() || last.parent().isValid() || m_data.isEmpty()) {
0117             return;
0118         }
0119         for (int i = first.row(); i <= last.row(); ++i) {
0120             m_nodes[i] = nodeForRow(i);
0121         }
0122         Q_EMIT dataChanged(index(first.row(), 0), index(last.row(), 0));
0123     });
0124 }
0125 
0126 int LocationQueryOverlayProxyModel::rowCount(const QModelIndex &parent) const
0127 {
0128     if (parent.isValid()) {
0129         return 0;
0130     }
0131     return m_nodes.size();
0132 }
0133 
0134 QVariant LocationQueryOverlayProxyModel::data(const QModelIndex &index, int role) const
0135 {
0136     if (!index.isValid()) {
0137         return {};
0138     }
0139 
0140     switch (role) {
0141         case ElementRole:
0142             return QVariant::fromValue(OSM::Element(&m_nodes[index.row()].overlayNode));
0143         case LevelRole:
0144             return 0;
0145         case HiddenElementRole:
0146             return QVariant::fromValue(m_nodes[index.row()].sourceElement);
0147     }
0148 
0149     return {};
0150 }
0151 
0152 QHash<int, QByteArray> LocationQueryOverlayProxyModel::roleNames() const
0153 {
0154     auto n = QAbstractListModel::roleNames();
0155     n.insert(ElementRole, "osmElement");
0156     n.insert(LevelRole, "level");
0157     n.insert(HiddenElementRole, "hiddenElement");
0158     return n;
0159 }
0160 
0161 void LocationQueryOverlayProxyModel::initialize()
0162 {
0163     if (m_data.isEmpty() || !m_sourceModel) {
0164         return;
0165     }
0166 
0167     m_nodes.clear();
0168     const auto rows = m_sourceModel->rowCount();
0169     m_nodes.reserve(rows);
0170     for (int i = 0; i < rows; ++i) {
0171         m_nodes.push_back(nodeForRow(i));
0172     }
0173 }
0174 
0175 static void setTagIfMissing(OSM::Node &node, OSM::TagKey tag, const QString &value)
0176 {
0177     if (OSM::tagValue(node, tag).isEmpty() && !value.isEmpty()) {
0178         OSM::setTagValue(node, tag, value.toUtf8());
0179     }
0180 }
0181 
0182 LocationQueryOverlayProxyModel::Info LocationQueryOverlayProxyModel::nodeForRow(int row) const
0183 {
0184     const auto idx = m_sourceModel->index(row, 0);
0185     const auto loc = idx.data(LocationQueryModel::LocationRole).value<Location>();
0186 
0187     Info info;
0188     info.overlayNode.coordinate = OSM::Coordinate(loc.latitude(), loc.longitude());
0189 
0190     switch (loc.type()) {
0191         case Location::Place:
0192         case Location::Stop:
0193         case Location::CarpoolPickupDropoff:
0194             qDebug() << "got a location type we didn't ask for:" << loc.type() << loc.name();
0195             break;
0196         case Location::RentedVehicleStation:
0197         {
0198             const auto station = loc.rentalVehicleStation();
0199 
0200             // try to find a matching node in the base OSM data
0201             for (const auto &n : m_data.dataSet().nodes) {
0202                 if (OSM::distance(n.coordinate, info.overlayNode.coordinate) < 10 && OSM::tagValue(n, m_tagKeys.amenity) == "bicycle_rental") {
0203                     qDebug() << "found matching node, cloning that!" << n.url();
0204                     info.sourceElement = OSM::Element(&n);
0205                     info.overlayNode = n;
0206                     OSM::setTagValue(info.overlayNode, m_tagKeys.mxoid, QByteArray::number(qlonglong(n.id)));
0207                     break;
0208                 }
0209             }
0210 
0211             info.overlayNode.id = m_data.dataSet().nextInternalId();
0212             OSM::setTagValue(info.overlayNode, m_tagKeys.amenity, "bicycle_rental");
0213             if (station.capacity() >= 0) {
0214                 OSM::setTagValue(info.overlayNode, m_tagKeys.capacity, QByteArray::number(station.capacity()));
0215             }
0216             OSM::setTagValue(info.overlayNode, m_tagKeys.realtimeAvailable, QByteArray::number(station.availableVehicles()));
0217             setTagIfMissing(info.overlayNode, m_tagKeys.network, station.network().name());
0218             setTagIfMissing(info.overlayNode, m_tagKeys.name, loc.name());
0219             setTagIfMissing(info.overlayNode, m_tagKeys.addr_street, loc.streetAddress());
0220             setTagIfMissing(info.overlayNode, m_tagKeys.addr_city, loc.locality());
0221             setTagIfMissing(info.overlayNode, m_tagKeys.addr_postcode, loc.postalCode());
0222 
0223             int i = 0;
0224             for (const auto &v : vehicle_type_map) {
0225                 if (station.availableVehicles(v.vehicleType) > 0) {
0226                     OSM::setTagValue(info.overlayNode, m_realtimeAvailableTagKeys[i], QByteArray::number(station.availableVehicles(v.vehicleType)));
0227                 }
0228                 ++i;
0229             }
0230 
0231             break;
0232         }
0233         case Location::RentedVehicle:
0234         {
0235             const auto vehicle = loc.data().value<RentalVehicle>();
0236 
0237             // free floating vehicles have no matching OSM element, so no point in searching for one
0238             info.overlayNode.id = m_data.dataSet().nextInternalId();
0239             switch (vehicle.type()) {
0240                 case RentalVehicle::Unknown:
0241                 case RentalVehicle::Bicycle:
0242                 case RentalVehicle::Pedelec:
0243                     OSM::setTagValue(info.overlayNode, m_tagKeys.vehicle, "bicycle_rental");
0244                     break;
0245                 case RentalVehicle::ElectricKickScooter:
0246                     OSM::setTagValue(info.overlayNode, m_tagKeys.vehicle, "scooter_rental");
0247                     break;
0248                 case RentalVehicle::ElectricMoped:
0249                     OSM::setTagValue(info.overlayNode, m_tagKeys.vehicle, "motorcycle_rental");
0250                     break;
0251                 case RentalVehicle::Car:
0252                     OSM::setTagValue(info.overlayNode, m_tagKeys.vehicle, "car_rental");
0253                     break;
0254             }
0255             OSM::setTagValue(info.overlayNode, m_tagKeys.name, loc.name().toUtf8());
0256             setTagIfMissing(info.overlayNode, m_tagKeys.network, vehicle.network().name());
0257             if (vehicle.remainingRange() >= 0) {
0258                 OSM::setTagValue(info.overlayNode, m_tagKeys.remainingRange, QByteArray::number(vehicle.remainingRange()));
0259             }
0260             break;
0261         }
0262         case Location::Equipment:
0263             break;
0264     }
0265     return info;
0266 }
0267 
0268 #include "moc_locationqueryoverlayproxymodel.cpp"