diff --git a/src/publictransport/CMakeLists.txt b/src/publictransport/CMakeLists.txt index 5c87236..47e81f3 100644 --- a/src/publictransport/CMakeLists.txt +++ b/src/publictransport/CMakeLists.txt @@ -1,63 +1,64 @@ set(kpublictransport_srcs departurereply.cpp departurerequest.cpp journeyreply.cpp journeyrequest.cpp manager.cpp reply.cpp backends/abstractbackend.cpp + backends/hafasmgatebackend.cpp backends/navitiabackend.cpp backends/navitiaparser.cpp datatypes/departure.cpp datatypes/journey.cpp datatypes/line.cpp datatypes/location.cpp networks/networks.qrc ) ecm_qt_declare_logging_category(kpublictransport_srcs HEADER logging.h IDENTIFIER KPublicTransport::Log CATEGORY_NAME org.kde.kpublictransport) add_library(KPublicTransport STATIC ${kpublictransport_srcs}) target_include_directories(KPublicTransport PUBLIC "$") target_link_libraries(KPublicTransport PUBLIC Qt5::Gui PRIVATE Qt5::Network ) ecm_generate_headers(KPublicTransport_FORWARDING_HEADERS HEADER_NAMES DepartureReply DepartureRequest JourneyReply JourneyRequest Manager Reply PREFIX KPublicTransport REQUIRED_HEADERS KPublicTransport_HEADERS ) # # ### for testing only ecm_generate_headers(KPublicTransport_Backends_FORWARDING_HEADERS HEADER_NAMES NavitiaParser PREFIX KPublicTransport REQUIRED_HEADERS KPublicTransport_Backends_HEADERS RELATIVE backends ) ecm_generate_headers(KPublicTransport_Datatypes_FORWARDING_HEADERS HEADER_NAMES Datatypes Departure Journey Line Location PREFIX KPublicTransport REQUIRED_HEADERS KPublicTransport_Datatypes_HEADERS RELATIVE datatypes ) install(FILES org_kde_kpublictransport.categories DESTINATION ${KDE_INSTALL_CONFDIR}) diff --git a/src/publictransport/backends/hafasmgatebackend.cpp b/src/publictransport/backends/hafasmgatebackend.cpp new file mode 100644 index 0000000..330ba2a --- /dev/null +++ b/src/publictransport/backends/hafasmgatebackend.cpp @@ -0,0 +1,119 @@ +/* + 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 +#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); + 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); + + auto netReq = QNetworkRequest(QUrl(m_endpoint)); + netReq.setHeader(QNetworkRequest::ContentTypeHeader, QLatin1String("application/json")); + auto netReply = nam->post(netReq, QJsonDocument(top).toJson()); + qDebug().noquote() << QJsonDocument(top).toJson(); + QObject::connect(netReply, &QNetworkReply::finished, [netReply, reply]() { + qDebug() << netReply->errorString(); + qDebug() << netReply->request().url(); + qDebug() << netReply->readAll(); + }); + + return false; +} diff --git a/src/publictransport/backends/hafasmgatebackend.h b/src/publictransport/backends/hafasmgatebackend.h new file mode 100644 index 0000000..4d1a7c9 --- /dev/null +++ b/src/publictransport/backends/hafasmgatebackend.h @@ -0,0 +1,51 @@ +/* + 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_HAFASMGATEBACKEND_H +#define KPUBLICTRANSPORT_HAFASMGATEBACKEND_H + +#include "abstractbackend.h" + +#include + +namespace KPublicTransport { + +/** Backend for the Hafas mgate.exe interface. */ +class HafasMgateBackend : public AbstractBackend +{ + Q_GADGET + Q_PROPERTY(QString endpoint MEMBER m_endpoint) + Q_PROPERTY(QString aid MEMBER m_aid) + Q_PROPERTY(QString clientId MEMBER m_clientId) + Q_PROPERTY(QString clientType MEMBER m_clientType) + Q_PROPERTY(QString version MEMBER m_version) +public: + HafasMgateBackend(); + bool queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const override; + bool queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const override; + +private: + QString m_endpoint; + QString m_aid; + QString m_clientId; + QString m_clientType; + QString m_version; +}; + +} + +#endif // KPUBLICTRANSPORT_HAFASMGATEBACKEND_H diff --git a/src/publictransport/manager.cpp b/src/publictransport/manager.cpp index d32b794..eef37e9 100644 --- a/src/publictransport/manager.cpp +++ b/src/publictransport/manager.cpp @@ -1,154 +1,157 @@ /* 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 "journeyreply.h" #include "logging.h" +#include "backends/hafasmgatebackend.h" #include "backends/navitiabackend.h" #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) { 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) { 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); mp.writeOnGadget(backend, it.value().toVariant()); } } 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->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->queryDeparture(reply, d->nam())) { ++pendingOps; } } reply->setPendingOps(pendingOps); return reply; } diff --git a/src/publictransport/networks/bvg.json b/src/publictransport/networks/bvg.json new file mode 100644 index 0000000..9ae6ffa --- /dev/null +++ b/src/publictransport/networks/bvg.json @@ -0,0 +1,10 @@ +{ + "type": "hafas_mgate", + "options": { + "endpoint": "https://bvg-apps.hafas.de/bin/mgate.exe", + "aid": "1Rxs112shyHLatUX4fofnmdxK", + "clientId": "BVG", + "clientType": "AND", + "version": "1.14" + } +} diff --git a/src/publictransport/networks/deutschebahn.json b/src/publictransport/networks/deutschebahn.json new file mode 100644 index 0000000..8c3c860 --- /dev/null +++ b/src/publictransport/networks/deutschebahn.json @@ -0,0 +1,10 @@ +{ + "type": "hafas_mgate", + "options": { + "endpoint": "https://reiseauskunft.bahn.de/bin/mgate.exe", + "aid": "n91dB8Z77MLdoR0K", + "clientId": "DB", + "clientType": "AND", + "version": "1.14" + } +} diff --git a/src/publictransport/networks/networks.qrc b/src/publictransport/networks/networks.qrc index a813926..462c332 100644 --- a/src/publictransport/networks/networks.qrc +++ b/src/publictransport/networks/networks.qrc @@ -1,7 +1,9 @@ + bvg.json + deutschebahn.json navitia.json + sncb.json sncf.json - diff --git a/src/publictransport/networks/sncb.json b/src/publictransport/networks/sncb.json new file mode 100644 index 0000000..bc68473 --- /dev/null +++ b/src/publictransport/networks/sncb.json @@ -0,0 +1,10 @@ +{ + "type": "hafas_mgate", + "options": { + "endpoint": "http://www.belgianrail.be/jp/sncb-nmbs-routeplanner/mgate.exe", + "aid": "sncb-mobi", + "clientId": "SNCB", + "clientType": "AND", + "version": "1.14" + } +} diff --git a/tests/departurequery.cpp b/tests/departurequery.cpp index e5c5c33..5bef610 100644 --- a/tests/departurequery.cpp +++ b/tests/departurequery.cpp @@ -1,115 +1,116 @@ /* 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 #include #include #include #include #include #include #include #include #include #include #include using namespace KPublicTransport; class QueryManager : public QObject { Q_OBJECT Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged) Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged) public: QueryManager() { ptMgr.setNetworkAccessManager(&nam); } - Q_INVOKABLE void queryDeparture(double fromLat, double fromLon) + Q_INVOKABLE void queryDeparture(double fromLat, double fromLon, const QString &id) { engine->rootContext()->setContextProperty(QStringLiteral("_departures"), QVariantList()); m_loading = true; emit loadingChanged(); m_errorMsg.clear(); emit errorMessageChanged(); Location from; from.setCoordinate(fromLat, fromLon); + from.setIdentifier(QLatin1String("hafasId"), id); // ### temporary, until we have other look-up methods auto reply = ptMgr.queryDeparture(DepartureRequest(from)); QObject::connect(reply, &DepartureReply::finished, [reply, this]{ m_loading = false; emit loadingChanged(); if (reply->error() == DepartureReply::NoError) { const auto res = reply->departures(); QVariantList l; l.reserve(res.size()); std::transform(res.begin(), res.end(), std::back_inserter(l), [](const auto &journey) { return QVariant::fromValue(journey); }); engine->rootContext()->setContextProperty(QStringLiteral("_departures"), l); for (const auto &departure : res) { qDebug() << departure.stopPoint().name() << departure.route().line().name() << departure.route().direction() << departure.scheduledTime(); } } else { m_errorMsg = reply->errorString(); emit errorMessageChanged(); } }); } bool loading() const { return m_loading; } QString errorMessage() const { return m_errorMsg; } QQmlEngine *engine = nullptr; signals: void loadingChanged(); void errorMessageChanged(); private: QNetworkAccessManager nam; Manager ptMgr; QString m_errorMsg; bool m_loading = false; }; int main(int argc, char **argv) { QCoreApplication::setApplicationName(QStringLiteral("departurequery")); QCoreApplication::setOrganizationName(QStringLiteral("KDE")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QGuiApplication app(argc, argv); qmlRegisterUncreatableType("org.kde.kpublictransport", 1, 0, "Line", {}); QueryManager mgr; QQmlApplicationEngine engine; mgr.engine = &engine; engine.rootContext()->setContextProperty(QStringLiteral("_queryMgr"), &mgr); engine.load(QStringLiteral("qrc:/departurequery.qml")); return app.exec(); } #include "departurequery.moc" diff --git a/tests/departurequery.qml b/tests/departurequery.qml index 22072e6..d255406 100644 --- a/tests/departurequery.qml +++ b/tests/departurequery.qml @@ -1,115 +1,115 @@ /* 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 . */ import QtQuick 2.5 import QtQuick.Layouts 1.1 import QtQuick.Controls 2.1 as QQC2 import org.kde.kirigami 2.0 as Kirigami import org.kde.kpublictransport 1.0 Kirigami.ApplicationWindow { title: "Departure Query" reachableModeEnabled: false width: 480 height: 720 pageStack.initialPage: departureQueryPage ListModel { id: exampleModel - ListElement { name: "CDG"; lat: 2.57110; lon: 49.00406 } - ListElement { name: "Paris Gare de Lyon"; lat: 2.37708; lon: 48.84388 } - ListElement { name: "ZRH"; lat: 8.56275; lon: 47.45050 } - ListElement { name: "Randa"; lat: 7.78315; lon: 46.09901 } - ListElement { name: "Brussels Gare de Midi"; lat: 4.33620; lon: 50.83588 } + ListElement { name: "CDG"; lat: 2.57110; lon: 49.00406; hafasId: "8700147" } // IBNR for DB: 8704997 + ListElement { name: "Paris Gare de Lyon"; lat: 2.37708; lon: 48.84388; hafasId: "8768600" } + ListElement { name: "ZRH"; lat: 8.56275; lon: 47.45050; hafasId: "8503016" } + ListElement { name: "Randa"; lat: 7.78315; lon: 46.09901; hafasId: "8501687" } + ListElement { name: "Brussels Gare de Midi"; lat: 4.33620; lon: 50.83588; hafasId: "8814001" } ListElement { name: "Fosdem"; lat: 4.38116; lon: 50.81360 } - ListElement { name: "VIE"; lat: 16.56312; lon: 48.12083 } + ListElement { name: "VIE"; lat: 16.56312; lon: 48.12083; hafasId: "008100353" } ListElement { name: "Akademy 2018 Accomodation"; lat: 16.37859; lon: 48.18282 } ListElement { name: "Akademy 2018 BBQ"; lat: 16.43191; lon: 48.21612 } ListElement { name: "LEI"; lat: -2.37251; lon: 36.84774; } ListElement { name: "Akademy 2017 Accomodation"; lat: -2.44788; lon: 36.83731 } ListElement { name: "Akademy 2017 Venue"; lat: -2.40377; lon: 36.82784 } ListElement { name: "TXL"; lat: 13.29281; lon: 52.55420; } ListElement { name: "Akademy 2016 Venue"; lat: 13.41644; lon: 52.52068 } - ListElement { name: "SXF"; lat: 13.51870; lon: 52.38841 } + ListElement { name: "SXF"; lat: 13.51870; lon: 52.38841; hafasId: "900260005" } ListElement { name: "Brno central station"; lat: 16.61287; lon: 49.19069 } ListElement { name: "Akademy 2014 venue"; lat: 16.57564; lon: 49.22462 } } Component { id: departureQueryPage Kirigami.Page { ColumnLayout { anchors.fill: parent QQC2.ComboBox { id: exampleSelector Layout.fillWidth: true model: exampleModel textRole: "name" onCurrentIndexChanged: { var obj = exampleModel.get(currentIndex); - _queryMgr.queryDeparture(obj.lat, obj.lon); + _queryMgr.queryDeparture(obj.lat, obj.lon, obj.hafasId); } } ListView { Layout.fillHeight: true Layout.fillWidth: true model: _departures delegate: Item { implicitHeight: delegateLayout.implicitHeight implicitWidth: delegateLayout.implicitWidth ColumnLayout { id: delegateLayout Text { text: "From: " + modelData.stopPoint.name } Text { text: "Line: " + modelData.route.line.modeString + " " + modelData.route.line.name + " to " + modelData.route.direction } Text { text: "Time: " + modelData.scheduledTime } } Rectangle { anchors.left: parent.left anchors.leftMargin: -8 height: parent.height width: 4 color: modelData.route.line.color } } QQC2.BusyIndicator { anchors.centerIn: parent running: _queryMgr.loading } QQC2.Label { anchors.centerIn: parent width: parent.width text: _queryMgr.errorMessage color: Kirigami.Theme.negativeTextColor wrapMode: Text.Wrap } } } } } }