Commit 921554db authored by Volker Krause's avatar Volker Krause

Add journey query support for the Hafas mgate.exe backends

parent f9e0d033
...@@ -23,6 +23,9 @@ ...@@ -23,6 +23,9 @@
#include <KPublicTransport/Departure> #include <KPublicTransport/Departure>
#include <KPublicTransport/DepartureReply> #include <KPublicTransport/DepartureReply>
#include <KPublicTransport/DepartureRequest> #include <KPublicTransport/DepartureRequest>
#include <KPublicTransport/Journey>
#include <KPublicTransport/JourneyReply>
#include <KPublicTransport/JourneyRequest>
#include <KPublicTransport/Location> #include <KPublicTransport/Location>
#include <KPublicTransport/LocationReply> #include <KPublicTransport/LocationReply>
#include <KPublicTransport/LocationRequest> #include <KPublicTransport/LocationRequest>
...@@ -51,7 +54,203 @@ bool HafasMgateBackend::isSecure() const ...@@ -51,7 +54,203 @@ bool HafasMgateBackend::isSecure() const
bool HafasMgateBackend::queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const bool HafasMgateBackend::queryJourney(JourneyReply *reply, QNetworkAccessManager *nam) const
{ {
m_parser.setLocationIdentifierType(locationIdentifierType()); m_parser.setLocationIdentifierType(locationIdentifierType());
return false; const auto request = reply->request();
const auto fromId = request.from().identifier(locationIdentifierType());
if (!fromId.isEmpty()) {
return queryJourney(reply, fromId, nam);
}
LocationRequest locReq;
locReq.setCoordinate(request.from().latitude(), request.from().longitude());
locReq.setName(request.from().name());
// TODO set max result = 1
// check if this location query is cached already
const auto cacheEntry = Cache::lookupLocation(backendId(), locReq.cacheKey());
switch (cacheEntry.type) {
case CacheHitType::Negative:
addError(reply, Reply::NotFoundError, {});
return false;
case CacheHitType::Positive:
if (cacheEntry.data.size() >= 1) {
return queryJourney(reply, cacheEntry.data[0].identifier(locationIdentifierType()), nam);
}
break;
case CacheHitType::Miss:
break;
}
// query from location
const auto locReply = postLocationQuery(locReq, nam);
if (!locReply) {
return false;
}
QObject::connect(locReply, &QNetworkReply::finished, [this, reply, locReply, locReq, nam]() {
switch (locReply->error()) {
case QNetworkReply::NoError:
{
auto res = m_parser.parseLocations(locReply->readAll());
if (m_parser.error() == Reply::NoError && !res.empty()) {
Cache::addLocationCacheEntry(backendId(), locReq.cacheKey(), res);
const auto id = res[0].identifier(locationIdentifierType());
if (!id.isEmpty()) {
queryJourney(reply, id, nam);
} else {
addError(reply, Reply::NotFoundError, QLatin1String("Location query found no result for departure."));
}
} else {
Cache::addNegativeLocationCacheEntry(backendId(), locReq.cacheKey());
addError(reply, m_parser.error(), m_parser.errorMessage());
}
break;
}
default:
addError(reply, Reply::NetworkError, locReply->errorString());
qCDebug(Log) << locReply->error() << locReply->errorString();
break;
}
locReply->deleteLater();
});
return true;
}
bool HafasMgateBackend::queryJourney(JourneyReply *reply, const QString &fromId, QNetworkAccessManager *nam) const
{
const auto request = reply->request();
const auto toId = request.to().identifier(locationIdentifierType());
if (!toId.isEmpty()) {
return queryJourney(reply, fromId, toId, nam);
}
LocationRequest locReq;
locReq.setCoordinate(request.to().latitude(), request.to().longitude());
locReq.setName(request.to().name());
// TODO set max result = 1
// check if this location query is cached already
const auto cacheEntry = Cache::lookupLocation(backendId(), locReq.cacheKey());
switch (cacheEntry.type) {
case CacheHitType::Negative:
addError(reply, Reply::NotFoundError, {});
return false;
case CacheHitType::Positive:
if (cacheEntry.data.size() >= 1) {
return queryJourney(reply, fromId, cacheEntry.data[0].identifier(locationIdentifierType()), nam);
}
break;
case CacheHitType::Miss:
break;
}
// query to location
const auto locReply = postLocationQuery(locReq, nam);
if (!locReply) {
addError(reply, Reply::NotFoundError, {});
return false;
}
QObject::connect(locReply, &QNetworkReply::finished, [this, fromId, reply, locReply, locReq, nam]() {
switch (locReply->error()) {
case QNetworkReply::NoError:
{
auto res = m_parser.parseLocations(locReply->readAll());
if (m_parser.error() == Reply::NoError && !res.empty()) {
Cache::addLocationCacheEntry(backendId(), locReq.cacheKey(), res);
const auto id = res[0].identifier(locationIdentifierType());
if (!id.isEmpty()) {
queryJourney(reply, fromId, id, nam);
} else {
addError(reply, Reply::NotFoundError, QLatin1String("Location query found no result for arrival."));
}
} else {
Cache::addNegativeLocationCacheEntry(backendId(), locReq.cacheKey());
addError(reply, m_parser.error(), m_parser.errorMessage());
}
break;
}
default:
addError(reply, Reply::NetworkError, locReply->errorString());
qCDebug(Log) << locReply->error() << locReply->errorString();
break;
}
locReply->deleteLater();
});
return true;
}
bool HafasMgateBackend::queryJourney(JourneyReply *reply, const QString &fromId, const QString &toId, QNetworkAccessManager *nam) const
{
qCDebug(Log) << backendId() << fromId << toId;
const auto request = reply->request();
QJsonObject tripSearch;
{
QJsonObject cfg;
cfg.insert(QLatin1String("polyEnc"), QLatin1String("GPA"));
QJsonArray arrLocL;
QJsonObject arrLoc;
arrLoc.insert(QLatin1String("extId"), toId);
arrLoc.insert(QLatin1String("type"), QLatin1String("S")); // 'S' == station
arrLocL.push_back(arrLoc);
QJsonArray depLocL;
QJsonObject depLoc;
depLoc.insert(QLatin1String("extId"), fromId);
depLoc.insert(QLatin1String("type"), QLatin1String("S"));
depLocL.push_back(depLoc);
QJsonArray jnyFltrL;
QJsonObject jnyFltr;
jnyFltr.insert(QLatin1String("mode"), QLatin1String("BIT"));
jnyFltr.insert(QLatin1String("type"), QLatin1String("PROD"));
jnyFltr.insert(QLatin1String("value"), QLatin1String("1111111001"));
jnyFltrL.push_back(jnyFltr);
QJsonObject req;
req.insert(QLatin1String("arrLocL"), arrLocL);
req.insert(QLatin1String("depLocL"), depLocL);
req.insert(QLatin1String("extChgTime"), -1);
req.insert(QLatin1String("getEco"), false);
req.insert(QLatin1String("getIST"), false);
req.insert(QLatin1String("getPasslist"), true); // ???
req.insert(QLatin1String("getPolyline"), false);
req.insert(QLatin1String("jnyFltrL"), jnyFltrL);
req.insert(QLatin1String("outDate"), request.dateTime().date().toString(QLatin1String("yyyyMMdd")));
req.insert(QLatin1String("outTime"), request.dateTime().time().toString(QLatin1String("hhmmss")));
req.insert(QLatin1String("outFrwd"), true);
tripSearch.insert(QLatin1String("cfg"), cfg);
tripSearch.insert(QLatin1String("meth"), QLatin1String("TripSearch"));
tripSearch.insert(QLatin1String("req"), req);
}
auto netReply = postRequest(tripSearch, nam);
QObject::connect(netReply, &QNetworkReply::finished, [netReply, reply, this]() {
switch (netReply->error()) {
case QNetworkReply::NoError:
{
auto res = m_parser.parseJourneys(netReply->readAll());
if (m_parser.error() == Reply::NoError) {
addResult(reply, std::move(res));
} else {
addError(reply, m_parser.error(), m_parser.errorMessage());
}
break;
}
default:
addError(reply, Reply::NetworkError, netReply->errorString());
qCDebug(Log) << netReply->error() << netReply->errorString();
break;
}
netReply->deleteLater();
});
return true;
} }
bool HafasMgateBackend::queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const bool HafasMgateBackend::queryDeparture(DepartureReply *reply, QNetworkAccessManager *nam) const
......
...@@ -59,6 +59,8 @@ private: ...@@ -59,6 +59,8 @@ private:
QNetworkReply* postRequest(const QJsonObject &svcReq, QNetworkAccessManager *nam) const; QNetworkReply* postRequest(const QJsonObject &svcReq, QNetworkAccessManager *nam) const;
QNetworkReply* postLocationQuery(const LocationRequest &req, QNetworkAccessManager *nam) const; QNetworkReply* postLocationQuery(const LocationRequest &req, QNetworkAccessManager *nam) const;
void queryDeparture(DepartureReply *reply, const QString &locationId, QNetworkAccessManager *nam) const; void queryDeparture(DepartureReply *reply, const QString &locationId, QNetworkAccessManager *nam) const;
bool queryJourney(JourneyReply *reply, const QString &fromId, QNetworkAccessManager *nam) const;
bool queryJourney(JourneyReply *reply, const QString &fromId, const QString &toId, QNetworkAccessManager *nam) const;
void setMicMacSalt(const QString &salt); void setMicMacSalt(const QString &salt);
void setChecksumSalt(const QString &salt); void setChecksumSalt(const QString &salt);
void setLineModeMap(const QJsonObject &obj); void setLineModeMap(const QJsonObject &obj);
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
#include "logging.h" #include "logging.h"
#include <KPublicTransport/Departure> #include <KPublicTransport/Departure>
#include <KPublicTransport/Journey>
#include <KPublicTransport/Line> #include <KPublicTransport/Line>
#include <QColor> #include <QColor>
...@@ -225,6 +226,92 @@ std::vector<Location> HafasMgateParser::parseLocations(const QByteArray &data) c ...@@ -225,6 +226,92 @@ std::vector<Location> HafasMgateParser::parseLocations(const QByteArray &data) c
return {}; return {};
} }
std::vector<Journey> HafasMgateParser::parseJourneys(const QByteArray &data) const
{
const auto topObj = QJsonDocument::fromJson(data).object();
// qDebug().noquote() << QJsonDocument(topObj).toJson();
const auto svcResL = topObj.value(QLatin1String("svcResL")).toArray();
for (const auto &v : svcResL) {
const auto obj = v.toObject();
if (obj.value(QLatin1String("meth")).toString() == QLatin1String("TripSearch")) {
if (parseError(obj)) {
return parseTripSearch(obj.value(QLatin1String("res")).toObject());
}
return {};
}
}
return {};
}
std::vector<Journey> HafasMgateParser::parseTripSearch(const QJsonObject &obj) const
{
const auto commonObj = obj.value(QLatin1String("common")).toObject();
const auto icos = parseIcos(commonObj.value(QLatin1String("icoL")).toArray());
const auto locs = parseLocations(commonObj.value(QLatin1String("locL")).toArray());
const auto lines = parseLines(commonObj.value(QLatin1String("prodL")).toArray(), icos);
std::vector<Journey> res;
const auto outConL = obj.value(QLatin1String("outConL")).toArray();
res.reserve(outConL.size());
for (const auto &outConV: outConL) {
const auto outCon = outConV.toObject();
const auto dateStr = outCon.value(QLatin1String("date")).toString();
const auto secL = outCon.value(QLatin1String("secL")).toArray();
std::vector<JourneySection> sections;
sections.reserve(secL.size());
for (const auto &secV : secL) {
const auto secObj = secV.toObject();
JourneySection section;
// TODO add real-time data
const auto dep = secObj.value(QLatin1String("dep")).toObject();
section.setDepartureTime(QDateTime::fromString(dateStr + dep.value(QLatin1String("dTimeS")).toString(), QLatin1String("yyyyMMddhhmmss")));
auto locIdx = dep.value(QLatin1String("locX")).toInt();
if ((unsigned int)locIdx < locs.size()) {
section.setFrom(locs[locIdx]);
}
const auto arr = secObj.value(QLatin1String("arr")).toObject();
section.setArrivalTime(QDateTime::fromString(dateStr + arr.value(QLatin1String("aTimeS")).toString(), QLatin1String("yyyyMMddhhmmss")));
locIdx = arr.value(QLatin1String("locX")).toInt();
if ((unsigned int)locIdx < locs.size()) {
section.setTo(locs[locIdx]);
}
const auto typeStr = secObj.value(QLatin1String("type")).toString();
if (typeStr == QLatin1String("JNY")) {
section.setMode(JourneySection::PublicTransport);
const auto jnyObj = secObj.value(QLatin1String("jny")).toObject();
Route route;
route.setDirection(jnyObj.value(QLatin1String("dirTxt")).toString());
const auto lineIdx = jnyObj.value(QLatin1String("prodX")).toInt();
if ((unsigned int)lineIdx < lines.size()) {
route.setLine(lines[lineIdx]);
}
section.setRoute(route);
} else if (typeStr == QLatin1String("WALK")) {
section.setMode(JourneySection::Walking);
}
sections.push_back(section);
}
Journey journey;
journey.setSections(std::move(sections));
res.push_back(journey);
}
return res;
}
Reply::Error HafasMgateParser::error() const Reply::Error HafasMgateParser::error() const
{ {
return m_error; return m_error;
......
...@@ -31,6 +31,7 @@ class QByteArray; ...@@ -31,6 +31,7 @@ class QByteArray;
namespace KPublicTransport { namespace KPublicTransport {
class Departure; class Departure;
class Journey;
class Location; class Location;
struct Ico { struct Ico {
...@@ -49,6 +50,7 @@ public: ...@@ -49,6 +50,7 @@ public:
std::vector<Departure> parseDepartures(const QByteArray &data) const; std::vector<Departure> parseDepartures(const QByteArray &data) const;
std::vector<Location> parseLocations(const QByteArray &data) const; std::vector<Location> parseLocations(const QByteArray &data) const;
std::vector<Journey> parseJourneys(const QByteArray &data) const;
Reply::Error error() const; Reply::Error error() const;
QString errorMessage() const; QString errorMessage() const;
...@@ -58,6 +60,7 @@ private: ...@@ -58,6 +60,7 @@ private:
std::vector<Departure> parseStationBoardResponse(const QJsonObject &obj) const; std::vector<Departure> parseStationBoardResponse(const QJsonObject &obj) const;
std::vector<Line> parseLines(const QJsonArray &prodL, const std::vector<Ico> &icos) const; std::vector<Line> parseLines(const QJsonArray &prodL, const std::vector<Ico> &icos) const;
std::vector<Location> parseLocations(const QJsonArray &locL) const; std::vector<Location> parseLocations(const QJsonArray &locL) const;
std::vector<Journey> parseTripSearch(const QJsonObject &obj) const;
bool parseError(const QJsonObject &obj) const; bool parseError(const QJsonObject &obj) const;
std::unordered_map<int, Line::Mode> m_lineModeMap; std::unordered_map<int, Line::Mode> m_lineModeMap;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment