From 003e66d8d93959209d0f227576de3b63e89c1b7c Mon Sep 17 00:00:00 2001 From: Stefan Ott Date: Sun, 20 Mar 2022 01:17:29 +0100 Subject: [PATCH] Add support for timetable.search.ch backend --- fahrplan2.pro | 2 + src/fahrplan_backend_manager.cpp | 1 + src/fahrplan_parser_thread.cpp | 3 + src/fahrplan_parser_thread.h | 1 + src/parser/parser_search_ch.cpp | 868 +++++++++++++++++++++++++++++++ src/parser/parser_search_ch.h | 169 ++++++ translations/fahrplan_de.ts | 193 +++++++ 7 files changed, 1237 insertions(+) create mode 100644 src/parser/parser_search_ch.cpp create mode 100644 src/parser/parser_search_ch.h diff --git a/fahrplan2.pro b/fahrplan2.pro index 1bf931a2..eba3529f 100644 --- a/fahrplan2.pro +++ b/fahrplan2.pro @@ -101,6 +101,7 @@ HEADERS += \ src/parser/parser_hafasxml.h \ src/parser/parser_abstract.h \ src/parser/parser_definitions.h \ + src/parser/parser_search_ch.h \ src/parser/parser_xmlsbbch.h \ src/parser/parser_xmlrejseplanendk.h \ src/parser/parser_xmloebbat.h \ @@ -135,6 +136,7 @@ SOURCES += src/main.cpp \ src/parser/parser_hafasxml.cpp \ src/parser/parser_abstract.cpp \ src/parser/parser_definitions.cpp \ + src/parser/parser_search_ch.cpp \ src/parser/parser_xmlsbbch.cpp \ src/parser/parser_xmlrejseplanendk.cpp \ src/parser/parser_xmloebbat.cpp \ diff --git a/src/fahrplan_backend_manager.cpp b/src/fahrplan_backend_manager.cpp index 6831aab0..a7684ff1 100644 --- a/src/fahrplan_backend_manager.cpp +++ b/src/fahrplan_backend_manager.cpp @@ -45,6 +45,7 @@ QStringList FahrplanBackendManager::getParserList() result.append(ParserSalzburgEFA::getName()); result.append(ParserResRobot::getName()); result.append(ParserFinlandMatka::getName()); + result.append(ParserSearchCH::getName()); // Make sure the index is in bounds if (currentParserIndex > (result.count() - 1) || currentParserIndex < 0) { diff --git a/src/fahrplan_parser_thread.cpp b/src/fahrplan_parser_thread.cpp index 599ed2b5..6e8a4c89 100644 --- a/src/fahrplan_parser_thread.cpp +++ b/src/fahrplan_parser_thread.cpp @@ -170,6 +170,9 @@ void FahrplanParserThread::run() case 15: m_parser = new ParserFinlandMatka(); break; + case 16: + m_parser = new ParserSearchCH(); + break; } m_name = m_parser->name(); diff --git a/src/fahrplan_parser_thread.h b/src/fahrplan_parser_thread.h index 4ede6256..cbaa3330 100644 --- a/src/fahrplan_parser_thread.h +++ b/src/fahrplan_parser_thread.h @@ -27,6 +27,7 @@ #include "parser/parser_xmloebbat.h" #include "parser/parser_xmlvasttrafikse.h" #include "parser/parser_xmlrejseplanendk.h" +#include "parser/parser_search_ch.h" #include "parser/parser_xmlsbbch.h" #include "parser/parser_xmlnri.h" #include "parser/parser_mobilebahnde.h" diff --git a/src/parser/parser_search_ch.cpp b/src/parser/parser_search_ch.cpp new file mode 100644 index 00000000..0ce87ab6 --- /dev/null +++ b/src/parser/parser_search_ch.cpp @@ -0,0 +1,868 @@ +/**************************************************************************** +** +** This file is a part of Fahrplan. +** +** This program is free software; you can redistribute it and/or modify +** it under the terms of the GNU 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 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 +#ifdef BUILD_FOR_QT5 +#include +#include +#include +#else +#include +#endif +#include +#include +#include +#include + +#include "parser_search_ch.h" + +using namespace parser_search_ch; + +/* + * TimetableRow Class + */ + +TimetableRow::TimetableRow(const QVariantMap& stop) + : stop(stop) +{ + const QVariant& lat = stop.value("lat"); + const QVariant& lon = stop.value("lon"); + + if (!lon.isNull() && !lat.isNull()) { + timetable.latitude = lat.toDouble(); + timetable.longitude = lon.toDouble(); + } +} + +void TimetableRow::load(const QVariantMap& departure) +{ + const QVariantMap& dest = departure.value("terminal").toMap(); + + timetable.destinationStation = dest.value("name").toString(); + timetable.time = ParserSearchCH::tsFromMap(departure, "time").time(); + + this->loadTrainType(departure); + this->loadDelay(departure); + this->loadDeparturePlatform(departure); +} + +void TimetableRow::appendTo(TimetableEntriesList& tt) { + tt.append(timetable); +} + +void TimetableRow::loadTrainTypeWithoutLine(const QVariant& trainType) +{ + const QString& type = trainType.toString(); + + if (type == "ship") { + timetable.trainType = tr("Ship"); + } else if (type == "funicular") { + timetable.trainType = tr("Funicular"); + } else if (type == "cablecar") { + timetable.trainType = tr("Cablecar"); + } else if (type == "gondola") { + timetable.trainType = tr("Gondola"); + } +} + +void TimetableRow::loadTrainType(const QVariantMap& departure) +{ + const QVariant& line = departure.value("line"); + const QVariant& type = departure.value("type"); + + if (type.toString() == "bus") { + timetable.trainType = tr("Bus %1").arg(line.toString()); + } else if (type.toString() == "post") { + timetable.trainType = tr("Bus %1").arg(line.toString()); + } else if (type.toString() == "tram") { + timetable.trainType = tr("Tram %1").arg(line.toString()); + } else if (line.isNull()) { + this->loadTrainTypeWithoutLine(type); + } else { + timetable.trainType = line.toString(); + } +} + +void TimetableRow::loadDeparturePlatform(const QVariantMap& departure) +{ + const QVariant& platform = departure.value("track"); + + if (platform.isNull()) { + timetable.currentStation = stop.value("name").toString(); + } else { + timetable.platform = platform.toString(); + } +} + +void TimetableRow::loadDelay(const QVariantMap& departure) +{ + const QVariant& delay = departure.value("dep_delay"); + + if (delay.isNull()) { + return; + } + + const QString& minutes = delay.toString(); + + if (minutes == "X") { + timetable.miscInfo = tr("Cancelled"); + } else if (minutes != "+0") { + timetable.miscInfo = tr("Departure delayed: %1'").arg(minutes); + } +} + +/* + * Class TrainTypeList + */ + +TrainTypeList::TrainTypeList() : + QObject() +{ +} + +void TrainTypeList::appendUnique(const QString& type) +{ + if (!types.contains(type)) { + types.append(type); + } +} + +void TrainTypeList::append(const QString& type) +{ + if (type == "B") { + appendUnique(tr("Bus")); + } else if (type == "BN") { + appendUnique(tr("Bus")); + } else if (type == "T") { + appendUnique(tr("Tram")); + } else if (type == "FUN") { + appendUnique(tr("Funi")); + } else if (type == "GB") { + appendUnique(tr("Gondola")); + } else if (type == "PB") { + appendUnique(tr("Cableway")); + } else if (type == "BAT") { + appendUnique(tr("Ship")); + } else { + appendUnique(type); + } +} + +QString TrainTypeList::toString() +{ + if (types.length() > 0) { + return types.join(" "); + } else { + return tr("Walk"); + } +} + +/* + * Class JourneySegment + */ + +JourneySegment::JourneySegment(const QVariantMap& leg, const QVariant& exit) +{ + this->setDepartureStation(leg.value("name").toString()); + this->setDirection(leg.value("terminal").toString()); + + this->loadDepartureTrack(leg); + this->loadDelay(leg); + this->loadTrainType(leg); + this->loadExit(leg, exit.toMap()); + +// this->setInternalData1("UNUSED"); +// this->setInternalData2("UNUSED"); +} + +void JourneySegment::loadDepartureTrack(const QVariantMap& leg) +{ + const QVariant& track = leg.value("track"); + + if (!track.isNull()) { + this->setDepartureInfo(tr("Track %1").arg(track.toString())); + } +} + +void JourneySegment::loadDelay(const QVariantMap& leg) +{ + const QVariant& delay = leg.value("dep_delay"); + + if (!delay.isNull()) { + if (delay.toString() == "X") { + this->setInfo(tr("Train cancelled")); + } else if (delay.toString() != "+0") { + QString dt = tr("Departure delayed: %1'").arg(delay.toString()); + this->setInfo(dt); + } + } +} + +void JourneySegment::loadTrainType(const QVariantMap& leg) +{ + const QVariant& line(leg.value("line")); + const QVariant& type(leg.value("type")); + + if (line.toString() == "Bus") { + this->setTrain(tr("Bus")); + } else if (type.toString() == "bus") { + this->setTrain(tr("Bus %1").arg(line.toString())); + } else if (type.toString() == "post") { + this->setTrain(tr("Bus %1").arg(line.toString())); + } else if (type.toString() == "night_bus") { + this->setTrain(tr("Bus %1").arg(line.toString())); + } else if (type.toString() == "tram") { + this->setTrain(tr("Tram %1").arg(line.toString())); + } else if (!line.isNull()) { + this->setTrain(line.toString()); + } else { + this->setTrain(leg.value("type_name").toString()); + } +} + +void JourneySegment::loadExit(const QVariantMap& leg, const QVariantMap& exit) +{ + this->setArrivalStation(exit.value("name").toString()); + + const QVariant& track = exit.value("track"); + if (!track.isNull()) { + this->setArrivalInfo(tr("Track %1").arg(track.toString())); + } + + this->setDepartureDateTime(ParserSearchCH::tsFromMap(leg, "departure")); + this->setArrivalDateTime(ParserSearchCH::tsFromMap(exit, "arrival")); +} + +/* + * Class JourneyConnection + */ + +JourneyConnection::JourneyConnection(int id) +{ + this->setId(QString::number(id++)); +// this->setInternalData1("UNUSED"); +// this->setInternalData2("UNUSED"); +} + +void JourneyConnection::load(const QVariantMap &dataRow) +{ + this->departure = ParserSearchCH::tsFromMap(dataRow, "departure"); + this->arrival = ParserSearchCH::tsFromMap(dataRow, "arrival"); + legs = dataRow.value("legs").toList(); + const QVariant& delay = dataRow.value("dep_delay"); + + this->setDate(this->departure.date()); + this->loadDepartureTime(departure, dataRow.value("dep_delay")); + this->loadArrivalTime(arrival, dataRow.value("arr_delay")); + this->loadDuration(dataRow.value("duration")); + this->loadTrainTypes(legs); + this->countTransfers(legs); + this->checkIfCancelled(delay); +} + +void JourneyConnection::loadDepartureTime(const QDateTime& ts, + const QVariant& delay) +{ + JourneyConnectionTime dep(ts, delay); + this->setDepartureTime(dep.toString()); +} + +void JourneyConnection::loadArrivalTime(const QDateTime& ts, + const QVariant& delay) +{ + JourneyConnectionTime arr(ts, delay); + this->setArrivalTime(arr.toString()); +} + +void JourneyConnection::loadDuration(const QVariant& duration) +{ + int min = duration.toInt() / 60; + this->setDuration(QString("%1:%2").arg(min/60).arg(min%60,2,10,QChar('0'))); +} + +void JourneyConnection::loadTrainTypes(const QVariantList& legs) +{ + TrainTypeList types; + + Q_FOREACH (const QVariant& leg, legs) { + const QVariantMap& details = leg.toMap(); + const QVariant& trainType = details.value("*G"); + + if (trainType.isNull()) { + const QVariant& time = details.value("runningtime"); + const QVariant& type = details.value("type"); + + if ((type.toString() == "walk") && time.toInt() > 600) { + types.append(tr("Walk")); + } + } else { + types.append(trainType.toString()); + } + } + + this->setTrainType(types.toString()); +} + +void JourneyConnection::countTransfers(const QVariantList& legs) +{ + int transfers = 0; + + Q_FOREACH (const QVariant& leg, legs) { + const QVariantMap& details = leg.toMap(); + const QVariant& type = details.value("type"); + + if (!type.isNull()) { + if (type.toString() == "walk") { + continue; + } + transfers++; + } + } + + if (transfers > 0) { + this->setTransfers(QString::number(transfers - 1)); + } +} + +void JourneyConnection::checkIfCancelled(const QVariant& dep_delay) +{ + if (!dep_delay.isNull()) { + if (dep_delay.toString() == "X") { + this->setMiscInfo("Cancelled"); + } + } +} + +/* + * Class JourneyConnectionDetails + */ + +JourneyConnectionDetails::JourneyConnectionDetails(JourneyConnection* con, + const JourneySearch& lastSearch) +{ + this->loadSegments(con); + + this->setId(con->id()); + this->setDepartureStation(lastSearch.from.name); + this->setArrivalStation(lastSearch.to.name); + + if (lastSearch.via.valid) { + this->setViaStation(lastSearch.via.name); + } + + this->setDuration(con->duration()); + this->setDepartureDateTime(con->departure); + this->setArrivalDateTime(con->arrival); +// this->setInfo("UNUSED"); +} + +void JourneyConnectionDetails::loadSegments(JourneyConnection* con) +{ + Q_FOREACH (const QVariant& row, con->legs) { + const QVariantMap& leg(row.toMap()); + const QVariant& exit(leg.value("exit")); + + /* The last segment (arrival) has no exit, skip it */ + if (exit.isNull()) { + continue; + } + + this->appendItem(new JourneySegment(leg, exit)); + } +} + +/* + * Class JourneyConnectionTime + */ + +JourneyConnectionTime::JourneyConnectionTime(const QDateTime& dateTime, + const QVariant& delayValue) + : ts(dateTime), delay(delayValue) {} + +QString JourneyConnectionTime::toString() +{ + QString time = ts.toLocalTime().time().toString("HH:mm"); + + if (delay.isNull() || (delay.toString() == "+0")) { + return time; + } else { + return QString("%1 %2'").arg(time, delay.toString()); + } +} + +/* + * Class JourneySearchResult + */ + +JourneySearchResult::JourneySearchResult(const JourneySearch& search) +{ + this->loadTimeInfo(search.when, search.mode); + + this->setDepartureStation(search.from.name); + this->setArrivalStation(search.to.name); + + if (search.via.valid) { + this->setViaStation(search.via.name); + } +} + +void JourneySearchResult::loadTimeInfo(const QDateTime& when, + const ParserAbstract::Mode& mode) +{ + QString info; + + if (mode == ParserAbstract::Arrival) { + info = tr("Arrivals %1"); + } else { + info = tr("Departures %1"); + } + + QString time(when.toString(tr("ddd MMM d, HH:mm"))); + this->setTimeInfo(info.arg(time)); +} + +/* + * Class ParserSearchCH + */ + +ParserSearchCH::ParserSearchCH(QObject *parent) : + ParserAbstract(parent) +{ +} + +bool ParserSearchCH::supportsGps() +{ + return true; +} + +bool ParserSearchCH::supportsVia() +{ + return true; +} + +bool ParserSearchCH::supportsTimeTable() +{ + return true; +} + +QStringList ParserSearchCH::getTrainRestrictions() +{ + QStringList result; + result.append(tr("All")); + result.append(tr("Train")); + result.append(tr("Tram")); + result.append(tr("Ship")); + result.append(tr("Bus")); + result.append(tr("Cableway")); + + return result; +} + + +/* + * Common code + */ + +#if defined(BUILD_FOR_QT5) +void ParserSearchCH::addRestrictionsToQuery(QUrlQuery& query, int restrictions) +#else +void ParserSearchCH::addRestrictionsToQuery(QUrl& query, int restrictions) +#endif +{ + switch (restrictions) { + case 1: + query.addQueryItem("transportation_types", "train"); + break; + case 2: + query.addQueryItem("transportation_types", "tram"); + break; + case 3: + query.addQueryItem("transportation_types", "ship"); + break; + case 4: + query.addQueryItem("transportation_types", "bus"); + break; + case 5: + query.addQueryItem("transportation_types", "cableway"); + break; + } +} + +/* Inspired by ParserAbstract::parseJson */ +QVariantList ParserSearchCH::parseJsonList(const QByteArray &json) const +{ + QVariantList doc; +#ifdef BUILD_FOR_QT5 + doc = QJsonDocument::fromJson(json).toVariant().toList(); +#else + QString utf8(QString::fromUtf8(json)); + + // Validation of JSON according to RFC 4627, section 6 + QString tmp(utf8); + if (tmp.replace(QRegExp("\"(\\\\.|[^\"\\\\])*\""), "") + .contains(QRegExp("[^,:{}\\[\\]0-9.\\-+Eaeflnr-u \\n\\r\\t]"))) + return doc; + + QScriptEngine *engine = new QScriptEngine(); + doc = engine->evaluate("(" + utf8 + ")").toVariant().toList(); + delete engine; +#endif + + return doc; +} + +void ParserSearchCH::sendRequest(QUrl url) +{ + QList > additionalHeaders; + additionalHeaders.append(QPair("Content-Type", "application/json")); + + qDebug() << "Sending request to " << url.toString(); + sendHttpRequest(url, NULL, additionalHeaders); +} + +QDateTime ParserSearchCH::tsFromMap(const QVariantMap& map, const QString& key) +{ + QString departure = map.value(key).toString(); + QDateTime dt = QDateTime::fromString(departure, "yyyy-MM-dd HH:mm:ss"); +#ifdef BUILD_FOR_QT5 + dt.setTimeZone(QTimeZone("Europe/Zurich")); +#endif + return dt.toLocalTime(); +} + + +/* + * Station lookup (by name or coordinates) + */ + +void ParserSearchCH::findStationsByName(const QString &stationName) +{ + if (stationName.length() < 2) { + return; + } + + if (currentRequestState != FahrplanNS::noneRequest) { + return; + } + + currentRequestState = FahrplanNS::stationsByNameRequest; + + QUrl url("http://timetable.search.ch/api/completion.json"); +#if defined(BUILD_FOR_QT5) + QUrlQuery query; +#else + QUrl query; +#endif + query.addQueryItem("term", stationName); +#if defined(BUILD_FOR_QT5) + url.setQuery(query); +#else + url.setQueryItems(query.queryItems()); +#endif + sendRequest(url); +} + +void ParserSearchCH::findStationsByCoordinates(qreal longitude, qreal latitude) +{ + if (currentRequestState != FahrplanNS::noneRequest) { + return; + } + + currentRequestState = FahrplanNS::stationsByCoordinatesRequest; + + QUrl url("http://timetable.search.ch/api/completion.json"); +#if defined(BUILD_FOR_QT5) + QUrlQuery query; +#else + QUrl query; +#endif + QString pos = QString::number(latitude) + "," + QString::number(longitude); + query.addQueryItem("latlon", pos); + +#if defined(BUILD_FOR_QT5) + url.setQuery(query); +#else + url.setQueryItems(query.queryItems()); +#endif + sendRequest(url); +} + +void ParserSearchCH::parseStationRow(StationsList& rows, const QVariantMap& row) +{ + const QVariant& icon = row.value("iconclass"); + const QVariant& label = row.value("label"); + + /* Ignore street addresses */ + if ((icon.toString() == "sl-icon-type-adr") || + (icon.toString() == "sl-icon-tel-business")) { + return; + } + + Station s; + if (icon.toString() == "sl-icon-type-train") { + s.miscInfo = tr("Train station"); + } else if (icon.toString() == "sl-icon-type-strain") { + s.miscInfo = tr("Train station"); + } else if (icon.toString() == "sl-icon-type-tram") { + s.miscInfo = tr("Tram stop"); + } else if (icon.toString() == "sl-icon-type-ship") { + s.miscInfo = tr("Port"); + } else if (icon.toString() == "sl-icon-type-bus") { + s.miscInfo = tr("Bus stop"); + } else if (icon.toString() == "sl-icon-type-funicular") { + s.miscInfo = tr("Funicular"); + } else if (icon.toString() == "sl-icon-type-gondola") { + s.miscInfo = tr("Gondola"); + } else if (icon.toString() == "sl-icon-type-cablecar") { + s.miscInfo = tr("Cablecar"); + } else { + qDebug() << "Unknown icon type: " << icon.toString(); + } + + if (!label.isNull()) + { + const QString& name = label.toString(); + s.id = name; + s.name = name; + rows.append(s); + } +} + +void ParserSearchCH::parseStations(QNetworkReply *networkReply) +{ + QByteArray allData(networkReply->readAll()); + + QVariantList stations = parseJsonList(allData); + if (stations.isEmpty()) { + qDebug() << "Invalid reply:" << allData; + emit errorOccured(tr("Cannot parse reply from the server")); + return; + } + + StationsList results; + Q_FOREACH (const QVariant& featureData, stations) { + parseStationRow(results, featureData.toMap()); + } + + emit stationsResult(results); +} + +void ParserSearchCH::parseStationsByCoordinates(QNetworkReply *networkReply) +{ + parseStations(networkReply); +} + +void ParserSearchCH::parseStationsByName(QNetworkReply *networkReply) +{ + parseStations(networkReply); +} + + +/* + * Timetable (station board) + */ + +void ParserSearchCH::getTimeTableForStation(const Station &station, + const Station &direction, const QDateTime &when, Mode mode, + int restrictions) +{ + Q_UNUSED(direction); + + if (currentRequestState != FahrplanNS::noneRequest) { + return; + } + + currentRequestState = FahrplanNS::getTimeTableForStationRequest; + + QUrl url("http://timetable.search.ch/api/stationboard.json"); +#if defined(BUILD_FOR_QT5) + QUrlQuery query; +#else + QUrl query; +#endif + query.addQueryItem("stop", station.name); + query.addQueryItem("limit", "15"); + query.addQueryItem("date", when.toString("MM/dd/yyyy")); + query.addQueryItem("time", when.toString("hh:mm")); + query.addQueryItem("show_tracks", "1"); + query.addQueryItem("show_delays", "1"); + + addRestrictionsToQuery(query, restrictions); + + if (mode == Arrival) { + query.addQueryItem("mode", "arrival"); + } +#if defined(BUILD_FOR_QT5) + url.setQuery(query); +#else + url.setQueryItems(query.queryItems()); +#endif + + sendRequest(url); +} + +void ParserSearchCH::parseTimeTable(QNetworkReply *networkReply) +{ + TimetableEntriesList timetable; + QByteArray allData(networkReply->readAll()); + + QVariantMap doc = parseJson(allData); + if (doc.isEmpty()) { + qDebug() << "Invalid reply:" << allData; + emit errorOccured(tr("Cannot parse reply from the server")); + return; + } + + const QVariantMap& stop = doc.value("stop").toMap(); + const QVariantList departures = doc.value("connections").toList(); + + Q_FOREACH (const QVariant& row, departures) { + TimetableRow entry(stop); + entry.load(row.toMap()); + entry.appendTo(timetable); + } + + emit timetableResult(timetable); +} + +/* + * Journey (route) + */ + +void ParserSearchCH::searchJourney(const Station &from, const Station &via, + const Station &to, const QDateTime &when, Mode mode, int restrictions) +{ + if (currentRequestState != FahrplanNS::noneRequest) { + return; + } + + currentRequestState = FahrplanNS::searchJourneyRequest; + + lastJourneySearch.mode = mode; + lastJourneySearch.from = from; + lastJourneySearch.to = to; + lastJourneySearch.via = via; + lastJourneySearch.when = when; + lastJourneySearch.restrictions = restrictions; + + QUrl url("http://timetable.search.ch/api/route.json"); +#if defined(BUILD_FOR_QT5) + QUrlQuery query; +#else + QUrl query; +#endif + query.addQueryItem("from", from.name); + query.addQueryItem("to", to.name); + query.addQueryItem("date", when.toString("MM/dd/yyyy")); + query.addQueryItem("time", when.toString("hh:mm")); + query.addQueryItem("show_delays", "1"); + + if (via.valid) { + query.addQueryItem("via", via.name); + } + + addRestrictionsToQuery(query, restrictions); + + if (mode == Arrival) { + query.addQueryItem("time_type", "arrival"); + } +#if defined(BUILD_FOR_QT5) + url.setQuery(query); +#else + url.setQueryItems(query.queryItems()); +#endif + + sendRequest(url); +} + +void ParserSearchCH::parseSearchJourney(QNetworkReply *networkReply) +{ + JourneySearchResult *result = new JourneySearchResult(lastJourneySearch); + QByteArray jsonData(networkReply->readAll()); + + QVariantMap doc = parseJson(jsonData); + if (doc.isEmpty()) { + qDebug() << "Invalid reply:" << jsonData; + emit errorOccured(tr("Cannot parse reply from the server")); + return; + } + + QVariantList rows = doc.value("connections").toList(); + + while (!details.isEmpty()) { + delete details.takeFirst(); + } + + Q_FOREACH (const QVariant& row, rows) { + JourneyConnection* conn = new JourneyConnection(result->itemcount()); + conn->load(row.toMap()); + + details.append(new JourneyConnectionDetails(conn, lastJourneySearch)); + result->appendItem(conn); + } + + emit journeyResult(result); +} + +void ParserSearchCH::getJourneyDetails(const QString &id) +{ + int i = id.toInt(); + + if (details.length() > i) { + emit journeyDetailsResult(details[i]); + } else { + emit errorOccured(tr("No journey details found.")); + } +} + +void ParserSearchCH::searchJourneyLater() +{ + QDateTime nextQueryTime; + + if (details.length() > 0) { + // last search found something, skip 1 minute ahead from last result + JourneyDetailResultList* result = details.last(); + nextQueryTime = result->departureDateTime().addSecs(60); + } else { + // last search found nothing, skip 1 hour ahead from search time + nextQueryTime = lastJourneySearch.when.addSecs(3600); + } + + searchJourney(lastJourneySearch.from, lastJourneySearch.via, + lastJourneySearch.to, nextQueryTime, Departure, + lastJourneySearch.restrictions); +} + +void ParserSearchCH::searchJourneyEarlier() +{ + QDateTime nextQueryTime; + + if (details.length() > 0) { + // last search found something, skip 1 minute ahead from last result + JourneyDetailResultList* result = details.first(); + nextQueryTime = result->arrivalDateTime().addSecs(-60); + } else { + // last search found nothing, skip 1 hour ahead from search time + nextQueryTime = lastJourneySearch.when.addSecs(-3600); + } + + searchJourney(lastJourneySearch.from, lastJourneySearch.via, + lastJourneySearch.to, nextQueryTime, Arrival, + lastJourneySearch.restrictions); +} diff --git a/src/parser/parser_search_ch.h b/src/parser/parser_search_ch.h new file mode 100644 index 00000000..dd23cca1 --- /dev/null +++ b/src/parser/parser_search_ch.h @@ -0,0 +1,169 @@ +#ifndef PARSER_SEARCH_CH_H +#define PARSER_SEARCH_CH_H + +#include +#include +#include + +#include "parser_abstract.h" + +namespace parser_search_ch +{ + class TimetableRow : public QObject + { + Q_OBJECT + + public: + TimetableRow(const QVariantMap& stop); + void load(const QVariantMap& departure); + void appendTo(TimetableEntriesList& tt); + private: + const QVariantMap& stop; + void loadTrainType(const QVariantMap& departure); + void loadTrainTypeWithoutLine(const QVariant& trainType); + void loadDelay(const QVariantMap& departure); + void loadDeparturePlatform(const QVariantMap&); + + TimetableEntry timetable; + }; + + class TrainTypeList : public QObject + { + Q_OBJECT + + public: + TrainTypeList(); + void append(const QString&); + QString toString(); + private: + void appendUnique(const QString&); + QStringList types; + }; + + class JourneySearch + { + public: + Station from; + Station via; + Station to; + QDateTime when; + ParserAbstract::Mode mode; + int restrictions; + }; + + class JourneySearchResult : public JourneyResultList + { + public: + JourneySearchResult(const JourneySearch&); + private: + void loadTimeInfo(const QDateTime&, const ParserAbstract::Mode&); + }; + + class JourneyConnection : public JourneyResultItem + { + public: + JourneyConnection(int id); + void load(const QVariantMap&); + private: + QDateTime departure; + QDateTime arrival; + QVariantList legs; + + void loadDepartureTime(const QDateTime&, const QVariant& delay); + void loadArrivalTime(const QDateTime&, const QVariant& delay); + void loadDuration(const QVariant&); + void loadTrainTypes(const QVariantList&); + void countTransfers(const QVariantList&); + void checkIfCancelled(const QVariant& dep_delay); + friend class JourneyConnectionDetails; + }; + + class JourneyConnectionDetails : public JourneyDetailResultList + { + public: + JourneyConnectionDetails(JourneyConnection*, const JourneySearch&); + private: + void loadSegments(JourneyConnection*); + }; + + class JourneyConnectionTime + { + public: + JourneyConnectionTime(const QDateTime& time, const QVariant& delay); + QString toString(); + private: + const QDateTime& ts; + const QVariant& delay; + }; + + class JourneySegment : public JourneyDetailResultItem + { + Q_OBJECT + public: + JourneySegment(const QVariantMap& leg, const QVariant& exit); + private: + void loadTrainType(const QVariantMap& leg); + void loadExit(const QVariantMap& leg, const QVariantMap& exit); + void loadDelay(const QVariantMap& leg); + void loadDepartureTrack(const QVariantMap& leg); + }; +} + +class ParserSearchCH : public ParserAbstract +{ + Q_OBJECT +public: + explicit ParserSearchCH(QObject *parent=0); + static QString getName() { return QString("%1 (timetable.search.ch)").arg(tr("Switzerland")); } + virtual QString name() { return getName(); } + virtual QString shortName() { return "search.ch"; } + static QDateTime tsFromMap(const QVariantMap& map, const QString& key); + +public slots: + virtual bool supportsGps(); + virtual bool supportsVia(); + virtual bool supportsTimeTable(); + virtual void findStationsByName(const QString &stationName); + virtual void findStationsByCoordinates(qreal longitude, qreal latitude); + virtual void getTimeTableForStation(const Station ¤tStation, + const Station &directionStation, + const QDateTime &dateTime, + ParserAbstract::Mode mode, + int trainrestrictions); + virtual void searchJourney(const Station &departureStation, + const Station &viaStation, + const Station &arrivalStation, + const QDateTime &dateTime, + ParserAbstract::Mode mode, + int trainrestrictions); + virtual void getJourneyDetails(const QString &id); + +protected: + QStringList getTrainRestrictions(); + virtual void parseStations(QNetworkReply *); + virtual void parseStationsByName(QNetworkReply *); + virtual void parseStationsByCoordinates(QNetworkReply *); + virtual void parseTimeTable(QNetworkReply *); + virtual void parseSearchJourney(QNetworkReply *); + virtual void searchJourneyLater(); + virtual void searchJourneyEarlier(); + virtual void sendRequest(QUrl url); + + QList details; + parser_search_ch::JourneySearch lastJourneySearch; + +private: + QVariantList parseJsonList(const QByteArray &json) const; +#if defined(BUILD_FOR_QT5) + void addRestrictionsToQuery(QUrlQuery&, int); +#else + void addRestrictionsToQuery(QUrl&, int); +#endif + void parseStationRow(StationsList& results, const QVariantMap& properties); + void setTTTrainTypeWithoutLine(TimetableEntry&, const QVariant&); + void setTTDepartureGPSPosition(TimetableEntry&, const QVariantMap&); + void setTTDeparturePlatform(TimetableEntry&, const QVariantMap&, const QVariantMap&); + void setTTDelay(TimetableEntry&, const QVariantMap&); +}; + +#endif diff --git a/translations/fahrplan_de.ts b/translations/fahrplan_de.ts index 4f923da5..9c63c133 100644 --- a/translations/fahrplan_de.ts +++ b/translations/fahrplan_de.ts @@ -2329,6 +2329,134 @@ Von der Fahrplan-App hinzugefügt. Bitte überprüfen Sie diese Informationen vo Salzburg + + ParserSearchCH + + + All + Alle + + + + Train + Zug + + + + Tram + Tram + + + + + Ship + Schiff + + + + + Bus + Bus + + + + Cableway + Seilbahn + + + + + Train station + Bahnhof + + + + Tram stop + Tramhaltestelle + + + + Port + Hafen + + + + Bus stop + Bushaltestelle + + + + + Funicular + Standseilbahn + + + + + Gondola + Gondel + + + + + Cablecar + Seilbahn + + + + + + Cannot parse reply from the server + Kann Antwort vom Server nicht verarbeiten + + + + + + + + Bus %1 + Bus %1 + + + + + Tram %1 + Tram %1 + + + + Cancelled + Ausfall + + + + + Departure delayed: %1' + Abfahrtsverspätung: %1' + + + + + Track %1 + Gleis %1 + + + + Train cancelled + Zugausfall + + + + No journey details found. + Verbindungsdetails nicht gefunden. + + + + Switzerland + Schweiz + + ParserSydneyEFA @@ -2854,4 +2982,69 @@ Von der Fahrplan-App hinzugefügt. Bitte überprüfen Sie diese Informationen vo Zeitplan + + parser_search_ch::JourneyConnection + + + Walk + Fussweg + + + + parser_search_ch::JourneySearchResult + + + Arrivals %1 + Ankünfte %1 + + + + Departures %1 + Abfahrten %1 + + + + ddd MMM d, HH:mm + ddd MMM d, HH:mm + + + + parser_search_ch::TrainTypeList + + + + Bus + Bus + + + + Tram + Tram + + + + Funi + Funi + + + + Gondola + Gondel + + + + Cableway + Seilbahn + + + + Ship + Schiff + + + + Walk + Fussweg + +