File indexing completed on 2024-05-12 04:42:34
0001 /* 0002 SPDX-FileCopyrightText: 2019 Volker Krause <vkrause@kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include "efaxmlparser.h" 0008 #include "efamodeoftransport.h" 0009 #include "logging.h" 0010 #include "scopedxmlstreamreader.h" 0011 #include "ifopt/ifoptutil.h" 0012 0013 #include <KPublicTransport/Journey> 0014 #include <KPublicTransport/Location> 0015 #include <KPublicTransport/Stopover> 0016 0017 #include <QDateTime> 0018 #include <QDebug> 0019 #include <QXmlStreamReader> 0020 0021 using namespace KPublicTransport; 0022 0023 void EfaXmlParser::parseLocationCommon(Location &loc, const ScopedXmlStreamReader &reader) const 0024 { 0025 if (reader.attributes().hasAttribute(QLatin1String("x")) && reader.attributes().hasAttribute(QLatin1String("y"))) { 0026 loc.setLatitude(reader.attributes().value(QLatin1String("y")).toDouble()); 0027 loc.setLongitude(reader.attributes().value(QLatin1String("x")).toDouble()); 0028 } 0029 0030 // can be already set on loc, so don't reset it if missing here 0031 const auto locality = reader.attributes().value(QLatin1String("place")).toString(); 0032 if (!locality.isEmpty()) { 0033 loc.setLocality(locality); 0034 } 0035 0036 const auto id = reader.attributes().value(QLatin1String("stopID")).toString(); 0037 if (!id.isEmpty()) { 0038 if (!isDummyStopId(id)) { 0039 loc.setIdentifier(m_locationIdentifierType, id); 0040 } 0041 } else { 0042 loc.setIdentifier(m_locationIdentifierType, reader.attributes().value(QLatin1String("stateless")).toString()); 0043 } 0044 0045 // check if IFOPT identifiers are present, in decreasing level of detail 0046 for (const auto &attr : {QLatin1String("pointGid"), QLatin1String("areaGid"), QLatin1String("gid")}) { 0047 const auto id = reader.attributes().value(attr); 0048 if (IfoptUtil::isValid(id)) { 0049 loc.setIdentifier(IfoptUtil::identifierType(), id.toString()); 0050 break; 0051 } 0052 } 0053 0054 const auto type = reader.attributes().value(QLatin1String("anyType")); 0055 if (type == QLatin1String("stop")) { 0056 loc.setType(Location::Stop); 0057 } else if (!type.isEmpty()) { 0058 qCDebug(Log) << "Unhandled EFA location type:" << type; 0059 } else if (!id.isEmpty() && !isDummyStopId(id)) { 0060 loc.setType(Location::Stop); 0061 } 0062 } 0063 0064 Location EfaXmlParser::parseItdOdvAssignedStop(const ScopedXmlStreamReader &reader) const 0065 { 0066 Location loc; 0067 parseLocationCommon(loc, reader); 0068 loc.setName(reader.attributes().value(QLatin1String("nameWithPlace")).toString()); 0069 loc.setType(Location::Stop); 0070 return loc; 0071 } 0072 0073 Location EfaXmlParser::parseOdvNameElem(ScopedXmlStreamReader &reader) const 0074 { 0075 Location loc; 0076 parseLocationCommon(loc, reader); 0077 loc.setName(reader.readElementText(QXmlStreamReader::SkipChildElements)); 0078 return loc; 0079 } 0080 0081 std::vector<Location> EfaXmlParser::parseStopFinderResponse(const QByteArray &data) 0082 { 0083 std::vector<Location> res; 0084 QXmlStreamReader xsr(data); 0085 ScopedXmlStreamReader reader(xsr); 0086 while (reader.readNextElement()) { 0087 if (reader.name() == QLatin1String("itdOdvAssignedStop") && reader.attributes().hasAttribute(QLatin1String("stopID"))) { 0088 res.push_back(parseItdOdvAssignedStop(reader)); 0089 } else if (reader.name() == QLatin1String("odvNameElem")) { 0090 if (reader.attributes().hasAttribute(QLatin1String("stopID"))) { 0091 res.push_back(parseOdvNameElem(reader)); 0092 } else if (reader.attributes().value(QLatin1String("anyType")) == QLatin1String("stop") 0093 && reader.attributes().hasAttribute(QLatin1String("stateless"))) 0094 { 0095 res.push_back(parseOdvNameElem(reader)); 0096 } 0097 } 0098 } 0099 return res; 0100 } 0101 0102 static QDateTime parseDateTime(ScopedXmlStreamReader &&reader) 0103 { 0104 QDateTime dt; 0105 while (reader.readNextSibling()) { 0106 if (reader.name() == QLatin1String("itdDate")) { 0107 QDate d( 0108 reader.attributes().value(QLatin1String("year")).toInt(), 0109 reader.attributes().value(QLatin1String("month")).toInt(), 0110 reader.attributes().value(QLatin1String("day")).toInt()); 0111 dt.setDate(d); 0112 } else if (reader.name() == QLatin1String("itdTime")) { 0113 QTime t( 0114 reader.attributes().value(QLatin1String("hour")).toInt(), 0115 reader.attributes().value(QLatin1String("minute")).toInt(), 0); 0116 dt.setTime(t); 0117 } 0118 } 0119 0120 return dt; 0121 } 0122 0123 struct { 0124 const char *name; 0125 Load::Category category; 0126 } static constexpr const occupancy_load_map[] = { 0127 { "MANY_SEATS", Load::Low }, 0128 { "FEW_SEATS", Load::Medium }, 0129 { "STANDING_ONLY", Load::High }, 0130 }; 0131 0132 Stopover EfaXmlParser::parseDmDeparture(ScopedXmlStreamReader &&reader) const 0133 { 0134 Stopover dep; 0135 dep.setScheduledPlatform(reader.attributes().value(QLatin1String("platformName")).toString()); 0136 0137 Location stop; 0138 const auto stopId = reader.attributes().value(QLatin1String("stopID")).toString(); 0139 const auto stopIt = m_locations.find(stopId); 0140 if (stopIt != m_locations.end()) { 0141 stop = stopIt.value(); 0142 } 0143 0144 parseLocationCommon(stop, reader); 0145 stop.setName(reader.attributes().value(QLatin1String("stopName")).toString()); 0146 stop.setType(Location::Stop); 0147 dep.setStopPoint(stop); 0148 0149 const auto occupancy = reader.attributes().value(QLatin1String("occupancy")); 0150 if (!occupancy.isEmpty()) { 0151 LoadInfo load; 0152 for (const auto &map : occupancy_load_map) { 0153 if (occupancy == QLatin1String(map.name)) { 0154 load.setLoad(map.category); 0155 break; 0156 } 0157 } 0158 dep.setLoadInformation({load}); 0159 } 0160 0161 while (reader.readNextSibling()) { 0162 if (reader.name() == QLatin1String("itdServingLine")) { 0163 Line line; 0164 line.setName(reader.attributes().value(QLatin1String("number")).toString()); 0165 line.setMode(EfaModeOfTransport::motTypeToLineMode(reader.attributes().value(QLatin1String("motType")).toInt())); 0166 Route route; 0167 route.setDirection(reader.attributes().value(QLatin1String("direction")).toString()); 0168 route.setLine(line); 0169 dep.setRoute(route); 0170 } else if (reader.name() == QLatin1String("itdDateTime")) { 0171 dep.setScheduledDepartureTime(parseDateTime(reader.subReader())); 0172 } else if (reader.name() == QLatin1String("itdInfoLinkList")) { 0173 dep.addNotes(parseInfoLink(reader.subReader())); 0174 } 0175 } 0176 0177 return dep; 0178 } 0179 0180 std::vector<Stopover> EfaXmlParser::parseDmResponse(const QByteArray &data) 0181 { 0182 std::vector<Stopover> res; 0183 QXmlStreamReader xsr(data); 0184 ScopedXmlStreamReader reader(xsr); 0185 while (reader.readNextElement()) { 0186 if (reader.name() == QLatin1String("itdRequest")) { 0187 m_requestContext.sessionId = reader.attributes().value(QLatin1String("sessionID")).toString(); 0188 } else if (reader.name() == QLatin1String("itdDepartureMonitorRequest")) { 0189 m_requestContext.requestId = reader.attributes().value(QLatin1String("requestID")).toString(); 0190 } else if (reader.name() == QLatin1String("itdOdvAssignedStop") && reader.attributes().hasAttribute(QLatin1String("stopID"))) { 0191 const auto loc = parseItdOdvAssignedStop(reader); 0192 m_locations[loc.identifier(m_locationIdentifierType)] = loc; 0193 } else if (reader.name() == QLatin1String("itdDeparture")) { 0194 res.push_back(parseDmDeparture(reader.subReader())); 0195 } 0196 } 0197 0198 return res; 0199 } 0200 0201 void EfaXmlParser::parseTripDeparture(ScopedXmlStreamReader &&reader, JourneySection §ion) const 0202 { 0203 Location loc; 0204 parseLocationCommon(loc, reader); 0205 loc.setName(reader.attributes().value(QLatin1String("name")).toString()); 0206 0207 section.setFrom(loc); 0208 // ### are those the correct ones? there's also just "platform" 0209 section.setScheduledDeparturePlatform(reader.attributes().value(QLatin1String("plannedPlatformName")).toString()); 0210 section.setExpectedDeparturePlatform(reader.attributes().value(QLatin1String("platformName")).toString()); 0211 0212 while (reader.readNextSibling()) { 0213 if (reader.name() == QLatin1String("itdDateTime")) { 0214 section.setExpectedDepartureTime(parseDateTime(reader.subReader())); 0215 } else if (reader.name() == QLatin1String("itdDateTimeTarget")) { 0216 section.setScheduledDepartureTime(parseDateTime(reader.subReader())); 0217 } 0218 } 0219 } 0220 0221 void EfaXmlParser::parseTripArrival(ScopedXmlStreamReader &&reader, JourneySection §ion) const 0222 { 0223 Location loc; 0224 parseLocationCommon(loc, reader); 0225 loc.setName(reader.attributes().value(QLatin1String("name")).toString()); 0226 0227 section.setTo(loc); 0228 // ### are those the correct ones? there's also just "platform" 0229 section.setScheduledArrivalPlatform(reader.attributes().value(QLatin1String("plannedPlatformName")).toString()); 0230 section.setExpectedArrivalPlatform(reader.attributes().value(QLatin1String("platformName")).toString()); 0231 0232 while (reader.readNextSibling()) { 0233 if (reader.name() == QLatin1String("itdDateTime")) { 0234 section.setExpectedArrivalTime(parseDateTime(reader.subReader())); 0235 } else if (reader.name() == QLatin1String("itdDateTimeTarget")) { 0236 section.setScheduledArrivalTime(parseDateTime(reader.subReader())); 0237 } 0238 } 0239 } 0240 0241 Stopover EfaXmlParser::parsePartialTripIntermediateStop(ScopedXmlStreamReader &&reader) const 0242 { 0243 Location loc; 0244 parseLocationCommon(loc, reader); 0245 loc.setName(reader.attributes().value(QLatin1String("name")).toString()); 0246 0247 Stopover stop; 0248 stop.setStopPoint(loc); 0249 stop.setScheduledPlatform(reader.attributes().value(QLatin1String("platform")).toString()); 0250 0251 bool result = false; 0252 auto depDelay = reader.attributes().value(QLatin1String("depDelay")).toInt(&result); 0253 if (!result) { 0254 depDelay = -1; 0255 } 0256 // TODO there is also arrDelay - but what's the corresponding date/time for that? 0257 0258 while (reader.readNextSibling()) { 0259 if (reader.name() == QLatin1String("itdDateTime")) { 0260 // TODO sometimes there are two itdDateTime elements here? 0261 // TODO sometimes there are invalid itdDateTime elements here? 0262 const auto dt = parseDateTime(reader.subReader()); 0263 if (dt.isValid()) { 0264 stop.setScheduledDepartureTime(dt); 0265 0266 if (depDelay >= 0) { 0267 stop.setExpectedDepartureTime(dt.addSecs(60 * depDelay)); 0268 } 0269 } 0270 } 0271 } 0272 0273 return stop; 0274 } 0275 0276 std::vector<Stopover> EfaXmlParser::parsePartialTripStopSequence(ScopedXmlStreamReader &&reader) const 0277 { 0278 std::vector<Stopover> stops; 0279 while (reader.readNextSibling()) { 0280 if (reader.name() == QLatin1String("itdPoint")) { 0281 stops.push_back(parsePartialTripIntermediateStop(reader.subReader())); 0282 } 0283 } 0284 0285 if (stops.size() >= 2) { // includes departure/arrival stops too 0286 stops.erase(std::prev(stops.end())); 0287 stops.erase(stops.begin()); 0288 } 0289 0290 return stops; 0291 } 0292 0293 struct { 0294 int type; 0295 JourneySection::Mode mode; 0296 } static const journey_section_types[] = { 0297 { 97, JourneySection::Waiting }, // technically: "do not change"? 0298 { 98, JourneySection::Transfer }, 0299 { 99, JourneySection::Walking }, 0300 { 100, JourneySection::Walking }, 0301 }; 0302 0303 std::vector<JourneySection> EfaXmlParser::parseTripPartialRoute(ScopedXmlStreamReader &&reader) const 0304 { 0305 std::vector<JourneySection> result; 0306 JourneySection section; 0307 if (reader.attributes().value(QLatin1String("type")) == QLatin1String("IT")) { 0308 section.setMode(JourneySection::Walking); 0309 } 0310 0311 QPolygonF sectionPoly, transferPoly; 0312 std::vector<PathDescription> pathDesc; 0313 while (reader.readNextSibling()) { 0314 if (reader.name() == QLatin1String("itdPoint")) { 0315 const auto type = reader.attributes().value(QLatin1String("usage")); 0316 if (type == QLatin1String("departure")) { 0317 parseTripDeparture(reader.subReader(), section); 0318 } else if (type == QLatin1String("arrival")) { 0319 parseTripArrival(reader.subReader(), section); 0320 } 0321 } else if (reader.name() == QLatin1String("itdMeansOfTransport")) { 0322 const auto type = reader.attributes().value(QLatin1String("type")).toInt(); 0323 for (const auto &m : journey_section_types) { 0324 if (m.type == type) { 0325 section.setMode(m.mode); 0326 break; 0327 } 0328 } 0329 0330 if (type < 90) { 0331 Line line; 0332 line.setName(reader.attributes().value(QLatin1String("shortname")).toString()); 0333 const auto prodName = reader.attributes().value(QLatin1String("productName")); 0334 if (prodName == QLatin1String("Fussweg")) { 0335 section.setMode(JourneySection::Walking); 0336 } else { 0337 line.setModeString(prodName.toString()); 0338 } 0339 line.setMode(EfaModeOfTransport::motTypeToLineMode(reader.attributes().value(QLatin1String("motType")).toInt())); 0340 Route route; 0341 route.setDirection(reader.attributes().value(QLatin1String("destination")).toString()); 0342 route.setLine(line); 0343 section.setRoute(route); 0344 section.setMode(JourneySection::PublicTransport); 0345 } else if (type > 100) { 0346 const auto itMode = motTypeToIndividualTransportMode(type); 0347 section.setIndividualTransport(itMode); 0348 section.setMode(JourneySection::IndividualTransport); 0349 } 0350 } else if (reader.name() == QLatin1String("infoLink")) { 0351 section.addNotes(parseInfoLink(reader.subReader())); 0352 } else if (reader.name() == QLatin1String("itdInfoTextList")) { 0353 auto subReader = reader.subReader(); 0354 while (subReader.readNextSibling()) { 0355 if (subReader.name() == QLatin1String("infoTextListElem")) { 0356 section.addNote(subReader.readElementText()); 0357 } 0358 } 0359 } else if (reader.name() == QLatin1String("itdStopSeq")) { 0360 section.setIntermediateStops(parsePartialTripStopSequence(reader.subReader())); 0361 } else if (reader.name() == QLatin1String("itdPathCoordinates")) { 0362 sectionPoly = parsePathCoordinates(reader.subReader()); 0363 } else if (reader.name() == QLatin1String("itdInterchangePathCoordinates")) { 0364 auto subreader = reader.subReader(); 0365 while (subreader.readNextSibling()) { 0366 if (subreader.name() == QLatin1String("itdPathCoordinates")) { 0367 transferPoly = parsePathCoordinates(subreader.subReader()); 0368 } 0369 } 0370 } else if (reader.name() == QLatin1String("itdITPathDescription") && !sectionPoly.isEmpty()) { 0371 auto subreader = reader.subReader(); 0372 while (subreader.readNextSibling()) { 0373 if (subreader.name() == QLatin1String("itdITPathDescriptionList")) { 0374 pathDesc = parsePathDescriptionList(subreader.subReader()); 0375 } 0376 } 0377 } 0378 } 0379 0380 if (!pathDesc.empty()) { 0381 resolvePathDescription(pathDesc); 0382 if (!transferPoly.empty() && section.mode() == JourneySection::PublicTransport) { 0383 // path description is actually for a subsequent transfer section 0384 const auto path = assemblePath(pathDesc, transferPoly); 0385 JourneySection transferSection; 0386 transferSection.setMode(JourneySection::Transfer); 0387 transferSection.setScheduledDepartureTime(section.scheduledArrivalTime()); 0388 const auto travelTime = std::accumulate(pathDesc.begin(), pathDesc.end(), 0, [](auto accu, const auto &desc) { 0389 return accu + desc.travelTime; 0390 }); 0391 transferSection.setScheduledArrivalTime(section.scheduledArrivalTime().addSecs(travelTime)); 0392 Location from; 0393 from.setLatitude(path.startPoint().y()); 0394 from.setLongitude(path.startPoint().x()); 0395 transferSection.setFrom(std::move(from)); 0396 Location to; 0397 to.setLatitude(path.endPoint().y()); 0398 to.setLongitude(path.endPoint().x()); 0399 transferSection.setTo(std::move(to)); 0400 transferSection.setPath(std::move(path)); 0401 result.push_back(std::move(transferSection)); 0402 } else if (section.mode() == JourneySection::Walking) { 0403 // path description is for this section 0404 section.setPath(assemblePath(pathDesc, sectionPoly)); 0405 } 0406 } 0407 if (!sectionPoly.isEmpty() && section.path().isEmpty()) { 0408 section.setPath(polygonToPath(sectionPoly)); 0409 } 0410 0411 result.insert(result.begin(), std::move(section)); 0412 return result; 0413 } 0414 0415 Journey EfaXmlParser::parseTripRoute(ScopedXmlStreamReader &&reader) const 0416 { 0417 Journey journey; 0418 std::vector<JourneySection> sections; 0419 0420 while (reader.readNextElement()) { 0421 if (reader.name() == QLatin1String("itdPartialRoute")) { 0422 auto sec = parseTripPartialRoute(reader.subReader()); 0423 std::move(sec.begin(), sec.end(), std::back_inserter(sections)); 0424 } 0425 } 0426 0427 journey.setSections(std::move(sections)); 0428 return journey; 0429 } 0430 0431 std::vector<Journey> EfaXmlParser::parseTripResponse(const QByteArray &data) 0432 { 0433 //qDebug().noquote() << data; 0434 std::vector<Journey> res; 0435 QXmlStreamReader xsr(data); 0436 ScopedXmlStreamReader reader(xsr); 0437 while (reader.readNextElement()) { 0438 if (reader.name() == QLatin1String("itdRequest")) { 0439 m_requestContext.sessionId = reader.attributes().value(QLatin1String("sessionID")).toString(); 0440 } else if (reader.name() == QLatin1String("itdTripRequest")) { 0441 m_requestContext.requestId = reader.attributes().value(QLatin1String("requestID")).toString(); 0442 } else if (reader.name() == QLatin1String("itdRoute")) { 0443 res.push_back(parseTripRoute(reader.subReader())); 0444 } 0445 } 0446 return res; 0447 } 0448 0449 QStringList EfaXmlParser::parseInfoLink(ScopedXmlStreamReader &&reader) const 0450 { 0451 QStringList l; 0452 while (reader.readNextElement()) { 0453 if (reader.name() == QLatin1String("infoLinkText") || reader.name() == QLatin1String("subtitle") 0454 || reader.name() == QLatin1String("wmlText") || reader.name() == QLatin1String("htmlText")) 0455 { 0456 l.push_back(reader.readElementText()); 0457 } 0458 } 0459 return l; 0460 } 0461 0462 QPolygonF EfaXmlParser::parsePathCoordinates(ScopedXmlStreamReader &&reader) const 0463 { 0464 QPolygonF poly; 0465 while (reader.readNextSibling()) { 0466 if (reader.name() == QLatin1String("itdCoordinateString")) { 0467 poly = parsePathCoordinatesElement(reader); 0468 } 0469 } 0470 return poly; 0471 } 0472 0473 std::vector<EfaXmlParser::PathDescription> EfaXmlParser::parsePathDescriptionList(ScopedXmlStreamReader &&reader) const 0474 { 0475 std::vector<PathDescription> descs; 0476 while (reader.readNextSibling()) { 0477 if (reader.name() == QLatin1String("itdITPathDescriptionElem")) { 0478 PathDescription desc; 0479 auto elemReader = reader.subReader(); 0480 while (elemReader.readNextSibling()) { 0481 if (elemReader.name() == QLatin1String("itdCoord")) { 0482 desc.point.setX(elemReader.attributes().value(QLatin1String("x")).toDouble()); 0483 desc.point.setY(elemReader.attributes().value(QLatin1String("y")).toDouble()); 0484 } else if (elemReader.name() == QLatin1String("fromPathCoordIdx")) { 0485 desc.fromIndex = elemReader.readElementText().toInt(); 0486 } else if (elemReader.name() == QLatin1String("toPathCoordIdx")) { 0487 desc.toIndex = elemReader.readElementText().toInt(); 0488 } else if (elemReader.name() == QLatin1String("streetname")) { 0489 desc.description = elemReader.readElementText(); 0490 } else if (elemReader.name() == QLatin1String("traveltime")) { 0491 desc.travelTime = elemReader.readElementText().toInt(); 0492 } else if (elemReader.name() == QLatin1String("niveau")) { 0493 // seems to match OSM floor level, but is always "0" for elevators/escalators 0494 desc.niveau = elemReader.readElementText().toInt(); 0495 } else if (elemReader.name() == QLatin1String("genAttrList")) { 0496 const auto attrs = parseGenericAttributeList(elemReader.subReader()); 0497 const auto indoorType = attrs.value(QStringLiteral("INDOOR_TYPE")); 0498 if (indoorType == QLatin1String("STAIRS")) { 0499 desc.maneuver = PathSection::Stairs; 0500 } else if (indoorType == QLatin1String("LIFT")) { 0501 desc.maneuver = PathSection::Elevator; 0502 } else if (indoorType == QLatin1String("ESCALATOR")) { 0503 desc.maneuver = PathSection::Escalator; 0504 } 0505 0506 bool floorLevelDifferenceValid = false; 0507 const auto floorLevelDifference = attrs.value(QStringLiteral("FLOOR_LEVEL_DIFFERENCE")).toInt(&floorLevelDifferenceValid); 0508 if (floorLevelDifferenceValid) { 0509 desc.niveauDelta = floorLevelDifference; 0510 } 0511 } 0512 // NOTE: skyDirection seems flipped by 180°, ie. pointing to the start point, should we ever need that 0513 // turnDirection, turningManoeuvre, from/toPathLink?? 0514 } 0515 descs.push_back(std::move(desc)); 0516 } 0517 } 0518 return descs; 0519 } 0520 0521 void EfaXmlParser::resolvePathDescription(std::vector<PathDescription> &descs) const 0522 { 0523 if (descs.size() < 3) { 0524 return; 0525 } 0526 0527 for (auto it = std::next(descs.begin()); it != std::prev(descs.end()); ++it) { 0528 if ((*it).maneuver != PathSection::Stairs && (*it).maneuver != PathSection::Elevator && (*it).maneuver != PathSection::Escalator) { 0529 continue; 0530 } 0531 const auto niveauBefore = (*std::prev(it)).niveau; 0532 const auto niveauAfter = (*std::next(it)).niveau; 0533 if (niveauAfter != niveauBefore && (*it).niveauDelta == 0) { 0534 (*it).niveauDelta = niveauAfter - niveauBefore; 0535 } 0536 } 0537 } 0538 0539 Path EfaXmlParser::assemblePath(const std::vector<PathDescription> &descs, const QPolygonF &poly) const 0540 { 0541 Path path; 0542 std::vector<PathSection> sections; 0543 0544 for (const auto &desc : descs) { 0545 if (desc.fromIndex < 0 || desc.toIndex < 0 || desc.fromIndex >= poly.size() || desc.toIndex >= poly.size() || desc.toIndex < desc.fromIndex) { 0546 qCWarning(Log) << "weird polygon indexes?" << desc.fromIndex << desc.toIndex << poly.size(); 0547 continue; 0548 } 0549 PathSection section; 0550 QPolygonF subPoly; 0551 subPoly.reserve(desc.toIndex - desc.fromIndex + 1); 0552 std::copy(poly.begin() + desc.fromIndex, poly.begin() + desc.toIndex + 1, std::back_inserter(subPoly)); 0553 section.setPath(subPoly); 0554 section.setDescription(desc.description); 0555 section.setManeuver(desc.maneuver); 0556 section.setFloorLevelChange(desc.niveauDelta); 0557 sections.push_back(std::move(section)); 0558 } 0559 0560 path.setSections(std::move(sections)); 0561 return path; 0562 } 0563 0564 QHash<QString, QString> EfaXmlParser::parseGenericAttributeList(ScopedXmlStreamReader &&reader) const 0565 { 0566 return parseKeyValueList(std::move(reader), QLatin1String("genAttrElem"), QLatin1String("name"), QLatin1String("value")); 0567 }