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"