File indexing completed on 2024-05-12 04:42:37

0001 /*
0002     SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "ivvassparser.h"
0008 #include "ivvassproductmap.h"
0009 #include "ifopt/ifoptutil.h"
0010 
0011 #include <KPublicTransport/Equipment>
0012 #include <KPublicTransport/Journey>
0013 #include <KPublicTransport/Stopover>
0014 
0015 #include <QByteArray>
0016 #include <QDebug>
0017 #include <QJsonArray>
0018 #include <QJsonDocument>
0019 #include <QJsonObject>
0020 #include <QRegularExpression>
0021 
0022 #include <cmath>
0023 
0024 using namespace KPublicTransport;
0025 
0026 IvvAssParser::IvvAssParser(const QTimeZone &tz, const QString &locationIdentifier)
0027     : m_timezone(tz)
0028     , m_locationIdentifier(locationIdentifier)
0029 {
0030 }
0031 
0032 IvvAssParser::LocationData IvvAssParser::parseLocation(const QJsonObject &stopObj) const
0033 {
0034     LocationData r;
0035     r.loc.setLatitude(stopObj.value(QLatin1String("lat")).toDouble(NAN));
0036     r.loc.setLongitude(stopObj.value(QLatin1String("lon")).toDouble(NAN));
0037     if (!r.loc.hasCoordinate()) {
0038         // sic, x/y are swapped here!
0039         r.loc.setLatitude(stopObj.value(QLatin1String("x")).toDouble(NAN));
0040         r.loc.setLongitude(stopObj.value(QLatin1String("y")).toDouble(NAN));
0041     }
0042 
0043     r.loc.setName(stopObj.value(QLatin1String("name")).toString());
0044     if (r.loc.name().isEmpty()) {
0045         r.loc.setName(stopObj.value(QLatin1String("description")).toString());
0046     }
0047     r.loc.setLocality(stopObj.value(QLatin1String("city")).toString());
0048     if (r.loc.locality().isEmpty()) {
0049         r.loc.setLocality(stopObj.value(QLatin1String("municipality")).toString());
0050     }
0051 
0052     if (stopObj.value(QLatin1String("type")).toString() == QLatin1String("coordinate")) {
0053         return r;
0054     }
0055 
0056     const auto id = stopObj.value(IfoptUtil::identifierType()).toString();
0057     if (IfoptUtil::isValid(id)) {
0058         r.loc.setIdentifier(IfoptUtil::identifierType(), id);
0059     } else {
0060         r.loc.setIdentifier(m_locationIdentifier, id);
0061     }
0062 
0063     // location is a platform: split out platform name
0064     if (stopObj.value(QLatin1String("subtype")).toString() == QLatin1String("Post")) {
0065         static QRegularExpression rx(QStringLiteral(R"((.*) \(((:?Gleis|Bahnsteig) .*)\)$)"));
0066         const auto match = rx.match(r.loc.name());
0067         if (match.hasMatch()) {
0068             r.loc.setName(match.captured(1));
0069             r.platform = match.captured(2);
0070         }
0071     }
0072 
0073     r.loc.setType(Location::Stop);
0074     return r;
0075 }
0076 
0077 std::vector<Location> IvvAssParser::parseLocations(const QByteArray &data)
0078 {
0079     const auto top = QJsonDocument::fromJson(data).object();
0080     if (!parseError(top)) {
0081         return {};
0082     }
0083     std::vector<Location> locs;
0084 
0085     const auto stops = top.value(QLatin1String("stops")).toArray();
0086     locs.reserve(stops.size());
0087     for (const auto &stopV : stops) {
0088         const auto stopObj = stopV.toObject();
0089         locs.push_back(parseLocation(stopObj).loc);
0090     }
0091     const auto objects = top.value(QLatin1String("objects")).toArray();
0092     locs.reserve(locs.size() + objects.size());
0093     for (const auto &objectV : objects) {
0094         const auto obj = objectV.toObject();
0095         const auto type = obj.value(QLatin1String("type")).toString();
0096         if (type != QLatin1String("stop")) { // poi, parking, etc
0097             continue;
0098         }
0099         locs.push_back(parseLocation(obj).loc);
0100 
0101         const auto elevators = obj.value(QLatin1String("elevators")).toArray();
0102         const auto escalators = obj.value(QLatin1String("escalators")).toArray();
0103         locs.reserve(locs.size() + elevators.size() + escalators.size());
0104         for (const auto &eleV : elevators) {
0105             const auto eleObj = eleV.toObject();
0106             auto ele = parseLocation(eleObj).loc;
0107             Equipment eq;
0108             eq.setType(Equipment::Elevator);
0109             eq.setDisruptionEffect(eleObj.value(QLatin1String("status")).toString() == QLatin1String("active") ? Disruption::NormalService : Disruption::NoService);
0110             ele.setData(std::move(eq));
0111             ele.setType(Location::Equipment);
0112             locs.push_back(std::move(ele));
0113         }
0114         for (const auto &escV : escalators) {
0115             const auto escObj = escV.toObject();
0116             auto esc = parseLocation(escObj).loc;
0117             Equipment eq;
0118             eq.setType(Equipment::Escalator);
0119             eq.setDisruptionEffect(escObj.value(QLatin1String("status")).toString() == QLatin1String("active") ? Disruption::NormalService : Disruption::NoService);
0120             esc.setData(std::move(eq));
0121             esc.setType(Location::Equipment);
0122             locs.push_back(std::move(esc));
0123         }
0124     }
0125 
0126     const auto vehicles = top.value(QLatin1String("vehicles")).toArray();
0127     locs.reserve(locs.size() + vehicles.size());
0128     for (const auto &vehicleV : vehicles) {
0129         auto v = parseLocation(vehicleV.toObject()).loc;
0130         v.setType(Location::RentedVehicle); // TODO can we get vehicle types and networks from somewhere?
0131         locs.push_back(std::move(v));
0132     }
0133 
0134     return locs;
0135 }
0136 
0137 static Route parseRoute(const QJsonObject &lineObj)
0138 {
0139     Line line;
0140     line.setName(lineObj.value(QLatin1String("number")).toString());
0141     line.setMode(IvvAssProductMap::parseProduct(lineObj.value(QLatin1String("product")).toString()));
0142 
0143     Route route;
0144     route.setLine(std::move(line));
0145     route.setDirection(lineObj.value(QLatin1String("direction")).toString());
0146     return route;
0147 }
0148 
0149 static void applyTimeZone(QDateTime &dt, const QTimeZone &tz)
0150 {
0151     if (dt.timeSpec() == Qt::OffsetFromUTC && tz.offsetFromUtc(dt) == dt.offsetFromUtc()) {
0152         dt.setTimeZone(tz);
0153     }
0154 }
0155 
0156 IvvAssParser::EventTime IvvAssParser::parseTime(const QJsonObject &obj, const char *baseKey, const char *scheduledKey) const
0157 {
0158     EventTime t;
0159     t.scheduled = QDateTime::fromString(obj.value(QLatin1String(scheduledKey)).toString(), Qt::ISODate);
0160     t.expected = QDateTime::fromString(obj.value(QLatin1String(baseKey)).toString(), Qt::ISODate);
0161     if (!t.scheduled.isValid() && t.expected.isValid()) {
0162         std::swap(t.scheduled, t.expected);
0163     }
0164     applyTimeZone(t.scheduled, m_timezone);
0165     applyTimeZone(t.expected, m_timezone);
0166     return t;
0167 }
0168 
0169 std::vector<Stopover> IvvAssParser::parseStopovers(const QByteArray &data)
0170 {
0171     const auto top = QJsonDocument::fromJson(data).object();
0172     if (!parseError(top)) {
0173         return {};
0174     }
0175     const auto timetable = top.value(QLatin1String("timetable")).toArray();
0176 
0177     std::vector<Stopover> stopovers;
0178     for (const auto &timetableV : timetable) {
0179         const auto timetableObj = timetableV.toObject();
0180         const auto stopObj = timetableObj.value(QLatin1String("stop")).toObject();
0181         const auto stop = parseLocation(stopObj);
0182 
0183         const auto events = timetableObj.value(QLatin1String("events")).toArray();
0184         stopovers.reserve(stopovers.size() + events.size());
0185         for (const auto &eventV : events) {
0186             const auto eventObj = eventV.toObject();
0187 
0188             Stopover s;
0189             s.setStopPoint(stop.loc);
0190             s.setScheduledPlatform(stop.platform);
0191             const auto t = parseTime(eventObj, "departure", "departureScheduled");
0192             s.setScheduledDepartureTime(t.scheduled);
0193             s.setExpectedDepartureTime(t.expected);
0194 
0195             const auto lineObj = eventObj.value(QLatin1String("line")).toObject();
0196             s.setRoute(parseRoute(lineObj));
0197 
0198             const auto postObj = eventObj.value(QLatin1String("post")).toObject();
0199             const auto postName = postObj.value(QLatin1String("name")).toString();
0200             if (postName.startsWith(QLatin1Char('(')) && postName.endsWith(QLatin1Char(')'))) {
0201                 s.setScheduledPlatform(postName.mid(1, postName.size() - 2));
0202             } else {
0203                 s.setScheduledPlatform(postName);
0204             }
0205 
0206             stopovers.push_back(std::move(s));
0207         }
0208     }
0209 
0210     return stopovers;
0211 }
0212 
0213 static std::vector<LoadInfo> parseDemand(const QJsonValue &demand)
0214 {
0215     if (!demand.isDouble()) {
0216         return {};
0217     }
0218 
0219     const auto d = demand.toInt();
0220     if (d < 0 || d > 2) {
0221         return {};
0222     }
0223 
0224     static constexpr const Load::Category load_category_map[] = { Load::Low, Load::Medium, Load::High };
0225 
0226     LoadInfo l;
0227     l.setLoad(load_category_map[d]);
0228     return {l};
0229 }
0230 
0231 static Path parseDirections(Path &&fullPath, const QJsonArray &directions)
0232 {
0233     if (directions.isEmpty() || fullPath.isEmpty()) {
0234         return std::move(fullPath);
0235     }
0236 
0237     const auto poly = fullPath.sections()[0].path();
0238 
0239     Path p;
0240     std::vector<PathSection> secs;
0241 
0242     int prevSecStart = 0;
0243     PathSection prevSec;
0244 
0245     for (const auto &dirV : directions) {
0246         const auto dirObj = dirV.toObject();
0247         const auto lat = dirObj.value(QLatin1String("x")).toDouble();
0248         const auto lon = dirObj.value(QLatin1String("y")).toDouble();
0249 
0250         int nextSecStart = prevSecStart;
0251         for (; nextSecStart < poly.size(); ++nextSecStart) {
0252             if (Location::distance(lat, lon, poly[nextSecStart].y(), poly[nextSecStart].x()) < 1.0f) {
0253                 break;
0254             }
0255         }
0256 
0257         if (nextSecStart > prevSecStart) {
0258             QPolygonF subPoly;
0259             subPoly.reserve(nextSecStart - prevSecStart + 1);
0260             std::copy(poly.begin() + prevSecStart, poly.begin() + nextSecStart, std::back_inserter(subPoly));
0261             subPoly.push_back({lon, lat});
0262             prevSec.setPath(std::move(subPoly));
0263             secs.push_back(prevSec);
0264         }
0265         prevSecStart = nextSecStart;
0266         prevSec.setDescription(dirObj.value(QLatin1String("street")).toString());
0267     }
0268     if (poly.size() > prevSecStart) {
0269         QPolygonF subPoly;
0270         subPoly.reserve(poly.size() - prevSecStart);
0271         std::copy(poly.begin() + prevSecStart, poly.end(), std::back_inserter(subPoly));
0272         prevSec.setPath(std::move(subPoly));
0273         secs.push_back(prevSec);
0274     }
0275 
0276     p.setSections(std::move(secs));
0277     return p;
0278 }
0279 
0280 std::vector<Journey> IvvAssParser::parseJourneys(const QByteArray &data)
0281 {
0282     const auto top = QJsonDocument::fromJson(data).object();
0283     if (!parseError(top)) {
0284         return {};
0285     }
0286     const auto routes = top.value(QLatin1String("routes")).toArray();
0287 
0288     std::vector<Journey> journeys;
0289     journeys.reserve(routes.size());
0290     for (const auto &routeV : routes) {
0291         const auto routeObj = routeV.toObject();
0292         Journey journey;
0293 
0294         const auto segments = routeObj.value(QLatin1String("segments")).toArray();
0295         std::vector<JourneySection> sections;
0296         sections.reserve(segments.size());
0297         for (const auto &segmentV : segments) {
0298             const auto segmentObj = segmentV.toObject();
0299             JourneySection s;
0300 
0301             const auto origin = parseLocation(segmentObj.value(QLatin1String("origin")).toObject());
0302             s.setFrom(origin.loc);
0303             s.setScheduledDeparturePlatform(origin.platform);
0304             const auto dest = parseLocation(segmentObj.value(QLatin1String("destination")).toObject());
0305             s.setTo(dest.loc);
0306             s.setScheduledArrivalPlatform(dest.platform);
0307 
0308             const auto dt = parseTime(segmentObj, "departure", "departureScheduled");
0309             s.setScheduledDepartureTime(dt.scheduled);
0310             s.setExpectedDepartureTime(dt.expected);
0311             const auto at = parseTime(segmentObj, "arrival", "arrivalScheduled");
0312             s.setScheduledArrivalTime(at.scheduled);
0313             s.setExpectedArrivalTime(at.expected);
0314 
0315             const auto type = segmentObj.value(QLatin1String("type")).toString();
0316             // TODO "publicTransport"
0317             s.setMode(type == QLatin1String("walk") ? JourneySection::Walking : JourneySection::PublicTransport);
0318 
0319             if (s.mode() == JourneySection::PublicTransport) {
0320                 const auto lineObj = segmentObj.value(QLatin1String("line")).toObject();
0321                 s.setRoute(parseRoute(lineObj));
0322             }
0323 
0324             const auto vias = segmentObj.value(QLatin1String("vias")).toArray();
0325             std::vector<Stopover> intermediateStops;
0326             intermediateStops.reserve(vias.size());
0327             for (const auto &viaV : vias) {
0328                 const auto viaObj = viaV.toObject();
0329                 Stopover stop;
0330                 const auto viaLoc = parseLocation(viaObj);
0331                 stop.setStopPoint(viaLoc.loc);
0332                 stop.setScheduledPlatform(viaLoc.platform);
0333 
0334                 const auto dt = parseTime(viaObj, "departure", "departureScheduled");
0335                 stop.setScheduledDepartureTime(dt.scheduled);
0336                 stop.setExpectedDepartureTime(dt.expected);
0337                 const auto at = parseTime(viaObj, "arrival", "arrivalScheduled");
0338                 stop.setScheduledArrivalTime(at.scheduled);
0339                 stop.setExpectedArrivalTime(at.expected);
0340 
0341                 const auto demand = viaObj.value(QLatin1String("demandEstimated"));
0342                 stop.setLoadInformation(parseDemand(demand));
0343 
0344                 intermediateStops.push_back(std::move(stop));
0345             }
0346             s.setIntermediateStops(std::move(intermediateStops));
0347 
0348             s.setDistance(segmentObj.value(QLatin1String("distance")).toInt());
0349             const auto polygon = segmentObj.value(QLatin1String("polygon")).toString();
0350             QPolygonF poly;
0351             const auto coords = QStringView(polygon).split(QLatin1Char(' '), Qt::SkipEmptyParts);
0352             poly.reserve(coords.size());
0353             for (const auto &coord : coords) {
0354                 const auto p = coord.split(QLatin1Char(','));
0355                 if (p.size() == 2) {
0356                     poly.push_back({p[1].toDouble(), p[0].toDouble()});
0357                 }
0358             }
0359             PathSection section;
0360             section.setPath(poly);
0361             Path path;
0362             path.setSections({section});
0363             s.setPath(parseDirections(std::move(path), segmentObj.value(QLatin1String("directions")).toArray()));
0364 
0365             const auto highestDemand = segmentObj.value(QLatin1String("highestDemandEstimated"));
0366             s.setLoadInformation(parseDemand(highestDemand));
0367 
0368             const auto infos = segmentObj.value(QLatin1String("infos")).toArray();
0369             for (const auto &infoV : infos) {
0370                 const auto infoObj = infoV.toObject();
0371                 s.addNote(infoObj.value(QLatin1String("text")).toString());
0372             }
0373 
0374             sections.push_back(std::move(s));
0375         }
0376 
0377         journey.setSections(std::move(sections));
0378         journeys.push_back(std::move(journey));
0379     }
0380 
0381     return journeys;
0382 }
0383 
0384 bool IvvAssParser::parseError(const QJsonObject &top)
0385 {
0386     errorMessage = top.value(QLatin1String("error")).toString();
0387     return errorMessage.isEmpty();
0388 }