diff --git a/transit/transit_types.hpp b/transit/transit_types.hpp index d872e949a9..3eb296b1c7 100644 --- a/transit/transit_types.hpp +++ b/transit/transit_types.hpp @@ -147,6 +147,7 @@ public: StopId GetId() const { return m_id.Get(); } FeatureId GetFeatureId() const { return m_featureIdentifiers.GetFeatureId(); } + OsmId GetOsmId() const { return m_featureIdentifiers.GetOsmId(); } TransferId GetTransferId() const { return m_transferId; } std::vector const & GetLineIds() const { return m_lineIds; } m2::PointD const & GetPoint() const { return m_point; } diff --git a/transit/world_feed/CMakeLists.txt b/transit/world_feed/CMakeLists.txt index c2e3d890c2..db622239a6 100644 --- a/transit/world_feed/CMakeLists.txt +++ b/transit/world_feed/CMakeLists.txt @@ -7,6 +7,8 @@ set(SRC date_time_helpers.hpp feed_helpers.cpp feed_helpers.hpp + subway_converter.cpp + subway_converter.hpp world_feed.cpp world_feed.hpp ) @@ -26,6 +28,8 @@ omim_link_libraries( routing_common transit descriptions + storage + search ugc drape partners_api diff --git a/transit/world_feed/gtfs_converter/gtfs_converter.cpp b/transit/world_feed/gtfs_converter/gtfs_converter.cpp index d5f555bd25..dc4f2ee7b4 100644 --- a/transit/world_feed/gtfs_converter/gtfs_converter.cpp +++ b/transit/world_feed/gtfs_converter/gtfs_converter.cpp @@ -1,6 +1,7 @@ #include "generator/affiliation.hpp" #include "transit/world_feed/color_picker.hpp" +#include "transit/world_feed/subway_converter.hpp" #include "transit/world_feed/world_feed.hpp" #include "platform/platform.hpp" @@ -13,7 +14,9 @@ #include "3party/gflags/src/gflags/gflags.h" DEFINE_string(path_mapping, "", "Path to the mapping file of TransitId to GTFS hash"); +// One of these two paths should be specified: |path_gtfs_feeds| and/or |path_subway_json|. DEFINE_string(path_gtfs_feeds, "", "Directory with GTFS feeds subdirectories"); +DEFINE_string(path_subway_json, "", "MAPS.ME json file with subway data from OSM"); DEFINE_string(path_json, "", "Output directory for dumping json files"); DEFINE_string(path_resources, "", "MAPS.ME resources directory"); DEFINE_string(start_feed, "", "Optional. Feed directory from which the process continues"); @@ -156,35 +159,17 @@ FeedStatus ReadFeed(gtfs::Feed & feed) return FeedStatus::OK; } -int main(int argc, char ** argv) +// Reads GTFS feeds from directories in |FLAGS_path_gtfs_feeds|. Converts each feed to the WorldFeed +// object and saves to the |FLAGS_path_json| path in the new transit line-by-line json format. +bool ConvertFeeds(transit::IdGenerator & generator, transit::ColorPicker & colorPicker, + feature::CountriesFilesAffiliation & mwmMatcher) { - google::SetUsageMessage("Reads GTFS feeds, produces json with global ids for generator."); - google::ParseCommandLineFlags(&argc, &argv, true); - auto const toolName = base::GetNameFromFullPath(argv[0]); - - if (FLAGS_path_mapping.empty() || FLAGS_path_gtfs_feeds.empty() || FLAGS_path_json.empty()) - { - LOG(LWARNING, ("Some of the required options are not present.")); - google::ShowUsageWithFlagsRestrict(argv[0], toolName.c_str()); - return -1; - } - - if (!Platform::IsDirectory(FLAGS_path_gtfs_feeds) || !Platform::IsDirectory(FLAGS_path_json) || - !Platform::IsDirectory(FLAGS_path_resources)) - { - LOG(LWARNING, - ("Some paths set in options are not valid. Check the directories:", - FLAGS_path_gtfs_feeds, FLAGS_path_json, FLAGS_path_resources)); - google::ShowUsageWithFlagsRestrict(argv[0], toolName.c_str()); - return -1; - } - auto const gtfsFeeds = GetGtfsFeedsInDirectory(FLAGS_path_gtfs_feeds); if (gtfsFeeds.empty()) { LOG(LERROR, ("No subdirectories with GTFS feeds found in", FLAGS_path_gtfs_feeds)); - return -1; + return false; } std::vector invalidFeeds; @@ -195,14 +180,6 @@ int main(int argc, char ** argv) size_t feedsTotal = gtfsFeeds.size(); bool pass = true; - transit::IdGenerator generator(FLAGS_path_mapping); - - GetPlatform().SetResourceDir(FLAGS_path_resources); - transit::ColorPicker colorPicker; - - feature::CountriesFilesAffiliation mwmMatcher(GetPlatform().ResourcesDir(), - false /* haveBordersForWholeWorld */); - for (size_t i = 0; i < gtfsFeeds.size(); ++i) { base::Timer feedTimer; @@ -259,13 +236,82 @@ int main(int argc, char ** argv) break; } - generator.Save(); - LOG(LINFO, ("Corrupted feeds paths:", invalidFeeds)); LOG(LINFO, ("Corrupted feeds:", invalidFeeds.size(), "/", feedsTotal)); + LOG(LINFO, ("Bad stop sequences:", transit::WorldFeed::GetCorruptedStopSequenceCount())); LOG(LINFO, ("Feeds with no shapes:", feedsWithNoShapesCount, "/", feedsTotal)); LOG(LINFO, ("Feeds parsed but not dumped:", feedsNotDumpedCount, "/", feedsTotal)); LOG(LINFO, ("Total dumped feeds:", feedsDumped, "/", feedsTotal)); - LOG(LINFO, ("Bad stop sequences:", transit::WorldFeed::GetCorruptedStopSequenceCount())); - return 0; + + return true; +} + +// Reads subway json from |FLAGS_path_subway_json|, converts it to the WorldFeed object and saves +// to the |FLAGS_path_json| path in the new transit line-by-line json format. +bool ConvertSubway(transit::IdGenerator & generator, transit::ColorPicker & colorPicker, + feature::CountriesFilesAffiliation & mwmMatcher, bool overwrite) +{ + transit::WorldFeed globalFeed(generator, colorPicker, mwmMatcher); + transit::SubwayConverter converter(FLAGS_path_subway_json, globalFeed); + + if (!converter.Convert()) + return false; + + globalFeed.Save(FLAGS_path_json, overwrite); + + return true; +} + +int main(int argc, char ** argv) +{ + google::SetUsageMessage("Reads GTFS feeds or subway transit.json, produces json with global ids for generator."); + google::ParseCommandLineFlags(&argc, &argv, true); + auto const toolName = base::GetNameFromFullPath(argv[0]); + + if (FLAGS_path_gtfs_feeds.empty() && FLAGS_path_subway_json.empty()) + { + LOG(LWARNING, ("Path to GTFS feeds directory or path to the subways json must be specified.")); + google::ShowUsageWithFlagsRestrict(argv[0], toolName.c_str()); + return EXIT_FAILURE; + } + + if (FLAGS_path_mapping.empty() || FLAGS_path_json.empty()) + { + LOG(LWARNING, ("Some of the required options are not present.")); + google::ShowUsageWithFlagsRestrict(argv[0], toolName.c_str()); + return EXIT_FAILURE; + } + + if ((!FLAGS_path_gtfs_feeds.empty() && !Platform::IsDirectory(FLAGS_path_gtfs_feeds)) || + !Platform::IsDirectory(FLAGS_path_json) || !Platform::IsDirectory(FLAGS_path_resources) || + (!FLAGS_path_subway_json.empty() && + !Platform::IsFileExistsByFullPath(FLAGS_path_subway_json))) + { + LOG(LWARNING, ("Some paths set in options are not valid. Check the directories:", + FLAGS_path_gtfs_feeds, FLAGS_path_json, FLAGS_path_resources)); + google::ShowUsageWithFlagsRestrict(argv[0], toolName.c_str()); + return EXIT_FAILURE; + } + + transit::IdGenerator generator(FLAGS_path_mapping); + + GetPlatform().SetResourceDir(FLAGS_path_resources); + transit::ColorPicker colorPicker; + + feature::CountriesFilesAffiliation mwmMatcher(GetPlatform().ResourcesDir(), + false /* haveBordersForWholeWorld */); + + if (!FLAGS_path_gtfs_feeds.empty() && !ConvertFeeds(generator, colorPicker, mwmMatcher)) + return EXIT_FAILURE; + + if (!FLAGS_path_subway_json.empty() && + !ConvertSubway(generator, colorPicker, mwmMatcher, + FLAGS_path_gtfs_feeds.empty() /* overwrite */)) + { + return EXIT_FAILURE; + } + + generator.Save(); + + return EXIT_SUCCESS; } diff --git a/transit/world_feed/subway_converter.cpp b/transit/world_feed/subway_converter.cpp new file mode 100644 index 0000000000..7378249f0c --- /dev/null +++ b/transit/world_feed/subway_converter.cpp @@ -0,0 +1,391 @@ +#include "transit/world_feed/subway_converter.hpp" + +#include "generator/transit_generator.hpp" + +#include "routing/fake_feature_ids.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#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) +{ + 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); +} + +// Returns polyline found by pair of stop ids. +std::vector GetShapeByStops(std::vector const & shapes, + routing::transit::StopId stopId1, + routing::transit::StopId stopId2) +{ + 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; + }); + + CHECK(itShape != shapes.end(), (stopId1, stopId2)); + + return itShape->GetPolyline(); +} +} // namespace + +namespace transit +{ +std::string const kHashPrefix = "mapsme_transit"; +std::string const kDefaultLang = "default"; +std::string const kSubwayRouteType = "subway"; +std::string const kDefaultHours = "24/7"; + +SubwayConverter::SubwayConverter(std::string const & subwayJson, WorldFeed & feed) + : m_subwayJson(subwayJson), m_feed(feed) +{ +} + +bool SubwayConverter::Convert() +{ + routing::transit::OsmIdToFeatureIdsMap emptyMapping; + routing::transit::DeserializeFromJson(emptyMapping, m_subwayJson, m_graphData); + + if (!ConvertNetworks()) + return false; + + if (!SplitEdges()) + return false; + + if (!ConvertLinesBasedData()) + return false; + + m_feed.ModifyLinesAndShapes(); + + if (!ConvertStops()) + return false; + + if (!ConvertTransfers()) + return false; + + // In contrast to the GTFS gates OSM gates for subways shouldn't be empty. + if (!ConvertGates()) + return false; + + return ConvertEdges(); +} + +bool SubwayConverter::ConvertNetworks() +{ + auto const & networksSubway = m_graphData.GetNetworks(); + m_feed.m_networks.m_data.reserve(networksSubway.size()); + + for (auto const & networkSubway : networksSubway) + { + // Subway network id is city id index approximately in interval (0, 400). + TransitId const networkId = networkSubway.GetId(); + CHECK(!routing::FakeFeatureIds::IsTransitFeature(networkId), (networkId)); + + Translations const title{{kDefaultLang, networkSubway.GetTitle()}}; + m_feed.m_networks.m_data.emplace(networkId, title); + } + + LOG(LINFO, + ("Converted", m_feed.m_networks.m_data.size(), "networks from subways to public transport.")); + + return !m_feed.m_networks.m_data.empty(); +} + +bool SubwayConverter::SplitEdges() +{ + for (auto const & edgeSubway : m_graphData.GetEdges()) + { + if (edgeSubway.GetTransfer()) + m_edgesTransferSubway.emplace_back(edgeSubway); + else + m_edgesSubway.emplace_back(edgeSubway); + } + + return !m_edgesSubway.empty() && !m_edgesTransferSubway.empty(); +} + +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); + TransitId const routeId = m_feed.m_idGenerator.MakeId(routeHash); + + RouteData routeData; + routeData.m_title = {{kDefaultLang, routeTitle}}; + routeData.m_routeType = kSubwayRouteType; + routeData.m_networkId = lineSubway.GetNetworkId(); + routeData.m_color = lineSubway.GetColor(); + + return {routeId, routeData}; +} + +std::pair SubwayConverter::MakeGate(routing::transit::Gate const & gateSubway) +{ + // This id is used only for storing gates in gtfs_converter tool. It is not saved to json. + TransitId const gateId = + m_feed.m_idGenerator.MakeId(BuildHash(kHashPrefix, std::to_string(gateSubway.GetOsmId()))); + GateData gateData; + + gateData.m_isEntrance = gateSubway.GetEntrance(); + gateData.m_isExit = gateSubway.GetExit(); + gateData.m_point = gateSubway.GetPoint(); + gateData.m_osmId = gateSubway.GetOsmId(); + + for (auto stopIdSubway : gateSubway.GetStopIds()) + { + gateData.m_weights.emplace_back(TimeFromGateToStop(m_stopIdMapping[stopIdSubway] /* stopId */, + gateSubway.GetWeight() /* timeSeconds */)); + } + + return {gateId, gateData}; +} + +std::pair SubwayConverter::MakeTransfer( + routing::transit::Transfer const & transferSubway) +{ + TransitId const transferId = + m_feed.m_idGenerator.MakeId(BuildHash(kHashPrefix, std::to_string(transferSubway.GetId()))); + + TransferData transferData; + transferData.m_point = transferSubway.GetPoint(); + + for (auto stopIdSubway : transferSubway.GetStopIds()) + transferData.m_stopsIds.emplace_back(m_stopIdMapping[stopIdSubway]); + + return {transferId, transferData}; +} + +std::pair SubwayConverter::MakeLine(routing::transit::Line const & lineSubway, + TransitId routeId) +{ + TransitId const lineId = lineSubway.GetId(); + CHECK(!routing::FakeFeatureIds::IsTransitFeature(lineId), (lineId)); + + LineData lineData; + lineData.m_routeId = routeId; + lineData.m_title = {{kDefaultLang, lineSubway.GetTitle()}}; + lineData.m_intervals = {LineInterval(lineSubway.GetInterval() /* headwayS */, + osmoh::OpeningHours(kDefaultHours) /* timeIntervals */)}; + lineData.m_serviceDays = osmoh::OpeningHours(kDefaultHours); + + return {lineId, lineData}; +} + +std::pair SubwayConverter::MakeEdge(routing::transit::Edge const & edgeSubway) +{ + auto const lineId = edgeSubway.GetLineId(); + EdgeId const edgeId(m_stopIdMapping[edgeSubway.GetStop1Id()], + m_stopIdMapping[edgeSubway.GetStop2Id()], lineId); + 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; + + 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}; +} + +std::pair SubwayConverter::MakeEdgeTransfer( + routing::transit::Edge const & edgeSubway) +{ + EdgeTransferId const edgeTransferId(m_stopIdMapping[edgeSubway.GetStop1Id()] /* fromStopId */, + m_stopIdMapping[edgeSubway.GetStop2Id()] /* toStopId */); + return {edgeTransferId, edgeSubway.GetWeight()}; +} + +std::pair SubwayConverter::MakeStop(routing::transit::Stop const & stopSubway) +{ + TransitId const stopId = m_stopIdMapping[stopSubway.GetId()]; + + StopData stopData; + stopData.m_point = stopSubway.GetPoint(); + stopData.m_osmId = stopSubway.GetOsmId(); + + return {stopId, stopData}; +} + +bool SubwayConverter::ConvertLinesBasedData() +{ + auto const & linesSubway = m_graphData.GetLines(); + m_feed.m_lines.m_data.reserve(linesSubway.size()); + m_feed.m_shapes.m_data.reserve(linesSubway.size()); + + auto const & shapesSubway = m_graphData.GetShapes(); + + for (auto const & lineSubway : linesSubway) + { + auto const [routeId, routeData] = MakeRoute(lineSubway); + m_feed.m_routes.m_data.emplace(routeId, routeData); + + auto [lineId, lineData] = MakeLine(lineSubway, routeId); + + TransitId const shapeId = m_feed.m_idGenerator.MakeId( + BuildHash(kHashPrefix, std::string("shape"), std::to_string(lineId))); + lineData.m_shapeId = shapeId; + + ShapeData shapeData; + shapeData.m_lineIds.insert(lineId); + + 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.")); + + for (size_t i = 0; i < rangeSubway.size(); ++i) + { + auto const stopIdSubway = rangeSubway[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 & edge = FindEdge(stopIdSubwayPrev, stopIdSubway, lineId); + + for (auto const & id : edge.GetShapeIds()) + { + auto const & polyline = GetShapeByStops(shapesSubway, id.GetStop1Id(), id.GetStop2Id()); + CHECK(!polyline.empty(), ()); + + // 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()); + } + + 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) + { + LOG(LWARNING, ("Edge duplicate in subways. stop1_id", stopIdSubwayPrev, "stop2_id", + stopIdSubway, "line_id", lineId)); + } + } + + m_feed.m_lines.m_data.emplace(lineId, lineData); + m_feed.m_shapes.m_data.emplace(shapeId, shapeData); + } + + LOG(LDEBUG, ("Converted", m_feed.m_routes.m_data.size(), "routes,", m_feed.m_lines.m_data.size(), + "lines from subways to public transport.")); + + return !m_feed.m_lines.m_data.empty(); +} + +bool SubwayConverter::ConvertStops() +{ + auto const & stopsSubway = m_graphData.GetStops(); + m_feed.m_stops.m_data.reserve(stopsSubway.size()); + + 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.")); + + return !m_feed.m_stops.m_data.empty(); +} + +bool SubwayConverter::ConvertTransfers() +{ + auto const & transfersSubway = m_graphData.GetTransfers(); + m_feed.m_transfers.m_data.reserve(transfersSubway.size()); + + for (auto const & transferSubway : transfersSubway) + { + auto const [transferId, transferData] = MakeTransfer(transferSubway); + m_feed.m_transfers.m_data.emplace(transferId, transferData); + } + + LOG(LINFO, ("Converted", m_feed.m_transfers.m_data.size(), + "transfers from subways to public transport.")); + + return !m_feed.m_transfers.m_data.empty(); +} + +bool SubwayConverter::ConvertGates() +{ + auto const & gatesSubway = m_graphData.GetGates(); + m_feed.m_gates.m_data.reserve(gatesSubway.size()); + + for (auto const & gateSubway : gatesSubway) + { + auto const [gateId, gateData] = MakeGate(gateSubway); + 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.")); + + return !m_feed.m_gates.m_data.empty(); +} + +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.")); + + 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.")); + + return !m_feed.m_edges.m_data.empty() && !m_feed.m_edgesTransfers.m_data.empty(); +} + +routing::transit::Edge SubwayConverter::FindEdge(routing::transit::StopId stop1Id, + routing::transit::StopId stop2Id, + routing::transit::LineId lineId) const +{ + auto const itEdge = std::find_if(m_edgesSubway.begin(), m_edgesSubway.end(), + [stop1Id, stop2Id, lineId](routing::transit::Edge const & edge) { + return edge.GetStop1Id() == stop1Id && + edge.GetStop2Id() == stop2Id && + edge.GetLineId() == lineId; + }); + + CHECK(itEdge != m_edgesSubway.end(), (stop1Id, stop2Id, lineId)); + + return *itEdge; +} +} // namespace transit diff --git a/transit/world_feed/subway_converter.hpp b/transit/world_feed/subway_converter.hpp new file mode 100644 index 0000000000..c036b59fbc --- /dev/null +++ b/transit/world_feed/subway_converter.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "generator/affiliation.hpp" + +#include "transit/transit_entities.hpp" +#include "transit/transit_graph_data.hpp" +#include "transit/world_feed/world_feed.hpp" + +#include "geometry/mercator.hpp" +#include "geometry/point2d.hpp" + +#include "defines.hpp" + +#include +#include +#include +#include +#include +#include + +#include "3party/opening_hours/opening_hours.hpp" + +namespace transit +{ +// Pair of points representing corresponding edge endings. +using EdgePoints = std::pair; + +// 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 +{ +public: + SubwayConverter(std::string const & subwayJson, WorldFeed & feed); + // Parses subway json and converts it to entities in WorldFeed |m_feed|. + bool Convert(); + +private: + bool ConvertNetworks(); + // Splits subway edges in two containers: |m_edgesSubway| for edges between two stops on the line + // and |m_edgesTransferSubway| for transfer edges. + bool SplitEdges(); + // Converts lines, creates routes based on the lines data and constructs shapes. + bool ConvertLinesBasedData(); + bool ConvertStops(); + bool ConvertTransfers(); + bool ConvertGates(); + bool ConvertEdges(); + + // 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); + std::pair MakeEdge(routing::transit::Edge const & edgeSubway); + std::pair MakeEdgeTransfer(routing::transit::Edge const & edgeSubway); + std::pair MakeStop(routing::transit::Stop const & stopSubway); + + routing::transit::Edge FindEdge(routing::transit::StopId stop1Id, + routing::transit::StopId stop2Id, + routing::transit::LineId lineId) const; + + // Path to the file with subways json. + std::string m_subwayJson; + // Transit graph for deserializing json from |m_subwayJson|. + routing::transit::GraphData m_graphData; + // Destination feed for converted items from subway. + WorldFeed & m_feed; + // Mapping of subway stop id to transit stop id. + std::unordered_map m_stopIdMapping; + // Subset of the |m_graphData| edges with no transfers. + 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 394ec2be27..ee3885f615 100644 --- a/transit/world_feed/world_feed.cpp +++ b/transit/world_feed/world_feed.cpp @@ -29,23 +29,6 @@ namespace { -template -auto BuildHash(Values... values) -{ - static std::string const delimiter = "_"; - - size_t constexpr paramsCount = sizeof...(Values); - size_t const delimitersSize = (paramsCount - 1) * delimiter.size(); - size_t const totalSize = (delimitersSize + ... + values.size()); - - std::string hash; - hash.reserve(totalSize); - (hash.append(values + delimiter), ...); - hash.pop_back(); - - return hash; -} - template void AddToRegions(C & container, ID const & id, transit::Regions const & regions) { @@ -266,7 +249,7 @@ IdGenerator::IdGenerator(std::string const & idMappingPath) } std::ifstream mappingFile; - mappingFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + mappingFile.exceptions(std::ifstream::badbit); try { @@ -285,7 +268,6 @@ IdGenerator::IdGenerator(std::string const & idMappingPath) // The first line of the mapping file is current free id. m_curId = static_cast(std::stol(idStr)); - CHECK(routing::FakeFeatureIds::IsTransitFeature(m_curId), (m_curId)); // Next lines are sequences of id and hash pairs, each on new line. while (std::getline(mappingFile, idStr)) @@ -296,7 +278,6 @@ IdGenerator::IdGenerator(std::string const & idMappingPath) std::tie(std::ignore, inserted) = m_hashToId.emplace(hash, id); CHECK(inserted, ("Not unique", id, hash)); - CHECK(routing::FakeFeatureIds::IsTransitFeature(id), (id)); } } catch (std::ifstream::failure const & se) @@ -618,7 +599,8 @@ bool WorldFeed::FillStopsEdges() EdgeData data; data.m_shapeLink.m_shapeId = shapeId; data.m_weight = - stopTime2.arrival_time.get_total_seconds() - stopTime1.departure_time.get_total_seconds(); + static_cast(stopTime2.arrival_time.get_total_seconds() - + stopTime1.departure_time.get_total_seconds()); auto [itEdge, insertedEdge] = m_edges.m_data.emplace(EdgeId(stop1Id, stop2Id, lineId), data); @@ -1266,7 +1248,11 @@ void Stops::Write(IdSet const & ids, std::ofstream & stream) const { auto const & stop = m_data.find(stopId)->second; auto node = base::NewJSONObject(); - ToJSONObject(*node, "id", stopId); + if (stop.m_osmId == 0) + ToJSONObject(*node, "id", stopId); + else + ToJSONObject(*node, "osm_id", stop.m_osmId); + json_object_set_new(node.get(), "point", PointToJson(stop.m_point).release()); json_object_set_new(node.get(), "title", TranslationsToJson(stop.m_title).release()); @@ -1340,7 +1326,10 @@ void Gates::Write(IdSet const & ids, std::ofstream & stream) const continue; auto node = base::NewJSONObject(); - ToJSONObject(*node, "id", gateId); + if (gate.m_osmId == 0) + ToJSONObject(*node, "id", gateId); + else + ToJSONObject(*node, "osm_id", gate.m_osmId); auto weightsArr = base::NewJSONArray(); diff --git a/transit/world_feed/world_feed.hpp b/transit/world_feed/world_feed.hpp index 2ad42f4a8e..bb5ef846df 100644 --- a/transit/world_feed/world_feed.hpp +++ b/transit/world_feed/world_feed.hpp @@ -121,6 +121,8 @@ struct StopData // |m_timetable| can be left empty. TimeTable m_timetable; + uint64_t m_osmId = 0; + // Field not intended for dumping to json: std::string m_gtfsParentId; }; @@ -209,6 +211,7 @@ struct GateData m2::PointD m_point; std::vector m_weights; + uint64_t m_osmId = 0; // Field not intended for dumping to json: std::string m_gtfsId; }; @@ -286,6 +289,7 @@ public: private: friend class WorldFeedIntegrationTests; + friend class SubwayConverter; void SaveRegions(std::string const & worldFeedDir, std::string const & region, bool overwrite); @@ -399,4 +403,22 @@ private: // If the feed explicitly specifies its language, we use its value. Otherwise set to default. std::string m_feedLanguage; }; + +// Creates concatenation of |values| separated by delimiter. +template +auto BuildHash(Values... values) +{ + static std::string const delimiter = "_"; + + size_t constexpr paramsCount = sizeof...(Values); + size_t const delimitersSize = (paramsCount - 1) * delimiter.size(); + size_t const totalSize = (delimitersSize + ... + values.size()); + + std::string hash; + hash.reserve(totalSize); + (hash.append(values + delimiter), ...); + hash.pop_back(); + + return hash; +} } // namespace transit