From e350506cc2adcb47085c754f52649ad1f02a6487 Mon Sep 17 00:00:00 2001 From: Olga Khlopkova Date: Fri, 2 Oct 2020 15:31:33 +0300 Subject: [PATCH] [transit] Split lines into segments for rendering on the subway layer. --- transit/world_feed/subway_converter.cpp | 580 +++++++++++++++++++++--- transit/world_feed/subway_converter.hpp | 26 +- transit/world_feed/world_feed.cpp | 341 ++++++++++++-- transit/world_feed/world_feed.hpp | 33 +- 4 files changed, 864 insertions(+), 116 deletions(-) diff --git a/transit/world_feed/subway_converter.cpp b/transit/world_feed/subway_converter.cpp index 49724b74e6..41be1411a8 100644 --- a/transit/world_feed/subway_converter.cpp +++ b/transit/world_feed/subway_converter.cpp @@ -9,38 +9,54 @@ #include #include +#include #include "3party/jansson/myjansson.hpp" #include "3party/opening_hours/opening_hours.hpp" namespace { -// Returns the index of the |point| in the |shape| polyline. -size_t FindPointIndex(std::vector const & shape, m2::PointD const & point) +double constexpr kEps = 1e-5; + +// Returns route id of the line. Route id is calculated in the same way as in the script +// tools/transit/transit_graph_generator.py. +uint32_t GetSubwayRouteId(routing::transit::LineId lineId) { - static double constexpr eps = 1e-6; - auto it = std::find_if(shape.begin(), shape.end(), [&point](m2::PointD const & p) { - return base::AlmostEqualAbs(p, point, eps); - }); - - CHECK(it != shape.end(), (point)); - - return std::distance(shape.begin(), it); + return static_cast(lineId >> 4); } -// Returns polyline found by pair of stop ids. -std::vector GetShapeByStops(std::vector const & shapes, - routing::transit::StopId stopId1, - routing::transit::StopId stopId2) +// Increments |lineSegment| indexes by |shapeLink| start index. +void ShiftSegmentOnShape(transit::LineSegment & lineSegment, transit::ShapeLink const & shapeLink) { - auto const itShape = std::find_if( - shapes.begin(), shapes.end(), [stopId1, stopId2](routing::transit::Shape const & shape) { - return shape.GetId().GetStop1Id() == stopId1 && shape.GetId().GetStop2Id() == stopId2; - }); + lineSegment.m_startIdx += shapeLink.m_startIndex; + lineSegment.m_endIdx += shapeLink.m_startIndex; +} - CHECK(itShape != shapes.end(), (stopId1, stopId2)); +// Returns segment edge points on the polyline. +std::pair GetSegmentEdgesOnPolyline( + std::vector const & polyline, transit::LineSegment const & segment) +{ + CHECK_GREATER(polyline.size(), std::max(segment.m_startIdx, segment.m_endIdx), ()); - return itShape->GetPolyline(); + m2::PointD const startPoint = polyline[segment.m_startIdx]; + m2::PointD const endPoint = polyline[segment.m_endIdx]; + + return {startPoint, endPoint}; +} + +// Calculates |segment| start and end indexes on the polyline with length |polylineSize| in +// assumption that this segment is reversed. Example: we have polyline [1, 2, 3, 4, 5, 6] and +// segment [5, 4]. We reversed this segment so it transformed to [4, 5] and found it on polyline. +// Its start and end indexes on the polyline are 3, 4. We want to calculate start and end indexes of +// the original segment [5, 4]. These indexes are 4, 3. +void UpdateReversedSegmentIndexes(transit::LineSegment & segment, size_t polylineSize) +{ + size_t const len = segment.m_endIdx - segment.m_startIdx + 1; + segment.m_endIdx = static_cast(polylineSize - segment.m_startIdx - 1); + segment.m_startIdx = static_cast(segment.m_endIdx - len + 1); + + CHECK_GREATER(segment.m_endIdx, segment.m_startIdx, ()); + CHECK_GREATER(polylineSize, segment.m_endIdx, ()); } } // namespace @@ -72,6 +88,8 @@ bool SubwayConverter::Convert() m_feed.ModifyLinesAndShapes(); + MinimizeReversedLinesCount(); + if (!ConvertStops()) return false; @@ -82,7 +100,14 @@ bool SubwayConverter::Convert() if (!ConvertGates()) return false; - return ConvertEdges(); + if (!ConvertEdges()) + return false; + + m_feed.SplitFeedIntoRegions(); + + PrepareLinesMetadata(); + + return true; } bool SubwayConverter::ConvertNetworks() @@ -121,13 +146,15 @@ bool SubwayConverter::SplitEdges() std::pair SubwayConverter::MakeRoute( routing::transit::Line const & lineSubway) { - std::string const & routeTitle = lineSubway.GetNumber(); - std::string const routeHash = - BuildHash(kHashPrefix, std::to_string(lineSubway.GetNetworkId()), routeTitle); + uint32_t routeSubwayId = GetSubwayRouteId(lineSubway.GetId()); + + std::string const routeHash = BuildHash(kHashPrefix, std::to_string(lineSubway.GetNetworkId()), + std::to_string(routeSubwayId)); + TransitId const routeId = m_feed.m_idGenerator.MakeId(routeHash); RouteData routeData; - routeData.m_title = routeTitle; + routeData.m_title = lineSubway.GetNumber(); routeData.m_routeType = kSubwayRouteType; routeData.m_networkId = lineSubway.GetNetworkId(); routeData.m_color = lineSubway.GetColor(); @@ -193,23 +220,10 @@ std::pair SubwayConverter::MakeEdge(routing::transit::Edge con EdgeData edgeData; edgeData.m_weight = edgeSubway.GetWeight(); - auto const it = m_edgesOnShapes.find(edgeId); - CHECK(it != m_edgesOnShapes.end(), (lineId)); - - m2::PointD const pointStart = it->second.first; - m2::PointD const pointEnd = it->second.second; + CHECK(m_feed.m_edgesOnShapes.find(edgeId) != m_feed.m_edgesOnShapes.end(), (lineId)); edgeData.m_shapeLink.m_shapeId = m_feed.m_lines.m_data[lineId].m_shapeLink.m_shapeId; - auto itShape = m_feed.m_shapes.m_data.find(edgeData.m_shapeLink.m_shapeId); - CHECK(itShape != m_feed.m_shapes.m_data.end(), ("Shape does not exist.")); - - auto const & shapePoints = itShape->second.m_points; - CHECK(!shapePoints.empty(), ("Shape is empty.")); - - edgeData.m_shapeLink.m_startIndex = FindPointIndex(shapePoints, pointStart); - edgeData.m_shapeLink.m_endIndex = FindPointIndex(shapePoints, pointEnd); - return {edgeId, edgeData}; } @@ -229,6 +243,9 @@ std::pair SubwayConverter::MakeStop(routing::transit::Stop stopData.m_point = stopSubway.GetPoint(); stopData.m_osmId = stopSubway.GetOsmId(); + if (stopSubway.GetFeatureId() != routing::transit::kInvalidFeatureId) + stopData.m_featureId = stopSubway.GetFeatureId(); + return {stopId, stopData}; } @@ -256,40 +273,69 @@ bool SubwayConverter::ConvertLinesBasedData() CHECK_EQUAL(lineSubway.GetStopIds().size(), 1, ("Line shouldn't be split into ranges.")); - auto const & rangeSubway = lineSubway.GetStopIds().front(); - CHECK_GREATER(rangeSubway.size(), 1, ("Range must include at least two stops.")); + auto const & stopIdsSubway = lineSubway.GetStopIds().front(); - for (size_t i = 0; i < rangeSubway.size(); ++i) + CHECK_GREATER(stopIdsSubway.size(), 1, ("Range must include at least two stops.")); + + for (size_t i = 0; i < stopIdsSubway.size(); ++i) { - auto const stopIdSubway = rangeSubway[i]; + auto const stopIdSubway = stopIdsSubway[i]; std::string const stopHash = BuildHash(kHashPrefix, std::to_string(stopIdSubway)); TransitId const stopId = m_feed.m_idGenerator.MakeId(stopHash); + lineData.m_stopIds.emplace_back(stopId); m_stopIdMapping.emplace(stopIdSubway, stopId); if (i == 0) continue; - auto const stopIdSubwayPrev = rangeSubway[i - 1]; + auto const stopIdSubwayPrev = stopIdsSubway[i - 1]; + CHECK(stopIdSubwayPrev != stopIdSubway, (stopIdSubway)); + auto const & edge = FindEdge(stopIdSubwayPrev, stopIdSubway, lineId); - for (auto const & id : edge.GetShapeIds()) + CHECK_LESS_OR_EQUAL(edge.GetShapeIds().size(), 1, (edge)); + + std::vector edgePoints; + + m2::PointD const prevPoint = FindById(m_graphData.GetStops(), stopIdSubwayPrev)->GetPoint(); + m2::PointD const curPoint = FindById(m_graphData.GetStops(), stopIdSubway)->GetPoint(); + + if (edge.GetShapeIds().empty()) { - auto const & polyline = GetShapeByStops(shapesSubway, id.GetStop1Id(), id.GetStop2Id()); - CHECK(!polyline.empty(), ()); + edgePoints.push_back(prevPoint); + edgePoints.push_back(curPoint); + } + else + { + routing::transit::ShapeId shapeIdSubway = edge.GetShapeIds().back(); + + auto polyline = FindById(shapesSubway, shapeIdSubway)->GetPolyline(); + + CHECK(polyline.size() > 1, ()); + + double const distToPrevStop = mercator::DistanceOnEarth(polyline.front(), prevPoint); + double const distToNextStop = mercator::DistanceOnEarth(polyline.front(), curPoint); + + if (distToPrevStop > distToNextStop) + std::reverse(polyline.begin(), polyline.end()); // We remove duplicate point from the shape before appending polyline to it. if (!shapeData.m_points.empty() && shapeData.m_points.back() == polyline.front()) shapeData.m_points.pop_back(); shapeData.m_points.insert(shapeData.m_points.end(), polyline.begin(), polyline.end()); + + edgePoints = polyline; } EdgeId const curEdge(m_stopIdMapping[stopIdSubwayPrev], stopId, lineId); - EdgePoints const edgePoints(shapeData.m_points.front(), shapeData.m_points.back()); - if (!m_edgesOnShapes.emplace(curEdge, edgePoints).second) + auto [itEdgeOnShape, inserted] = + m_feed.m_edgesOnShapes.emplace(curEdge, std::vector>{edgePoints}); + if (inserted) { + itEdgeOnShape->second.push_back(edgePoints); LOG(LWARNING, ("Edge duplicate in subways. stop1_id", stopIdSubwayPrev, "stop2_id", stopIdSubway, "line_id", lineId)); } @@ -300,7 +346,7 @@ bool SubwayConverter::ConvertLinesBasedData() } LOG(LDEBUG, ("Converted", m_feed.m_routes.m_data.size(), "routes,", m_feed.m_lines.m_data.size(), - "lines from subways to public transport.")); + "lines.")); return !m_feed.m_lines.m_data.empty(); } @@ -313,8 +359,7 @@ bool SubwayConverter::ConvertStops() for (auto const & stopSubway : stopsSubway) m_feed.m_stops.m_data.emplace(MakeStop(stopSubway)); - LOG(LINFO, - ("Converted", m_feed.m_stops.m_data.size(), "stops from subways to public transport.")); + LOG(LINFO, ("Converted", m_feed.m_stops.m_data.size(), "stops.")); return !m_feed.m_stops.m_data.empty(); } @@ -327,6 +372,28 @@ bool SubwayConverter::ConvertTransfers() for (auto const & transferSubway : transfersSubway) { auto const [transferId, transferData] = MakeTransfer(transferSubway); + + std::map> routeToStops; + + for (auto const & stopId : transferData.m_stopsIds) + { + for (auto const & [lineId, lineData] : m_feed.m_lines.m_data) + { + auto it = std::find(lineData.m_stopIds.begin(), lineData.m_stopIds.end(), stopId); + if (it != lineData.m_stopIds.end()) + { + routeToStops[lineData.m_routeId].insert(stopId); + } + } + } + + // We don't count as transfers tranfer points between lines on the same route, so we skip them. + if (routeToStops.size() < 2) + { + LOG(LINFO, ("Skip transfer on route", transferId)); + continue; + } + m_feed.m_transfers.m_data.emplace(transferId, transferData); // All stops are already present in |m_feed| before the |ConvertTransfers()| call. @@ -334,8 +401,7 @@ bool SubwayConverter::ConvertTransfers() LinkTransferIdToStop(m_feed.m_stops.m_data.at(stopId), transferId); } - LOG(LINFO, ("Converted", m_feed.m_transfers.m_data.size(), - "transfers from subways to public transport.")); + LOG(LINFO, ("Converted", m_feed.m_transfers.m_data.size(), "transfers.")); return !m_feed.m_transfers.m_data.empty(); } @@ -351,8 +417,7 @@ bool SubwayConverter::ConvertGates() m_feed.m_gates.m_data.emplace(gateId, gateData); } - LOG(LINFO, - ("Converted", m_feed.m_gates.m_data.size(), "gates from subways to public transport.")); + LOG(LINFO, ("Converted", m_feed.m_gates.m_data.size(), "gates.")); return !m_feed.m_gates.m_data.empty(); } @@ -362,18 +427,413 @@ bool SubwayConverter::ConvertEdges() for (auto const & edgeSubway : m_edgesSubway) m_feed.m_edges.m_data.emplace(MakeEdge(edgeSubway)); - LOG(LINFO, - ("Converted", m_feed.m_edges.m_data.size(), "edges from subways to public transport.")); + LOG(LINFO, ("Converted", m_feed.m_edges.m_data.size(), "edges.")); for (auto const & edgeTransferSubway : m_edgesTransferSubway) m_feed.m_edgesTransfers.m_data.emplace(MakeEdgeTransfer(edgeTransferSubway)); - LOG(LINFO, ("Converted", m_feed.m_edgesTransfers.m_data.size(), - "edges from subways to transfer edges in public transport.")); + LOG(LINFO, ("Converted", m_feed.m_edgesTransfers.m_data.size(), "transfer edges.")); return !m_feed.m_edges.m_data.empty() && !m_feed.m_edgesTransfers.m_data.empty(); } +void SubwayConverter::MinimizeReversedLinesCount() +{ + for (auto & [lineId, lineData] : m_feed.m_lines.m_data) + { + if (lineData.m_shapeLink.m_startIndex < lineData.m_shapeLink.m_endIndex) + continue; + + auto revStopIds = GetReversed(lineData.m_stopIds); + + bool reversed = false; + + for (auto const & [lineIdStraight, lineDataStraight] : m_feed.m_lines.m_data) + { + if (lineIdStraight == lineId || + lineDataStraight.m_shapeLink.m_startIndex > lineDataStraight.m_shapeLink.m_endIndex || + lineDataStraight.m_shapeLink.m_shapeId != lineData.m_shapeLink.m_shapeId) + { + continue; + } + + if (revStopIds == lineDataStraight.m_stopIds) + { + lineData.m_shapeLink = lineDataStraight.m_shapeLink; + LOG(LDEBUG, ("Reversed line", lineId, "to line", lineIdStraight, "shapeLink", + lineData.m_shapeLink)); + reversed = true; + break; + } + } + + if (!reversed) + { + std::swap(lineData.m_shapeLink.m_startIndex, lineData.m_shapeLink.m_endIndex); + LOG(LDEBUG, ("Reversed line", lineId, "shapeLink", lineData.m_shapeLink)); + } + } +} + +std::vector SubwayConverter::GetLinesOnScheme( + std::unordered_map const & linesInRegion) const +{ + // Color of line to shape link and one of line ids with this link. + std::map> colorsToLines; + + for (auto const & [lineId, lineData] : linesInRegion) + { + if (lineData.m_splineParent) + { + LOG(LINFO, ("Line is short spline. We skip it. Id", lineId)); + continue; + } + + auto itLine = m_feed.m_lines.m_data.find(lineId); + CHECK(itLine != m_feed.m_lines.m_data.end(), ()); + + TransitId const routeId = itLine->second.m_routeId; + auto itRoute = m_feed.m_routes.m_data.find(routeId); + CHECK(itRoute != m_feed.m_routes.m_data.end(), ()); + std::string const & color = itRoute->second.m_color; + + ShapeLink const & newShapeLink = lineData.m_shapeLink; + + auto [it, inserted] = colorsToLines.emplace(color, std::map()); + + if (inserted) + { + it->second[newShapeLink] = lineId; + continue; + } + + bool insert = true; + std::vector linksForRemoval; + + for (auto const & [shapeLink, lineId] : it->second) + { + if (shapeLink.m_shapeId != newShapeLink.m_shapeId) + continue; + + // New shape link is fully included into the existing one. + if (shapeLink.m_startIndex <= newShapeLink.m_startIndex && + shapeLink.m_endIndex >= newShapeLink.m_endIndex) + { + insert = false; + continue; + } + + // Existing shape link is fully included into the new one. It should be removed. + if (newShapeLink.m_startIndex <= shapeLink.m_startIndex && + newShapeLink.m_endIndex >= shapeLink.m_endIndex) + { + linksForRemoval.push_back(shapeLink); + } + } + + for (auto const sl : linksForRemoval) + it->second.erase(sl); + + if (insert) + it->second[newShapeLink] = lineId; + } + + std::vector linesOnScheme; + + for (auto const & [color, linksToLines] : colorsToLines) + { + CHECK(!linksToLines.empty(), (color)); + + for (auto const & [shapeLink, lineId] : linksToLines) + { + LineSchemeData data; + data.m_lineId = lineId; + data.m_color = color; + data.m_shapeLink = shapeLink; + + linesOnScheme.push_back(data); + } + } + + return linesOnScheme; +} + +enum class LineSegmentState +{ + Start = 0, + Finish +}; + +struct LineSegmentInfo +{ + LineSegmentInfo() = default; + LineSegmentInfo(LineSegmentState const & state, bool codirectional) + : m_state(state), m_codirectional(codirectional) + { + } + + LineSegmentState m_state = LineSegmentState::Start; + bool m_codirectional = false; +}; + +struct LinePointState +{ + std::map parallelLineStates; + m2::PointD m_firstPoint; +}; + +using LineGeometry = std::vector; +using ColorToLinepartsCache = std::map>; + +bool Equal(LineGeometry const & line1, LineGeometry const & line2) +{ + if (line1.size() != line2.size()) + return false; + + for (size_t i = 0; i < line1.size(); ++i) + { + if (!base::AlmostEqualAbs(line1[i], line2[i], kEps)) + return false; + } + + return true; +} + +bool AddToCache(std::string const & color, LineGeometry const & linePart, + ColorToLinepartsCache & cache) +{ + auto [it, inserted] = cache.emplace(color, std::vector()); + + if (inserted) + { + it->second.push_back(linePart); + return true; + } + + std::vector linePartRev = GetReversed(linePart); + + for (LineGeometry const & cachedPart : it->second) + { + if (Equal(cachedPart, linePart) || Equal(cachedPart, linePartRev)) + return false; + } + + it->second.push_back(linePart); + return true; +} + +void SubwayConverter::CalculateLinePriorities(std::vector const & linesOnScheme) +{ + ColorToLinepartsCache routeSegmentsCache; + + for (auto const & lineSchemeData : linesOnScheme) + { + auto const lineId = lineSchemeData.m_lineId; + std::map linePoints; + + for (auto const & linePart : lineSchemeData.m_lineParts) + { + auto & startPointState = linePoints[linePart.m_segment.m_startIdx]; + auto & endPointState = linePoints[linePart.m_segment.m_endIdx]; + + startPointState.m_firstPoint = linePart.m_firstPoint; + + for (auto const & [parallelLineId, parallelFirstPoint] : linePart.m_commonLines) + { + bool const codirectional = + base::AlmostEqualAbs(linePart.m_firstPoint, parallelFirstPoint, kEps); + + startPointState.parallelLineStates[parallelLineId] = + LineSegmentInfo(LineSegmentState::Start, codirectional); + endPointState.parallelLineStates[parallelLineId] = + LineSegmentInfo(LineSegmentState::Finish, codirectional); + } + } + + linePoints.emplace(lineSchemeData.m_shapeLink.m_startIndex, LinePointState()); + linePoints.emplace(lineSchemeData.m_shapeLink.m_endIndex, LinePointState()); + + std::map parallelLines; + + for (auto it = linePoints.begin(); it != linePoints.end(); ++it) + { + auto itNext = std::next(it); + if (itNext == linePoints.end()) + break; + + auto & startLinePointState = it->second; + size_t startIndex = it->first; + size_t endIndex = itNext->first; + + for (auto const & [id, info] : startLinePointState.parallelLineStates) + { + if (info.m_state == LineSegmentState::Start) + { + auto [itParLine, insertedParLine] = parallelLines.emplace(id, info.m_codirectional); + if (!insertedParLine) + CHECK_EQUAL(itParLine->second, info.m_codirectional, ()); + } + else + { + parallelLines.erase(id); + } + } + + TransitId const routeId = m_feed.m_lines.m_data.at(lineId).m_routeId; + std::string color = m_feed.m_routes.m_data.at(routeId).m_color; + + std::map colors{{color, true /* codirectional */}}; + bool colorCopy = false; + + for (auto const & [id, codirectional] : parallelLines) + { + TransitId const parallelRoute = m_feed.m_lines.m_data.at(id).m_routeId; + auto const parallelColor = m_feed.m_routes.m_data.at(parallelRoute).m_color; + colors.emplace(parallelColor, codirectional); + + if (parallelColor == color && id < lineId) + colorCopy = true; + } + + if (colorCopy) + { + LOG(LINFO, ("Skip line segment with color copy", color, "line id", lineId)); + continue; + } + + LineSegmentOrder lso; + lso.m_segment = + LineSegment(static_cast(startIndex), static_cast(endIndex)); + + auto const & polyline = + m_feed.m_shapes.m_data.at(lineSchemeData.m_shapeLink.m_shapeId).m_points; + + if (!AddToCache(color, + GetPolylinePart(polyline, lso.m_segment.m_startIdx, lso.m_segment.m_endIdx), + routeSegmentsCache)) + { + continue; + } + + auto itColor = colors.find(color); + CHECK(itColor != colors.end(), ()); + + size_t const index = std::distance(colors.begin(), itColor); + + lso.m_order = CalcSegmentOrder(index, colors.size()); + + bool reversed = false; + + if (index > 0 && !colors.begin()->second /* codirectional */) + { + lso.m_order = -lso.m_order; + reversed = true; + } + + m_feed.m_linesMetadata.m_data[lineId].push_back(lso); + + LOG(LINFO, + ("routeId", routeId, "lineId", lineId, "start", startIndex, "end", endIndex, "len", + endIndex - startIndex + 1, "order", lso.m_order, "index", index, "reversed", reversed, + "|| lines count:", parallelLines.size(), "colors count:", colors.size())); + } + } +} + +void SubwayConverter::PrepareLinesMetadata() +{ + for (auto const & [region, linesInRegion] : m_feed.m_splitting.m_lines) + { + LOG(LINFO, ("Handling", region, "region")); + + std::vector linesOnScheme = GetLinesOnScheme(linesInRegion); + + for (size_t i = 0; i < linesOnScheme.size() - 1; ++i) + { + auto & line1 = linesOnScheme[i]; + auto const & shapeLink1 = linesInRegion.at(line1.m_lineId).m_shapeLink; + + // |polyline1| is sub-polyline of the shapeLink1 geometry. + auto const polyline1 = + GetPolylinePart(m_feed.m_shapes.m_data.at(shapeLink1.m_shapeId).m_points, + shapeLink1.m_startIndex, shapeLink1.m_endIndex); + + for (size_t j = i + 1; j < linesOnScheme.size(); ++j) + { + auto & line2 = linesOnScheme[j]; + auto const & shapeLink2 = linesInRegion.at(line2.m_lineId).m_shapeLink; + + if (line1.m_shapeLink.m_shapeId == line2.m_shapeLink.m_shapeId) + { + CHECK_LESS(shapeLink1.m_startIndex, shapeLink1.m_endIndex, ()); + CHECK_LESS(shapeLink2.m_startIndex, shapeLink2.m_endIndex, ()); + + std::optional inter = + GetIntersection(shapeLink1.m_startIndex, shapeLink1.m_endIndex, + shapeLink2.m_startIndex, shapeLink2.m_endIndex); + + if (inter != std::nullopt) + { + LineSegment const segment = inter.value(); + m2::PointD const & startPoint = polyline1[segment.m_startIdx]; + + UpdateLinePart(line1.m_lineParts, segment, startPoint, line2.m_lineId, startPoint); + UpdateLinePart(line2.m_lineParts, segment, startPoint, line1.m_lineId, startPoint); + } + } + else + { + // |polyline2| is sub-polyline of the shapeLink2 geometry. + auto polyline2 = GetPolylinePart(m_feed.m_shapes.m_data.at(shapeLink2.m_shapeId).m_points, + shapeLink2.m_startIndex, shapeLink2.m_endIndex); + + auto [segments1, segments2] = FindIntersections(polyline1, polyline2); + + if (segments1.empty()) + { + auto polyline2Rev = GetReversed(polyline2); + std::tie(segments1, segments2) = FindIntersections(polyline1, polyline2Rev); + + if (!segments1.empty()) + { + for (auto & seg : segments2) + UpdateReversedSegmentIndexes(seg, polyline2.size()); + } + } + + if (!segments1.empty()) + { + for (size_t k = 0; k < segments1.size(); ++k) + { + auto const & [startPoint1, endPoint1] = + GetSegmentEdgesOnPolyline(polyline1, segments1[k]); + auto const & [startPoint2, endPoint2] = + GetSegmentEdgesOnPolyline(polyline2, segments2[k]); + + CHECK(base::AlmostEqualAbs(startPoint1, startPoint2, kEps) && + base::AlmostEqualAbs(endPoint1, endPoint2, kEps) || + base::AlmostEqualAbs(startPoint1, endPoint2, kEps) && + base::AlmostEqualAbs(endPoint1, startPoint2, kEps), + ()); + + ShiftSegmentOnShape(segments1[k], shapeLink1); + ShiftSegmentOnShape(segments2[k], shapeLink2); + + UpdateLinePart(line1.m_lineParts, segments1[k], startPoint1, line2.m_lineId, + startPoint2); + UpdateLinePart(line2.m_lineParts, segments2[k], startPoint2, line1.m_lineId, + startPoint1); + } + } + } + } + } + + CalculateLinePriorities(linesOnScheme); + LOG(LINFO, ("Prepared metadata for lines in", region)); + } +} + routing::transit::Edge SubwayConverter::FindEdge(routing::transit::StopId stop1Id, routing::transit::StopId stop2Id, routing::transit::LineId lineId) const diff --git a/transit/world_feed/subway_converter.hpp b/transit/world_feed/subway_converter.hpp index c036b59fbc..68e1f2cf58 100644 --- a/transit/world_feed/subway_converter.hpp +++ b/transit/world_feed/subway_converter.hpp @@ -4,6 +4,7 @@ #include "transit/transit_entities.hpp" #include "transit/transit_graph_data.hpp" +#include "transit/world_feed/feed_helpers.hpp" #include "transit/world_feed/world_feed.hpp" #include "geometry/mercator.hpp" @@ -22,9 +23,7 @@ namespace transit { -// Pair of points representing corresponding edge endings. -using EdgePoints = std::pair; - +using LineIdToStops = std::unordered_map; // Converts public transport data from the MAPS.ME old transit.json format (which contains only // subway data) to the new line-by-line jsons used for handling data extracted from GTFS. class SubwayConverter @@ -45,14 +44,29 @@ private: bool ConvertTransfers(); bool ConvertGates(); bool ConvertEdges(); + // Tries to minimize the reversed lines count where it is possible by reversing line geometry. + void MinimizeReversedLinesCount(); + // Returns line ids with corresponding shape links and route ids. There can be may lines inside + // the route with same shapeLink. We keep only one of them. These line ids are used in + // |PrepareLinesMetadata()|. + std::vector GetLinesOnScheme( + std::unordered_map const & linesInRegion) const; + // Finds common overlapping (parallel on the subway layer) segments on polylines. Motivation: + // we shouldn't draw parallel lines of different routes on top of each other so the user can’t + // tell which lines go where (the only visible line is the one that is drawn last). We need these + // lines to be drawn in parallel in corresponding routes colours. + void PrepareLinesMetadata(); + // Calculates order for each of the parallel lines in the overlapping segment. In drape frontend + // we use this order as an offset for drawing line. + void CalculateLinePriorities(std::vector const & linesOnScheme); // Methods for creating id & data pairs for |m_feed| based on the subway items. std::pair MakeRoute(routing::transit::Line const & lineSubway); std::pair MakeGate(routing::transit::Gate const & gateSubway); std::pair MakeTransfer( routing::transit::Transfer const & transferSubway); - std::pair MakeLine(routing::transit::Line const & lineSubway, - TransitId routeId); + static std::pair MakeLine(routing::transit::Line const & lineSubway, + TransitId routeId); std::pair MakeEdge(routing::transit::Edge const & edgeSubway); std::pair MakeEdgeTransfer(routing::transit::Edge const & edgeSubway); std::pair MakeStop(routing::transit::Stop const & stopSubway); @@ -73,7 +87,5 @@ private: std::vector m_edgesSubway; // Subset of the |m_graphData| edges with transfers. std::vector m_edgesTransferSubway; - // Mapping of the edge to its ending points on the shape polyline. - std::unordered_map m_edgesOnShapes; }; } // namespace transit diff --git a/transit/world_feed/world_feed.cpp b/transit/world_feed/world_feed.cpp index 89ce2adf2f..39450cb8f7 100644 --- a/transit/world_feed/world_feed.cpp +++ b/transit/world_feed/world_feed.cpp @@ -22,7 +22,6 @@ #include #include "3party/boost/boost/algorithm/string.hpp" -#include "3party/boost/boost/container_hash/hash.hpp" #include "3party/jansson/myjansson.hpp" @@ -747,50 +746,77 @@ void WorldFeed::ModifyLinesAndShapes() for (size_t i = 1; i < links.size(); ++i) { - auto const lineId = links[i].m_lineId; - auto & lineData = m_lines.m_data[lineId]; - auto const shapeId = links[i].m_shapeId; + auto const lineIdNeedle = links[i].m_lineId; + auto & lineDataNeedle = m_lines.m_data[lineIdNeedle]; + auto const shapeIdNeedle = links[i].m_shapeId; - auto [itCache, inserted] = matchingCache.emplace(shapeId, 0); + auto [itCache, inserted] = matchingCache.emplace(shapeIdNeedle, 0); if (!inserted) { if (itCache->second != 0) { - lineData.m_shapeId = 0; - lineData.m_shapeLink = m_lines.m_data[itCache->second].m_shapeLink; + lineDataNeedle.m_shapeId = 0; + lineDataNeedle.m_shapeLink = m_lines.m_data[itCache->second].m_shapeLink; } continue; } - auto const & points = m_shapes.m_data[shapeId].m_points; + auto const & pointsNeedle = m_shapes.m_data[shapeIdNeedle].m_points; + auto pointsNeedleRev = pointsNeedle; + std::reverse(pointsNeedleRev.begin(), pointsNeedleRev.end()); for (size_t j = 0; j < i; ++j) { - auto const & curLineId = links[j].m_lineId; + auto const & lineIdHaystack = links[j].m_lineId; - // We skip shapes which are already included to other shapes. - if (m_lines.m_data[curLineId].m_shapeId == 0) + // We skip shapes which are already included into other shapes. + if (m_lines.m_data[lineIdHaystack].m_shapeId == 0) continue; - auto const curShapeId = links[j].m_shapeId; + auto const shapeIdHaystack = links[j].m_shapeId; - if (curShapeId == shapeId) + if (shapeIdHaystack == shapeIdNeedle) continue; - auto const & curPoints = m_shapes.m_data[curShapeId].m_points; + auto const & pointsHaystack = m_shapes.m_data[shapeIdHaystack].m_points; - auto const it = std::search(curPoints.begin(), curPoints.end(), points.begin(), points.end()); + auto const it = std::search(pointsHaystack.begin(), pointsHaystack.end(), + pointsNeedle.begin(), pointsNeedle.end()); - if (it == curPoints.end()) - continue; + if (it == pointsHaystack.end()) + { + auto const itRev = std::search(pointsHaystack.begin(), pointsHaystack.end(), + pointsNeedleRev.begin(), pointsNeedleRev.end()); + if (itRev == pointsHaystack.end()) + continue; - // Shape with |points| polyline is fully contained in the shape with |curPoints| polyline. - lineData.m_shapeId = 0; - lineData.m_shapeLink.m_shapeId = curShapeId; - lineData.m_shapeLink.m_startIndex = std::distance(curPoints.begin(), it); - lineData.m_shapeLink.m_endIndex = lineData.m_shapeLink.m_startIndex + points.size() - 1; - itCache->second = lineId; - shapesForRemoval.insert(shapeId); + // Shape with |pointsNeedleRev| polyline is fully contained in the shape with + // |pointsHaystack| polyline. + lineDataNeedle.m_shapeId = 0; + lineDataNeedle.m_shapeLink.m_shapeId = shapeIdHaystack; + lineDataNeedle.m_shapeLink.m_endIndex = + static_cast(std::distance(pointsHaystack.begin(), itRev)); + lineDataNeedle.m_shapeLink.m_startIndex = static_cast( + lineDataNeedle.m_shapeLink.m_endIndex + pointsNeedleRev.size() - 1); + + itCache->second = lineIdNeedle; + shapesForRemoval.insert(shapeIdNeedle); + ++subShapesCount; + break; + } + + // Shape with |pointsNeedle| polyline is fully contained in the shape |pointsHaystack|. + lineDataNeedle.m_shapeId = 0; + lineDataNeedle.m_shapeLink.m_shapeId = shapeIdHaystack; + lineDataNeedle.m_shapeLink.m_startIndex = + static_cast(std::distance(pointsHaystack.begin(), it)); + + CHECK_GREATER_OR_EQUAL(pointsNeedle.size(), 2, ()); + + lineDataNeedle.m_shapeLink.m_endIndex = + static_cast(lineDataNeedle.m_shapeLink.m_startIndex + pointsNeedle.size() - 1); + itCache->second = lineIdNeedle; + shapesForRemoval.insert(shapeIdNeedle); ++subShapesCount; break; } @@ -803,7 +829,11 @@ void WorldFeed::ModifyLinesAndShapes() lineData.m_shapeLink.m_shapeId = lineData.m_shapeId; lineData.m_shapeLink.m_startIndex = 0; - lineData.m_shapeLink.m_endIndex = m_shapes.m_data[lineData.m_shapeId].m_points.size(); + + CHECK_GREATER_OR_EQUAL(m_shapes.m_data[lineData.m_shapeId].m_points.size(), 2, ()); + + lineData.m_shapeLink.m_endIndex = + static_cast(m_shapes.m_data[lineData.m_shapeId].m_points.size() - 1); lineData.m_shapeId = 0; } @@ -988,8 +1018,8 @@ size_t WorldFeed::ModifyShapes() if (stopsOnLines.m_isValid) { - itEdge->second.m_shapeLink.m_startIndex = stop1.m_index; - itEdge->second.m_shapeLink.m_endIndex = stop2.m_index; + itEdge->second.m_shapeLink.m_startIndex = static_cast(stop1.m_index); + itEdge->second.m_shapeLink.m_endIndex = static_cast(stop2.m_index); } else { @@ -1395,25 +1425,56 @@ void Routes::Write(IdSet const & ids, std::ofstream & stream) const } } -void Lines::Write(std::unordered_map const & ids, std::ofstream & stream) const +void Lines::Write(std::unordered_map const & ids, + std::ofstream & stream) const { - for (auto const & [lineId, stopIds] : ids) + for (auto const & [lineId, data] : ids) { auto const & line = m_data.find(lineId)->second; auto node = base::NewJSONObject(); ToJSONObject(*node, "id", lineId); ToJSONObject(*node, "route_id", line.m_routeId); - json_object_set_new(node.get(), "shape", ShapeLinkToJson(line.m_shapeLink).release()); + json_object_set_new(node.get(), "shape", ShapeLinkToJson(data.m_shapeLink).release()); ToJSONObject(*node, "title", line.m_title); // Save only stop ids inside current region. - json_object_set_new(node.get(), "stops_ids", VectorToJson(stopIds).release()); + json_object_set_new(node.get(), "stops_ids", VectorToJson(data.m_stopIds).release()); json_object_set_new(node.get(), "schedule", ScheduleToJson(line.m_schedule).release()); WriteJson(node.get(), stream); } } +void LinesMetadata::Write(std::unordered_map const & linesInRegion, + std::ofstream & stream) const +{ + for (auto const & [lineId, lineData] : linesInRegion) + { + auto it = m_data.find(lineId); + if (it == m_data.end()) + continue; + + auto const & lineMetaData = it->second; + auto node = base::NewJSONObject(); + ToJSONObject(*node, "id", lineId); + + auto segmentsOnShape = base::NewJSONArray(); + + for (auto const & lineSegmentOrder : lineMetaData) + { + auto segmentData = base::NewJSONObject(); + ToJSONObject(*segmentData, "order", lineSegmentOrder.m_order); + ToJSONObject(*segmentData, "start_index", lineSegmentOrder.m_segment.m_startIdx); + ToJSONObject(*segmentData, "end_index", lineSegmentOrder.m_segment.m_endIdx); + json_array_append_new(segmentsOnShape.get(), segmentData.release()); + } + + json_object_set_new(node.get(), "shape_segments", segmentsOnShape.release()); + + WriteJson(node.get(), stream); + } +} + void Shapes::Write(IdSet const & ids, std::ofstream & stream) const { for (auto shapeId : ids) @@ -1438,11 +1499,17 @@ void Stops::Write(IdSet const & ids, std::ofstream & stream) const { auto const & stop = m_data.find(stopId)->second; auto node = base::NewJSONObject(); - if (stop.m_osmId == 0) - ToJSONObject(*node, "id", stopId); - else + + ToJSONObject(*node, "id", stopId); + + if (stop.m_osmId != 0) + { ToJSONObject(*node, "osm_id", stop.m_osmId); + if (stop.m_featureId != 0) + ToJSONObject(*node, "feature_id", stop.m_featureId); + } + json_object_set_new(node.get(), "point", PointToJson(stop.m_point).release()); if (stop.m_title.empty()) @@ -1563,10 +1630,16 @@ void Gates::Write(IdSet const & ids, std::ofstream & stream) const void WorldFeed::SplitFeedIntoRegions() { + LOG(LINFO, ("Started splitting feed into regions.")); + SplitStopsBasedData(); LOG(LINFO, ("Split stops into", m_splitting.m_stops.size(), "regions.")); SplitLinesBasedData(); SplitSupplementalData(); + + m_feedIsSplitIntoRegions = true; + + LOG(LINFO, ("Finished splitting feed into regions.")); } void WorldFeed::SplitStopsBasedData() @@ -1589,25 +1662,197 @@ void WorldFeed::SplitStopsBasedData() } } +bool IsSplineSubset(IdList const & stops, IdList const & stopsOther) +{ + if (stops.size() >= stopsOther.size()) + return false; + + for (TransitId stopId : stops) + { + auto const it = std::find(stopsOther.begin(), stopsOther.end(), stopId); + if (it == stopsOther.end()) + return false; + } + + return true; +} + +std::optional WorldFeed::GetParentLineForSpline(TransitId lineId) const +{ + LineData const & lineData = m_lines.m_data.at(lineId); + IdList const & stops = lineData.m_stopIds; + TransitId const routeId = lineData.m_routeId; + + for (auto const & [lineIdOther, lineData] : m_lines.m_data) + { + if (lineIdOther == lineId) + continue; + + LineData const & lineDataOther = m_lines.m_data.at(lineIdOther); + if (lineDataOther.m_routeId != routeId) + continue; + + if (IsSplineSubset(stops, lineDataOther.m_stopIds)) + return lineIdOther; + } + + return std::nullopt; +} + +TransitId WorldFeed::GetSplineParent(TransitId lineId, std::string const & region) const +{ + TransitId parentId = lineId; + auto const & linesInRegion = m_splitting.m_lines.at(region); + auto itSplineParent = linesInRegion.find(parentId); + + while (itSplineParent != linesInRegion.end()) + { + if (!itSplineParent->second.m_splineParent) + break; + + parentId = itSplineParent->second.m_splineParent.value(); + itSplineParent = linesInRegion.find(parentId); + } + + return parentId; +} + +bool WorldFeed::PrepareEdgesInRegion(std::string const & region) +{ + auto & edgeIdsInRegion = m_splitting.m_edges.at(region); + + for (auto const & edgeId : edgeIdsInRegion) + { + TransitId const parentLineId = GetSplineParent(edgeId.m_lineId, region); + TransitId const shapeId = m_lines.m_data.at(parentLineId).m_shapeLink.m_shapeId; + + auto & edgeData = m_edges.m_data.at(edgeId); + + auto itShape = m_shapes.m_data.find(shapeId); + CHECK(itShape != m_shapes.m_data.end(), ("Shape does not exist.")); + + auto const & shapePoints = itShape->second.m_points; + CHECK(!shapePoints.empty(), ("Shape is empty.")); + + auto const it = m_edgesOnShapes.find(edgeId); + CHECK(it != m_edgesOnShapes.end(), (edgeId.m_lineId)); + + for (auto const & polyline : it->second) + { + std::tie(edgeData.m_shapeLink.m_startIndex, edgeData.m_shapeLink.m_endIndex) = + FindSegmentOnShape(shapePoints, polyline); + if (!(edgeData.m_shapeLink.m_startIndex == 0 && edgeData.m_shapeLink.m_endIndex == 0)) + break; + } + + if (edgeData.m_shapeLink.m_startIndex == 0 && edgeData.m_shapeLink.m_endIndex == 0) + { + for (auto const & polyline : it->second) + { + auto r = polyline; + std::reverse(r.begin(), r.end()); + std::tie(edgeData.m_shapeLink.m_startIndex, edgeData.m_shapeLink.m_endIndex) = + FindSegmentOnShape(shapePoints, r); + if (!(edgeData.m_shapeLink.m_startIndex == 0 && edgeData.m_shapeLink.m_endIndex == 0)) + break; + } + } + + if (edgeData.m_shapeLink.m_startIndex == 0 && edgeData.m_shapeLink.m_endIndex == 0) + { + std::tie(edgeData.m_shapeLink.m_startIndex, edgeData.m_shapeLink.m_endIndex) = + FindPointsOnShape(shapePoints, it->second[0].front(), it->second[0].back()); + } + } + + return !edgeIdsInRegion.empty(); +} + void WorldFeed::SplitLinesBasedData() { + std::map> additionalStops; // Fill regional lines and corresponding shapes and routes. for (auto const & [lineId, lineData] : m_lines.m_data) { - for (auto stopId : lineData.m_stopIds) + for (auto const & [region, stopIds] : m_splitting.m_stops) { - for (auto const & [region, stopIds] : m_splitting.m_stops) - { - if (stopIds.find(stopId) == stopIds.end()) - continue; + auto const & [firstStopIdx, lastStopIdx] = GetStopsRange(lineData.m_stopIds, stopIds); - m_splitting.m_lines[region][lineId].emplace_back(stopId); - m_splitting.m_shapes[region].emplace(lineData.m_shapeLink.m_shapeId); - m_splitting.m_routes[region].emplace(lineData.m_routeId); + // Line doesn't intersect this region. + if (!StopIndexIsSet(firstStopIdx)) + continue; + + auto & lineInRegion = m_splitting.m_lines[region][lineId]; + + // We save stop ids which belong to the region and its surroundings. + for (size_t i = firstStopIdx; i <= lastStopIdx; ++i) + { + lineInRegion.m_stopIds.emplace_back(lineData.m_stopIds[i]); + additionalStops[region].insert(lineData.m_stopIds[i]); } + + m_splitting.m_shapes[region].emplace(lineData.m_shapeLink.m_shapeId); + m_splitting.m_routes[region].emplace(lineData.m_routeId); } } + for (auto & [region, linesInRegion] : m_splitting.m_lines) + { + for (auto & [lineId, lineInRegion] : linesInRegion) + lineInRegion.m_splineParent = GetParentLineForSpline(lineId); + } + + for (auto & [region, edges] : m_splitting.m_edges) + { + PrepareEdgesInRegion(region); + } + + for (auto & [region, linesInRegion] : m_splitting.m_lines) + { + for (auto & [lineId, lineInRegion] : linesInRegion) + { + auto const & lineData = m_lines.m_data.at(lineId); + auto const & stopIds = m_splitting.m_stops.at(region); + auto const & [firstStopIdx, lastStopIdx] = GetStopsRange(lineData.m_stopIds, stopIds); + + CHECK(StopIndexIsSet(firstStopIdx), ()); + + lineInRegion.m_shapeLink.m_shapeId = lineData.m_shapeLink.m_shapeId; + + auto const edgeFirst = m_edges.m_data.at( + EdgeId(lineData.m_stopIds[firstStopIdx], lineData.m_stopIds[firstStopIdx + 1], lineId)); + CHECK(!(edgeFirst.m_shapeLink.m_startIndex == 0 && edgeFirst.m_shapeLink.m_endIndex == 0), + ()); + + auto const edgeLast = m_edges.m_data.at( + EdgeId(lineData.m_stopIds[lastStopIdx - 1], lineData.m_stopIds[lastStopIdx], lineId)); + CHECK(!(edgeLast.m_shapeLink.m_startIndex == 0 && edgeLast.m_shapeLink.m_endIndex == 0), ()); + + lineInRegion.m_shapeLink.m_startIndex = + std::min(edgeFirst.m_shapeLink.m_startIndex, edgeLast.m_shapeLink.m_endIndex); + lineInRegion.m_shapeLink.m_endIndex = + std::max(edgeFirst.m_shapeLink.m_startIndex, edgeLast.m_shapeLink.m_endIndex); + + if (lineInRegion.m_shapeLink.m_startIndex == 0 && lineInRegion.m_shapeLink.m_endIndex == 0) + { + CHECK_EQUAL(edgeFirst.m_shapeLink.m_shapeId, edgeLast.m_shapeLink.m_shapeId, ()); + CHECK_EQUAL(edgeFirst.m_shapeLink.m_startIndex, edgeLast.m_shapeLink.m_endIndex, ()); + CHECK_EQUAL(edgeFirst.m_shapeLink.m_endIndex, edgeLast.m_shapeLink.m_startIndex, ()); + + lineInRegion.m_shapeLink.m_startIndex = + std::min(edgeFirst.m_shapeLink.m_startIndex, edgeFirst.m_shapeLink.m_endIndex); + lineInRegion.m_shapeLink.m_endIndex = + std::max(edgeFirst.m_shapeLink.m_startIndex, edgeFirst.m_shapeLink.m_endIndex); + } + + m_splitting.m_shapes[region].emplace(lineData.m_shapeLink.m_shapeId); + m_splitting.m_routes[region].emplace(lineData.m_routeId); + } + } + + for (auto const & [region, ids] : additionalStops) + m_splitting.m_stops[region].insert(ids.begin(), ids.end()); + // Fill regional networks based on routes. for (auto const & [region, routeIds] : m_splitting.m_routes) { @@ -1657,6 +1902,11 @@ void WorldFeed::SaveRegions(std::string const & worldFeedDir, std::string const ()); CHECK(DumpData(m_lines, m_splitting.m_lines[region], base::JoinPath(path, kLinesFile), overwrite), ()); + + CHECK(DumpData(m_linesMetadata, m_splitting.m_lines[region], + base::JoinPath(path, kLinesMetadataFile), overwrite), + ()); + CHECK(DumpData(m_shapes, m_splitting.m_shapes[region], base::JoinPath(path, kShapesFile), overwrite), ()); @@ -1688,10 +1938,11 @@ bool WorldFeed::Save(std::string const & worldFeedDir, bool overwrite) } CHECK(!m_edges.m_data.empty(), ()); - LOG(LINFO, ("Started splitting feed into regions.")); - SplitFeedIntoRegions(); - LOG(LINFO, ("Finished splitting feed into regions. Saving to", worldFeedDir)); + if (!m_feedIsSplitIntoRegions) + SplitFeedIntoRegions(); + + LOG(LINFO, ("Saving feed to", worldFeedDir)); for (auto const & regionAndData : m_splitting.m_stops) SaveRegions(worldFeedDir, regionAndData.first, overwrite); diff --git a/transit/world_feed/world_feed.hpp b/transit/world_feed/world_feed.hpp index 4d415ed318..7478ac015f 100644 --- a/transit/world_feed/world_feed.hpp +++ b/transit/world_feed/world_feed.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include #include @@ -85,16 +86,28 @@ struct LineData std::unordered_set m_gtfsServiceIds; }; +struct LineSegmentInRegion +{ + // Stops in the region. + IdList m_stopIds; + // Spline line id to its parent line id mapping. + std::optional m_splineParent = std::nullopt; + // Indexes of the line shape link may differ in different regions. + ShapeLink m_shapeLink; +}; + struct Lines { - void Write(std::unordered_map const & ids, std::ofstream & stream) const; + void Write(std::unordered_map const & ids, + std::ofstream & stream) const; std::unordered_map m_data; }; struct LinesMetadata { - void Write(std::unordered_map const & ids, std::ofstream & stream) const; + void Write(std::unordered_map const & linesInRegion, + std::ofstream & stream) const; // Line id to line additional data (e.g. for rendering). std::unordered_map m_data; @@ -234,7 +247,8 @@ struct StopsOnLines }; using IdsInRegion = std::unordered_map; -using LineIdsInRegion = std::unordered_map>; +using LinesInRegion = + std::unordered_map>; using EdgeIdsInRegion = std::unordered_map; using EdgeTransferIdsInRegion = std::unordered_map; @@ -244,7 +258,7 @@ struct TransitByRegion { IdsInRegion m_networks; IdsInRegion m_routes; - LineIdsInRegion m_lines; + LinesInRegion m_lines; IdsInRegion m_shapes; IdsInRegion m_stops; EdgeIdsInRegion m_edges; @@ -253,6 +267,9 @@ struct TransitByRegion IdsInRegion m_gates; }; +// Pair of points representing corresponding edge endings. +using EdgePoints = std::pair; + // Class for merging scattered GTFS feeds into one World feed with static ids. // The usage scenario consists of steps: // 1) Initialize |WorldFeed| instance with |IdGenerator| for correct id assignment to GTFS entities, @@ -304,6 +321,11 @@ private: bool UpdateStop(TransitId stopId, gtfs::StopTime const & stopTime, std::string const & stopHash, TransitId lineId); + std::optional GetParentLineForSpline(TransitId lineId) const; + bool PrepareEdgesInRegion(std::string const & region); + + TransitId GetSplineParent(TransitId lineId, std::string const & region) const; + std::unordered_map> GetStopsForShapeMatching(); // Adds stops projections to shapes. Updates corresponding links to shapes. @@ -357,6 +379,9 @@ private: Transfers m_transfers; Gates m_gates; + // Mapping of the edge to its ending points on the shape polyline. + std::unordered_map>, EdgeIdHasher> m_edgesOnShapes; + // Ids of entities for json'izing, split by regions. TransitByRegion m_splitting;