diff --git a/src/publictransport/backends/abstractbackend.cpp b/src/publictransport/backends/abstractbackend.cpp index 3ceef5b..81b6f03 100644 --- a/src/publictransport/backends/abstractbackend.cpp +++ b/src/publictransport/backends/abstractbackend.cpp @@ -1,37 +1,65 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "abstractbackend.h" +#include + +#include +#include + using namespace KPublicTransport; AbstractBackend::AbstractBackend() = default; AbstractBackend::~AbstractBackend() = default; +QString AbstractBackend::backendId() const +{ + return m_backendId; +} + +void AbstractBackend::setBackendId(const QString& id) +{ + m_backendId = id; +} + +bool AbstractBackend::isLocationExcluded(const Location &loc) const +{ + if (loc.hasCoordinate() && !m_geoFilter.isEmpty()) { + return !m_geoFilter.containsPoint({loc.latitude(), loc.longitude()}, Qt::WindingFill); + } + return false; +} + +void AbstractBackend::setGeoFilter(const QPolygonF &poly) +{ + m_geoFilter = poly; +} + bool AbstractBackend::queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const { Q_UNUSED(reply); Q_UNUSED(nam); return false; } bool AbstractBackend::queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const { Q_UNUSED(reply); Q_UNUSED(nam); return false; } diff --git a/src/publictransport/backends/abstractbackend.h b/src/publictransport/backends/abstractbackend.h index 9b8b074..e70613f 100644 --- a/src/publictransport/backends/abstractbackend.h +++ b/src/publictransport/backends/abstractbackend.h @@ -1,66 +1,81 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef KPUBLICTRANSPORT_ABSTRACTBACKEND_H #define KPUBLICTRANSPORT_ABSTRACTBACKEND_H #include "reply.h" +#include + class QNetworkAccessManager; namespace KPublicTransport { class DepartureReply; class JourneyReply; +class Location; /** Abstract base class for transport provider backends. */ class AbstractBackend { Q_GADGET public: AbstractBackend(); virtual ~AbstractBackend(); + /** Identifer for this backend. + * Use e.g. for distinguishing backend-specific cache locations etc. + */ + QString backendId() const; + void setBackendId(const QString &id); + + /** Checks if this location has been filtered by the network configuration. */ + bool isLocationExcluded(const Location &loc) const; + void setGeoFilter(const QPolygonF &poly); + /** Perform a journey query. * @return @c true if performing an async operation, @c false otherwise. */ virtual bool queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const; /** Perform a departure query. * @return @c true if performing an async operation, @c false otherwise. */ virtual bool queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const; protected: /** Helper function to call non-public Reply API. */ template inline static void addResult(T *reply, Args&&... args) { reply->addResult(std::forward(args)...); } inline static void addError(Reply *reply, Reply::Error error, const QString &errorMsg) { reply->addError(error, errorMsg); } private: Q_DISABLE_COPY(AbstractBackend) + QString m_backendId; + QPolygonF m_geoFilter; }; } #endif // KPUBLICTRANSPORT_ABSTRACTBACKEND_H diff --git a/src/publictransport/backends/hafasmgatebackend.cpp b/src/publictransport/backends/hafasmgatebackend.cpp index efb02a3..80438e6 100644 --- a/src/publictransport/backends/hafasmgatebackend.cpp +++ b/src/publictransport/backends/hafasmgatebackend.cpp @@ -1,187 +1,187 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "hafasmgatebackend.h" #include "hafasmgateparser.h" #include "logging.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPublicTransport; HafasMgateBackend::HafasMgateBackend() = default; bool HafasMgateBackend::queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const { return false; } bool HafasMgateBackend::queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const { const auto request = reply->request(); const auto id = request.stop().identifier(QLatin1String("hafasId")); // ### temporary, until we have proper name lookup if (id.isEmpty()) { return false; } QJsonObject top; { QJsonObject auth; auth.insert(QLatin1String("aid"), m_aid); auth.insert(QLatin1String("type"), QLatin1String("AID")); top.insert(QLatin1String("auth"), auth); } { QJsonObject client; client.insert(QLatin1String("id"), m_clientId); client.insert(QLatin1String("type"), m_clientType); if (!m_clientVersion.isEmpty()) { client.insert(QLatin1String("v"), m_clientVersion); } if (!m_clientName.isEmpty()) { client.insert(QLatin1String("name"), m_clientName); } top.insert(QLatin1String("client"), client); } top.insert(QLatin1String("formatted"), false); top.insert(QLatin1String("lang"), QLatin1String("eng")); { QJsonArray svcReq; { QJsonObject req; req.insert(QLatin1String("getServerDateTime"), true); req.insert(QLatin1String("getTimeTablePeriod"), false); QJsonObject serverInfo; serverInfo.insert(QLatin1String("meth"), QLatin1String("ServerInfo")); serverInfo.insert(QLatin1String("req"), req); svcReq.push_back(serverInfo); } { QJsonObject cfg; cfg.insert(QLatin1String("polyEnc"), QLatin1String("GPA")); QJsonObject req; req.insert(QLatin1String("date"), request.dateTime().toString(QLatin1String("yyyyMMdd"))); req.insert(QLatin1String("maxJny"), 12); req.insert(QLatin1String("stbFltrEquiv"), true); QJsonObject stbLoc; stbLoc.insert(QLatin1String("extId"), id); stbLoc.insert(QLatin1String("state"), QLatin1String("F")); stbLoc.insert(QLatin1String("type"), QLatin1String("S")); req.insert(QLatin1String("stbLoc"), stbLoc); req.insert(QLatin1String("time"), request.dateTime().toString(QLatin1String("hhmmss"))); req.insert(QLatin1String("type"), QLatin1String("DEP")); QJsonObject stationBoard; stationBoard.insert(QLatin1String("cfg"), cfg); stationBoard.insert(QLatin1String("meth"), QLatin1String("StationBoard")); stationBoard.insert(QLatin1String("req"), req); svcReq.push_back(stationBoard); } top.insert(QLatin1String("svcReqL"), svcReq); } top.insert(QLatin1String("ver"), m_version); const auto content = QJsonDocument(top).toJson(); QUrl url(m_endpoint); QUrlQuery query; if (!m_micMacSalt.isEmpty()) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(content); const auto mic = md5.result().toHex(); query.addQueryItem(QLatin1String("mic"), QString::fromLatin1(mic)); md5.reset(); // yes, mic is added as hex-encoded string, and the salt is added as raw bytes md5.addData(mic); md5.addData(m_micMacSalt); query.addQueryItem(QLatin1String("mac"), QString::fromLatin1(md5.result().toHex())); } if (!m_checksumSalt.isEmpty()) { QCryptographicHash md5(QCryptographicHash::Md5); md5.addData(content); md5.addData(m_checksumSalt); query.addQueryItem(QLatin1String("checksum"), QString::fromLatin1(md5.result().toHex())); } url.setQuery(query); auto netReq = QNetworkRequest(url); netReq.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); auto netReply = nam->post(netReq, content); qDebug() << netReq.url(); - qDebug().noquote() << QJsonDocument(top).toJson(); +// qDebug().noquote() << QJsonDocument(top).toJson(); QObject::connect(netReply, &QNetworkReply::finished, [netReply, reply, this]() { qDebug() << netReply->request().url(); switch (netReply->error()) { case QNetworkReply::NoError: addResult(reply, m_parser.parseDepartures(netReply->readAll())); break; default: addError(reply, Reply::NetworkError, netReply->errorString()); qCDebug(Log) << netReply->error() << netReply->errorString(); break; } netReply->deleteLater(); }); return true; } void HafasMgateBackend::setMicMacSalt(const QString &salt) { m_micMacSalt = QByteArray::fromHex(salt.toUtf8()); } void HafasMgateBackend::setChecksumSalt(const QString &salt) { m_checksumSalt = QByteArray::fromHex(salt.toUtf8()); } void HafasMgateBackend::setLineModeMap(const QJsonObject& obj) { const auto idx = Line::staticMetaObject.indexOfEnumerator("Mode"); Q_ASSERT(idx >= 0); const auto me = Line::staticMetaObject.enumerator(idx); std::unordered_map modeMap; for (auto it = obj.begin(); it != obj.end(); ++it) { modeMap[it.key().toInt()] = static_cast(me.keyToValue(it.value().toString().toUtf8())); } m_parser.setLineModeMap(std::move(modeMap)); } diff --git a/src/publictransport/manager.cpp b/src/publictransport/manager.cpp index dead590..4dccb93 100644 --- a/src/publictransport/manager.cpp +++ b/src/publictransport/manager.cpp @@ -1,161 +1,187 @@ /* Copyright (C) 2018 Volker Krause This program is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "manager.h" #include "departurereply.h" +#include "departurerequest.h" #include "journeyreply.h" +#include "journeyrequest.h" #include "logging.h" +#include + #include "backends/hafasmgatebackend.h" #include "backends/navitiabackend.h" #include +#include #include #include #include #include using namespace KPublicTransport; static inline void initResources() { Q_INIT_RESOURCE(networks); } namespace KPublicTransport { class ManagerPrivate { public: QNetworkAccessManager* nam(); void loadNetworks(); std::unique_ptr loadNetwork(const QJsonObject &obj); template std::unique_ptr loadNetwork(const QJsonObject &obj); QNetworkAccessManager *m_nam = nullptr; std::vector> m_backends; }; } QNetworkAccessManager* ManagerPrivate::nam() { if (!m_nam) { m_nam = new QNetworkAccessManager; } return m_nam; } void ManagerPrivate::loadNetworks() { QDirIterator it(QStringLiteral(":/org.kde.pim/kpublictransport/networks")); while (it.hasNext()) { QFile f(it.next()); if (!f.open(QFile::ReadOnly)) { qCWarning(Log) << "Failed to open public transport network configuration:" << f.errorString(); continue; } QJsonParseError error; const auto doc = QJsonDocument::fromJson(f.readAll(), &error); if (error.error != QJsonParseError::NoError) { qCWarning(Log) << "Failed to parse public transport network configuration:" << error.errorString() << it.fileName(); continue; } auto net = loadNetwork(doc.object()); if (net) { + net->setBackendId(it.fileInfo().baseName()); m_backends.push_back(std::move(net)); } else { qCWarning(Log) << "Failed to load public transport network configuration config:" << it.fileName(); } } qCDebug(Log) << m_backends.size() << "public transport network configurations loaded"; } std::unique_ptr ManagerPrivate::loadNetwork(const QJsonObject &obj) { const auto type = obj.value(QLatin1String("type")).toString(); if (type == QLatin1String("navitia")) { return loadNetwork(obj); } else if (type == QLatin1String("hafas_mgate")) { return loadNetwork(obj); } qCWarning(Log) << "Unknown backend type:" << type; return {}; } -static void applyBackendOptions(void *backend, const QMetaObject *mo, const QJsonObject &obj) +static void applyBackendOptions(AbstractBackend *backend, const QMetaObject *mo, const QJsonObject &obj) { const auto opts = obj.value(QLatin1String("options")).toObject(); for (auto it = opts.begin(); it != opts.end(); ++it) { const auto idx = mo->indexOfProperty(it.key().toUtf8()); const auto mp = mo->property(idx); if (it.value().isObject()) { mp.writeOnGadget(backend, it.value().toObject()); } else { mp.writeOnGadget(backend, it.value().toVariant()); } } + + const auto filter = obj.value(QLatin1String("filter")).toObject(); + const auto geoFilter = filter.value(QLatin1String("geo")).toArray(); + if (!geoFilter.isEmpty()) { + QPolygonF poly; + poly.reserve(geoFilter.size()); + for (const auto &coordV : geoFilter) { + const auto coordA = coordV.toArray(); + poly.push_back({coordA[0].toDouble(), coordA[1].toDouble()}); + } + backend->setGeoFilter(poly); + } } template std::unique_ptr ManagerPrivate::loadNetwork(const QJsonObject &obj) { std::unique_ptr backend(new T); applyBackendOptions(backend.get(), &T::staticMetaObject, obj); return backend; } Manager::Manager() : d(new ManagerPrivate) { initResources(); d->loadNetworks(); } Manager::Manager(Manager&&) noexcept = default; Manager::~Manager() = default; void Manager::setNetworkAccessManager(QNetworkAccessManager *nam) { // TODO delete d->nam if we created it ourselves d->m_nam = nam; } JourneyReply* Manager::queryJourney(const JourneyRequest &req) const { auto reply = new JourneyReply(req); int pendingOps = 0; for (const auto &backend : d->m_backends) { + if (backend->isLocationExcluded(req.from()) && backend->isLocationExcluded(req.to())) { + qCDebug(Log) << "Skiping backend based on location filter:" << backend->backendId(); + continue; + } if (backend->queryJourney(reply, d->nam())) { ++pendingOps; } } reply->setPendingOps(pendingOps); return reply; } DepartureReply* Manager::queryDeparture(const DepartureRequest &req) const { auto reply = new DepartureReply(req); int pendingOps = 0; for (const auto &backend : d->m_backends) { + if (backend->isLocationExcluded(req.stop())) { + qCDebug(Log) << "Skiping backend based on location filter:" << backend->backendId(); + continue; + } if (backend->queryDeparture(reply, d->nam())) { ++pendingOps; } } reply->setPendingOps(pendingOps); return reply; } diff --git a/src/publictransport/networks/vbb.json b/src/publictransport/networks/vbb.json index 2b05de0..5bc4c91 100644 --- a/src/publictransport/networks/vbb.json +++ b/src/publictransport/networks/vbb.json @@ -1,20 +1,23 @@ { "type": "hafas_mgate", "options": { "endpoint": "https://fahrinfo.vbb.de/bin/mgate.exe", "aid": "hafas-vbb-apps", "clientId": "VBB", "clientType": "AND", "version": "1.14", "micMacSalt": "5243544a4d3266467846667878516649", "lineModeMap": { "1": "RapidTransit", "2": "Metro", "4": "Tramway", "8": "Bus", "16": "Ferry", "32": "LongDistanceTrain", "64": "LocalTrain" } + }, + "filter": { + "geo": [ [ 11.05, 53.255 ], [ 14.45, 53.658 ], [ 14.908, 51.4295 ], [ 13.149, 51.25 ] ] } }