1 /*
2 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6
7 #include "navitiaparser.h"
8 #include "../geo/geojson_p.h"
9
10 #include <KPublicTransport/Attribution>
11 #include <KPublicTransport/Journey>
12 #include <KPublicTransport/Line>
13 #include <KPublicTransport/RentalVehicle>
14 #include <KPublicTransport/Stopover>
15
16 #include <QColor>
17 #include <QDebug>
18 #include <QJsonDocument>
19 #include <QJsonObject>
20 #include <QTimeZone>
21
22 using namespace KPublicTransport;
23
24 NavitiaParser::NavitiaParser() = default;
25 NavitiaParser::~NavitiaParser() = default;
26
parseDateTime(const QJsonValue & v,const QTimeZone & tz)27 static QDateTime parseDateTime(const QJsonValue &v, const QTimeZone &tz)
28 {
29 auto dt = QDateTime::fromString(v.toString(), QStringLiteral("yyyyMMddTHHmmss"));
30 if (tz.isValid()) {
31 dt.setTimeZone(tz);
32 }
33 return dt;
34 }
35
36 struct {
37 const char *name;
38 Line::Mode mode;
39 } static const navitia_physical_modes[] = {
40 { "Air", Line::Air },
41 { "Boat", Line::Boat },
42 { "Bus", Line::Bus },
43 { "BusRapidTransit", Line::BusRapidTransit },
44 { "Coach", Line::Coach },
45 { "Ferry", Line::Ferry },
46 { "Funicular", Line::Funicular },
47 { "LocalTrain", Line::LocalTrain },
48 { "LongDistanceTrain", Line::LongDistanceTrain },
49 { "Metro", Line::Metro },
50 { "RailShuttle", Line::RailShuttle },
51 { "RapidTransit", Line::RapidTransit },
52 { "Shuttle", Line::Shuttle },
53 { "Taxi", Line::Taxi },
54 { "Train", Line::Train },
55 { "Tramway", Line::Tramway }
56 };
57
parsePhysicalMode(const QString & mode)58 static Line::Mode parsePhysicalMode(const QString &mode)
59 {
60 const auto modeStr = mode.toLatin1();
61 if (!modeStr.startsWith("physical_mode:")) {
62 return Line::Unknown;
63 }
64 for (auto it = std::begin(navitia_physical_modes); it != std::end(navitia_physical_modes); ++it) {
65 if (strcmp(modeStr.constData() + 14, it->name) == 0) {
66 return it->mode;
67 }
68 }
69
70 return Line::Unknown;
71 }
72
parseAdminRegion(Location & loc,const QJsonObject & ar)73 static void parseAdminRegion(Location &loc, const QJsonObject &ar)
74 {
75 const auto level = ar.value(QLatin1String("level")).toInt();
76 if (level == 8) {
77 loc.setLocality(ar.value(QLatin1String("name")).toString());
78 }
79 else if (level == 10) {
80 loc.setPostalCode(ar.value(QLatin1String("zip_code")).toString());
81 }
82 }
83
parseLocation(const QJsonObject & obj)84 static Location parseLocation(const QJsonObject &obj)
85 {
86 Location loc;
87 loc.setName(obj.value(QLatin1String("label")).toString());
88 // TODO parse more fields
89
90 const auto coord = obj.value(QLatin1String("coord")).toObject();
91 loc.setCoordinate(coord.value(QLatin1String("lat")).toString().toDouble(), coord.value(QLatin1String("lon")).toString().toDouble());
92
93 auto tz = obj.value(QLatin1String("timezone")).toString();
94 if (tz.isEmpty()) {
95 tz = obj.value(QLatin1String("stop_area")).toObject().value(QLatin1String("timezone")).toString();
96 }
97 if (!tz.isEmpty()) {
98 loc.setTimeZone(QTimeZone(tz.toUtf8()));
99 }
100
101 const auto ars = obj.value(QLatin1String("administrative_regions")).toArray();
102 for (const auto &ar : ars) {
103 parseAdminRegion(loc, ar.toObject());
104 }
105
106 auto codes = obj.value(QLatin1String("codes")).toArray();
107 if (codes.empty()) {
108 codes = obj.value(QLatin1String("stop_area")).toObject().value(QLatin1String("codes")).toArray();
109 }
110 for (const auto &codeV : qAsConst(codes)) {
111 const auto code = codeV.toObject();
112 if (code.value(QLatin1String("type")).toString() == QLatin1String("UIC8")) {
113 loc.setIdentifier(QStringLiteral("uic"), code.value(QLatin1String("value")).toString().left(7));
114 }
115 }
116 const auto id = obj.value(QLatin1String("id")).toString();
117 if (id.startsWith(QLatin1String("poi:osm:node:"))) {
118 loc.setIdentifier(QStringLiteral("osm"), QLatin1Char('n') + QStringView(id).mid(13));
119 }
120
121 const auto poi_type = obj.value(QLatin1String("poi_type")).toObject().value(QLatin1String("id")).toString();
122 if (poi_type == QLatin1String("poi_type:amenity:bicycle_rental")) {
123 RentalVehicleNetwork network;
124 network.setName(obj.value(QLatin1String("properties")).toObject().value(QLatin1String("network")).toString());
125
126 RentalVehicleStation station;
127 station.setNetwork(network);
128 const auto standsObj = obj.value(QLatin1String("stands")).toObject();
129 station.setAvailableVehicles(standsObj.value(QLatin1String("available_bikes")).toInt(-1));
130 station.setCapacity(standsObj.value(QLatin1String("total_stands")).toInt(-1));
131
132 loc.setType(Location::RentedVehicleStation);
133 loc.setData(station);
134 }
135
136 return loc;
137 }
138
parseWrappedLocation(const QJsonObject & obj)139 static Location parseWrappedLocation(const QJsonObject &obj)
140 {
141 const auto type = obj.value(QLatin1String("embedded_type")).toString();
142 auto loc = parseLocation(obj.value(type).toObject());
143 loc.setName(obj.value(QLatin1String("name")).toString());
144 if (type == QLatin1String("stop_area") || type == QLatin1String("stop_point")) {
145 loc.setType(Location::Stop);
146 }
147 return loc;
148 }
149
parseStopDateTime(const QJsonObject & dtObj,Stopover & departure)150 static void parseStopDateTime(const QJsonObject &dtObj, Stopover &departure)
151 {
152 departure.setScheduledDepartureTime(parseDateTime(dtObj.value(QLatin1String("base_departure_date_time")), departure.stopPoint().timeZone()));
153 departure.setScheduledArrivalTime(parseDateTime(dtObj.value(QLatin1String("base_arrival_date_time")), departure.stopPoint().timeZone()));
154 if (dtObj.value(QLatin1String("data_freshness")).toString() != QLatin1String("base_schedule")) {
155 departure.setExpectedDepartureTime(parseDateTime(dtObj.value(QLatin1String("departure_date_time")), departure.stopPoint().timeZone()));
156 departure.setExpectedArrivalTime(parseDateTime(dtObj.value(QLatin1String("arrival_date_time")), departure.stopPoint().timeZone()));
157 }
158 }
159
parsePathWithInstructionStartCoordinate(const QPolygonF & pathLineString,const QJsonArray & pathArray)160 static Path parsePathWithInstructionStartCoordinate(const QPolygonF &pathLineString, const QJsonArray &pathArray)
161 {
162 std::vector<PathSection> pathSections;
163 pathSections.reserve(pathArray.size());
164 PathSection prevPathSec;
165 int prevPolyIdx = 0;
166 bool isFirstSection = true;
167 for (const auto &pathV : pathArray) {
168 const auto pathObj = pathV.toObject();
169 PathSection pathSec;
170 pathSec.setDescription(pathObj.value(QLatin1String("instruction")).toString());
171 if (pathSec.description().isEmpty()) {
172 pathSec.setDescription(pathObj.value(QLatin1String("name")).toString());
173 }
174
175 if (!isFirstSection) {
176 const auto coordObj = pathObj.value(QLatin1String("instruction_start_coordinate")).toObject();
177 const QPointF coord(coordObj.value(QLatin1String("lon")).toString().toDouble(), coordObj.value(QLatin1String("lat")).toString().toDouble());
178 const auto it = std::min_element(pathLineString.begin() + prevPolyIdx, pathLineString.end(), [coord](QPointF lhs, QPointF rhs) {
179 return Location::distance(lhs.y(), lhs.x(), coord.y(), coord.x()) < Location::distance(rhs.y(), rhs.x(), coord.y(), coord.x());
180 });
181 int polyIdx = std::distance(pathLineString.begin(), it);
182
183 QPolygonF subPoly;
184 subPoly.reserve(polyIdx - prevPolyIdx + 1);
185 std::copy(pathLineString.begin() + prevPolyIdx, pathLineString.begin() + polyIdx + 1, std::back_inserter(subPoly));
186 prevPathSec.setPath(std::move(subPoly));
187 prevPolyIdx = polyIdx;
188 pathSections.push_back(std::move(prevPathSec));
189 } else {
190 isFirstSection = false;
191 }
192 prevPathSec = pathSec;
193 }
194
195 QPolygonF subPoly;
196 subPoly.reserve(prevPolyIdx - pathLineString.size() + 1);
197 std::copy(pathLineString.begin() + prevPolyIdx, pathLineString.end(), std::back_inserter(subPoly));
198 prevPathSec.setPath(std::move(subPoly));
199 pathSections.push_back(std::move(prevPathSec));
200
201 Path path;
202 path.setSections(std::move(pathSections));
203 return path;
204 }
205
parsePathFromLength(const QPolygonF & pathLineString,const QJsonArray & pathArray)206 static Path parsePathFromLength(const QPolygonF &pathLineString, const QJsonArray &pathArray)
207 {
208 std::vector<PathSection> pathSections;
209 pathSections.reserve(pathArray.size());
210 int prevPolyIdx = 0;
211 for (const auto &pathV : pathArray) {
212 const auto pathObj = pathV.toObject();
213 PathSection pathSec;
214 pathSec.setDescription(pathObj.value(QLatin1String("instruction")).toString());
215 if (pathSec.description().isEmpty()) {
216 pathSec.setDescription(pathObj.value(QLatin1String("name")).toString());
217 }
218
219 int polyIdx = prevPolyIdx + 1;
220 const auto length = pathObj.value(QLatin1String("length")).toInt();
221 for (float l = 0.0f, prevDelta = std::numeric_limits<float>::max(); polyIdx < pathLineString.size(); ++polyIdx) {
222 l += Location::distance(pathLineString.at(polyIdx - 1).y(), pathLineString.at(polyIdx - 1).x(), pathLineString.at(polyIdx).y(), pathLineString.at(polyIdx).x());
223 auto delta = length - l;
224 if (delta <= 0) {
225 if (prevDelta < -delta) {
226 --polyIdx;
227 }
228 break;
229 }
230 prevDelta = delta;
231 }
232
233 QPolygonF subPoly;
234 subPoly.reserve(polyIdx - prevPolyIdx + 1);
235 std::copy(pathLineString.begin() + prevPolyIdx, pathLineString.begin() + std::min(polyIdx + 1, pathLineString.size()), std::back_inserter(subPoly));
236 pathSec.setPath(std::move(subPoly));
237 prevPolyIdx = polyIdx;
238 pathSections.push_back(std::move(pathSec));
239 }
240
241 // if there's a trailing polygon end, attached that to the last section
242 if (prevPolyIdx + 1 < pathLineString.size()) {
243 auto poly = pathSections.back().path();
244 std::copy(pathLineString.begin() + prevPolyIdx + 1, pathLineString.end(), std::back_inserter(poly));
245 pathSections.back().setPath(std::move(poly));
246 }
247
248 Path path;
249 path.setSections(std::move(pathSections));
250 return path;
251 }
252
parseJourneySection(const QJsonObject & obj) const253 JourneySection NavitiaParser::parseJourneySection(const QJsonObject &obj) const
254 {
255 JourneySection section;
256 section.setFrom(parseWrappedLocation(obj.value(QLatin1String("from")).toObject()));
257 section.setTo(parseWrappedLocation(obj.value(QLatin1String("to")).toObject()));
258
259 const auto displayInfo = obj.value(QLatin1String("display_informations")).toObject();
260 Line line;
261 line.setName(displayInfo.value(QLatin1String("label")).toString());
262 line.setColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("color")).toString()));
263 line.setTextColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("text_color")).toString()));
264 line.setModeString(displayInfo.value(QLatin1String("commercial_mode")).toString());
265 const auto links = obj.value(QLatin1String("links")).toArray();
266 for (const auto &v : links) {
267 const auto link = v.toObject();
268 const auto type = link.value(QLatin1String("type")).toString();
269 if (type == QLatin1String("physical_mode")) {
270 line.setMode(parsePhysicalMode(link.value(QLatin1String("id")).toString()));
271 }
272 parseDisruptionLink(section, link);
273 }
274 const auto displayLinks = displayInfo.value(QLatin1String("links")).toArray();
275 for (const auto &v : displayLinks) {
276 const auto link = v.toObject();
277 parseDisruptionLink(section, link);
278 }
279
280 Route route;
281 route.setDirection(displayInfo.value(QLatin1String("direction")).toString());
282 route.setLine(line);
283 section.setRoute(route);
284
285 const auto hasRealTime = obj.value(QLatin1String("data_freshness")).toString() != QLatin1String("base_schedule");
286 section.setScheduledDepartureTime(parseDateTime(obj.value(QLatin1String("base_departure_date_time")), section.from().timeZone()));
287 section.setScheduledArrivalTime(parseDateTime(obj.value(QLatin1String("base_arrival_date_time")), section.to().timeZone()));
288 if (hasRealTime) {
289 section.setScheduledArrivalTime(parseDateTime(obj.value(QLatin1String("arrival_date_time")), section.to().timeZone()));
290 section.setScheduledDepartureTime(parseDateTime(obj.value(QLatin1String("departure_date_time")), section.from().timeZone()));
291 }
292
293 const auto typeStr = obj.value(QLatin1String("type")).toString();
294 if (typeStr == QLatin1String("public_transport")) {
295 section.setMode(JourneySection::PublicTransport);
296 // TODO we have no type for parking/rent/return yet
297 } else if (typeStr == QLatin1String("transfer") || typeStr == QLatin1String("park") ||
298 typeStr == QLatin1String("bss_rent") || typeStr == QLatin1String("bss_put_back")) {
299 section.setMode(JourneySection::Transfer);
300 } else if (typeStr == QLatin1String("street_network") || typeStr == QLatin1String("walking") || typeStr == QLatin1String("crow_fly")) {
301 const auto modeStr = obj.value(QLatin1String("mode")).toString();
302 if (modeStr == QLatin1String("bike")) {
303 if (section.from().type() == Location::RentedVehicleStation) {
304 section.setMode(JourneySection::RentedVehicle);
305 RentalVehicle v;
306 v.setType(RentalVehicle::Bicycle);
307 v.setNetwork(section.from().rentalVehicleStation().network());
308 section.setRentalVehicle(v);
309 } else {
310 section.setMode(JourneySection::IndividualTransport);
311 section.setIndividualTransport({IndividualTransport::Bike, IndividualTransport::None});
312 }
313 } else if (modeStr == QLatin1String("car")) {
314 section.setMode(JourneySection::IndividualTransport);
315 section.setIndividualTransport({IndividualTransport::Car, IndividualTransport::None});
316 } else {
317 section.setMode(JourneySection::Walking);
318 }
319 section.setDistance(obj.value(QLatin1String("geojson")).toObject().value(QLatin1String("properties")).toArray().at(0).toObject().value(QLatin1String("length")).toInt());
320 } else if (typeStr == QLatin1String("waiting")) {
321 section.setMode(JourneySection::Waiting);
322 }
323
324 const auto stopsDtA = obj.value(QLatin1String("stop_date_times")).toArray();
325 if (stopsDtA.size() > 2) { // departure/arrival are included, we don't want that
326 std::vector<Stopover> stops;
327 stops.reserve(stopsDtA.size() - 2);
328 for (auto it = std::next(stopsDtA.begin()); it != std::prev(stopsDtA.end()); ++it) {
329 const auto obj = (*it).toObject();
330 Stopover stop;
331 stop.setStopPoint(parseLocation(obj.value(QLatin1String("stop_point")).toObject()));
332 parseStopDateTime(obj, stop);
333 if (!hasRealTime) { // intermediate stops seems to miss the "data_freshness" field, so propagate that
334 stop.setExpectedArrivalTime({});
335 stop.setExpectedDepartureTime({});
336 }
337 stops.push_back(std::move(stop));
338 }
339 section.setIntermediateStops(std::move(stops));
340 }
341
342 const auto emissionObj = obj.value(QLatin1String("co2_emission")).toObject();
343 section.setCo2Emission(emissionObj.value(QLatin1String("value")).toDouble(-1));
344
345 const auto pathLineString = GeoJson::readLineString(obj.value(QLatin1String("geojson")).toObject());
346 const auto pathArray = obj.value(QLatin1String("path")).toArray();
347 if (!pathArray.empty()) {
348 const auto hasInstrStartCoordinate = pathArray.at(0).toObject().contains(QLatin1String("instruction_start_coordinate"));
349 Path path;
350 if (hasInstrStartCoordinate) {
351 path = parsePathWithInstructionStartCoordinate(pathLineString, pathArray);
352 } else {
353 path = parsePathFromLength(pathLineString, pathArray);
354 }
355 section.setPath(std::move(path));
356 } else if (!pathLineString.isEmpty()) {
357 Path path;
358 PathSection pathSection;
359 pathSection.setPath(pathLineString);
360 path.setSections({pathSection});
361 section.setPath(std::move(path));
362 }
363
364 return section;
365 }
366
parseJourney(const QJsonObject & obj) const367 Journey NavitiaParser::parseJourney(const QJsonObject &obj) const
368 {
369 Journey journey;
370
371 const auto secArray = obj.value(QLatin1String("sections")).toArray();
372 std::vector<JourneySection> sections;
373 sections.reserve(secArray.size());
374 for (const auto &v : secArray) {
375 sections.push_back(parseJourneySection(v.toObject()));
376 }
377 journey.setSections(std::move(sections));
378 return journey;
379 }
380
parseJourneys(const QByteArray & data)381 std::vector<Journey> NavitiaParser::parseJourneys(const QByteArray &data)
382 {
383 const auto topObj = QJsonDocument::fromJson(data).object();
384 m_disruptions = topObj.value(QLatin1String("disruptions")).toArray();
385 const auto journeys = topObj.value(QLatin1String("journeys")).toArray();
386
387 std::vector<Journey> res;
388 res.reserve(journeys.size());
389
390 for (const auto &v : journeys) {
391 res.push_back(parseJourney(v.toObject()));
392 }
393
394 parseLinks(topObj.value(QLatin1String("links")).toArray());
395 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray());
396 return res;
397 }
398
parseDeparture(const QJsonObject & obj) const399 Stopover NavitiaParser::parseDeparture(const QJsonObject &obj) const
400 {
401 // TODO remove code duplication with journey parsing
402 Stopover departure;
403 const auto displayInfo = obj.value(QLatin1String("display_informations")).toObject();
404
405 Line line;
406 line.setName(displayInfo.value(QLatin1String("label")).toString());
407 line.setColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("color")).toString()));
408 line.setTextColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("text_color")).toString()));
409 line.setModeString(displayInfo.value(QLatin1String("commercial_mode")).toString());
410 const auto links = obj.value(QLatin1String("links")).toArray();
411 for (const auto &v : links) {
412 const auto link = v.toObject();
413 if (link.value(QLatin1String("type")).toString() == QLatin1String("physical_mode")) {
414 line.setMode(parsePhysicalMode(link.value(QLatin1String("id")).toString()));
415 }
416 parseDisruptionLink(departure, link);
417 }
418 const auto displayLinks = displayInfo.value(QLatin1String("links")).toArray();
419 for (const auto &v : displayLinks) {
420 const auto link = v.toObject();
421 parseDisruptionLink(departure, link);
422 }
423
424 Route route;
425 route.setDirection(displayInfo.value(QLatin1String("direction")).toString());
426 const auto routeObj = obj.value(QLatin1String("route")).toObject();
427 const auto destObj = routeObj.value(QLatin1String("direction")).toObject();
428 route.setDestination(parseWrappedLocation(destObj));
429 route.setLine(line);
430
431 departure.setRoute(route);
432 departure.setStopPoint(parseLocation(obj.value(QLatin1String("stop_point")).toObject()));
433 parseStopDateTime(obj.value(QLatin1String("stop_date_time")).toObject(), departure);
434
435 return departure;
436 }
437
parseDepartures(const QByteArray & data)438 std::vector<Stopover> NavitiaParser::parseDepartures(const QByteArray &data)
439 {
440 const auto topObj = QJsonDocument::fromJson(data).object();
441 m_disruptions = topObj.value(QLatin1String("disruptions")).toArray();
442 const auto departures = topObj.value(QLatin1String("departures")).toArray();
443
444 std::vector<Stopover> res;
445 res.reserve(departures.size());
446
447 for (const auto &v : departures) {
448 res.push_back(parseDeparture(v.toObject()));
449 }
450
451 parseLinks(topObj.value(QLatin1String("links")).toArray());
452 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray());
453 return res;
454 }
455
parsePlacesNearby(const QByteArray & data)456 std::vector<Location> NavitiaParser::parsePlacesNearby(const QByteArray &data)
457 {
458 const auto topObj = QJsonDocument::fromJson(data).object();
459 const auto placesNearby = topObj.value(QLatin1String("places_nearby")).toArray();
460
461 std::vector<Location> res;
462 res.reserve(placesNearby.size());
463
464 for (const auto &v : placesNearby) {
465 res.push_back(parseWrappedLocation(v.toObject()));
466 }
467
468 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray());
469 return res;
470 }
471
parsePlaces(const QByteArray & data)472 std::vector<Location> NavitiaParser::parsePlaces(const QByteArray &data)
473 {
474 const auto topObj = QJsonDocument::fromJson(data).object();
475 const auto placesNearby = topObj.value(QLatin1String("places")).toArray();
476
477 std::vector<Location> res;
478 res.reserve(placesNearby.size());
479
480 for (const auto &v : placesNearby) {
481 res.push_back(parseWrappedLocation(v.toObject()));
482 }
483
484 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray());
485 return res;
486 }
487
parseErrorMessage(const QByteArray & data)488 QString NavitiaParser::parseErrorMessage(const QByteArray &data)
489 {
490 const auto topObj = QJsonDocument::fromJson(data).object();
491 const auto errorObj = topObj.value(QLatin1String("error")).toObject();
492
493 // id field contains error enum, might also be useful
494 return errorObj.value(QLatin1String("message")).toString();
495 }
496
parseLinks(const QJsonArray & links)497 void NavitiaParser::parseLinks(const QJsonArray &links)
498 {
499 for (const auto &v : links) {
500 const auto link = v.toObject();
501 const auto rel = link.value(QLatin1String("rel")).toString();
502 if (rel == QLatin1String("next")) {
503 nextLink = QUrl(link.value(QLatin1String("href")).toString());
504 } else if (rel == QLatin1String("prev")) {
505 prevLink = QUrl(link.value(QLatin1String("href")).toString());
506 }
507 }
508 }
509
parseAttributions(const QJsonArray & feeds)510 void NavitiaParser::parseAttributions(const QJsonArray& feeds)
511 {
512 for (const auto &v : feeds) {
513 const auto feed = v.toObject();
514 Attribution attr;
515 attr.setName(feed.value(QLatin1String("name")).toString());
516 QUrl url(feed.value(QLatin1String("url")).toString());
517 if (!url.isEmpty()) {
518 url.setScheme(QStringLiteral("https")); // missing in a few cases
519 }
520 attr.setUrl(url);
521 attr.setLicense(feed.value(QLatin1String("license")).toString());
522 // TODO map known licenses to spdx links
523 attributions.push_back(std::move(attr));
524 }
525 }
526
findDisruption(const QString & id) const527 QJsonObject NavitiaParser::findDisruption(const QString &id) const
528 {
529 for (const auto &v : m_disruptions) {
530 const auto disruption = v.toObject();
531 if (disruption.value(QLatin1String("uri")).toString() == id) {
532 return disruption;
533 }
534 }
535 return {};
536 }
537
disruptionMessage(const QJsonObject & distruption)538 static QString disruptionMessage(const QJsonObject &distruption)
539 {
540 const auto msgs = distruption.value(QLatin1String("messages")).toArray();
541 for (const auto &msgV : msgs) {
542 const auto msg = msgV.toObject();
543 const auto types = msg.value(QLatin1String("channel")).toObject().value(QLatin1String("types")).toArray();
544 for (const auto &typeV : types) {
545 if (typeV.toString() == QLatin1String("web")) {
546 return msg.value(QLatin1String("text")).toString();
547 }
548 }
549 }
550 return {};
551 }
552
553 template <typename T>
parseDisruptionLink(T & element,const QJsonObject & link) const554 void NavitiaParser::parseDisruptionLink(T &element, const QJsonObject &link) const
555 {
556 const auto type = link.value(QLatin1String("type")).toString();
557 if (type != QLatin1String("disruption")) {
558 return;
559 }
560
561 const auto id = link.value(QLatin1String("id")).toString();
562 const auto disruption = findDisruption(id);
563 if (disruption.value(QLatin1String("severity")).toObject().value(QLatin1String("effect")).toString() == QLatin1String("NO_SERVICE")) {
564 element.setDisruptionEffect(Disruption::NoService);
565 }
566 element.addNote(disruptionMessage(disruption));
567 }
568