KPublicTransport

journeyreply.cpp
1 /*
2  SPDX-FileCopyrightText: 2018 Volker Krause <[email protected]>
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 
22 using namespace KPublicTransport;
23 
24 namespace KPublicTransport {
25 class JourneyReplyPrivate : public ReplyPrivate {
26 public:
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 
38 void 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 
73 bool JourneyReplyPrivate::needToWaitForAssets() const
74 {
75  return request.downloadAssets();
76 }
77 
78 static 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 
89 static bool isImplausibleSection(const JourneySection &section)
90 {
91  if (section.mode() == JourneySection::Transfer && section.from().hasCoordinate() && section.to().hasCoordinate()) {
92  const auto distance = Location::distance(section.from(), section.to());
93  if (section.duration() > 0 && (distance / section.duration()) > 30) {
94  qCDebug(Log) << "discarding journey based on insane transfer speed:" << (distance / section.duration()) << "m/s";
95  return true;
96  }
97  if (distance > 100000) {
98  qCDebug(Log) << "discarding journey with insane transfer distance:" << distance << "m" << section.from().name() << section.to().name();
99  return true;
100  }
101  }
102  return false;
103 }
104 
105 void JourneyReplyPrivate::postProcessJourneys(std::vector<Journey> &journeys)
106 {
107  // try to fill gaps in timezone data
108  for (auto &journey : journeys) {
109  auto sections = journey.takeSections();
110  for (auto &section : sections) {
111  if (section.mode() == JourneySection::Walking) {
112  if (!section.from().timeZone().isValid() && section.to().timeZone().isValid()) {
113  auto from = section.from();
114  from.setTimeZone(section.to().timeZone());
115  section.setFrom(from);
116  auto dt = section.scheduledDepartureTime();
117  dt.setTimeZone(from.timeZone());
118  section.setScheduledDepartureTime(dt);
119  }
120  if (section.from().timeZone().isValid() && !section.to().timeZone().isValid()) {
121  auto to = section.to();
122  to.setTimeZone(section.from().timeZone());
123  section.setTo(to);
124  auto dt = section.scheduledArrivalTime();
125  dt.setTimeZone(to.timeZone());
126  section.setScheduledArrivalTime(dt);
127  }
128  }
129  }
130  journey.setSections(std::move(sections));
131  }
132 
133  // clean up non-transport sections
134  for (auto &journey : journeys) {
135  auto sections = journey.takeSections();
136 
137  // merge adjacent walking sections (yes, we do get that from backends...)
138  for (auto it = sections.begin(); it != sections.end();) {
139  if (it == sections.begin()) {
140  ++it;
141  continue;
142  }
143  auto prevIt = it - 1;
144  if ((*it).mode() == JourneySection::Walking && (*prevIt).mode() == JourneySection::Walking) {
145  (*prevIt).setTo((*it).to());
146  (*prevIt).setScheduledArrivalTime((*it).scheduledArrivalTime());
147  (*prevIt).setExpectedArrivalTime((*it).expectedArrivalTime());
148  (*prevIt).setDistance((*prevIt).distance() + (*it).distance());
149  it = sections.erase(it);
150  continue;
151  }
152 
153  ++it;
154  }
155 
156  // remove pointless sections such as 0-length walks
157  sections.erase(std::remove_if(sections.begin(), sections.end(), isPointlessSection), sections.end());
158  journey.setSections(std::move(sections));
159  }
160 
161  // remove empty or implausible journeys
162  journeys.erase(std::remove_if(journeys.begin(), journeys.end(), [](const auto &journey) {
163  return journey.sections().empty() || std::any_of(journey.sections().begin(), journey.sections().end(), isImplausibleSection);
164  }), journeys.end());
165 }
166 
167 JourneyReply::JourneyReply(const JourneyRequest &req, QObject *parent)
168  : Reply(new JourneyReplyPrivate, parent)
169 {
170  Q_D(JourneyReply);
171  d->request = req;
172  d->nextRequest = req;
173  d->prevRequest = req;
174 }
175 
176 JourneyReply::~JourneyReply() = default;
177 
179 {
180  Q_D(const JourneyReply);
181  return d->request;
182 }
183 
184 const std::vector<Journey>& JourneyReply::result() const
185 {
186  Q_D(const JourneyReply);
187  return d->journeys;
188 }
189 
190 std::vector<Journey>&& JourneyReply::takeResult()
191 {
192  Q_D(JourneyReply);
193  return std::move(d->journeys);
194 }
195 
197 {
198  Q_D(const JourneyReply);
199  if (d->nextRequest.contexts().empty()) {
200  return {};
201  }
202  return d->nextRequest;
203 }
204 
206 {
207  Q_D(const JourneyReply);
208  if (d->prevRequest.contexts().empty()) {
209  return {};
210  }
211  return d->prevRequest;
212 }
213 
214 void JourneyReply::addResult(const AbstractBackend *backend, std::vector<Journey> &&res)
215 {
216  Q_D(JourneyReply);
217  d->postProcessJourneys(res);
218 
219  // update context for next/prev requests
220  // do this first, before res gets moved from below
221  if (d->request.dateTimeMode() == JourneyRequest::Departure && !res.empty()) {
222  // we create a context for later queries here in any case, since we can emulate that generically without backend support
223  auto context = d->nextRequest.context(backend);
224  context.type = RequestContext::Next;
225  for (const auto &jny : res) {
226  context.dateTime = std::max(context.dateTime, jny.scheduledDepartureTime());
227  }
228  d->nextRequest.setContext(backend, std::move(context));
229 
230  context = d->prevRequest.context(backend);
231  context.type = RequestContext::Previous;
232  context.dateTime = res[0].scheduledArrivalTime(); // "invalid" is the minimum...
233  for (const auto &jny : res) {
234  context.dateTime = std::min(context.dateTime, jny.scheduledArrivalTime());
235  }
236  d->prevRequest.setContext(backend, std::move(context));
237  }
238 
239  // if this is a backend with a static timezone, apply this to the result
240  if (backend->timeZone().isValid()) {
241  for (auto &jny : res) {
242  JourneyUtil::applyTimeZone(jny, backend->timeZone());
243  }
244  }
245 
246  // apply line meta data
247  for (auto &jny : res) {
248  JourneyUtil::applyMetaData(jny, request().downloadAssets());
249  }
250 
251  // cache negative hits, positive ones are too short-lived
252  if (res.empty()) {
253  Cache::addNegativeDepartureCacheEntry(backend->backendId(), request().cacheKey());
254  }
255 
256  // apply static attributions if @p backend contributed to the results
257  addAttribution(backend->attribution());
258 
259  // update result
260  if (!res.empty()) {
261  if (d->journeys.empty()) {
262  d->journeys = std::move(res);
263  } else {
264  d->journeys.insert(d->journeys.end(), res.begin(), res.end());
265  }
266  d->emitUpdated(this);
267  }
268 
269  d->pendingOps--;
270  d->emitFinishedIfDone(this);
271 }
272 
273 void JourneyReply::setNextContext(const AbstractBackend *backend, const QVariant &data)
274 {
275  Q_D(JourneyReply);
276  auto context = d->nextRequest.context(backend);
277  context.type = RequestContext::Next;
278  context.backendData = data;
279  d->nextRequest.setContext(backend, std::move(context));
280 }
281 
282 void JourneyReply::setPreviousContext(const AbstractBackend *backend, const QVariant &data)
283 {
284  Q_D(JourneyReply);
285  auto context = d->prevRequest.context(backend);
286  context.type = RequestContext::Previous;
287  context.backendData = data;
288  d->prevRequest.setContext(backend, std::move(context));
289 }
290 
291 void JourneyReply::addError(const AbstractBackend *backend, Reply::Error error, const QString &errorMsg)
292 {
293  if (error == Reply::NotFoundError) {
294  Cache::addNegativeJourneyCacheEntry(backend->backendId(), request().cacheKey());
295  } else {
296  qCDebug(Log) << backend->backendId() << error << errorMsg;
297  }
298  Reply::addError(error, errorMsg);
299 }
JourneyRequest nextRequest() const
Returns a request object for querying journeys following the ones returned by this reply...
Query operations and data types for accessing realtime public transport information from online servi...
Definition: attribution.cpp:16
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:619
dateTime() represents the desired departure time.
const std::vector< Journey > & result() const
Returns the retrieved journeys.
QDateTime scheduledArrivalTime
Planned arrival time.
Definition: journey.h:67
QString cacheKey() const
Unique string representation used for caching results.
static Journey merge(const Journey &lhs, const Journey &rhs)
Merge two instances.
Definition: journey.cpp:651
A segment of a journey plan.
Definition: journey.h:31
void setTimeZone(const QTimeZone &toZone)
std::vector< Journey > && takeResult()
Returns the retrieved journeys for moving elsewhere.
QDateTime scheduledDepartureTime
Planned departure time.
Definition: journey.h:56
Journey query response.
Definition: journeyreply.h:21
void error(QWidget *parent, const QString &text, const QString &caption=QString(), Options options=Notify)
The requested journey/departure/place could not be found.
Definition: reply.h:34
Mode mode
Mode of transport for this section.
Definition: journey.h:53
int distance(const GeoCoordinates &coord1, const GeoCoordinates &coord2)
int duration
Duration of the section in seconds.
Definition: journey.h:78
bool isEmpty() const
Returns true if this is an empty/not-set path.
Definition: path.cpp:109
QTimeZone timeZone() const
The timezone this location is in, if known.
Definition: location.cpp:81
Error
Error types.
Definition: reply.h:31
Nothing went wrong.
Definition: reply.h:32
KPublicTransport::Location to
Arrival location of this segment.
Definition: journey.h:85
QString name
Human-readable name of the location.
Definition: location.h:50
bool isValid() const const
JourneyRequest previousRequest() const
Returns a request object for querying journeys preceding the ones returned by this reply...
KPublicTransport::Path path
Movement path for this journey section.
Definition: journey.h:141
static float distance(float lat1, float lon1, float lat2, float lon2)
Compute the distance between two geo coordinates, in meters.
Definition: location.cpp:424
JourneyRequest request() const
The request this is the reply for.
Describes a journey search.
KPublicTransport::Location from
Departure location of this segment.
Definition: journey.h:83
Query response base class.
Definition: reply.h:24
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Sat Oct 23 2021 23:05:21 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.