File indexing completed on 2024-05-12 04:42:38
0001 /* 0002 SPDX-FileCopyrightText: 2018 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "navitiaparser.h" 0008 #include "navitiaphysicalmode.h" 0009 #include "../geo/geojson_p.h" 0010 0011 #include <KPublicTransport/Attribution> 0012 #include <KPublicTransport/Journey> 0013 #include <KPublicTransport/Line> 0014 #include <KPublicTransport/RentalVehicle> 0015 #include <KPublicTransport/Stopover> 0016 0017 #include <QColor> 0018 #include <QDebug> 0019 #include <QJsonDocument> 0020 #include <QJsonObject> 0021 #include <QTimeZone> 0022 0023 using namespace KPublicTransport; 0024 0025 NavitiaParser::NavitiaParser() = default; 0026 NavitiaParser::~NavitiaParser() = default; 0027 0028 static QDateTime parseDateTime(const QJsonValue &v, const QTimeZone &tz) 0029 { 0030 auto dt = QDateTime::fromString(v.toString(), QStringLiteral("yyyyMMddTHHmmss")); 0031 if (tz.isValid()) { 0032 dt.setTimeZone(tz); 0033 } 0034 return dt; 0035 } 0036 0037 static void parseAdminRegion(Location &loc, const QJsonObject &ar) 0038 { 0039 const auto level = ar.value(QLatin1String("level")).toInt(); 0040 if (level == 8) { 0041 loc.setLocality(ar.value(QLatin1String("name")).toString()); 0042 } 0043 else if (level == 10) { 0044 loc.setPostalCode(ar.value(QLatin1String("zip_code")).toString()); 0045 } 0046 } 0047 0048 static Location parseLocation(const QJsonObject &obj) 0049 { 0050 Location loc; 0051 loc.setName(obj.value(QLatin1String("label")).toString()); 0052 // TODO parse more fields 0053 0054 const auto coord = obj.value(QLatin1String("coord")).toObject(); 0055 loc.setCoordinate(coord.value(QLatin1String("lat")).toString().toDouble(), coord.value(QLatin1String("lon")).toString().toDouble()); 0056 0057 auto tz = obj.value(QLatin1String("timezone")).toString(); 0058 if (tz.isEmpty()) { 0059 tz = obj.value(QLatin1String("stop_area")).toObject().value(QLatin1String("timezone")).toString(); 0060 } 0061 if (!tz.isEmpty()) { 0062 loc.setTimeZone(QTimeZone(tz.toUtf8())); 0063 } 0064 0065 const auto ars = obj.value(QLatin1String("administrative_regions")).toArray(); 0066 for (const auto &ar : ars) { 0067 parseAdminRegion(loc, ar.toObject()); 0068 } 0069 0070 auto codes = obj.value(QLatin1String("codes")).toArray(); 0071 if (codes.empty()) { 0072 codes = obj.value(QLatin1String("stop_area")).toObject().value(QLatin1String("codes")).toArray(); 0073 } 0074 for (const auto &codeV : std::as_const(codes)) { 0075 const auto code = codeV.toObject(); 0076 if (code.value(QLatin1String("type")).toString() == QLatin1String("UIC8")) { 0077 loc.setIdentifier(QStringLiteral("uic"), code.value(QLatin1String("value")).toString().left(7)); 0078 } 0079 } 0080 const auto id = obj.value(QLatin1String("id")).toString(); 0081 if (id.startsWith(QLatin1String("poi:osm:node:"))) { 0082 loc.setIdentifier(QStringLiteral("osm"), QLatin1Char('n') + QStringView(id).mid(13)); 0083 } 0084 0085 const auto poi_type = obj.value(QLatin1String("poi_type")).toObject().value(QLatin1String("id")).toString(); 0086 if (poi_type == QLatin1String("poi_type:amenity:bicycle_rental")) { 0087 RentalVehicleNetwork network; 0088 network.setName(obj.value(QLatin1String("properties")).toObject().value(QLatin1String("network")).toString()); 0089 0090 RentalVehicleStation station; 0091 station.setNetwork(network); 0092 const auto standsObj = obj.value(QLatin1String("stands")).toObject(); 0093 station.setAvailableVehicles(standsObj.value(QLatin1String("available_bikes")).toInt(-1)); 0094 station.setCapacity(standsObj.value(QLatin1String("total_stands")).toInt(-1)); 0095 0096 loc.setType(Location::RentedVehicleStation); 0097 loc.setData(station); 0098 } 0099 0100 return loc; 0101 } 0102 0103 static Location parseWrappedLocation(const QJsonObject &obj) 0104 { 0105 const auto type = obj.value(QLatin1String("embedded_type")).toString(); 0106 auto loc = parseLocation(obj.value(type).toObject()); 0107 loc.setName(obj.value(QLatin1String("name")).toString()); 0108 if (type == QLatin1String("stop_area") || type == QLatin1String("stop_point")) { 0109 loc.setType(Location::Stop); 0110 } 0111 return loc; 0112 } 0113 0114 static void parseStopDateTime(const QJsonObject &dtObj, Stopover &departure) 0115 { 0116 departure.setScheduledDepartureTime(parseDateTime(dtObj.value(QLatin1String("base_departure_date_time")), departure.stopPoint().timeZone())); 0117 departure.setScheduledArrivalTime(parseDateTime(dtObj.value(QLatin1String("base_arrival_date_time")), departure.stopPoint().timeZone())); 0118 if (dtObj.value(QLatin1String("data_freshness")).toString() != QLatin1String("base_schedule")) { 0119 departure.setExpectedDepartureTime(parseDateTime(dtObj.value(QLatin1String("departure_date_time")), departure.stopPoint().timeZone())); 0120 departure.setExpectedArrivalTime(parseDateTime(dtObj.value(QLatin1String("arrival_date_time")), departure.stopPoint().timeZone())); 0121 } 0122 } 0123 0124 static Path parsePathWithInstructionStartCoordinate(const QPolygonF &pathLineString, const QJsonArray &pathArray) 0125 { 0126 std::vector<PathSection> pathSections; 0127 pathSections.reserve(pathArray.size()); 0128 PathSection prevPathSec; 0129 int prevPolyIdx = 0; 0130 bool isFirstSection = true; 0131 for (const auto &pathV : pathArray) { 0132 const auto pathObj = pathV.toObject(); 0133 PathSection pathSec; 0134 pathSec.setDescription(pathObj.value(QLatin1String("instruction")).toString()); 0135 if (pathSec.description().isEmpty()) { 0136 pathSec.setDescription(pathObj.value(QLatin1String("name")).toString()); 0137 } 0138 0139 if (!isFirstSection) { 0140 const auto coordObj = pathObj.value(QLatin1String("instruction_start_coordinate")).toObject(); 0141 const QPointF coord(coordObj.value(QLatin1String("lon")).toString().toDouble(), coordObj.value(QLatin1String("lat")).toString().toDouble()); 0142 const auto it = std::min_element(pathLineString.begin() + prevPolyIdx, pathLineString.end(), [coord](QPointF lhs, QPointF rhs) { 0143 return Location::distance(lhs.y(), lhs.x(), coord.y(), coord.x()) < Location::distance(rhs.y(), rhs.x(), coord.y(), coord.x()); 0144 }); 0145 int polyIdx = std::distance(pathLineString.begin(), it); 0146 0147 QPolygonF subPoly; 0148 subPoly.reserve(polyIdx - prevPolyIdx + 1); 0149 std::copy(pathLineString.begin() + prevPolyIdx, pathLineString.begin() + polyIdx + 1, std::back_inserter(subPoly)); 0150 prevPathSec.setPath(std::move(subPoly)); 0151 prevPolyIdx = polyIdx; 0152 pathSections.push_back(std::move(prevPathSec)); 0153 } else { 0154 isFirstSection = false; 0155 } 0156 prevPathSec = pathSec; 0157 } 0158 0159 QPolygonF subPoly; 0160 subPoly.reserve(prevPolyIdx - pathLineString.size() + 1); 0161 std::copy(pathLineString.begin() + prevPolyIdx, pathLineString.end(), std::back_inserter(subPoly)); 0162 prevPathSec.setPath(std::move(subPoly)); 0163 pathSections.push_back(std::move(prevPathSec)); 0164 0165 Path path; 0166 path.setSections(std::move(pathSections)); 0167 return path; 0168 } 0169 0170 static Path parsePathFromLength(const QPolygonF &pathLineString, const QJsonArray &pathArray) 0171 { 0172 std::vector<PathSection> pathSections; 0173 pathSections.reserve(pathArray.size()); 0174 int prevPolyIdx = 0; 0175 for (const auto &pathV : pathArray) { 0176 const auto pathObj = pathV.toObject(); 0177 PathSection pathSec; 0178 pathSec.setDescription(pathObj.value(QLatin1String("instruction")).toString()); 0179 if (pathSec.description().isEmpty()) { 0180 pathSec.setDescription(pathObj.value(QLatin1String("name")).toString()); 0181 } 0182 0183 int polyIdx = prevPolyIdx + 1; 0184 const auto length = pathObj.value(QLatin1String("length")).toInt(); 0185 for (float l = 0.0f, prevDelta = std::numeric_limits<float>::max(); polyIdx < pathLineString.size(); ++polyIdx) { 0186 l += Location::distance(pathLineString.at(polyIdx - 1).y(), pathLineString.at(polyIdx - 1).x(), pathLineString.at(polyIdx).y(), pathLineString.at(polyIdx).x()); 0187 auto delta = length - l; 0188 if (delta <= 0) { 0189 if (prevDelta < -delta) { 0190 --polyIdx; 0191 } 0192 break; 0193 } 0194 prevDelta = delta; 0195 } 0196 0197 QPolygonF subPoly; 0198 subPoly.reserve(polyIdx - prevPolyIdx + 1); 0199 std::copy(pathLineString.begin() + prevPolyIdx, pathLineString.begin() + std::min<int>(polyIdx + 1, pathLineString.size()), std::back_inserter(subPoly)); 0200 pathSec.setPath(std::move(subPoly)); 0201 prevPolyIdx = polyIdx; 0202 pathSections.push_back(std::move(pathSec)); 0203 } 0204 0205 // if there's a trailing polygon end, attached that to the last section 0206 if (prevPolyIdx + 1 < pathLineString.size()) { 0207 auto poly = pathSections.back().path(); 0208 std::copy(pathLineString.begin() + prevPolyIdx + 1, pathLineString.end(), std::back_inserter(poly)); 0209 pathSections.back().setPath(std::move(poly)); 0210 } 0211 0212 Path path; 0213 path.setSections(std::move(pathSections)); 0214 return path; 0215 } 0216 0217 JourneySection NavitiaParser::parseJourneySection(const QJsonObject &obj) const 0218 { 0219 JourneySection section; 0220 section.setFrom(parseWrappedLocation(obj.value(QLatin1String("from")).toObject())); 0221 section.setTo(parseWrappedLocation(obj.value(QLatin1String("to")).toObject())); 0222 0223 const auto displayInfo = obj.value(QLatin1String("display_informations")).toObject(); 0224 Line line; 0225 line.setName(displayInfo.value(QLatin1String("label")).toString()); 0226 line.setColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("color")).toString())); 0227 line.setTextColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("text_color")).toString())); 0228 line.setModeString(displayInfo.value(QLatin1String("commercial_mode")).toString()); 0229 const auto links = obj.value(QLatin1String("links")).toArray(); 0230 for (const auto &v : links) { 0231 const auto link = v.toObject(); 0232 const auto type = link.value(QLatin1String("type")).toString(); 0233 if (type == QLatin1String("physical_mode")) { 0234 line.setMode(NavitiaPhysicalMode::parsePhysicalMode(link.value(QLatin1String("id")).toString())); 0235 } 0236 parseDisruptionLink(section, link); 0237 } 0238 const auto displayLinks = displayInfo.value(QLatin1String("links")).toArray(); 0239 for (const auto &v : displayLinks) { 0240 const auto link = v.toObject(); 0241 parseDisruptionLink(section, link); 0242 } 0243 0244 Route route; 0245 route.setDirection(displayInfo.value(QLatin1String("direction")).toString()); 0246 route.setLine(line); 0247 section.setRoute(route); 0248 0249 const auto hasRealTime = obj.value(QLatin1String("data_freshness")).toString() != QLatin1String("base_schedule"); 0250 section.setScheduledDepartureTime(parseDateTime(obj.value(QLatin1String("base_departure_date_time")), section.from().timeZone())); 0251 section.setScheduledArrivalTime(parseDateTime(obj.value(QLatin1String("base_arrival_date_time")), section.to().timeZone())); 0252 if (hasRealTime) { 0253 section.setScheduledArrivalTime(parseDateTime(obj.value(QLatin1String("arrival_date_time")), section.to().timeZone())); 0254 section.setScheduledDepartureTime(parseDateTime(obj.value(QLatin1String("departure_date_time")), section.from().timeZone())); 0255 } 0256 0257 const auto typeStr = obj.value(QLatin1String("type")).toString(); 0258 if (typeStr == QLatin1String("public_transport")) { 0259 section.setMode(JourneySection::PublicTransport); 0260 // TODO we have no type for parking/rent/return yet 0261 } else if (typeStr == QLatin1String("transfer") || typeStr == QLatin1String("park") || 0262 typeStr == QLatin1String("bss_rent") || typeStr == QLatin1String("bss_put_back")) { 0263 section.setMode(JourneySection::Transfer); 0264 } else if (typeStr == QLatin1String("street_network") || typeStr == QLatin1String("walking") || typeStr == QLatin1String("crow_fly")) { 0265 const auto modeStr = obj.value(QLatin1String("mode")).toString(); 0266 if (modeStr == QLatin1String("bike")) { 0267 if (section.from().type() == Location::RentedVehicleStation) { 0268 section.setMode(JourneySection::RentedVehicle); 0269 RentalVehicle v; 0270 v.setType(RentalVehicle::Bicycle); 0271 v.setNetwork(section.from().rentalVehicleStation().network()); 0272 section.setRentalVehicle(v); 0273 } else { 0274 section.setMode(JourneySection::IndividualTransport); 0275 section.setIndividualTransport({IndividualTransport::Bike, IndividualTransport::None}); 0276 } 0277 } else if (modeStr == QLatin1String("car")) { 0278 section.setMode(JourneySection::IndividualTransport); 0279 section.setIndividualTransport({IndividualTransport::Car, IndividualTransport::None}); 0280 } else { 0281 section.setMode(JourneySection::Walking); 0282 } 0283 section.setDistance(obj.value(QLatin1String("geojson")).toObject().value(QLatin1String("properties")).toArray().at(0).toObject().value(QLatin1String("length")).toInt()); 0284 } else if (typeStr == QLatin1String("waiting")) { 0285 section.setMode(JourneySection::Waiting); 0286 } 0287 0288 const auto stopsDtA = obj.value(QLatin1String("stop_date_times")).toArray(); 0289 if (stopsDtA.size() > 2) { // departure/arrival are included, we don't want that 0290 std::vector<Stopover> stops; 0291 stops.reserve(stopsDtA.size() - 2); 0292 for (auto it = std::next(stopsDtA.begin()); it != std::prev(stopsDtA.end()); ++it) { 0293 const auto obj = (*it).toObject(); 0294 Stopover stop; 0295 stop.setStopPoint(parseLocation(obj.value(QLatin1String("stop_point")).toObject())); 0296 parseStopDateTime(obj, stop); 0297 if (!hasRealTime) { // intermediate stops seems to miss the "data_freshness" field, so propagate that 0298 stop.setExpectedArrivalTime({}); 0299 stop.setExpectedDepartureTime({}); 0300 } 0301 stops.push_back(std::move(stop)); 0302 } 0303 section.setIntermediateStops(std::move(stops)); 0304 } 0305 0306 const auto emissionObj = obj.value(QLatin1String("co2_emission")).toObject(); 0307 section.setCo2Emission(emissionObj.value(QLatin1String("value")).toDouble(-1)); 0308 0309 const auto pathLineString = GeoJson::readLineString(obj.value(QLatin1String("geojson")).toObject()); 0310 const auto pathArray = obj.value(QLatin1String("path")).toArray(); 0311 if (!pathArray.empty()) { 0312 const auto hasInstrStartCoordinate = pathArray.at(0).toObject().contains(QLatin1String("instruction_start_coordinate")); 0313 Path path; 0314 if (hasInstrStartCoordinate) { 0315 path = parsePathWithInstructionStartCoordinate(pathLineString, pathArray); 0316 } else { 0317 path = parsePathFromLength(pathLineString, pathArray); 0318 } 0319 section.setPath(std::move(path)); 0320 } else if (!pathLineString.isEmpty()) { 0321 Path path; 0322 PathSection pathSection; 0323 pathSection.setPath(pathLineString); 0324 path.setSections({pathSection}); 0325 section.setPath(std::move(path)); 0326 } 0327 0328 return section; 0329 } 0330 0331 Journey NavitiaParser::parseJourney(const QJsonObject &obj) const 0332 { 0333 Journey journey; 0334 0335 const auto secArray = obj.value(QLatin1String("sections")).toArray(); 0336 std::vector<JourneySection> sections; 0337 sections.reserve(secArray.size()); 0338 for (const auto &v : secArray) { 0339 sections.push_back(parseJourneySection(v.toObject())); 0340 } 0341 journey.setSections(std::move(sections)); 0342 return journey; 0343 } 0344 0345 std::vector<Journey> NavitiaParser::parseJourneys(const QByteArray &data) 0346 { 0347 const auto topObj = QJsonDocument::fromJson(data).object(); 0348 m_disruptions = topObj.value(QLatin1String("disruptions")).toArray(); 0349 const auto journeys = topObj.value(QLatin1String("journeys")).toArray(); 0350 0351 std::vector<Journey> res; 0352 res.reserve(journeys.size()); 0353 0354 for (const auto &v : journeys) { 0355 res.push_back(parseJourney(v.toObject())); 0356 } 0357 0358 parseLinks(topObj.value(QLatin1String("links")).toArray()); 0359 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray()); 0360 return res; 0361 } 0362 0363 Stopover NavitiaParser::parseDeparture(const QJsonObject &obj) const 0364 { 0365 // TODO remove code duplication with journey parsing 0366 Stopover departure; 0367 const auto displayInfo = obj.value(QLatin1String("display_informations")).toObject(); 0368 0369 Line line; 0370 line.setName(displayInfo.value(QLatin1String("label")).toString()); 0371 line.setColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("color")).toString())); 0372 line.setTextColor(QColor(QLatin1Char('#') + displayInfo.value(QLatin1String("text_color")).toString())); 0373 line.setModeString(displayInfo.value(QLatin1String("commercial_mode")).toString()); 0374 const auto links = obj.value(QLatin1String("links")).toArray(); 0375 for (const auto &v : links) { 0376 const auto link = v.toObject(); 0377 if (link.value(QLatin1String("type")).toString() == QLatin1String("physical_mode")) { 0378 line.setMode(NavitiaPhysicalMode::parsePhysicalMode(link.value(QLatin1String("id")).toString())); 0379 } 0380 parseDisruptionLink(departure, link); 0381 } 0382 const auto displayLinks = displayInfo.value(QLatin1String("links")).toArray(); 0383 for (const auto &v : displayLinks) { 0384 const auto link = v.toObject(); 0385 parseDisruptionLink(departure, link); 0386 } 0387 0388 Route route; 0389 route.setDirection(displayInfo.value(QLatin1String("direction")).toString()); 0390 const auto routeObj = obj.value(QLatin1String("route")).toObject(); 0391 const auto destObj = routeObj.value(QLatin1String("direction")).toObject(); 0392 route.setDestination(parseWrappedLocation(destObj)); 0393 route.setLine(line); 0394 0395 departure.setRoute(route); 0396 departure.setStopPoint(parseLocation(obj.value(QLatin1String("stop_point")).toObject())); 0397 parseStopDateTime(obj.value(QLatin1String("stop_date_time")).toObject(), departure); 0398 0399 return departure; 0400 } 0401 0402 std::vector<Stopover> NavitiaParser::parseDepartures(const QByteArray &data) 0403 { 0404 const auto topObj = QJsonDocument::fromJson(data).object(); 0405 m_disruptions = topObj.value(QLatin1String("disruptions")).toArray(); 0406 const auto departures = topObj.value(QLatin1String("departures")).toArray(); 0407 0408 std::vector<Stopover> res; 0409 res.reserve(departures.size()); 0410 0411 for (const auto &v : departures) { 0412 res.push_back(parseDeparture(v.toObject())); 0413 } 0414 0415 parseLinks(topObj.value(QLatin1String("links")).toArray()); 0416 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray()); 0417 return res; 0418 } 0419 0420 std::vector<Location> NavitiaParser::parsePlacesNearby(const QByteArray &data) 0421 { 0422 const auto topObj = QJsonDocument::fromJson(data).object(); 0423 const auto placesNearby = topObj.value(QLatin1String("places_nearby")).toArray(); 0424 0425 std::vector<Location> res; 0426 res.reserve(placesNearby.size()); 0427 0428 for (const auto &v : placesNearby) { 0429 res.push_back(parseWrappedLocation(v.toObject())); 0430 } 0431 0432 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray()); 0433 return res; 0434 } 0435 0436 std::vector<Location> NavitiaParser::parsePlaces(const QByteArray &data) 0437 { 0438 const auto topObj = QJsonDocument::fromJson(data).object(); 0439 const auto placesNearby = topObj.value(QLatin1String("places")).toArray(); 0440 0441 std::vector<Location> res; 0442 res.reserve(placesNearby.size()); 0443 0444 for (const auto &v : placesNearby) { 0445 res.push_back(parseWrappedLocation(v.toObject())); 0446 } 0447 0448 parseAttributions(topObj.value(QLatin1String("feed_publishers")).toArray()); 0449 return res; 0450 } 0451 0452 QString NavitiaParser::parseErrorMessage(const QByteArray &data) 0453 { 0454 const auto topObj = QJsonDocument::fromJson(data).object(); 0455 const auto errorObj = topObj.value(QLatin1String("error")).toObject(); 0456 0457 // id field contains error enum, might also be useful 0458 return errorObj.value(QLatin1String("message")).toString(); 0459 } 0460 0461 void NavitiaParser::parseLinks(const QJsonArray &links) 0462 { 0463 for (const auto &v : links) { 0464 const auto link = v.toObject(); 0465 const auto rel = link.value(QLatin1String("rel")).toString(); 0466 if (rel == QLatin1String("next")) { 0467 nextLink = QUrl(link.value(QLatin1String("href")).toString()); 0468 } else if (rel == QLatin1String("prev")) { 0469 prevLink = QUrl(link.value(QLatin1String("href")).toString()); 0470 } 0471 } 0472 } 0473 0474 void NavitiaParser::parseAttributions(const QJsonArray& feeds) 0475 { 0476 for (const auto &v : feeds) { 0477 const auto feed = v.toObject(); 0478 Attribution attr; 0479 attr.setName(feed.value(QLatin1String("name")).toString()); 0480 QUrl url(feed.value(QLatin1String("url")).toString()); 0481 if (!url.isEmpty()) { 0482 url.setScheme(QStringLiteral("https")); // missing in a few cases 0483 } 0484 attr.setUrl(url); 0485 attr.setLicense(feed.value(QLatin1String("license")).toString()); 0486 // TODO map known licenses to spdx links 0487 attributions.push_back(std::move(attr)); 0488 } 0489 } 0490 0491 QJsonObject NavitiaParser::findDisruption(const QString &id) const 0492 { 0493 for (const auto &v : m_disruptions) { 0494 const auto disruption = v.toObject(); 0495 if (disruption.value(QLatin1String("uri")).toString() == id) { 0496 return disruption; 0497 } 0498 } 0499 return {}; 0500 } 0501 0502 static QString disruptionMessage(const QJsonObject &distruption) 0503 { 0504 const auto msgs = distruption.value(QLatin1String("messages")).toArray(); 0505 for (const auto &msgV : msgs) { 0506 const auto msg = msgV.toObject(); 0507 const auto types = msg.value(QLatin1String("channel")).toObject().value(QLatin1String("types")).toArray(); 0508 for (const auto &typeV : types) { 0509 if (typeV.toString() == QLatin1String("web")) { 0510 return msg.value(QLatin1String("text")).toString(); 0511 } 0512 } 0513 } 0514 return {}; 0515 } 0516 0517 template <typename T> 0518 void NavitiaParser::parseDisruptionLink(T &element, const QJsonObject &link) const 0519 { 0520 const auto type = link.value(QLatin1String("type")).toString(); 0521 if (type != QLatin1String("disruption")) { 0522 return; 0523 } 0524 0525 const auto id = link.value(QLatin1String("id")).toString(); 0526 const auto disruption = findDisruption(id); 0527 if (disruption.value(QLatin1String("severity")).toObject().value(QLatin1String("effect")).toString() == QLatin1String("NO_SERVICE")) { 0528 element.setDisruptionEffect(Disruption::NoService); 0529 } 0530 element.addNote(disruptionMessage(disruption)); 0531 }