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

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "gbfsvehicletypes.h"
0008 
0009 #include "gbfs.h"
0010 #include "gbfsreader.h"
0011 #include "gbfsservice.h"
0012 #include "gbfsstore.h"
0013 
0014 #include <QDebug>
0015 #include <QJsonArray>
0016 #include <QJsonDocument>
0017 #include <QJsonObject>
0018 
0019 using namespace KPublicTransport;
0020 
0021 namespace KPublicTransport {
0022 static bool operator<(const GBFSVehicleType &lhs, const GBFSVehicleType &rhs)
0023 {
0024     return lhs.typeId < rhs.typeId;
0025 }
0026 static bool operator<(const GBFSVehicleType &lhs, QStringView rhs)
0027 {
0028     return lhs.typeId < rhs;
0029 }
0030 }
0031 
0032 template <typename T>
0033 struct value_map_entry
0034 {
0035     const char *name;
0036     T value;
0037 };
0038 
0039 static constexpr const value_map_entry<GBFSVehicleType::FormFactor> form_factor_map[] = {
0040     { "bicycle", GBFSVehicleType::Bicycle },
0041     { "car", GBFSVehicleType::Car },
0042     { "cargo_bicycle", GBFSVehicleType::CargoBicycle },
0043     { "moped", GBFSVehicleType::Moped },
0044     { "scooter", GBFSVehicleType::Scooter },
0045     { "scooter_seating", GBFSVehicleType::Scooter },
0046     { "scooter_standing", GBFSVehicleType::Scooter },
0047     { "other", GBFSVehicleType::Other },
0048 };
0049 
0050 static constexpr const value_map_entry<GBFSVehicleType::PropulsionType> propulsion_map[] = {
0051     { "human", GBFSVehicleType::Human },
0052     { "electric_assist", GBFSVehicleType::ElectricAssist },
0053     { "electric", GBFSVehicleType::Electric },
0054     { "combustion", GBFSVehicleType::Combustion },
0055     { "combustion_diesel", GBFSVehicleType::Combustion },
0056     { "hybrid", GBFSVehicleType::Combustion },
0057     { "plug_in_hybrid", GBFSVehicleType::Combustion },
0058 };
0059 
0060 template <typename T, std::size_t N>
0061 static T lookupValue(const value_map_entry<T>(&map)[N], QStringView name)
0062 {
0063     for (const auto &entry : map) {
0064         if (name.compare(QLatin1String(entry.name), Qt::CaseInsensitive) == 0) {
0065             return entry.value;
0066         }
0067     }
0068     qDebug() << "unknown value:" << name;
0069     return {};
0070 }
0071 
0072 // some services don't prove a vehicle_types file but use somewhat descriptive fixed values
0073 // try to support that as well to the extend possible
0074 struct fallback_entry {
0075     GBFSVehicleType::FormFactor formFactor;
0076     GBFSVehicleType::PropulsionType propulsionType;
0077 };
0078 static constexpr const value_map_entry<fallback_entry> fallback_type_map[] = {
0079     { "bike", { GBFSVehicleType::Bicycle, GBFSVehicleType::Human } },
0080     { "moped", { GBFSVehicleType::Moped, GBFSVehicleType::UndefinedPropulsion } },
0081     { "scooter", { GBFSVehicleType::Scooter, GBFSVehicleType::UndefinedPropulsion } },
0082     { "ebike", { GBFSVehicleType::Bicycle, GBFSVehicleType::ElectricAssist } },
0083     { "electric_moped", { GBFSVehicleType::Moped, GBFSVehicleType::Electric } },
0084 };
0085 
0086 GBFSVehicleType GBFSVehicleType::fromJson(const QJsonObject &obj)
0087 {
0088     GBFSVehicleType v;
0089     v.typeId = obj.value(QLatin1String("vehicle_type_id")).toString();
0090     v.name = obj.value(QLatin1String("name")).toString();
0091     v.formFactor = lookupValue(form_factor_map, obj.value(QLatin1String("form_factor")).toString());
0092     v.propulsionType = lookupValue(propulsion_map, obj.value(QLatin1String("propulsion_type")).toString());
0093     return v;
0094 }
0095 
0096 GBFSVehicleTypes::GBFSVehicleTypes(const GBFSService &feed)
0097 {
0098     GBFSStore store(feed.systemId);
0099     const auto doc = store.loadData(GBFS::VehicleTypes);
0100     const auto types = GBFSReader::dataValue(doc, QLatin1String("vehicle_types")).toArray();
0101 
0102     m_vehicleTypes.reserve(types.size());
0103     for (const auto &typeVal : types) {
0104         auto v = GBFSVehicleType::fromJson(typeVal.toObject());
0105         if (!v.typeId.isEmpty()) {
0106             m_vehicleTypes.push_back(std::move(v));
0107         }
0108     }
0109 
0110     std::sort(m_vehicleTypes.begin(), m_vehicleTypes.end());
0111     qDebug() << "Found" << m_vehicleTypes.size() << "vehicle types.";
0112 }
0113 
0114 GBFSVehicleTypes::~GBFSVehicleTypes() = default;
0115 
0116 GBFSVehicleType GBFSVehicleTypes::vehicleType(QStringView typeId) const
0117 {
0118     if (typeId.empty()) {
0119         return {};
0120     }
0121 
0122     const auto it = std::lower_bound(m_vehicleTypes.begin(), m_vehicleTypes.end(), typeId);
0123     if (it != m_vehicleTypes.end() && (*it).typeId == typeId) {
0124         return (*it);
0125     }
0126 
0127     // fallback for non-compliant services without vehicle_types file
0128     if (m_vehicleTypes.empty()) {
0129         for (const auto &val : fallback_type_map) {
0130             if (QLatin1String(val.name) == typeId) {
0131                 GBFSVehicleType v;
0132                 v.formFactor = val.value.formFactor;
0133                 v.propulsionType = val.value.propulsionType;
0134                 return v;
0135             }
0136         }
0137         qDebug() << "unknown fallback vehicle type:" << typeId;
0138     }
0139 
0140     return {};
0141 }