KPublicTransport

journeyreply.cpp
1/*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "journeyreply.h"
8#include "reply_p.h"
9#include "journeyrequest.h"
10#include "requestcontext_p.h"
11#include "logging.h"
12#include "backends/abstractbackend.h"
13#include "backends/cache.h"
14#include "datatypes/journeyutil_p.h"
15
16#include <KPublicTransport/Journey>
17#include <KPublicTransport/Location>
18
19#include <QDateTime>
20#include <QTimeZone>
21
22using namespace KPublicTransport;
23
24namespace KPublicTransport {
25class JourneyReplyPrivate : public ReplyPrivate {
26public:
27 void finalizeResult() override;
28 bool needToWaitForAssets() const override;
29 static void postProcessJourneys(std::vector<Journey> &journeys);
30
31 JourneyRequest request;
32 JourneyRequest nextRequest;
33 JourneyRequest prevRequest;
34 std::vector<Journey> journeys;
35};
36}
37
38void JourneyReplyPrivate::finalizeResult()
39{
40 if (journeys.empty()) {
41 return;
42 }
43
45 errorMsg.clear();
46
47 // merge results, aligned by first transport departure
48 std::sort(journeys.begin(), journeys.end(), JourneyUtil::firstTransportDepartureLessThan);
49 for (auto it = journeys.begin(); it != journeys.end(); ++it) {
50 for (auto mergeIt = it + 1; mergeIt != journeys.end();) {
51 if (!JourneyUtil::firstTransportDepartureEqual(*it, *mergeIt)) {
52 break;
53 }
54
55 if (Journey::isSame(*it, *mergeIt)) {
56 *it = Journey::merge(*it, *mergeIt);
57 mergeIt = journeys.erase(mergeIt);
58 } else {
59 ++mergeIt;
60 }
61 }
62 }
63
64 // sort by departure time for display
65 std::sort(journeys.begin(), journeys.end(), [](const auto &lhs, const auto &rhs) {
66 return lhs.scheduledDepartureTime() < rhs.scheduledDepartureTime();
67 });
68
69 nextRequest.purgeLoops(request);
70 prevRequest.purgeLoops(request);
71}
72
73bool JourneyReplyPrivate::needToWaitForAssets() const
74{
75 return request.downloadAssets();
76}
77
78static bool isPointlessSection(const JourneySection &section)
79{
80 if (section.mode() == JourneySection::Waiting) {
81 return section.duration() < 60;
82 }
83 if (section.mode() == JourneySection::Walking) {
84 return section.duration() < 60 && section.path().isEmpty();
85 }
86 return false;
87}
88
89static bool isImplausibleSection(const JourneySection &section)
90{
91 if ((section.mode() == JourneySection::Transfer || section.mode() == JourneySection::Walking)
92 && section.from().hasCoordinate() && section.to().hasCoordinate())
93 {
94 const auto distance = Location::distance(section.from(), section.to());
95 if (section.duration() > 0 && (distance / section.duration()) > 30) {
96 qCDebug(Log) << "discarding journey based on insane transfer/walking speed:" << (distance / section.duration()) << "m/s";
97 return true;
98 }
99 if (distance > 100000) {
100 qCDebug(Log) << "discarding journey with insane transfer/walking distance:" << distance << "m" << section.from().name() << section.to().name();
101 return true;
102 }
103 }
104 return false;
105}
106
107void JourneyReplyPrivate::postProcessJourneys(std::vector<Journey> &journeys)
108{
109 // try to fill gaps in timezone data
110 for (auto &journey : journeys) {
111 auto sections = journey.takeSections();
112 for (auto &section : sections) {
113 if (section.mode() == JourneySection::Walking) {
114 if (!section.from().timeZone().isValid() && section.to().timeZone().isValid()) {
115 auto from = section.from();
116 from.setTimeZone(section.to().timeZone());
117 section.setFrom(from);
118 auto dt = section.scheduledDepartureTime();
119 dt.setTimeZone(from.timeZone());
120 section.setScheduledDepartureTime(dt);
121 }
122 if (section.from().timeZone().isValid() && !section.to().timeZone().isValid()) {
123 auto to = section.to();
124 to.setTimeZone(section.from().timeZone());
125 section.setTo(to);
126 auto dt = section.scheduledArrivalTime();
127 dt.setTimeZone(to.timeZone());
128 section.setScheduledArrivalTime(dt);
129 }
130 }
131 }
132 journey.setSections(std::move(sections));
133 }
134
135 // clean up non-transport sections
136 for (auto &journey : journeys) {
137 auto sections = journey.takeSections();
138
139 // merge adjacent walking sections (yes, we do get that from backends...)
140 for (auto it = sections.begin(); it != sections.end();) {
141 if (it == sections.begin()) {
142 ++it;
143 continue;
144 }
145 auto prevIt = it - 1;
146 if ((*it).mode() == JourneySection::Walking && (*prevIt).mode() == JourneySection::Walking) {
147 (*prevIt).setTo((*it).to());
148 (*prevIt).setScheduledArrivalTime((*it).scheduledArrivalTime());
149 (*prevIt).setExpectedArrivalTime((*it).expectedArrivalTime());
150 (*prevIt).setDistance((*prevIt).distance() + (*it).distance());
151 it = sections.erase(it);
152 continue;
153 }
154
155 ++it;
156 }
157
158 // remove pointless sections such as 0-length walks
159 sections.erase(std::remove_if(sections.begin(), sections.end(), isPointlessSection), sections.end());
160
161 // remove implausible paths
162 for (auto &section : sections) {
163 if (!section.from().hasCoordinate() || !section.to().hasCoordinate() || section.path().isEmpty()) {
164 continue;
165 }
166
167 const auto pointDist = Location::distance(section.from(), section.to());
168 const auto pathDist = section.path().distance();
169 if (pathDist > pointDist * 10) {
170 qCDebug(Log) << "Dropping implausibly long path:" << pointDist << pathDist;
171 section.setPath({});
172 }
173 }
174
175 journey.setSections(std::move(sections));
176 }
177
178 // remove empty or implausible journeys
179 journeys.erase(std::remove_if(journeys.begin(), journeys.end(), [](const auto &journey) {
180 return journey.sections().empty() || std::any_of(journey.sections().begin(), journey.sections().end(), isImplausibleSection);
181 }), journeys.end());
182}
183
184JourneyReply::JourneyReply(const JourneyRequest &req, QObject *parent)
185 : Reply(new JourneyReplyPrivate, parent)
186{
188 d->request = req;
189 d->nextRequest = req;
190 d->prevRequest = req;
191}
192
193JourneyReply::~JourneyReply() = default;
194
196{
197 Q_D(const JourneyReply);
198 return d->request;
199}
200
201const std::vector<Journey>& JourneyReply::result() const
202{
203 Q_D(const JourneyReply);
204 return d->journeys;
205}
206
207std::vector<Journey>&& JourneyReply::takeResult()
208{
210 return std::move(d->journeys);
211}
212
214{
215 Q_D(const JourneyReply);
216 if (d->nextRequest.contexts().empty()) {
217 return {};
218 }
219 return d->nextRequest;
220}
221
223{
224 Q_D(const JourneyReply);
225 if (d->prevRequest.contexts().empty()) {
226 return {};
227 }
228 return d->prevRequest;
229}
230
231void JourneyReply::addResult(const AbstractBackend *backend, std::vector<Journey> &&res)
232{
234 d->postProcessJourneys(res);
235
236 // update context for next/prev requests
237 // do this first, before res gets moved from below
238 if (d->request.dateTimeMode() == JourneyRequest::Departure && !res.empty()) {
239 // we create a context for later queries here in any case, since we can emulate that generically without backend support
240 auto context = d->nextRequest.context(backend);
241 context.type = RequestContext::Next;
242 for (const auto &jny : res) {
243 context.dateTime = std::max(context.dateTime, jny.scheduledDepartureTime());
244 }
245 d->nextRequest.setContext(backend, std::move(context));
246
247 context = d->prevRequest.context(backend);
248 context.type = RequestContext::Previous;
249 context.dateTime = res[0].scheduledArrivalTime(); // "invalid" is the minimum...
250 for (const auto &jny : res) {
251 context.dateTime = std::min(context.dateTime, jny.scheduledArrivalTime());
252 }
253 d->prevRequest.setContext(backend, std::move(context));
254 }
255
256 // if this is a backend with a static timezone, apply this to the result
257 if (backend->timeZone().isValid()) {
258 for (auto &jny : res) {
259 JourneyUtil::applyTimeZone(jny, backend->timeZone());
260 }
261 }
262
263 // apply line meta data
264 for (auto &jny : res) {
265 jny.applyMetaData(request().downloadAssets());
266 }
267
268 // cache negative hits, positive ones are too short-lived
269 if (res.empty()) {
270 Cache::addNegativeJourneyCacheEntry(backend->backendId(), request().cacheKey());
271 }
272
273 // apply static attributions if @p backend contributed to the results
274 addAttribution(backend->attribution());
275
276 // update result
277 if (!res.empty()) {
278 if (d->journeys.empty()) {
279 d->journeys = std::move(res);
280 } else {
281 d->journeys.insert(d->journeys.end(), res.begin(), res.end());
282 }
283 d->emitUpdated(this);
284 }
285
286 d->pendingOps--;
287 d->emitFinishedIfDone(this);
288}
289
290void JourneyReply::setNextContext(const AbstractBackend *backend, const QVariant &data)
291{
293 auto context = d->nextRequest.context(backend);
294 context.type = RequestContext::Next;
295 context.backendData = data;
296 d->nextRequest.setContext(backend, std::move(context));
297}
298
299void JourneyReply::setPreviousContext(const AbstractBackend *backend, const QVariant &data)
300{
302 auto context = d->prevRequest.context(backend);
303 context.type = RequestContext::Previous;
304 context.backendData = data;
305 d->prevRequest.setContext(backend, std::move(context));
306}
307
308void JourneyReply::addError(const AbstractBackend *backend, Reply::Error error, const QString &errorMsg)
309{
311 Cache::addNegativeJourneyCacheEntry(backend->backendId(), request().cacheKey());
312 } else {
313 qCDebug(Log) << backend->backendId() << error << errorMsg;
314 }
315 Reply::addError(error, errorMsg);
316}
Journey query response.
const std::vector< Journey > & result() const
Returns the retrieved journeys.
JourneyRequest nextRequest() const
Returns a request object for querying journeys following the ones returned by this reply.
std::vector< Journey > && takeResult()
Returns the retrieved journeys for moving elsewhere.
JourneyRequest previousRequest() const
Returns a request object for querying journeys preceding the ones returned by this reply.
JourneyRequest request() const
The request this is the reply for.
Describes a journey search.
bool downloadAssets
Download graphic assets such as line logos for the data requested here.
@ Departure
dateTime() represents the desired departure time.
A segment of a journey plan.
Definition journey.h:32
KPublicTransport::Path path
Movement path for this journey section.
Definition journey.h:141
KPublicTransport::Location from
Departure location of this segment.
Definition journey.h:83
QDateTime scheduledDepartureTime
Planned departure time.
Definition journey.h:56
KPublicTransport::Location to
Arrival location of this segment.
Definition journey.h:85
int duration
Duration of the section in seconds.
Definition journey.h:78
Mode mode
Mode of transport for this section.
Definition journey.h:53
QDateTime scheduledArrivalTime
Planned arrival time.
Definition journey.h:67
static bool isSame(const Journey &lhs, const Journey &rhs)
Checks if two instances refer to the same journey (which does not necessarily mean they are exactly e...
Definition journey.cpp:674
static Journey merge(const Journey &lhs, const Journey &rhs)
Merge two instances.
Definition journey.cpp:706
QTimeZone timeZone() const
The timezone this location is in, if known.
Definition location.cpp:81
static float distance(float lat1, float lon1, float lat2, float lon2)
Compute the distance between two geo coordinates, in meters.
Definition location.cpp:425
QString name
Human-readable name of the location.
Definition location.h:50
int distance
The length of this path in meters.
Definition path.h:98
bool isEmpty() const
Returns true if this is an empty/not-set path.
Definition path.cpp:109
Query response base class.
Definition reply.h:25
Error error() const
Error code.
Definition reply.cpp:46
Error
Error types.
Definition reply.h:31
@ NoError
Nothing went wrong.
Definition reply.h:32
@ NotFoundError
The requested journey/departure/place could not be found.
Definition reply.h:34
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
Query operations and data types for accessing realtime public transport information from online servi...
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
void setTimeZone(const QTimeZone &toZone)
bool isValid() const const
Q_D(Todo)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:13:06 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.