[transit] Split lines into segments for rendering on the subway layer.

This commit is contained in:
Olga Khlopkova 2020-10-02 15:31:33 +03:00 committed by Anatoliy V. Tomilov
parent e572ae2365
commit e350506cc2
4 changed files with 864 additions and 116 deletions

View file

@ -9,38 +9,54 @@
#include <algorithm>
#include <iterator>
#include <limits>
#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<m2::PointD> 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<uint32_t>(lineId >> 4);
}
// Returns polyline found by pair of stop ids.
std::vector<m2::PointD> GetShapeByStops(std::vector<routing::transit::Shape> 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<m2::PointD, m2::PointD> GetSegmentEdgesOnPolyline(
std::vector<m2::PointD> 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<uint32_t>(polylineSize - segment.m_startIdx - 1);
segment.m_startIdx = static_cast<uint32_t>(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<TransitId, RouteData> 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<EdgeId, EdgeData> 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<TransitId, StopData> 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<m2::PointD> 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<std::vector<m2::PointD>>{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<TransitId, std::set<TransitId>> 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<LineSchemeData> SubwayConverter::GetLinesOnScheme(
std::unordered_map<TransitId, LineSegmentInRegion> const & linesInRegion) const
{
// Color of line to shape link and one of line ids with this link.
std::map<std::string, std::map<ShapeLink, TransitId>> 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<ShapeLink, TransitId>());
if (inserted)
{
it->second[newShapeLink] = lineId;
continue;
}
bool insert = true;
std::vector<ShapeLink> 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<LineSchemeData> 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<TransitId, LineSegmentInfo> parallelLineStates;
m2::PointD m_firstPoint;
};
using LineGeometry = std::vector<m2::PointD>;
using ColorToLinepartsCache = std::map<std::string, std::vector<LineGeometry>>;
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<LineGeometry>());
if (inserted)
{
it->second.push_back(linePart);
return true;
}
std::vector<m2::PointD> 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<LineSchemeData> const & linesOnScheme)
{
ColorToLinepartsCache routeSegmentsCache;
for (auto const & lineSchemeData : linesOnScheme)
{
auto const lineId = lineSchemeData.m_lineId;
std::map<size_t, LinePointState> 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<TransitId, bool> 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<std::string, bool> 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<uint32_t>(startIndex), static_cast<uint32_t>(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<LineSchemeData> 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<LineSegment> 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

View file

@ -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<m2::PointD, m2::PointD>;
using LineIdToStops = std::unordered_map<TransitId, IdList>;
// 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<LineSchemeData> GetLinesOnScheme(
std::unordered_map<TransitId, LineSegmentInRegion> 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 cant
// 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<LineSchemeData> const & linesOnScheme);
// Methods for creating id & data pairs for |m_feed| based on the subway items.
std::pair<TransitId, RouteData> MakeRoute(routing::transit::Line const & lineSubway);
std::pair<TransitId, GateData> MakeGate(routing::transit::Gate const & gateSubway);
std::pair<TransitId, TransferData> MakeTransfer(
routing::transit::Transfer const & transferSubway);
std::pair<TransitId, LineData> MakeLine(routing::transit::Line const & lineSubway,
TransitId routeId);
static std::pair<TransitId, LineData> MakeLine(routing::transit::Line const & lineSubway,
TransitId routeId);
std::pair<EdgeId, EdgeData> MakeEdge(routing::transit::Edge const & edgeSubway);
std::pair<EdgeTransferId, size_t> MakeEdgeTransfer(routing::transit::Edge const & edgeSubway);
std::pair<TransitId, StopData> MakeStop(routing::transit::Stop const & stopSubway);
@ -73,7 +87,5 @@ private:
std::vector<routing::transit::Edge> m_edgesSubway;
// Subset of the |m_graphData| edges with transfers.
std::vector<routing::transit::Edge> m_edgesTransferSubway;
// Mapping of the edge to its ending points on the shape polyline.
std::unordered_map<EdgeId, EdgePoints, EdgeIdHasher> m_edgesOnShapes;
};
} // namespace transit

View file

@ -22,7 +22,6 @@
#include <utility>
#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<uint32_t>(std::distance(pointsHaystack.begin(), itRev));
lineDataNeedle.m_shapeLink.m_startIndex = static_cast<uint32_t>(
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<uint32_t>(std::distance(pointsHaystack.begin(), it));
CHECK_GREATER_OR_EQUAL(pointsNeedle.size(), 2, ());
lineDataNeedle.m_shapeLink.m_endIndex =
static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(stop1.m_index);
itEdge->second.m_shapeLink.m_endIndex = static_cast<uint32_t>(stop2.m_index);
}
else
{
@ -1395,25 +1425,56 @@ void Routes::Write(IdSet const & ids, std::ofstream & stream) const
}
}
void Lines::Write(std::unordered_map<TransitId, IdList> const & ids, std::ofstream & stream) const
void Lines::Write(std::unordered_map<TransitId, LineSegmentInRegion> 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<TransitId, LineSegmentInRegion> 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<TransitId> 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<std::string, std::set<TransitId>> 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);

View file

@ -13,6 +13,7 @@
#include <cstdint>
#include <fstream>
#include <optional>
#include <string>
#include <unordered_map>
#include <unordered_set>
@ -85,16 +86,28 @@ struct LineData
std::unordered_set<std::string> m_gtfsServiceIds;
};
struct LineSegmentInRegion
{
// Stops in the region.
IdList m_stopIds;
// Spline line id to its parent line id mapping.
std::optional<TransitId> 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<TransitId, IdList> const & ids, std::ofstream & stream) const;
void Write(std::unordered_map<TransitId, LineSegmentInRegion> const & ids,
std::ofstream & stream) const;
std::unordered_map<TransitId, LineData> m_data;
};
struct LinesMetadata
{
void Write(std::unordered_map<TransitId, IdList> const & ids, std::ofstream & stream) const;
void Write(std::unordered_map<TransitId, LineSegmentInRegion> const & linesInRegion,
std::ofstream & stream) const;
// Line id to line additional data (e.g. for rendering).
std::unordered_map<TransitId, LineSegmentsOrder> m_data;
@ -234,7 +247,8 @@ struct StopsOnLines
};
using IdsInRegion = std::unordered_map<std::string, IdSet>;
using LineIdsInRegion = std::unordered_map<std::string, std::unordered_map<TransitId, IdList>>;
using LinesInRegion =
std::unordered_map<std::string, std::unordered_map<TransitId, LineSegmentInRegion>>;
using EdgeIdsInRegion = std::unordered_map<std::string, IdEdgeSet>;
using EdgeTransferIdsInRegion = std::unordered_map<std::string, IdEdgeTransferSet>;
@ -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<m2::PointD, m2::PointD>;
// 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<TransitId> GetParentLineForSpline(TransitId lineId) const;
bool PrepareEdgesInRegion(std::string const & region);
TransitId GetSplineParent(TransitId lineId, std::string const & region) const;
std::unordered_map<TransitId, std::vector<StopsOnLines>> 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<EdgeId, std::vector<std::vector<m2::PointD>>, EdgeIdHasher> m_edgesOnShapes;
// Ids of entities for json'izing, split by regions.
TransitByRegion m_splitting;