Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
37
libs/transit/CMakeLists.txt
Normal file
37
libs/transit/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
project(transit)
|
||||
|
||||
set(SRC
|
||||
experimental/transit_data.cpp
|
||||
experimental/transit_data.hpp
|
||||
experimental/transit_types_experimental.cpp
|
||||
experimental/transit_types_experimental.hpp
|
||||
transit_display_info.hpp
|
||||
transit_entities.hpp
|
||||
transit_graph_data.cpp
|
||||
transit_graph_data.hpp
|
||||
transit_serdes.hpp
|
||||
transit_types.cpp
|
||||
transit_types.hpp
|
||||
transit_schedule.cpp
|
||||
transit_schedule.hpp
|
||||
transit_version.cpp
|
||||
transit_version.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
PUBLIC
|
||||
base
|
||||
coding
|
||||
cppjansson
|
||||
geometry
|
||||
indexer
|
||||
)
|
||||
|
||||
if (PLATFORM_DESKTOP)
|
||||
add_subdirectory(world_feed)
|
||||
endif()
|
||||
|
||||
omim_add_test_subdirectory(transit_experimental_tests)
|
||||
omim_add_test_subdirectory(transit_tests)
|
||||
716
libs/transit/experimental/transit_data.cpp
Normal file
716
libs/transit/experimental/transit_data.cpp
Normal file
|
|
@ -0,0 +1,716 @@
|
|||
#include "transit/experimental/transit_data.hpp"
|
||||
|
||||
#include "transit/transit_entities.hpp"
|
||||
#include "transit/transit_serdes.hpp"
|
||||
#include "transit/transit_version.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <fstream>
|
||||
#include <tuple>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
namespace experimental
|
||||
{
|
||||
template <class E>
|
||||
void ReadItems(uint32_t start, uint32_t end, std::string const & entityName, NonOwningReaderSource & src,
|
||||
std::vector<E> & entities)
|
||||
{
|
||||
routing::transit::Deserializer<NonOwningReaderSource> deserializer(src);
|
||||
|
||||
CHECK_EQUAL(src.Pos(), start, ("Wrong", TRANSIT_FILE_TAG, "section format for:", entityName));
|
||||
deserializer(entities);
|
||||
CHECK_EQUAL(src.Pos(), end, ("Wrong", TRANSIT_FILE_TAG, "section format for", entityName));
|
||||
}
|
||||
|
||||
struct ClearVisitor
|
||||
{
|
||||
template <class C>
|
||||
void operator()(C & container, char const * /* entityName */) const
|
||||
{
|
||||
container.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct SortVisitor
|
||||
{
|
||||
template <class C>
|
||||
void operator()(C & container, char const * /* entityName */) const
|
||||
{
|
||||
std::sort(container.begin(), container.end());
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckValidVisitor
|
||||
{
|
||||
template <class C>
|
||||
void operator()(C const & container, char const * entityName) const
|
||||
{
|
||||
for (auto const & item : container)
|
||||
CHECK(item.IsValid(), (item, "is not valid in", entityName));
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckUniqueVisitor
|
||||
{
|
||||
template <class C>
|
||||
void operator()(C const & container, char const * entityName) const
|
||||
{
|
||||
auto const it = std::adjacent_find(container.begin(), container.end());
|
||||
CHECK(it == container.end(), (*it, "is not unique in", entityName));
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckSortedVisitor
|
||||
{
|
||||
template <typename C>
|
||||
void operator()(C const & container, char const * entityName) const
|
||||
{
|
||||
CHECK(std::is_sorted(container.begin(), container.end()), (entityName, "is not sorted."));
|
||||
}
|
||||
};
|
||||
|
||||
TransitId GetIdFromJson(json_t * obj)
|
||||
{
|
||||
TransitId id;
|
||||
FromJSONObject(obj, "id", id);
|
||||
return id;
|
||||
}
|
||||
|
||||
std::vector<TimeFromGateToStop> GetWeightsFromJson(json_t * obj)
|
||||
{
|
||||
json_t * arr = base::GetJSONObligatoryField(obj, "weights");
|
||||
CHECK(json_is_array(arr), ());
|
||||
|
||||
size_t const count = json_array_size(arr);
|
||||
std::vector<TimeFromGateToStop> weights;
|
||||
weights.reserve(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arr, i);
|
||||
|
||||
TimeFromGateToStop weight;
|
||||
FromJSONObject(item, "stop_id", weight.m_stopId);
|
||||
FromJSONObject(item, "time_to_stop", weight.m_timeSeconds);
|
||||
weights.emplace_back(weight);
|
||||
}
|
||||
|
||||
CHECK(!weights.empty(), ());
|
||||
return weights;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::vector<T> GetVectorFromJson(json_t * obj, std::string const & field, bool obligatory = true)
|
||||
{
|
||||
json_t * arr = base::GetJSONOptionalField(obj, field);
|
||||
if (!arr)
|
||||
{
|
||||
if (obligatory)
|
||||
CHECK(false, ("Obligatory field", field, "is absent."));
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
CHECK(json_is_array(arr), ());
|
||||
|
||||
size_t const count = json_array_size(arr);
|
||||
std::vector<T> elements;
|
||||
elements.reserve(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arr, i);
|
||||
T element = 0;
|
||||
FromJSON(item, element);
|
||||
elements.emplace_back(element);
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
m2::PointD GetPointFromJson(json_t * obj)
|
||||
{
|
||||
CHECK(json_is_object(obj), ());
|
||||
|
||||
m2::PointD point;
|
||||
FromJSONObject(obj, "x", point.x);
|
||||
FromJSONObject(obj, "y", point.y);
|
||||
return point;
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> GetPointsFromJson(json_t * obj)
|
||||
{
|
||||
json_t * arr = base::GetJSONObligatoryField(obj, "points");
|
||||
CHECK(json_is_array(arr), ());
|
||||
|
||||
std::vector<m2::PointD> points;
|
||||
size_t const count = json_array_size(arr);
|
||||
points.reserve(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arr, i);
|
||||
points.emplace_back(GetPointFromJson(item));
|
||||
}
|
||||
|
||||
return points;
|
||||
}
|
||||
|
||||
TimeTable GetTimeTableFromJson(json_t * obj)
|
||||
{
|
||||
json_t * arr = base::GetJSONOptionalField(obj, "timetable");
|
||||
if (!arr)
|
||||
return TimeTable{};
|
||||
|
||||
CHECK(json_is_array(arr), ());
|
||||
|
||||
TimeTable timetable;
|
||||
|
||||
for (size_t i = 0; i < json_array_size(arr); ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arr, i);
|
||||
CHECK(json_is_object(item), ());
|
||||
|
||||
TransitId lineId;
|
||||
FromJSONObject(item, "line_id", lineId);
|
||||
|
||||
std::vector<TimeInterval> timeIntervals;
|
||||
|
||||
auto const & rawValues = GetVectorFromJson<uint64_t>(item, "intervals");
|
||||
timeIntervals.reserve(rawValues.size());
|
||||
for (auto const & rawValue : rawValues)
|
||||
timeIntervals.push_back(TimeInterval(rawValue));
|
||||
|
||||
timetable[lineId] = timeIntervals;
|
||||
}
|
||||
|
||||
return timetable;
|
||||
}
|
||||
|
||||
Translations GetTranslationsFromJson(json_t * obj, std::string const & field)
|
||||
{
|
||||
json_t * arr = base::GetJSONObligatoryField(obj, field);
|
||||
CHECK(json_is_array(arr), ());
|
||||
Translations translations;
|
||||
|
||||
for (size_t i = 0; i < json_array_size(arr); ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arr, i);
|
||||
CHECK(json_is_object(item), ());
|
||||
std::string lang;
|
||||
std::string text;
|
||||
FromJSONObject(item, "lang", lang);
|
||||
FromJSONObject(item, "text", text);
|
||||
CHECK(translations.emplace(lang, text).second, ());
|
||||
}
|
||||
|
||||
return translations;
|
||||
}
|
||||
|
||||
ShapeLink GetShapeLinkFromJson(json_t * obj)
|
||||
{
|
||||
json_t * shapeLinkObj = base::GetJSONObligatoryField(obj, "shape");
|
||||
CHECK(json_is_object(shapeLinkObj), ());
|
||||
|
||||
ShapeLink shapeLink;
|
||||
FromJSONObject(shapeLinkObj, "id", shapeLink.m_shapeId);
|
||||
FromJSONObject(shapeLinkObj, "start_index", shapeLink.m_startIndex);
|
||||
FromJSONObject(shapeLinkObj, "end_index", shapeLink.m_endIndex);
|
||||
|
||||
return shapeLink;
|
||||
}
|
||||
|
||||
FrequencyIntervals GetFrequencyIntervals(json_t * obj, std::string const & field)
|
||||
{
|
||||
json_t * arrTimeIntervals = base::GetJSONObligatoryField(obj, field);
|
||||
size_t const countTimeIntervals = json_array_size(arrTimeIntervals);
|
||||
FrequencyIntervals frequencyIntervals;
|
||||
|
||||
for (size_t i = 0; i < countTimeIntervals; ++i)
|
||||
{
|
||||
json_t * itemTimeIterval = json_array_get(arrTimeIntervals, i);
|
||||
|
||||
uint64_t rawTime = 0;
|
||||
FromJSONObject(itemTimeIterval, "time_interval", rawTime);
|
||||
|
||||
Frequency frequency = 0;
|
||||
FromJSONObject(itemTimeIterval, "frequency", frequency);
|
||||
|
||||
frequencyIntervals.AddInterval(TimeInterval(rawTime), frequency);
|
||||
}
|
||||
|
||||
return frequencyIntervals;
|
||||
}
|
||||
|
||||
Schedule GetScheduleFromJson(json_t * obj)
|
||||
{
|
||||
Schedule schedule;
|
||||
|
||||
json_t * scheduleObj = base::GetJSONObligatoryField(obj, "schedule");
|
||||
|
||||
Frequency defaultFrequency = 0;
|
||||
FromJSONObject(scheduleObj, "def_frequency", defaultFrequency);
|
||||
schedule.SetDefaultFrequency(defaultFrequency);
|
||||
|
||||
if (json_t * arrIntervals = base::GetJSONOptionalField(scheduleObj, "intervals"); arrIntervals)
|
||||
{
|
||||
for (size_t i = 0; i < json_array_size(arrIntervals); ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arrIntervals, i);
|
||||
uint32_t rawData = 0;
|
||||
FromJSONObject(item, "dates_interval", rawData);
|
||||
|
||||
schedule.AddDatesInterval(DatesInterval(rawData), GetFrequencyIntervals(item, "time_intervals"));
|
||||
}
|
||||
}
|
||||
|
||||
if (json_t * arrExceptions = base::GetJSONOptionalField(scheduleObj, "exceptions"); arrExceptions)
|
||||
{
|
||||
for (size_t i = 0; i < json_array_size(arrExceptions); ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arrExceptions, i);
|
||||
uint32_t rawData = 0;
|
||||
FromJSONObject(item, "exception", rawData);
|
||||
|
||||
schedule.AddDateException(DateException(rawData), GetFrequencyIntervals(item, "time_intervals"));
|
||||
}
|
||||
}
|
||||
|
||||
return schedule;
|
||||
}
|
||||
|
||||
std::tuple<OsmId, FeatureId, TransitId> CalculateIds(base::Json const & obj, OsmIdToFeatureIdsMap const & mapping)
|
||||
{
|
||||
OsmId osmId = kInvalidOsmId;
|
||||
FeatureId featureId = kInvalidFeatureId;
|
||||
TransitId id = kInvalidTransitId;
|
||||
|
||||
// Osm id is present in subway items and is absent in all other public transport items.
|
||||
FromJSONObjectOptionalField(obj.get(), "osm_id", osmId);
|
||||
FromJSONObjectOptionalField(obj.get(), "id", id);
|
||||
|
||||
if (osmId == 0)
|
||||
{
|
||||
osmId = kInvalidOsmId;
|
||||
}
|
||||
else
|
||||
{
|
||||
FromJSONObjectOptionalField(obj.get(), "feature_id", featureId);
|
||||
base::GeoObjectId const geoId(osmId);
|
||||
auto const it = mapping.find(geoId);
|
||||
if (it != mapping.cend())
|
||||
{
|
||||
CHECK(!it->second.empty(),
|
||||
("Osm id", osmId, "encoded as", geoId.GetEncodedId(), "from transit does not correspond to any feature."));
|
||||
if (it->second.size() != 1)
|
||||
{
|
||||
// |osmId| corresponds to several feature ids. It may happen in case of stops,
|
||||
// if a stop is present as a relation. It's a rare case.
|
||||
LOG(LWARNING,
|
||||
("Osm id", osmId, "encoded as", geoId.GetEncodedId(), "corresponds to", it->second.size(), "feature ids."));
|
||||
}
|
||||
featureId = it->second[0];
|
||||
}
|
||||
}
|
||||
|
||||
return {osmId, featureId, id};
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Network> & networks)
|
||||
{
|
||||
std::string title;
|
||||
FromJSONObject(obj.get(), "title", title);
|
||||
networks.emplace_back(GetIdFromJson(obj.get()), title);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Route> & routes)
|
||||
{
|
||||
TransitId const id = GetIdFromJson(obj.get());
|
||||
TransitId networkId;
|
||||
std::string routeType;
|
||||
std::string color;
|
||||
std::string title;
|
||||
|
||||
FromJSONObject(obj.get(), "network_id", networkId);
|
||||
FromJSONObject(obj.get(), "color", color);
|
||||
FromJSONObject(obj.get(), "type", routeType);
|
||||
FromJSONObject(obj.get(), "title", title);
|
||||
|
||||
routes.emplace_back(id, networkId, routeType, title, color);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Line> & lines)
|
||||
{
|
||||
TransitId const id = GetIdFromJson(obj.get());
|
||||
TransitId routeId;
|
||||
FromJSONObject(obj.get(), "route_id", routeId);
|
||||
ShapeLink const shapeLink = GetShapeLinkFromJson(obj.get());
|
||||
std::string title;
|
||||
FromJSONObject(obj.get(), "title", title);
|
||||
|
||||
IdList const & stopIds = GetVectorFromJson<TransitId>(obj.get(), "stops_ids");
|
||||
Schedule const & schedule = GetScheduleFromJson(obj.get());
|
||||
|
||||
lines.emplace_back(id, routeId, shapeLink, title, stopIds, schedule);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<LineMetadata> & linesMetadata)
|
||||
{
|
||||
TransitId const id = GetIdFromJson(obj.get());
|
||||
|
||||
json_t * arr = base::GetJSONObligatoryField(obj.get(), "shape_segments");
|
||||
|
||||
CHECK(json_is_array(arr), ());
|
||||
|
||||
size_t const count = json_array_size(arr);
|
||||
LineSegmentsOrder segmentsOrder;
|
||||
segmentsOrder.reserve(count);
|
||||
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
json_t * item = json_array_get(arr, i);
|
||||
|
||||
LineSegmentOrder lineSegmentOrder;
|
||||
FromJSONObject(item, "order", lineSegmentOrder.m_order);
|
||||
FromJSONObject(item, "start_index", lineSegmentOrder.m_segment.m_startIdx);
|
||||
FromJSONObject(item, "end_index", lineSegmentOrder.m_segment.m_endIdx);
|
||||
segmentsOrder.emplace_back(lineSegmentOrder);
|
||||
}
|
||||
|
||||
linesMetadata.emplace_back(id, segmentsOrder);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Stop> & stops, OsmIdToFeatureIdsMap const & mapping)
|
||||
{
|
||||
auto const & [osmId, featureId, id] = CalculateIds(obj, mapping);
|
||||
|
||||
std::string title;
|
||||
FromJSONObject(obj.get(), "title", title);
|
||||
TimeTable const timetable = GetTimeTableFromJson(obj.get());
|
||||
m2::PointD const point = GetPointFromJson(base::GetJSONObligatoryField(obj.get(), "point"));
|
||||
IdList const & transferIds = GetVectorFromJson<TransitId>(obj.get(), "transfer_ids", false /* obligatory */);
|
||||
|
||||
stops.emplace_back(id, featureId, osmId, title, timetable, point, transferIds);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Shape> & shapes)
|
||||
{
|
||||
TransitId const id = GetIdFromJson(obj.get());
|
||||
std::vector<m2::PointD> const polyline = GetPointsFromJson(obj.get());
|
||||
shapes.emplace_back(id, polyline);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Edge> & edges, EdgeIdToFeatureId & edgeFeatureIds)
|
||||
{
|
||||
TransitId stopFrom = kInvalidTransitId;
|
||||
TransitId stopTo = kInvalidTransitId;
|
||||
|
||||
FromJSONObject(obj.get(), "stop_id_from", stopFrom);
|
||||
FromJSONObject(obj.get(), "stop_id_to", stopTo);
|
||||
EdgeWeight weight;
|
||||
FromJSONObject(obj.get(), "weight", weight);
|
||||
|
||||
TransitId lineId = 0;
|
||||
ShapeLink shapeLink;
|
||||
bool isTransfer = false;
|
||||
|
||||
FromJSONObjectOptionalField(obj.get(), "line_id", lineId);
|
||||
|
||||
TransitId featureId = kInvalidFeatureId;
|
||||
FromJSONObject(obj.get(), "feature_id", featureId);
|
||||
|
||||
if (lineId == 0)
|
||||
{
|
||||
lineId = kInvalidTransitId;
|
||||
isTransfer = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
shapeLink = GetShapeLinkFromJson(obj.get());
|
||||
}
|
||||
|
||||
edges.emplace_back(stopFrom, stopTo, weight, lineId, isTransfer, shapeLink);
|
||||
edgeFeatureIds.emplace(EdgeId(stopFrom, stopTo, lineId), featureId);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Transfer> & transfers)
|
||||
{
|
||||
TransitId const id = GetIdFromJson(obj.get());
|
||||
m2::PointD const & point = GetPointFromJson(base::GetJSONObligatoryField(obj.get(), "point"));
|
||||
IdList const & stopIds = GetVectorFromJson<TransitId>(obj.get(), "stops_ids");
|
||||
transfers.emplace_back(id, point, stopIds);
|
||||
}
|
||||
|
||||
void Read(base::Json const & obj, std::vector<Gate> & gates, OsmIdToFeatureIdsMap const & mapping)
|
||||
{
|
||||
auto const & [osmId, featureId, id] = CalculateIds(obj, mapping);
|
||||
|
||||
std::vector<TimeFromGateToStop> const weights = GetWeightsFromJson(obj.get());
|
||||
|
||||
bool isEntrance = false;
|
||||
bool isExit = false;
|
||||
FromJSONObject(obj.get(), "entrance", isEntrance);
|
||||
FromJSONObject(obj.get(), "exit", isExit);
|
||||
|
||||
m2::PointD const point = GetPointFromJson(base::GetJSONObligatoryField(obj.get(), "point"));
|
||||
|
||||
gates.emplace_back(id, featureId, osmId, isEntrance, isExit, weights, point);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void ReadData(std::string const & path, Args &&... args)
|
||||
{
|
||||
std::ifstream input;
|
||||
input.exceptions(std::ifstream::badbit);
|
||||
|
||||
try
|
||||
{
|
||||
input.open(path);
|
||||
CHECK(input.is_open(), (path));
|
||||
std::string line;
|
||||
|
||||
while (std::getline(input, line))
|
||||
{
|
||||
if (line.empty())
|
||||
continue;
|
||||
|
||||
base::Json jsonObject(line);
|
||||
CHECK(jsonObject.get() != nullptr, ("Error parsing json from line:", line));
|
||||
Read(jsonObject, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
catch (std::ifstream::failure const & se)
|
||||
{
|
||||
LOG(LERROR, ("Exception reading line-by-line json from file", path, se.what()));
|
||||
}
|
||||
catch (base::Json::Exception const & je)
|
||||
{
|
||||
LOG(LERROR, ("Exception parsing json", path, je.what()));
|
||||
}
|
||||
}
|
||||
|
||||
void TransitData::DeserializeFromJson(std::string const & dirWithJsons, OsmIdToFeatureIdsMap const & mapping)
|
||||
{
|
||||
ReadData(base::JoinPath(dirWithJsons, kNetworksFile), m_networks);
|
||||
ReadData(base::JoinPath(dirWithJsons, kRoutesFile), m_routes);
|
||||
ReadData(base::JoinPath(dirWithJsons, kLinesFile), m_lines);
|
||||
ReadData(base::JoinPath(dirWithJsons, kLinesMetadataFile), m_linesMetadata);
|
||||
ReadData(base::JoinPath(dirWithJsons, kStopsFile), m_stops, mapping);
|
||||
ReadData(base::JoinPath(dirWithJsons, kShapesFile), m_shapes);
|
||||
ReadData(base::JoinPath(dirWithJsons, kEdgesFile), m_edges, m_edgeFeatureIds);
|
||||
ReadData(base::JoinPath(dirWithJsons, kEdgesTransferFile), m_edges, m_edgeFeatureIds);
|
||||
ReadData(base::JoinPath(dirWithJsons, kTransfersFile), m_transfers);
|
||||
ReadData(base::JoinPath(dirWithJsons, kGatesFile), m_gates, mapping);
|
||||
}
|
||||
|
||||
void TransitData::Serialize(Writer & writer)
|
||||
{
|
||||
auto const startOffset = writer.Pos();
|
||||
|
||||
routing::transit::Serializer<Writer> serializer(writer);
|
||||
routing::transit::FixedSizeSerializer<Writer> fixedSizeSerializer(writer);
|
||||
m_header = TransitHeader();
|
||||
m_header.m_version = static_cast<uint16_t>(TransitVersion::AllPublicTransport);
|
||||
fixedSizeSerializer(m_header);
|
||||
|
||||
m_header.m_stopsOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_stops);
|
||||
|
||||
m_header.m_gatesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_gates);
|
||||
|
||||
m_header.m_edgesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_edges);
|
||||
|
||||
m_header.m_transfersOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_transfers);
|
||||
|
||||
m_header.m_linesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_lines);
|
||||
|
||||
m_header.m_linesMetadataOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_linesMetadata);
|
||||
|
||||
m_header.m_shapesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_shapes);
|
||||
|
||||
m_header.m_routesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_routes);
|
||||
|
||||
m_header.m_networksOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_networks);
|
||||
|
||||
m_header.m_endOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
|
||||
// Overwriting updated header.
|
||||
CHECK(m_header.IsValid(), (m_header));
|
||||
auto const endOffset = writer.Pos();
|
||||
writer.Seek(startOffset);
|
||||
fixedSizeSerializer(m_header);
|
||||
writer.Seek(endOffset);
|
||||
|
||||
LOG(LINFO, (TRANSIT_FILE_TAG, "experimental section is ready. Header:", m_header));
|
||||
}
|
||||
|
||||
void TransitData::Deserialize(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
ReadGates(src);
|
||||
ReadEdges(src);
|
||||
ReadTransfers(src);
|
||||
ReadLines(src);
|
||||
ReadLinesMetadata(src);
|
||||
ReadShapes(src);
|
||||
ReadRoutes(src);
|
||||
ReadNetworks(src);
|
||||
});
|
||||
}
|
||||
|
||||
void TransitData::DeserializeForRouting(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
ReadGates(src);
|
||||
ReadEdges(src);
|
||||
src.Skip(m_header.m_linesOffset - src.Pos());
|
||||
ReadLines(src);
|
||||
});
|
||||
}
|
||||
|
||||
void TransitData::DeserializeForRendering(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
src.Skip(m_header.m_edgesOffset - src.Pos());
|
||||
ReadEdges(src);
|
||||
src.Skip(m_header.m_transfersOffset - src.Pos());
|
||||
ReadTransfers(src);
|
||||
ReadLines(src);
|
||||
ReadLinesMetadata(src);
|
||||
ReadShapes(src);
|
||||
ReadRoutes(src);
|
||||
});
|
||||
}
|
||||
|
||||
void TransitData::DeserializeForCrossMwm(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
src.Skip(m_header.m_edgesOffset - src.Pos());
|
||||
ReadEdges(src);
|
||||
});
|
||||
}
|
||||
|
||||
void TransitData::Clear()
|
||||
{
|
||||
ClearVisitor const visitor;
|
||||
Visit(visitor);
|
||||
}
|
||||
|
||||
void TransitData::CheckValid() const
|
||||
{
|
||||
CheckValidVisitor const visitor;
|
||||
Visit(visitor);
|
||||
}
|
||||
|
||||
void TransitData::CheckSorted() const
|
||||
{
|
||||
CheckSortedVisitor const visitor;
|
||||
Visit(visitor);
|
||||
}
|
||||
|
||||
void TransitData::CheckUnique() const
|
||||
{
|
||||
CheckUniqueVisitor const visitor;
|
||||
Visit(visitor);
|
||||
}
|
||||
|
||||
bool TransitData::IsEmpty() const
|
||||
{
|
||||
// |m_transfers| and |m_gates| may be empty and it is ok.
|
||||
return m_networks.empty() || m_routes.empty() || m_lines.empty() || m_shapes.empty() || m_stops.empty() ||
|
||||
m_edges.empty();
|
||||
}
|
||||
|
||||
void TransitData::Sort()
|
||||
{
|
||||
SortVisitor const visitor;
|
||||
Visit(visitor);
|
||||
}
|
||||
|
||||
void TransitData::SetGatePedestrianSegments(size_t gateIdx, std::vector<SingleMwmSegment> const & seg)
|
||||
{
|
||||
CHECK_LESS(gateIdx, m_gates.size(), ());
|
||||
m_gates[gateIdx].SetBestPedestrianSegments(seg);
|
||||
}
|
||||
|
||||
void TransitData::SetStopPedestrianSegments(size_t stopIdx, std::vector<SingleMwmSegment> const & seg)
|
||||
{
|
||||
CHECK_LESS(stopIdx, m_stops.size(), ());
|
||||
m_stops[stopIdx].SetBestPedestrianSegments(seg);
|
||||
}
|
||||
|
||||
void TransitData::ReadHeader(NonOwningReaderSource & src)
|
||||
{
|
||||
routing::transit::FixedSizeDeserializer<NonOwningReaderSource> fixedSizeDeserializer(src);
|
||||
fixedSizeDeserializer(m_header);
|
||||
CHECK_EQUAL(src.Pos(), m_header.m_stopsOffset, ("Wrong", TRANSIT_FILE_TAG, "section format."));
|
||||
CHECK(m_header.IsValid(), ());
|
||||
}
|
||||
|
||||
void TransitData::ReadStops(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_stopsOffset, m_header.m_gatesOffset, "stops", src, m_stops);
|
||||
}
|
||||
|
||||
void TransitData::ReadGates(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_gatesOffset, m_header.m_edgesOffset, "gates", src, m_gates);
|
||||
}
|
||||
|
||||
void TransitData::ReadEdges(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_edgesOffset, m_header.m_transfersOffset, "edges", src, m_edges);
|
||||
}
|
||||
|
||||
void TransitData::ReadTransfers(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_transfersOffset, m_header.m_linesOffset, "transfers", src, m_transfers);
|
||||
}
|
||||
|
||||
void TransitData::ReadLines(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_linesOffset, m_header.m_linesMetadataOffset, "lines", src, m_lines);
|
||||
}
|
||||
|
||||
void TransitData::ReadLinesMetadata(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_linesMetadataOffset, m_header.m_shapesOffset, "linesMetadata", src, m_linesMetadata);
|
||||
}
|
||||
|
||||
void TransitData::ReadShapes(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_shapesOffset, m_header.m_routesOffset, "shapes", src, m_shapes);
|
||||
}
|
||||
|
||||
void TransitData::ReadRoutes(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_routesOffset, m_header.m_networksOffset, "routes", src, m_routes);
|
||||
}
|
||||
|
||||
void TransitData::ReadNetworks(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_networksOffset, m_header.m_endOffset, "networks", src, m_networks);
|
||||
}
|
||||
} // namespace experimental
|
||||
} // namespace transit
|
||||
110
libs/transit/experimental/transit_data.hpp
Normal file
110
libs/transit/experimental/transit_data.hpp
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
#pragma once
|
||||
|
||||
#include "transit/experimental/transit_types_experimental.hpp"
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/writer.hpp"
|
||||
|
||||
#include "base/geo_object_id.hpp"
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "cppjansson/cppjansson.hpp"
|
||||
|
||||
namespace transit
|
||||
{
|
||||
namespace experimental
|
||||
{
|
||||
using OsmIdToFeatureIdsMap = std::map<base::GeoObjectId, std::vector<FeatureId>>;
|
||||
using EdgeIdToFeatureId = std::unordered_map<EdgeId, uint32_t, EdgeIdHasher>;
|
||||
// Functions for parsing one line of line-by-line json and creating corresponding item in container.
|
||||
void Read(base::Json const & obj, std::vector<Network> & networks);
|
||||
void Read(base::Json const & obj, std::vector<Route> & routes);
|
||||
void Read(base::Json const & obj, std::vector<Line> & lines);
|
||||
void Read(base::Json const & obj, std::vector<LineMetadata> & linesMetadata);
|
||||
void Read(base::Json const & obj, std::vector<Stop> & stops, OsmIdToFeatureIdsMap const & mapping);
|
||||
void Read(base::Json const & obj, std::vector<Shape> & shapes);
|
||||
void Read(base::Json const & obj, std::vector<Edge> & edges, EdgeIdToFeatureId & edgeFeatureIds);
|
||||
void Read(base::Json const & obj, std::vector<Transfer> & transfers);
|
||||
void Read(base::Json const & obj, std::vector<Gate> & gates, OsmIdToFeatureIdsMap const & mapping);
|
||||
|
||||
/// \brief The class contains all the information to make TRANSIT_FILE_TAG section.
|
||||
class TransitData
|
||||
{
|
||||
public:
|
||||
void DeserializeFromJson(std::string const & dirWithJsons, OsmIdToFeatureIdsMap const & mapping);
|
||||
/// \note This method changes only |m_header| and fills it with correct offsets.
|
||||
void Serialize(Writer & writer);
|
||||
void Deserialize(Reader & reader);
|
||||
void DeserializeForRouting(Reader & reader);
|
||||
void DeserializeForRendering(Reader & reader);
|
||||
void DeserializeForCrossMwm(Reader & reader);
|
||||
void Clear();
|
||||
void CheckValid() const;
|
||||
void CheckSorted() const;
|
||||
void CheckUnique() const;
|
||||
bool IsEmpty() const;
|
||||
/// \brief Sorts all class fields by their ids.
|
||||
void Sort();
|
||||
void SetGatePedestrianSegments(size_t gateIdx, std::vector<SingleMwmSegment> const & seg);
|
||||
void SetStopPedestrianSegments(size_t stopIdx, std::vector<SingleMwmSegment> const & seg);
|
||||
|
||||
std::vector<Stop> const & GetStops() const { return m_stops; }
|
||||
std::vector<Gate> const & GetGates() const { return m_gates; }
|
||||
std::vector<Edge> const & GetEdges() const { return m_edges; }
|
||||
std::vector<Transfer> const & GetTransfers() const { return m_transfers; }
|
||||
std::vector<Line> const & GetLines() const { return m_lines; }
|
||||
std::vector<LineMetadata> const & GetLinesMetadata() const { return m_linesMetadata; }
|
||||
std::vector<Shape> const & GetShapes() const { return m_shapes; }
|
||||
std::vector<Route> const & GetRoutes() const { return m_routes; }
|
||||
std::vector<Network> const & GetNetworks() const { return m_networks; }
|
||||
|
||||
EdgeIdToFeatureId const & GetEdgeIdToFeatureId() const { return m_edgeFeatureIds; }
|
||||
|
||||
private:
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(TransitData, visitor(m_stops, "stops"), visitor(m_gates, "gates"),
|
||||
visitor(m_edges, "edges"), visitor(m_transfers, "transfers"),
|
||||
visitor(m_lines, "lines"), visitor(m_shapes, "shapes"),
|
||||
visitor(m_networks, "networks"), visitor(m_routes, "routes"))
|
||||
friend TransitData FillTestTransitData();
|
||||
/// \brief Reads transit form |src|.
|
||||
/// \note Before calling any of the method except for ReadHeader() |m_header| has to be filled.
|
||||
void ReadHeader(NonOwningReaderSource & src);
|
||||
void ReadStops(NonOwningReaderSource & src);
|
||||
void ReadGates(NonOwningReaderSource & src);
|
||||
void ReadEdges(NonOwningReaderSource & src);
|
||||
void ReadTransfers(NonOwningReaderSource & src);
|
||||
void ReadLines(NonOwningReaderSource & src);
|
||||
void ReadLinesMetadata(NonOwningReaderSource & src);
|
||||
void ReadShapes(NonOwningReaderSource & src);
|
||||
void ReadRoutes(NonOwningReaderSource & src);
|
||||
void ReadNetworks(NonOwningReaderSource & src);
|
||||
|
||||
template <typename Fn>
|
||||
void DeserializeWith(Reader & reader, Fn && fn)
|
||||
{
|
||||
NonOwningReaderSource src(reader);
|
||||
ReadHeader(src);
|
||||
fn(src);
|
||||
}
|
||||
|
||||
TransitHeader m_header;
|
||||
std::vector<Network> m_networks;
|
||||
std::vector<Route> m_routes;
|
||||
std::vector<Stop> m_stops;
|
||||
std::vector<Gate> m_gates;
|
||||
std::vector<Edge> m_edges;
|
||||
std::vector<Transfer> m_transfers;
|
||||
std::vector<Line> m_lines;
|
||||
std::vector<LineMetadata> m_linesMetadata;
|
||||
std::vector<Shape> m_shapes;
|
||||
|
||||
EdgeIdToFeatureId m_edgeFeatureIds;
|
||||
};
|
||||
} // namespace experimental
|
||||
} // namespace transit
|
||||
567
libs/transit/experimental/transit_types_experimental.cpp
Normal file
567
libs/transit/experimental/transit_types_experimental.cpp
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
#include "transit/experimental/transit_types_experimental.hpp"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
namespace experimental
|
||||
{
|
||||
std::string GetTranslation(Translations const & titles)
|
||||
{
|
||||
CHECK(!titles.empty(), ());
|
||||
|
||||
// If there is only one language we return title in this only translation.
|
||||
if (titles.size() == 1)
|
||||
return titles.begin()->second;
|
||||
|
||||
// Otherwise we try to extract default language for this region.
|
||||
auto it = titles.find("default");
|
||||
if (it != titles.end())
|
||||
return it->second;
|
||||
|
||||
// If there is no default language we return one of the represented translations.
|
||||
return titles.begin()->second;
|
||||
}
|
||||
|
||||
// TransitHeader ----------------------------------------------------------------------------------
|
||||
bool TransitHeader::IsValid() const
|
||||
{
|
||||
return m_stopsOffset <= m_gatesOffset && m_gatesOffset <= m_transfersOffset && m_transfersOffset <= m_linesOffset &&
|
||||
m_linesOffset <= m_linesMetadataOffset && m_linesMetadataOffset <= m_shapesOffset &&
|
||||
m_shapesOffset <= m_routesOffset && m_routesOffset <= m_networksOffset && m_networksOffset <= m_endOffset;
|
||||
}
|
||||
|
||||
// SingleMwmSegment --------------------------------------------------------------------------------
|
||||
SingleMwmSegment::SingleMwmSegment(FeatureId featureId, uint32_t segmentIdx, bool forward)
|
||||
: m_featureId(featureId)
|
||||
, m_segmentIdx(segmentIdx)
|
||||
, m_forward(forward)
|
||||
{}
|
||||
|
||||
bool SingleMwmSegment::operator==(SingleMwmSegment const & rhs) const
|
||||
{
|
||||
return std::tie(m_featureId, m_segmentIdx, m_forward) == std::tie(rhs.m_featureId, rhs.m_segmentIdx, rhs.m_forward);
|
||||
}
|
||||
|
||||
// IdBundle ----------------------------------------------------------------------------------------
|
||||
IdBundle::IdBundle(bool serializeFeatureIdOnly) : m_serializeFeatureIdOnly(serializeFeatureIdOnly) {}
|
||||
|
||||
IdBundle::IdBundle(FeatureId featureId, OsmId osmId, bool serializeFeatureIdOnly)
|
||||
: m_featureId(featureId)
|
||||
, m_osmId(osmId)
|
||||
, m_serializeFeatureIdOnly(serializeFeatureIdOnly)
|
||||
{}
|
||||
|
||||
bool IdBundle::operator<(IdBundle const & rhs) const
|
||||
{
|
||||
CHECK_EQUAL(m_serializeFeatureIdOnly, rhs.m_serializeFeatureIdOnly, ());
|
||||
|
||||
if (m_serializeFeatureIdOnly)
|
||||
return m_featureId < rhs.m_featureId;
|
||||
|
||||
return std::tie(m_featureId, m_osmId) < std::tie(rhs.m_featureId, rhs.m_osmId);
|
||||
}
|
||||
|
||||
bool IdBundle::operator==(IdBundle const & rhs) const
|
||||
{
|
||||
CHECK_EQUAL(m_serializeFeatureIdOnly, rhs.m_serializeFeatureIdOnly, ());
|
||||
return m_serializeFeatureIdOnly ? m_featureId == rhs.m_featureId
|
||||
: m_featureId == rhs.m_featureId && m_osmId == rhs.m_osmId;
|
||||
}
|
||||
|
||||
bool IdBundle::operator!=(IdBundle const & rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool IdBundle::IsValid() const
|
||||
{
|
||||
return m_serializeFeatureIdOnly ? m_featureId != kInvalidFeatureId
|
||||
: m_featureId != kInvalidFeatureId && m_osmId != kInvalidOsmId;
|
||||
}
|
||||
|
||||
void IdBundle::SetOsmId(OsmId osmId)
|
||||
{
|
||||
m_osmId = osmId;
|
||||
}
|
||||
|
||||
void IdBundle::SetFeatureId(FeatureId featureId)
|
||||
{
|
||||
m_featureId = featureId;
|
||||
}
|
||||
|
||||
OsmId IdBundle::GetOsmId() const
|
||||
{
|
||||
return m_osmId;
|
||||
}
|
||||
|
||||
FeatureId IdBundle::GetFeatureId() const
|
||||
{
|
||||
return m_featureId;
|
||||
}
|
||||
|
||||
bool IdBundle::SerializeFeatureIdOnly() const
|
||||
{
|
||||
return m_serializeFeatureIdOnly;
|
||||
}
|
||||
|
||||
// Network -----------------------------------------------------------------------------------------
|
||||
Network::Network(TransitId id, std::string const & title) : m_id(id), m_title(title) {}
|
||||
|
||||
Network::Network(TransitId id) : m_id(id), m_title{} {}
|
||||
|
||||
bool Network::operator<(Network const & rhs) const
|
||||
{
|
||||
return m_id < rhs.m_id;
|
||||
}
|
||||
|
||||
bool Network::operator==(Network const & rhs) const
|
||||
{
|
||||
return m_id == rhs.m_id;
|
||||
}
|
||||
|
||||
bool Network::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidTransitId;
|
||||
}
|
||||
|
||||
TransitId Network::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
std::string const & Network::GetTitle() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
// Route -------------------------------------------------------------------------------------------
|
||||
Route::Route(TransitId id, TransitId networkId, std::string const & routeType, std::string const & title,
|
||||
std::string const & color)
|
||||
: m_id(id)
|
||||
, m_networkId(networkId)
|
||||
, m_routeType(routeType)
|
||||
, m_title(title)
|
||||
, m_color(color)
|
||||
{}
|
||||
|
||||
bool Route::operator<(Route const & rhs) const
|
||||
{
|
||||
return m_id < rhs.m_id;
|
||||
}
|
||||
|
||||
bool Route::operator==(Route const & rhs) const
|
||||
{
|
||||
return m_id == rhs.m_id;
|
||||
}
|
||||
|
||||
bool Route::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidTransitId && m_networkId != kInvalidTransitId && !m_routeType.empty();
|
||||
}
|
||||
|
||||
TransitId Route::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
std::string const & Route::GetTitle() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
std::string const & Route::GetType() const
|
||||
{
|
||||
return m_routeType;
|
||||
}
|
||||
|
||||
std::string const & Route::GetColor() const
|
||||
{
|
||||
return m_color;
|
||||
}
|
||||
|
||||
TransitId Route::GetNetworkId() const
|
||||
{
|
||||
return m_networkId;
|
||||
}
|
||||
|
||||
// Line --------------------------------------------------------------------------------------------
|
||||
Line::Line(TransitId id, TransitId routeId, ShapeLink const & shapeLink, std::string const & title,
|
||||
IdList const & stopIds, Schedule const & schedule)
|
||||
: m_id(id)
|
||||
, m_routeId(routeId)
|
||||
, m_shapeLink(shapeLink)
|
||||
, m_title(title)
|
||||
, m_stopIds(stopIds)
|
||||
, m_schedule(schedule)
|
||||
{}
|
||||
|
||||
bool Line::operator<(Line const & rhs) const
|
||||
{
|
||||
return m_id < rhs.m_id;
|
||||
}
|
||||
|
||||
bool Line::operator==(Line const & rhs) const
|
||||
{
|
||||
return m_id == rhs.m_id;
|
||||
}
|
||||
|
||||
bool Line::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidTransitId && m_routeId != kInvalidTransitId && m_shapeLink.m_shapeId != kInvalidTransitId &&
|
||||
!m_stopIds.empty();
|
||||
}
|
||||
|
||||
TransitId Line::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
std::string const & Line::GetTitle() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
TransitId Line::GetRouteId() const
|
||||
{
|
||||
return m_routeId;
|
||||
}
|
||||
|
||||
ShapeLink const & Line::GetShapeLink() const
|
||||
{
|
||||
return m_shapeLink;
|
||||
}
|
||||
|
||||
IdList const & Line::GetStopIds() const
|
||||
{
|
||||
return m_stopIds;
|
||||
}
|
||||
|
||||
Schedule const & Line::GetSchedule() const
|
||||
{
|
||||
return m_schedule;
|
||||
}
|
||||
|
||||
// LineMetadata ------------------------------------------------------------------------------------
|
||||
LineMetadata::LineMetadata(TransitId id, LineSegmentsOrder const & segmentsOrder)
|
||||
: m_id(id)
|
||||
, m_segmentsOrder(segmentsOrder)
|
||||
{}
|
||||
|
||||
bool LineMetadata::operator<(LineMetadata const & rhs) const
|
||||
{
|
||||
return m_id < rhs.GetId();
|
||||
}
|
||||
bool LineMetadata::operator==(LineMetadata const & rhs) const
|
||||
{
|
||||
return m_id == rhs.GetId();
|
||||
}
|
||||
|
||||
bool LineMetadata::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidTransitId;
|
||||
}
|
||||
|
||||
TransitId LineMetadata::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
LineSegmentsOrder const & LineMetadata::GetLineSegmentsOrder() const
|
||||
{
|
||||
return m_segmentsOrder;
|
||||
}
|
||||
|
||||
// Stop --------------------------------------------------------------------------------------------
|
||||
Stop::Stop() : m_ids(true /* serializeFeatureIdOnly */) {}
|
||||
|
||||
Stop::Stop(TransitId id, FeatureId featureId, OsmId osmId, std::string const & title, TimeTable const & timetable,
|
||||
m2::PointD const & point, IdList const & transferIds)
|
||||
: m_id(id)
|
||||
, m_ids(featureId, osmId, true /* serializeFeatureIdOnly */)
|
||||
, m_title(title)
|
||||
, m_timetable(timetable)
|
||||
, m_point(point)
|
||||
, m_transferIds(transferIds)
|
||||
{}
|
||||
|
||||
Stop::Stop(TransitId id) : m_id(id), m_ids(true /* serializeFeatureIdOnly */) {}
|
||||
|
||||
bool Stop::operator<(Stop const & rhs) const
|
||||
{
|
||||
if (m_id != kInvalidTransitId || rhs.m_id != kInvalidTransitId)
|
||||
return m_id < rhs.m_id;
|
||||
|
||||
return m_ids.GetFeatureId() < rhs.m_ids.GetFeatureId();
|
||||
}
|
||||
|
||||
bool Stop::operator==(Stop const & rhs) const
|
||||
{
|
||||
if (m_id != kInvalidTransitId || rhs.m_id != kInvalidTransitId)
|
||||
return m_id == rhs.m_id;
|
||||
|
||||
return m_ids.GetFeatureId() == rhs.m_ids.GetFeatureId() && m_ids.GetOsmId() == rhs.m_ids.GetOsmId();
|
||||
}
|
||||
|
||||
bool Stop::IsValid() const
|
||||
{
|
||||
return ((m_id != kInvalidTransitId) || (m_ids.GetOsmId() != kInvalidOsmId) ||
|
||||
(m_ids.GetFeatureId() != kInvalidFeatureId));
|
||||
}
|
||||
|
||||
FeatureId Stop::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
FeatureId Stop::GetFeatureId() const
|
||||
{
|
||||
return m_ids.GetFeatureId();
|
||||
}
|
||||
|
||||
OsmId Stop::GetOsmId() const
|
||||
{
|
||||
return m_ids.GetOsmId();
|
||||
}
|
||||
|
||||
std::string const & Stop::GetTitle() const
|
||||
{
|
||||
return m_title;
|
||||
}
|
||||
|
||||
TimeTable const & Stop::GetTimeTable() const
|
||||
{
|
||||
return m_timetable;
|
||||
}
|
||||
|
||||
m2::PointD const & Stop::GetPoint() const
|
||||
{
|
||||
return m_point;
|
||||
}
|
||||
|
||||
IdList const & Stop::GetTransferIds() const
|
||||
{
|
||||
return m_transferIds;
|
||||
}
|
||||
|
||||
void Stop::SetBestPedestrianSegments(std::vector<SingleMwmSegment> const & seg)
|
||||
{
|
||||
m_bestPedestrianSegments = seg;
|
||||
}
|
||||
|
||||
std::vector<SingleMwmSegment> const & Stop::GetBestPedestrianSegments() const
|
||||
{
|
||||
return m_bestPedestrianSegments;
|
||||
}
|
||||
|
||||
// Gate --------------------------------------------------------------------------------------------
|
||||
Gate::Gate() : m_ids(false /* serializeFeatureIdOnly */) {}
|
||||
|
||||
Gate::Gate(TransitId id, FeatureId featureId, OsmId osmId, bool entrance, bool exit,
|
||||
std::vector<TimeFromGateToStop> const & weights, m2::PointD const & point)
|
||||
: m_id(id)
|
||||
, m_ids(featureId, osmId, false /* serializeFeatureIdOnly */)
|
||||
, m_entrance(entrance)
|
||||
, m_exit(exit)
|
||||
, m_weights(weights)
|
||||
, m_point(point)
|
||||
{}
|
||||
|
||||
bool Gate::operator<(Gate const & rhs) const
|
||||
{
|
||||
return std::tie(m_id, m_ids, m_entrance, m_exit) < std::tie(rhs.m_id, rhs.m_ids, rhs.m_entrance, rhs.m_exit);
|
||||
}
|
||||
|
||||
bool Gate::operator==(Gate const & rhs) const
|
||||
{
|
||||
return std::tie(m_id, m_ids, m_entrance, m_exit) == std::tie(rhs.m_id, rhs.m_ids, rhs.m_entrance, rhs.m_exit);
|
||||
}
|
||||
|
||||
bool Gate::IsValid() const
|
||||
{
|
||||
return ((m_id != kInvalidTransitId) || (m_ids.GetOsmId() != kInvalidOsmId)) && (m_entrance || m_exit) &&
|
||||
!m_weights.empty();
|
||||
}
|
||||
|
||||
void Gate::SetBestPedestrianSegments(std::vector<SingleMwmSegment> const & seg)
|
||||
{
|
||||
m_bestPedestrianSegments = seg;
|
||||
}
|
||||
|
||||
FeatureId Gate::GetFeatureId() const
|
||||
{
|
||||
return m_ids.GetFeatureId();
|
||||
}
|
||||
|
||||
OsmId Gate::GetOsmId() const
|
||||
{
|
||||
return m_ids.GetOsmId();
|
||||
}
|
||||
|
||||
TransitId Gate::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
std::vector<SingleMwmSegment> const & Gate::GetBestPedestrianSegments() const
|
||||
{
|
||||
return m_bestPedestrianSegments;
|
||||
}
|
||||
|
||||
bool Gate::IsEntrance() const
|
||||
{
|
||||
return m_entrance;
|
||||
}
|
||||
|
||||
bool Gate::IsExit() const
|
||||
{
|
||||
return m_exit;
|
||||
}
|
||||
|
||||
std::vector<TimeFromGateToStop> const & Gate::GetStopsWithWeight() const
|
||||
{
|
||||
return m_weights;
|
||||
}
|
||||
|
||||
m2::PointD const & Gate::GetPoint() const
|
||||
{
|
||||
return m_point;
|
||||
}
|
||||
|
||||
// Edge --------------------------------------------------------------------------------------------
|
||||
Edge::Edge(TransitId stop1Id, TransitId stop2Id, EdgeWeight weight, TransitId lineId, bool transfer,
|
||||
ShapeLink const & shapeLink)
|
||||
: m_stop1Id(stop1Id)
|
||||
, m_stop2Id(stop2Id)
|
||||
, m_weight(weight)
|
||||
, m_isTransfer(transfer)
|
||||
, m_lineId(lineId)
|
||||
, m_shapeLink(shapeLink)
|
||||
{}
|
||||
|
||||
bool Edge::operator<(Edge const & rhs) const
|
||||
{
|
||||
return std::tie(m_stop1Id, m_stop2Id, m_lineId) < std::tie(rhs.m_stop1Id, rhs.m_stop2Id, rhs.m_lineId);
|
||||
}
|
||||
|
||||
bool Edge::operator==(Edge const & rhs) const
|
||||
{
|
||||
return std::tie(m_stop1Id, m_stop2Id, m_lineId) == std::tie(rhs.m_stop1Id, rhs.m_stop2Id, rhs.m_lineId);
|
||||
}
|
||||
|
||||
bool Edge::operator!=(Edge const & rhs) const
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
bool Edge::IsValid() const
|
||||
{
|
||||
if (m_isTransfer && (m_lineId != kInvalidTransitId || m_shapeLink.m_shapeId != kInvalidTransitId))
|
||||
return false;
|
||||
|
||||
if (!m_isTransfer && m_lineId == kInvalidTransitId)
|
||||
return false;
|
||||
|
||||
return m_stop1Id != kInvalidTransitId && m_stop2Id != kInvalidTransitId;
|
||||
}
|
||||
|
||||
void Edge::SetWeight(EdgeWeight weight)
|
||||
{
|
||||
m_weight = weight;
|
||||
}
|
||||
|
||||
TransitId Edge::GetStop1Id() const
|
||||
{
|
||||
return m_stop1Id;
|
||||
}
|
||||
|
||||
TransitId Edge::GetStop2Id() const
|
||||
{
|
||||
return m_stop2Id;
|
||||
}
|
||||
|
||||
EdgeWeight Edge::GetWeight() const
|
||||
{
|
||||
return m_weight;
|
||||
}
|
||||
|
||||
TransitId Edge::GetLineId() const
|
||||
{
|
||||
return m_lineId;
|
||||
}
|
||||
|
||||
bool Edge::IsTransfer() const
|
||||
{
|
||||
return m_isTransfer;
|
||||
}
|
||||
|
||||
ShapeLink const & Edge::GetShapeLink() const
|
||||
{
|
||||
return m_shapeLink;
|
||||
}
|
||||
|
||||
// Transfer ----------------------------------------------------------------------------------------
|
||||
Transfer::Transfer(TransitId id, m2::PointD const & point, IdList const & stopIds)
|
||||
: m_id(id)
|
||||
, m_point(point)
|
||||
, m_stopIds(stopIds)
|
||||
{}
|
||||
|
||||
bool Transfer::operator<(Transfer const & rhs) const
|
||||
{
|
||||
return m_id < rhs.m_id;
|
||||
}
|
||||
|
||||
bool Transfer::operator==(Transfer const & rhs) const
|
||||
{
|
||||
return m_id == rhs.m_id;
|
||||
}
|
||||
|
||||
bool Transfer::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidTransitId && m_stopIds.size() > 1;
|
||||
}
|
||||
|
||||
TransitId Transfer::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
m2::PointD const & Transfer::GetPoint() const
|
||||
{
|
||||
return m_point;
|
||||
}
|
||||
|
||||
IdList const & Transfer::GetStopIds() const
|
||||
{
|
||||
return m_stopIds;
|
||||
}
|
||||
|
||||
// Shape -------------------------------------------------------------------------------------------
|
||||
Shape::Shape(TransitId id, std::vector<m2::PointD> const & polyline) : m_id(id), m_polyline(polyline) {}
|
||||
|
||||
bool Shape::operator<(Shape const & rhs) const
|
||||
{
|
||||
return m_id < rhs.m_id;
|
||||
}
|
||||
|
||||
bool Shape::operator==(Shape const & rhs) const
|
||||
{
|
||||
return m_id == rhs.m_id;
|
||||
}
|
||||
|
||||
bool Shape::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidTransitId && m_polyline.size() > 1;
|
||||
}
|
||||
|
||||
TransitId Shape::GetId() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> const & Shape::GetPolyline() const
|
||||
{
|
||||
return m_polyline;
|
||||
}
|
||||
} // namespace experimental
|
||||
} // namespace transit
|
||||
410
libs/transit/experimental/transit_types_experimental.hpp
Normal file
410
libs/transit/experimental/transit_types_experimental.hpp
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
#pragma once
|
||||
|
||||
#include "transit/transit_entities.hpp"
|
||||
|
||||
#include "indexer/feature_decl.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// This is the implementation of the new transit classes which support not only subway, but also
|
||||
// public transport types from GTFS. Since it is experimental it exists at one time with
|
||||
// subway classes for handling networks, stops and other transit entities. When the time comes this
|
||||
// transit implementation will completely replace the subway classes, they will be removed, and the
|
||||
// experimental namespace will be also removed.
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
template <class Sink>
|
||||
class Serializer;
|
||||
template <class Source>
|
||||
class Deserializer;
|
||||
template <typename Sink>
|
||||
class FixedSizeSerializer;
|
||||
template <typename Sink>
|
||||
class FixedSizeDeserializer;
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
|
||||
namespace transit
|
||||
{
|
||||
namespace experimental
|
||||
{
|
||||
#define DECLARE_TRANSIT_TYPES_FRIENDS \
|
||||
template <class Sink> \
|
||||
friend class routing::transit::Serializer; \
|
||||
template <class Source> \
|
||||
friend class routing::transit::Deserializer; \
|
||||
template <typename Sink> \
|
||||
friend class routing::transit::FixedSizeSerializer; \
|
||||
template <typename Sink> \
|
||||
friend class routing::transit::FixedSizeDeserializer;
|
||||
|
||||
using FeatureId = uint32_t;
|
||||
using OsmId = uint64_t;
|
||||
|
||||
OsmId constexpr kInvalidOsmId = std::numeric_limits<OsmId>::max();
|
||||
|
||||
class SingleMwmSegment
|
||||
{
|
||||
public:
|
||||
SingleMwmSegment() = default;
|
||||
SingleMwmSegment(FeatureId featureId, uint32_t segmentIdx, bool forward);
|
||||
|
||||
FeatureId GetFeatureId() const { return m_featureId; }
|
||||
uint32_t GetSegmentIdx() const { return m_segmentIdx; }
|
||||
bool IsForward() const { return m_forward; }
|
||||
|
||||
bool operator==(SingleMwmSegment const & rhs) const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(SingleMwmSegment, visitor(m_featureId, "feature_id"),
|
||||
visitor(m_segmentIdx, "segment_idx"), visitor(m_forward, "forward"))
|
||||
|
||||
FeatureId m_featureId = kInvalidFeatureId;
|
||||
uint32_t m_segmentIdx = 0;
|
||||
bool m_forward = false;
|
||||
};
|
||||
|
||||
/// \brief This class represents osm id and feature id of the same feature.
|
||||
class IdBundle
|
||||
{
|
||||
public:
|
||||
explicit IdBundle(bool serializeFeatureIdOnly);
|
||||
IdBundle(FeatureId featureId, OsmId osmId, bool serializeFeatureIdOnly);
|
||||
|
||||
bool operator<(IdBundle const & rhs) const;
|
||||
bool operator==(IdBundle const & rhs) const;
|
||||
bool operator!=(IdBundle const & rhs) const;
|
||||
bool IsValid() const;
|
||||
void SetFeatureId(FeatureId featureId);
|
||||
void SetOsmId(OsmId osmId);
|
||||
|
||||
FeatureId GetFeatureId() const;
|
||||
OsmId GetOsmId() const;
|
||||
bool SerializeFeatureIdOnly() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(IdBundle, visitor(m_featureId, "feature_id"), visitor(m_osmId, "osm_id"))
|
||||
|
||||
FeatureId m_featureId = kInvalidFeatureId;
|
||||
OsmId m_osmId = kInvalidOsmId;
|
||||
bool m_serializeFeatureIdOnly = true;
|
||||
};
|
||||
|
||||
struct TransitHeader
|
||||
{
|
||||
TransitHeader() = default;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(TransitHeader, visitor(m_version, "version"), visitor(m_reserve, "reserve"),
|
||||
visitor(m_stopsOffset, "stops"), visitor(m_gatesOffset, "gatesOffset"),
|
||||
visitor(m_edgesOffset, "edgesOffset"), visitor(m_transfersOffset, "transfersOffset"),
|
||||
visitor(m_linesOffset, "linesOffset"),
|
||||
visitor(m_linesMetadataOffset, "linesMetadataOffset"),
|
||||
visitor(m_shapesOffset, "shapesOffset"), visitor(m_routesOffset, "routesOffset"),
|
||||
visitor(m_networksOffset, "networksOffset"), visitor(m_endOffset, "endOffset"))
|
||||
|
||||
uint16_t m_version = 0;
|
||||
uint16_t m_reserve = 0;
|
||||
uint32_t m_stopsOffset = 0;
|
||||
uint32_t m_gatesOffset = 0;
|
||||
uint32_t m_edgesOffset = 0;
|
||||
uint32_t m_transfersOffset = 0;
|
||||
uint32_t m_linesOffset = 0;
|
||||
uint32_t m_linesMetadataOffset = 0;
|
||||
uint32_t m_shapesOffset = 0;
|
||||
uint32_t m_routesOffset = 0;
|
||||
uint32_t m_networksOffset = 0;
|
||||
uint32_t m_endOffset = 0;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TransitHeader) == 44, "Wrong header size of transit section.");
|
||||
|
||||
class Network
|
||||
{
|
||||
public:
|
||||
Network() = default;
|
||||
Network(TransitId id, std::string const & title);
|
||||
explicit Network(TransitId id);
|
||||
|
||||
bool operator<(Network const & rhs) const;
|
||||
bool operator==(Network const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
TransitId GetId() const;
|
||||
std::string const & GetTitle() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Network, visitor(m_id, "id"), visitor(m_title, "title"))
|
||||
|
||||
TransitId m_id = kInvalidTransitId;
|
||||
std::string m_title;
|
||||
};
|
||||
|
||||
class Route
|
||||
{
|
||||
public:
|
||||
Route() = default;
|
||||
Route(TransitId id, TransitId networkId, std::string const & routeType, std::string const & title,
|
||||
std::string const & color);
|
||||
|
||||
bool operator<(Route const & rhs) const;
|
||||
bool operator==(Route const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
TransitId GetId() const;
|
||||
std::string const & GetTitle() const;
|
||||
std::string const & GetType() const;
|
||||
std::string const & GetColor() const;
|
||||
TransitId GetNetworkId() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Route, visitor(m_id, "id"), visitor(m_networkId, "network_id"),
|
||||
visitor(m_routeType, "type"), visitor(m_title, "title"), visitor(m_color, "color"))
|
||||
TransitId m_id = kInvalidTransitId;
|
||||
TransitId m_networkId = kInvalidTransitId;
|
||||
std::string m_routeType;
|
||||
std::string m_title;
|
||||
std::string m_color;
|
||||
};
|
||||
|
||||
class Line
|
||||
{
|
||||
public:
|
||||
Line() = default;
|
||||
Line(TransitId id, TransitId routeId, ShapeLink const & shapeLink, std::string const & title, IdList const & stopIds,
|
||||
Schedule const & schedule);
|
||||
|
||||
bool operator<(Line const & rhs) const;
|
||||
bool operator==(Line const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
TransitId GetId() const;
|
||||
std::string const & GetTitle() const;
|
||||
TransitId GetRouteId() const;
|
||||
ShapeLink const & GetShapeLink() const;
|
||||
IdList const & GetStopIds() const;
|
||||
Schedule const & GetSchedule() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Line, visitor(m_id, "id"), visitor(m_routeId, "route_id"),
|
||||
visitor(m_shapeLink, "shape_link"), visitor(m_title, "title"),
|
||||
visitor(m_stopIds, "stop_ids"), visitor(m_schedule, "schedule"))
|
||||
TransitId m_id = kInvalidTransitId;
|
||||
TransitId m_routeId = kInvalidTransitId;
|
||||
ShapeLink m_shapeLink;
|
||||
std::string m_title;
|
||||
IdList m_stopIds;
|
||||
Schedule m_schedule;
|
||||
};
|
||||
|
||||
class LineMetadata
|
||||
{
|
||||
public:
|
||||
LineMetadata() = default;
|
||||
LineMetadata(TransitId id, LineSegmentsOrder const & segmentsOrder);
|
||||
|
||||
bool operator<(LineMetadata const & rhs) const;
|
||||
bool operator==(LineMetadata const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
TransitId GetId() const;
|
||||
LineSegmentsOrder const & GetLineSegmentsOrder() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(LineMetadata, visitor(m_id, "id"), visitor(m_segmentsOrder, "segments_order"))
|
||||
TransitId m_id = kInvalidTransitId;
|
||||
LineSegmentsOrder m_segmentsOrder;
|
||||
};
|
||||
|
||||
class Stop
|
||||
{
|
||||
public:
|
||||
Stop();
|
||||
Stop(TransitId id, FeatureId featureId, OsmId osmId, std::string const & title, TimeTable const & timetable,
|
||||
m2::PointD const & point, IdList const & transferIds);
|
||||
explicit Stop(TransitId id);
|
||||
|
||||
bool operator<(Stop const & rhs) const;
|
||||
bool operator==(Stop const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
void SetBestPedestrianSegments(std::vector<SingleMwmSegment> const & seg);
|
||||
std::vector<SingleMwmSegment> const & GetBestPedestrianSegments() const;
|
||||
|
||||
FeatureId GetId() const;
|
||||
FeatureId GetFeatureId() const;
|
||||
OsmId GetOsmId() const;
|
||||
std::string const & GetTitle() const;
|
||||
TimeTable const & GetTimeTable() const;
|
||||
m2::PointD const & GetPoint() const;
|
||||
IdList const & GetTransferIds() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Stop, visitor(m_id, "id"), visitor(m_ids, "id_bundle"),
|
||||
visitor(m_bestPedestrianSegments, "best_pedestrian_segments"),
|
||||
visitor(m_title, "title"), visitor(m_timetable, "timetable"),
|
||||
visitor(m_point, "point"), visitor(m_transferIds, "transfer_ids"))
|
||||
TransitId m_id = kInvalidTransitId;
|
||||
IdBundle m_ids;
|
||||
// |m_bestPedestrianSegments| are segments which can be used for pedestrian routing to leave and
|
||||
// enter the gate. The segments may be invalid because of map date. If so there's no pedestrian
|
||||
// segment which can be used to reach the stop.
|
||||
std::vector<SingleMwmSegment> m_bestPedestrianSegments;
|
||||
std::string m_title;
|
||||
TimeTable m_timetable;
|
||||
m2::PointD m_point;
|
||||
IdList m_transferIds;
|
||||
};
|
||||
|
||||
class Gate
|
||||
{
|
||||
public:
|
||||
Gate();
|
||||
Gate(TransitId id, FeatureId featureId, OsmId osmId, bool entrance, bool exit,
|
||||
std::vector<TimeFromGateToStop> const & weights, m2::PointD const & point);
|
||||
|
||||
bool operator<(Gate const & rhs) const;
|
||||
bool operator==(Gate const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
TransitId GetId() const;
|
||||
FeatureId GetFeatureId() const;
|
||||
OsmId GetOsmId() const;
|
||||
|
||||
std::vector<SingleMwmSegment> const & GetBestPedestrianSegments() const;
|
||||
void SetBestPedestrianSegments(std::vector<SingleMwmSegment> const & seg);
|
||||
|
||||
bool IsEntrance() const;
|
||||
bool IsExit() const;
|
||||
std::vector<TimeFromGateToStop> const & GetStopsWithWeight() const;
|
||||
m2::PointD const & GetPoint() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Gate, visitor(m_id, "id"), visitor(m_ids, "id_bundle"),
|
||||
visitor(m_bestPedestrianSegments, "best_pedestrian_segments"),
|
||||
visitor(m_entrance, "entrance"), visitor(m_exit, "exit"),
|
||||
visitor(m_weights, "weights"), visitor(m_point, "point"))
|
||||
|
||||
TransitId m_id = kInvalidTransitId;
|
||||
// |m_ids| contains feature id of a feature which represents gates. Usually it's a
|
||||
// point feature.
|
||||
IdBundle m_ids;
|
||||
// |m_bestPedestrianSegments| are segments which can be used for pedestrian routing to leave and
|
||||
// enter the gate. The segments may be invalid because of map date. If so there's no pedestrian
|
||||
// segment which can be used to reach the gate.
|
||||
std::vector<SingleMwmSegment> m_bestPedestrianSegments;
|
||||
bool m_entrance = true;
|
||||
bool m_exit = true;
|
||||
std::vector<TimeFromGateToStop> m_weights;
|
||||
m2::PointD m_point;
|
||||
};
|
||||
|
||||
class Edge
|
||||
{
|
||||
public:
|
||||
Edge() = default;
|
||||
Edge(TransitId stop1Id, TransitId stop2Id, EdgeWeight weight, TransitId lineId, bool transfer,
|
||||
ShapeLink const & shapeLink);
|
||||
|
||||
bool operator<(Edge const & rhs) const;
|
||||
bool operator==(Edge const & rhs) const;
|
||||
bool operator!=(Edge const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
void SetWeight(EdgeWeight weight);
|
||||
|
||||
TransitId GetStop1Id() const;
|
||||
TransitId GetStop2Id() const;
|
||||
EdgeWeight GetWeight() const;
|
||||
TransitId GetLineId() const;
|
||||
bool IsTransfer() const;
|
||||
ShapeLink const & GetShapeLink() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Edge, visitor(m_stop1Id, "stop1_id"), visitor(m_stop2Id, "stop2_id"),
|
||||
visitor(m_weight, "weight"), visitor(m_isTransfer, "is_transfer"),
|
||||
visitor(m_lineId, "line_id"), visitor(m_shapeLink, "shape_link"))
|
||||
|
||||
TransitId m_stop1Id = kInvalidTransitId;
|
||||
TransitId m_stop2Id = kInvalidTransitId;
|
||||
EdgeWeight m_weight = 0;
|
||||
bool m_isTransfer = false;
|
||||
TransitId m_lineId = kInvalidTransitId;
|
||||
ShapeLink m_shapeLink;
|
||||
};
|
||||
|
||||
class Transfer
|
||||
{
|
||||
public:
|
||||
Transfer() = default;
|
||||
Transfer(TransitId id, m2::PointD const & point, IdList const & stopIds);
|
||||
|
||||
bool operator<(Transfer const & rhs) const;
|
||||
bool operator==(Transfer const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
TransitId GetId() const;
|
||||
m2::PointD const & GetPoint() const;
|
||||
IdList const & GetStopIds() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Transfer, visitor(m_id, "id"), visitor(m_point, "point"),
|
||||
visitor(m_stopIds, "stop_ids"))
|
||||
|
||||
TransitId m_id = kInvalidTransitId;
|
||||
m2::PointD m_point;
|
||||
IdList m_stopIds;
|
||||
};
|
||||
|
||||
class Shape
|
||||
{
|
||||
public:
|
||||
Shape() = default;
|
||||
Shape(TransitId id, std::vector<m2::PointD> const & polyline);
|
||||
|
||||
bool operator<(Shape const & rhs) const;
|
||||
bool operator==(Shape const & rhs) const;
|
||||
|
||||
bool IsValid() const;
|
||||
|
||||
TransitId GetId() const;
|
||||
std::vector<m2::PointD> const & GetPolyline() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Shape, visitor(m_id, "id"), visitor(m_polyline, "polyline"))
|
||||
|
||||
TransitId m_id;
|
||||
std::vector<m2::PointD> m_polyline;
|
||||
};
|
||||
|
||||
#undef DECLARE_TRANSIT_TYPES_FRIENDS
|
||||
} // namespace experimental
|
||||
} // namespace transit
|
||||
64
libs/transit/transit_display_info.hpp
Normal file
64
libs/transit/transit_display_info.hpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#pragma once
|
||||
|
||||
#include "transit/experimental/transit_types_experimental.hpp"
|
||||
#include "transit/transit_entities.hpp"
|
||||
#include "transit/transit_types.hpp"
|
||||
#include "transit/transit_version.hpp"
|
||||
|
||||
#include "indexer/feature_decl.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
struct TransitFeatureInfo
|
||||
{
|
||||
bool m_isGate = false;
|
||||
std::string m_gateSymbolName;
|
||||
std::string m_title;
|
||||
m2::PointD m_point;
|
||||
};
|
||||
|
||||
using TransitFeaturesInfo = std::map<FeatureID, TransitFeatureInfo>;
|
||||
|
||||
// We have two completely different versions of transit section: the actual one with subway-only
|
||||
// info and the experimental one with all public transport (PT) info. So we keep the old data
|
||||
// with suffix Subway and the new data with suffix PT. In the future we will remove the subway
|
||||
// version (after a year or so of PT usage).
|
||||
using TransitStopsInfo = std::map<routing::transit::StopId, routing::transit::Stop>;
|
||||
using TransitTransfersInfo = std::map<routing::transit::TransferId, routing::transit::Transfer>;
|
||||
using TransitShapesInfo = std::map<routing::transit::ShapeId, routing::transit::Shape>;
|
||||
using TransitLinesInfo = std::map<routing::transit::LineId, routing::transit::Line>;
|
||||
using TransitNetworksInfo = std::map<routing::transit::NetworkId, routing::transit::Network>;
|
||||
|
||||
using TransitStopsInfoPT = std::map<::transit::TransitId, ::transit::experimental::Stop>;
|
||||
using TransitTransfersInfoPT = std::map<::transit::TransitId, ::transit::experimental::Transfer>;
|
||||
using TransitShapesInfoPT = std::map<::transit::TransitId, ::transit::experimental::Shape>;
|
||||
using TransitLinesInfoPT = std::map<::transit::TransitId, ::transit::experimental::Line>;
|
||||
using TransitLinesMetadataInfoPT = std::map<::transit::TransitId, ::transit::experimental::LineMetadata>;
|
||||
using TransitRoutesInfoPT = std::map<::transit::TransitId, ::transit::experimental::Route>;
|
||||
using TransitNetworksInfoPT = std::map<::transit::TransitId, ::transit::experimental::Network>;
|
||||
using TransitEdgesInfoPT = std::unordered_map<::transit::EdgeId, ::transit::EdgeData, ::transit::EdgeIdHasher>;
|
||||
struct TransitDisplayInfo
|
||||
{
|
||||
::transit::TransitVersion m_transitVersion;
|
||||
|
||||
TransitFeaturesInfo m_features;
|
||||
|
||||
TransitNetworksInfo m_networksSubway;
|
||||
TransitLinesInfo m_linesSubway;
|
||||
TransitStopsInfo m_stopsSubway;
|
||||
TransitTransfersInfo m_transfersSubway;
|
||||
TransitShapesInfo m_shapesSubway;
|
||||
|
||||
TransitNetworksInfoPT m_networksPT;
|
||||
TransitLinesInfoPT m_linesPT;
|
||||
TransitLinesMetadataInfoPT m_linesMetadataPT;
|
||||
TransitRoutesInfoPT m_routesPT;
|
||||
TransitStopsInfoPT m_stopsPT;
|
||||
TransitTransfersInfoPT m_transfersPT;
|
||||
TransitShapesInfoPT m_shapesPT;
|
||||
TransitEdgesInfoPT m_edgesPT;
|
||||
};
|
||||
|
||||
using TransitDisplayInfos = std::map<MwmSet::MwmId, std::unique_ptr<TransitDisplayInfo>>;
|
||||
204
libs/transit/transit_entities.hpp
Normal file
204
libs/transit/transit_entities.hpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
#pragma once
|
||||
|
||||
#include "transit/transit_schedule.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/macros.hpp"
|
||||
#include "base/newtype.hpp"
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include "std/boost_container_hash.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/opening_hours/opening_hours.hpp"
|
||||
|
||||
namespace routing
|
||||
{
|
||||
inline double constexpr kTransitMaxSpeedKMpH = 400.0;
|
||||
} // namespace routing
|
||||
|
||||
namespace transit
|
||||
{
|
||||
// File names for saving resulting data exported from GTFS.
|
||||
inline std::string const kTransitFileExtension = std::string(TRANSIT_FILE_EXTENSION);
|
||||
inline std::string const kNetworksFile = "networks" + kTransitFileExtension;
|
||||
inline std::string const kRoutesFile = "routes" + kTransitFileExtension;
|
||||
inline std::string const kLinesFile = "lines" + kTransitFileExtension;
|
||||
inline std::string const kLinesMetadataFile = "lines_metadata" + kTransitFileExtension;
|
||||
inline std::string const kShapesFile = "shapes" + kTransitFileExtension;
|
||||
inline std::string const kStopsFile = "stops" + kTransitFileExtension;
|
||||
inline std::string const kEdgesFile = "edges" + kTransitFileExtension;
|
||||
inline std::string const kEdgesTransferFile = "edges_transfer" + kTransitFileExtension;
|
||||
inline std::string const kTransfersFile = "transfers" + kTransitFileExtension;
|
||||
inline std::string const kGatesFile = "gates" + kTransitFileExtension;
|
||||
|
||||
// Route types shown on the subway layer.
|
||||
inline std::unordered_set<std::string> const kSubwayLayerTypes{"subway", "train", "light_rail", "monorail"};
|
||||
|
||||
// Unique id for transit entities. It is generated by gtfs_converter and is persistent between
|
||||
// re-runs. Generated based on the unique string hash of the GTFS entity. Lies in the interval
|
||||
// |routing::FakeFeatureIds::IsTransitFeature()|. If the GTFS entity is renamed or the new GTFS
|
||||
// feed is added the new id is generated by |IdGenerator::MakeId()|.
|
||||
using TransitId = uint32_t;
|
||||
inline TransitId constexpr kInvalidTransitId = std::numeric_limits<TransitId>::max();
|
||||
|
||||
// Mapping of language id to text. Language id exists in |StringUtf8Multilang::kLanguages|.
|
||||
using Translations = std::unordered_map<std::string, std::string>;
|
||||
|
||||
struct TimeFromGateToStop
|
||||
{
|
||||
TimeFromGateToStop() = default;
|
||||
TimeFromGateToStop(TransitId stopId, uint32_t timeSeconds) : m_stopId(stopId), m_timeSeconds(timeSeconds) {}
|
||||
bool operator==(TimeFromGateToStop const & rhs) const
|
||||
{
|
||||
return std::tie(m_stopId, m_timeSeconds) == std::tie(rhs.m_stopId, rhs.m_timeSeconds);
|
||||
}
|
||||
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(TimeFromGateToStop, visitor(m_stopId, "stopId"),
|
||||
visitor(m_timeSeconds, "timeSeconds"))
|
||||
|
||||
TransitId m_stopId = kInvalidTransitId;
|
||||
uint32_t m_timeSeconds = 0;
|
||||
};
|
||||
|
||||
using IdList = std::vector<TransitId>;
|
||||
using IdSet = std::unordered_set<TransitId>;
|
||||
using TimeTable = std::unordered_map<TransitId, std::vector<TimeInterval>>;
|
||||
using EdgeWeight = uint32_t;
|
||||
|
||||
// Link to the shape: shape id and indexes in the corresponding polyline.
|
||||
struct ShapeLink
|
||||
{
|
||||
ShapeLink() = default;
|
||||
ShapeLink(TransitId id, uint32_t startIndex, uint32_t endIndex)
|
||||
: m_shapeId(id)
|
||||
, m_startIndex(startIndex)
|
||||
, m_endIndex(endIndex)
|
||||
{}
|
||||
|
||||
bool operator==(ShapeLink const & rhs) const
|
||||
{
|
||||
return std::tie(m_shapeId, m_startIndex, m_endIndex) == std::tie(rhs.m_shapeId, rhs.m_startIndex, rhs.m_endIndex);
|
||||
}
|
||||
|
||||
bool operator<(ShapeLink const & rhs) const
|
||||
{
|
||||
return std::tie(m_shapeId, m_startIndex, m_endIndex) < std::tie(rhs.m_shapeId, rhs.m_startIndex, rhs.m_endIndex);
|
||||
}
|
||||
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(ShapeLink, visitor(m_shapeId, "id"), visitor(m_startIndex, "startIndex"),
|
||||
visitor(m_endIndex, "endIndex"))
|
||||
|
||||
TransitId m_shapeId = kInvalidTransitId;
|
||||
uint32_t m_startIndex = 0;
|
||||
uint32_t m_endIndex = 0;
|
||||
};
|
||||
|
||||
struct EdgeId
|
||||
{
|
||||
EdgeId() = default;
|
||||
EdgeId(TransitId fromStopId, TransitId toStopId, TransitId lineId)
|
||||
: m_fromStopId(fromStopId)
|
||||
, m_toStopId(toStopId)
|
||||
, m_lineId(lineId)
|
||||
{}
|
||||
|
||||
bool operator==(EdgeId const & other) const
|
||||
{
|
||||
return std::tie(m_fromStopId, m_toStopId, m_lineId) ==
|
||||
std::tie(other.m_fromStopId, other.m_toStopId, other.m_lineId);
|
||||
}
|
||||
|
||||
TransitId m_fromStopId = 0;
|
||||
TransitId m_toStopId = 0;
|
||||
TransitId m_lineId = 0;
|
||||
};
|
||||
|
||||
struct EdgeIdHasher
|
||||
{
|
||||
size_t operator()(EdgeId const & key) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
boost::hash_combine(seed, key.m_fromStopId);
|
||||
boost::hash_combine(seed, key.m_toStopId);
|
||||
boost::hash_combine(seed, key.m_lineId);
|
||||
return seed;
|
||||
}
|
||||
};
|
||||
|
||||
using IdEdgeSet = std::unordered_set<EdgeId, EdgeIdHasher>;
|
||||
|
||||
struct EdgeData
|
||||
{
|
||||
EdgeData() = default;
|
||||
EdgeData(ShapeLink const & shapeLink, EdgeWeight const & weight) : m_shapeLink(shapeLink), m_weight(weight) {}
|
||||
|
||||
explicit EdgeData(EdgeWeight const & weight) : m_weight(weight) {}
|
||||
|
||||
ShapeLink m_shapeLink;
|
||||
EdgeWeight m_weight = 0;
|
||||
|
||||
// Feature id for cross-mwm transit section. It is used in Segment class as a feature id for
|
||||
// transit routing case.
|
||||
uint32_t m_featureId = std::numeric_limits<uint32_t>::max();
|
||||
};
|
||||
|
||||
struct LineSegment
|
||||
{
|
||||
LineSegment() = default;
|
||||
explicit LineSegment(uint32_t startIdx) : m_startIdx(startIdx) {}
|
||||
LineSegment(uint32_t startIdx, uint32_t endIdx) : m_startIdx(startIdx), m_endIdx(endIdx) {}
|
||||
|
||||
bool operator==(LineSegment const & rhs) const { return m_startIdx == rhs.m_startIdx && m_endIdx == rhs.m_endIdx; }
|
||||
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(LineSegment, visitor(m_startIdx, "startIdx"), visitor(m_endIdx, "endIdx"))
|
||||
|
||||
uint32_t m_startIdx = 0;
|
||||
uint32_t m_endIdx = 0;
|
||||
};
|
||||
|
||||
using LineSegments = std::vector<LineSegment>;
|
||||
|
||||
struct LineSegmentOrder
|
||||
{
|
||||
LineSegmentOrder() = default;
|
||||
LineSegmentOrder(LineSegment const & lineSegment, int order) : m_segment(lineSegment), m_order(order) {}
|
||||
|
||||
bool operator==(LineSegmentOrder const & rhs) const { return m_order == rhs.m_order && m_segment == rhs.m_segment; }
|
||||
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(LineSegmentOrder, visitor(m_segment, "segment"), visitor(m_order, "order"))
|
||||
|
||||
LineSegment m_segment;
|
||||
int m_order = 0;
|
||||
};
|
||||
|
||||
using LineSegmentsOrder = std::vector<LineSegmentOrder>;
|
||||
|
||||
template <class T, class I>
|
||||
typename std::vector<T>::const_iterator FindById(std::vector<T> const & container, I const & id, bool exists = true)
|
||||
{
|
||||
auto const it = std::find_if(container.begin(), container.end(), [id](T const & obj) { return obj.GetId() == id; });
|
||||
|
||||
if (exists)
|
||||
CHECK(it != container.end(), (id));
|
||||
return it;
|
||||
}
|
||||
|
||||
inline std::vector<m2::PointD> GetPolylinePart(std::vector<m2::PointD> const & polyline, size_t startIdx, size_t endIdx)
|
||||
{
|
||||
CHECK_GREATER(endIdx, startIdx, ());
|
||||
CHECK_GREATER(polyline.size(), endIdx, ());
|
||||
|
||||
return std::vector<m2::PointD>(polyline.begin() + startIdx, polyline.begin() + endIdx + 1);
|
||||
}
|
||||
} // namespace transit
|
||||
10
libs/transit/transit_experimental_tests/CMakeLists.txt
Normal file
10
libs/transit/transit_experimental_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
project(transit_experimental_tests)
|
||||
|
||||
set(SRC
|
||||
parse_transit_from_json_tests.cpp
|
||||
transit_serdes_tests.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} transit)
|
||||
|
|
@ -0,0 +1,281 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/transit_tests/transit_tools.hpp"
|
||||
|
||||
#include "transit/experimental/transit_data.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
namespace experimental
|
||||
{
|
||||
template <typename... Args>
|
||||
void FillContainer(std::vector<std::string> const & lineByLineJsons, Args &&... args)
|
||||
{
|
||||
for (auto const & line : lineByLineJsons)
|
||||
Read(base::Json(line.c_str()), std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_Network)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"id":4032061478,
|
||||
"title":"Tránsito Golden Gate"
|
||||
})",
|
||||
R"({
|
||||
"id":4035419389,
|
||||
"title":"Caltrain"
|
||||
})"};
|
||||
|
||||
std::vector<Network> const networksPlan = {Network(4032061478 /* transitId */, "Tránsito Golden Gate" /* title */),
|
||||
Network(4035419389 /* transitId */, "Caltrain" /* title */)};
|
||||
|
||||
std::vector<Network> networksFact;
|
||||
|
||||
FillContainer(lineByLineJson, networksFact);
|
||||
TestEqual(networksFact, networksPlan);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_Route)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"id":4036206863,
|
||||
"network_id":4036206862,
|
||||
"color":"pink_dark",
|
||||
"type":"rail",
|
||||
"title":"Línea principal"
|
||||
})",
|
||||
R"({
|
||||
"id":4027700598,
|
||||
"network_id":4027700597,
|
||||
"color":"blue",
|
||||
"type":"bus",
|
||||
"title":"East Route"
|
||||
})"};
|
||||
|
||||
std::vector<Route> const routesPlan = {Route(4036206863 /* id */, 4036206862 /* networkId */, "rail" /* routeType */,
|
||||
"Línea principal" /* title */, "pink_dark" /* color */),
|
||||
Route(4027700598 /* id */, 4027700597 /* networkId */, "bus" /* routeType */,
|
||||
"East Route" /* title */, "blue" /* color */)};
|
||||
|
||||
std::vector<Route> routesFact;
|
||||
|
||||
FillContainer(lineByLineJson, routesFact);
|
||||
TestEqual(routesFact, routesPlan);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_Line)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"id":4036591532,
|
||||
"route_id":4036591423,
|
||||
"shape":{
|
||||
"id":4036591460,
|
||||
"start_index":415,
|
||||
"end_index":1691
|
||||
},
|
||||
"title":"Downtown",
|
||||
"stops_ids":[
|
||||
4036592571,
|
||||
4036592572,
|
||||
4036592573
|
||||
],
|
||||
"schedule":{
|
||||
"def_frequency":4060,
|
||||
"intervals":[
|
||||
{
|
||||
"dates_interval":75007489,
|
||||
"time_intervals":[{"time_interval":6530334760, "frequency":3000}]
|
||||
}],
|
||||
"exceptions":[
|
||||
{"exception":519, "time_intervals": []},
|
||||
{"exception":357, "time_intervals": []}
|
||||
]
|
||||
}
|
||||
})"};
|
||||
|
||||
FrequencyIntervals frequencies;
|
||||
frequencies.AddInterval(TimeInterval(6530334760), 3000);
|
||||
|
||||
Schedule schedule;
|
||||
schedule.SetDefaultFrequency(4060);
|
||||
|
||||
schedule.AddDatesInterval(DatesInterval(75007489), frequencies);
|
||||
schedule.AddDateException(DateException(519), {});
|
||||
schedule.AddDateException(DateException(357), {});
|
||||
|
||||
std::vector<Line> const linesPlan = {Line(4036591532 /* id */, 4036591423 /* routeId */,
|
||||
ShapeLink(4036591460 /* id */, 415 /* startIndex */, 1691 /* endIndex */),
|
||||
"Downtown" /* title */, IdList{4036592571, 4036592572, 4036592573},
|
||||
schedule)};
|
||||
|
||||
std::vector<Line> linesFact;
|
||||
|
||||
FillContainer(lineByLineJson, linesFact);
|
||||
TestEqual(linesFact, linesPlan);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_LineMetadata)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"id":56,
|
||||
"shape_segments":[
|
||||
{
|
||||
"order":-1,
|
||||
"start_index":34,
|
||||
"end_index":99
|
||||
},
|
||||
{
|
||||
"order":-3,
|
||||
"start_index":99,
|
||||
"end_index":1069
|
||||
}
|
||||
]
|
||||
})"};
|
||||
|
||||
std::vector<LineMetadata> const linesMetadataPlan = {
|
||||
LineMetadata(56 /* id */, LineSegmentsOrder{LineSegmentOrder({34, 99}, -1), LineSegmentOrder({99, 1069}, -3)})};
|
||||
|
||||
std::vector<LineMetadata> linesMetadataFact;
|
||||
|
||||
FillContainer(lineByLineJson, linesMetadataFact);
|
||||
TestEqual(linesMetadataFact, linesMetadataPlan);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_Stop)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"id":4036592706,
|
||||
"point":{
|
||||
"x":-121.74124399999999,
|
||||
"y":41.042765953900343
|
||||
},
|
||||
"title":"Balfour Rd & Foothill Dr",
|
||||
"timetable":[{"line_id":204,"intervals":[11400205248]}],
|
||||
"transfer_ids":[
|
||||
4036593809,
|
||||
4036595406
|
||||
]
|
||||
})"};
|
||||
|
||||
std::vector<Stop> const stopsPlan = {
|
||||
Stop(4036592706 /* id */, kInvalidFeatureId /* featureId */, kInvalidOsmId /* osmId */,
|
||||
"Balfour Rd & Foothill Dr", TimeTable{{204, std::vector<TimeInterval>{TimeInterval(11400205248)}}},
|
||||
m2::PointD(-121.74124, 41.04276), {4036593809, 4036595406} /* transferIds */)};
|
||||
|
||||
std::vector<Stop> stopsFact;
|
||||
|
||||
OsmIdToFeatureIdsMap dummyMapping;
|
||||
FillContainer(lineByLineJson, stopsFact, dummyMapping);
|
||||
TestEqual(stopsFact, stopsPlan);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_Gate)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"id":4034666808,
|
||||
"weights":[
|
||||
{
|
||||
"stop_id":4034666299,
|
||||
"time_to_stop":76
|
||||
},
|
||||
{
|
||||
"stop_id":4034666190,
|
||||
"time_to_stop":61
|
||||
}
|
||||
],
|
||||
"exit":true,
|
||||
"entrance":true,
|
||||
"point":{
|
||||
"x":-121.840608,
|
||||
"y":40.19395285260439
|
||||
}
|
||||
})"};
|
||||
|
||||
std::vector<Gate> const gatesPlan = {
|
||||
Gate(4034666808 /* id */, kInvalidFeatureId /* featureId */, kInvalidOsmId /* osmId */, true /* entrance */,
|
||||
true /* exit */,
|
||||
std::vector<TimeFromGateToStop>{TimeFromGateToStop(4034666299 /* stopId */, 76 /* timeSeconds */),
|
||||
TimeFromGateToStop(4034666190 /* stopId */, 61 /* timeSeconds */)},
|
||||
m2::PointD(-121.8406, 40.19395))};
|
||||
|
||||
std::vector<Gate> gatesFact;
|
||||
|
||||
OsmIdToFeatureIdsMap dummyMapping;
|
||||
FillContainer(lineByLineJson, gatesFact, dummyMapping);
|
||||
TestEqual(gatesFact, gatesPlan);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_Edge)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"line_id":4036591958,
|
||||
"stop_id_from":4036592295,
|
||||
"stop_id_to":4036592296,
|
||||
"feature_id":10731,
|
||||
"weight":69,
|
||||
"shape":{
|
||||
"id":4036591484,
|
||||
"start_index":592,
|
||||
"end_index":609
|
||||
}
|
||||
})",
|
||||
R"({
|
||||
"stop_id_from":4030648032,
|
||||
"stop_id_to":4030648073,
|
||||
"feature_id":860522,
|
||||
"weight":40
|
||||
}
|
||||
)"};
|
||||
|
||||
std::vector<Edge> const edgesPlan = {
|
||||
Edge(4036592295 /* stop1Id */, 4036592296 /* stop2Id */, 69 /* weight */, 4036591958 /* lineId */,
|
||||
false /* transfer */, ShapeLink(4036591484 /* shapeId */, 592 /* startIndex */, 609 /* endIndex */)),
|
||||
Edge(4030648032 /* stop1Id */, 4030648073 /* stop2Id */, 40 /* weight */, kInvalidTransitId /* lineId */,
|
||||
true /* transfer */, ShapeLink(kInvalidTransitId /* shapeId */, 0 /* startIndex */, 0 /* endIndex */))};
|
||||
|
||||
std::vector<Edge> edgesFact;
|
||||
EdgeIdToFeatureId edgeIdToFeatureId;
|
||||
|
||||
FillContainer(lineByLineJson, edgesFact, edgeIdToFeatureId);
|
||||
TestEqual(edgesFact, edgesPlan);
|
||||
}
|
||||
|
||||
UNIT_TEST(ReadJson_Transfer)
|
||||
{
|
||||
std::vector<std::string> const lineByLineJson{
|
||||
R"({
|
||||
"id":4029752024,
|
||||
"point":{
|
||||
"x":-122.16915443000001,
|
||||
"y":40.41578164679229
|
||||
},
|
||||
"stops_ids":[
|
||||
4029751945,
|
||||
4029752010
|
||||
]
|
||||
}
|
||||
)"};
|
||||
|
||||
std::vector<Transfer> const transfersPlan = {Transfer(
|
||||
4029752024 /* id */, m2::PointD(-122.16915, 40.41578) /* point */, IdList{4029751945, 4029752010} /* stopIds */)};
|
||||
|
||||
std::vector<Transfer> transfersFact;
|
||||
|
||||
FillContainer(lineByLineJson, transfersFact);
|
||||
TestEqual(transfersFact, transfersPlan);
|
||||
}
|
||||
} // namespace experimental
|
||||
} // namespace transit
|
||||
212
libs/transit/transit_experimental_tests/transit_serdes_tests.cpp
Normal file
212
libs/transit/transit_experimental_tests/transit_serdes_tests.cpp
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/transit_tests/transit_tools.hpp"
|
||||
|
||||
#include "transit/experimental/transit_data.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
namespace experimental
|
||||
{
|
||||
enum class TransitUseCase
|
||||
{
|
||||
AllData,
|
||||
Routing,
|
||||
Rendering,
|
||||
CrossMwm
|
||||
};
|
||||
|
||||
void TestEqual(TransitData const & actualTransit, TransitData const & expectedTransit, TransitUseCase const & useCase)
|
||||
{
|
||||
switch (useCase)
|
||||
{
|
||||
case TransitUseCase::AllData:
|
||||
TestEqual(actualTransit.GetStops(), expectedTransit.GetStops());
|
||||
TestEqual(actualTransit.GetGates(), expectedTransit.GetGates());
|
||||
TestEqual(actualTransit.GetEdges(), expectedTransit.GetEdges());
|
||||
TestEqual(actualTransit.GetTransfers(), expectedTransit.GetTransfers());
|
||||
TestEqual(actualTransit.GetLines(), expectedTransit.GetLines());
|
||||
TestEqual(actualTransit.GetLinesMetadata(), expectedTransit.GetLinesMetadata());
|
||||
TestEqual(actualTransit.GetShapes(), expectedTransit.GetShapes());
|
||||
TestEqual(actualTransit.GetRoutes(), expectedTransit.GetRoutes());
|
||||
TestEqual(actualTransit.GetNetworks(), expectedTransit.GetNetworks());
|
||||
break;
|
||||
|
||||
case TransitUseCase::Routing:
|
||||
TestEqual(actualTransit.GetStops(), expectedTransit.GetStops());
|
||||
TestEqual(actualTransit.GetGates(), expectedTransit.GetGates());
|
||||
TestEqual(actualTransit.GetEdges(), expectedTransit.GetEdges());
|
||||
TEST(actualTransit.GetTransfers().empty(), ());
|
||||
TestEqual(actualTransit.GetLines(), expectedTransit.GetLines());
|
||||
TEST(actualTransit.GetShapes().empty(), ());
|
||||
TEST(actualTransit.GetRoutes().empty(), ());
|
||||
TEST(actualTransit.GetNetworks().empty(), ());
|
||||
break;
|
||||
|
||||
case TransitUseCase::Rendering:
|
||||
TestEqual(actualTransit.GetStops(), expectedTransit.GetStops());
|
||||
TEST(actualTransit.GetGates().empty(), ());
|
||||
TestEqual(actualTransit.GetEdges(), expectedTransit.GetEdges());
|
||||
TestEqual(actualTransit.GetTransfers(), expectedTransit.GetTransfers());
|
||||
TestEqual(actualTransit.GetLines(), expectedTransit.GetLines());
|
||||
TestEqual(actualTransit.GetLinesMetadata(), expectedTransit.GetLinesMetadata());
|
||||
TestEqual(actualTransit.GetShapes(), expectedTransit.GetShapes());
|
||||
TestEqual(actualTransit.GetRoutes(), expectedTransit.GetRoutes());
|
||||
TEST(actualTransit.GetNetworks().empty(), ());
|
||||
break;
|
||||
|
||||
case TransitUseCase::CrossMwm:
|
||||
TestEqual(actualTransit.GetStops(), expectedTransit.GetStops());
|
||||
TEST(actualTransit.GetGates().empty(), ());
|
||||
TestEqual(actualTransit.GetEdges(), expectedTransit.GetEdges());
|
||||
TEST(actualTransit.GetTransfers().empty(), ());
|
||||
TEST(actualTransit.GetLines().empty(), ());
|
||||
TEST(actualTransit.GetShapes().empty(), ());
|
||||
TEST(actualTransit.GetRoutes().empty(), ());
|
||||
TEST(actualTransit.GetNetworks().empty(), ());
|
||||
break;
|
||||
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
void SerDesTransit(TransitData & src, TransitData & dst, TransitUseCase const & useCase)
|
||||
{
|
||||
std::vector<uint8_t> buffer;
|
||||
MemWriter<decltype(buffer)> writer(buffer);
|
||||
src.Serialize(writer);
|
||||
|
||||
MemReader reader(buffer.data(), buffer.size());
|
||||
switch (useCase)
|
||||
{
|
||||
case TransitUseCase::AllData: dst.Deserialize(reader); break;
|
||||
case TransitUseCase::Routing: dst.DeserializeForRouting(reader); break;
|
||||
case TransitUseCase::Rendering: dst.DeserializeForRendering(reader); break;
|
||||
case TransitUseCase::CrossMwm: dst.DeserializeForCrossMwm(reader); break;
|
||||
default: UNREACHABLE();
|
||||
}
|
||||
|
||||
dst.CheckValid();
|
||||
}
|
||||
|
||||
TransitData FillTestTransitData()
|
||||
{
|
||||
TransitData data;
|
||||
data.m_networks = {Network(4032061671 /* transitId */, "ГУП МосГорТранс" /* title */),
|
||||
Network(4035419440 /* transitId */, "EMPRESA DE TRANSPORTE DEL SUR SRL" /* title */),
|
||||
Network(4035418196 /* transitId */, "Buslink Sunraysia" /* title */)};
|
||||
|
||||
data.m_routes = {Route(4036206872 /* id */, 4035419440 /* networkId */, "bus" /* routeType */,
|
||||
"Echuca/Moama - Melbourne Via Shepparton" /* title */, "gray" /* color */),
|
||||
Route(4027700598 /* id */, 4035418196 /* networkId */, "ferry" /* routeType */,
|
||||
"Mount Beauty - Melbourne Via Brigh" /* title */, "purple" /* color */),
|
||||
Route(4027700599 /* id */, 4032061671 /* networkId */, "ferry" /* routeType */,
|
||||
"Киевский вокзал - парк Зарядье" /* title */, "purple" /* color */)};
|
||||
|
||||
FrequencyIntervals frequencies;
|
||||
frequencies.AddInterval(TimeInterval(6530334760), 3000);
|
||||
|
||||
Schedule schedule;
|
||||
schedule.SetDefaultFrequency(4060);
|
||||
|
||||
schedule.AddDatesInterval(DatesInterval(75007489), frequencies);
|
||||
|
||||
data.m_lines = {Line(4036598626 /* id */, 4036206872 /* routeId */,
|
||||
ShapeLink(4036591460 /* id */, 0 /* startIndex */, 2690 /* endIndex */), "740G" /* title */,
|
||||
IdList{4036592571, 4036592572, 4036592573, 4036592574, 4036592575, 4036592576}, schedule),
|
||||
Line(4036598627 /* id */, 4036206872 /* routeId */,
|
||||
ShapeLink(4036591461 /* id */, 356 /* startIndex */, 40690 /* endIndex */), "" /* title */,
|
||||
IdList{4027013783, 4027013784, 4027013785, 4027013786, 4027013787, 4027013788, 4027013789,
|
||||
4027013790, 4027013791, 4027013792, 4027013793, 4027013794, 4027013795, 4027013796,
|
||||
4027013797, 4027013798, 4027013799, 4027013800, 4027013801},
|
||||
schedule)};
|
||||
|
||||
data.m_linesMetadata = {LineMetadata(4036598626 /* id */, LineSegmentsOrder{LineSegmentOrder({0, 100}, 0),
|
||||
LineSegmentOrder({100, 205}, 2)}),
|
||||
LineMetadata(4036598627 /* id */, LineSegmentsOrder{})};
|
||||
|
||||
data.m_stops = {
|
||||
Stop(4026990853 /* id */, kInvalidFeatureId /* featureId */, kInvalidOsmId /* osmId */,
|
||||
"CARLOS DIHEL 2500-2598" /* title */, TimeTable{{204, std::vector<TimeInterval>{TimeInterval(11400205248)}}},
|
||||
m2::PointD(-58.57196, -36.82596), {} /* transferIds */),
|
||||
Stop(4026990854 /* id */, kInvalidFeatureId /* featureId */, kInvalidOsmId /* osmId */,
|
||||
"QUIROGA 1901-1999" /* title */, TimeTable{}, m2::PointD(-58.57196, -36.82967), {} /* transferIds */)};
|
||||
|
||||
data.SetStopPedestrianSegments(0 /* stopIdx */,
|
||||
{SingleMwmSegment(981 /* featureId */, 0 /* segmentIdx */, true /* forward */),
|
||||
SingleMwmSegment(91762 /* featureId */, 108 /* segmentIdx */, false /* forward */)});
|
||||
data.SetStopPedestrianSegments(1 /* stopIdx */,
|
||||
{SingleMwmSegment(15000 /* featureId */, 100 /* segmentIdx */, false /* forward */)});
|
||||
|
||||
data.m_gates = {
|
||||
Gate(4034666808 /* id */, kInvalidFeatureId /* featureId */, kInvalidOsmId /* osmId */, true /* entrance */,
|
||||
true /* exit */,
|
||||
std::vector<TimeFromGateToStop>{TimeFromGateToStop(4026990854 /* stopId */, 76 /* timeSeconds */)},
|
||||
m2::PointD(-121.84063, 40.19393)),
|
||||
Gate(4034666809 /* id */, kInvalidFeatureId /* featureId */, kInvalidOsmId /* osmId */, false /* entrance */,
|
||||
true /* exit */,
|
||||
std::vector<TimeFromGateToStop>{TimeFromGateToStop(4026990857 /* stopId */, 0 /* timeSeconds */),
|
||||
TimeFromGateToStop(4026990858 /* stopId */, 0 /* timeSeconds */)},
|
||||
m2::PointD(-58.96030, -36.40335)),
|
||||
Gate(4034666810 /* id */, kInvalidFeatureId /* featureId */, kInvalidOsmId /* osmId */, true /* entrance */,
|
||||
false /* exit */,
|
||||
std::vector<TimeFromGateToStop>{TimeFromGateToStop(4026990889 /* stopId */, 80 /* timeSeconds */),
|
||||
TimeFromGateToStop(4026990890 /* stopId */, 108 /* timeSeconds */),
|
||||
TimeFromGateToStop(4026990891 /* stopId */, 90 /* timeSeconds */)},
|
||||
m2::PointD(-58.96030, -36.40335))};
|
||||
|
||||
data.SetGatePedestrianSegments(1 /* gateIdx */,
|
||||
{SingleMwmSegment(6861 /* featureId */, 15 /* segmentIdx */, true /* forward */),
|
||||
SingleMwmSegment(307920 /* featureId */, 2 /* segmentIdx */, false /* forward */)});
|
||||
|
||||
data.m_edges = {
|
||||
Edge(4036592295 /* stop1Id */, 4036592296 /* stop2Id */, 69 /* weight */, 4036591958 /* lineId */,
|
||||
false /* transfer */, ShapeLink(4036591484 /* shapeId */, 592 /* startIndex */, 609 /* endIndex */)),
|
||||
Edge(4030648032 /* stop1Id */, 4030648073 /* stop2Id */, 40 /* weight */, kInvalidTransitId /* lineId */,
|
||||
true /* transfer */, ShapeLink(kInvalidTransitId /* shapeId */, 0 /* startIndex */, 0 /* endIndex */))};
|
||||
|
||||
data.m_transfers = {Transfer(4029752024 /* id */, m2::PointD(-122.16915, 40.41578) /* point */,
|
||||
IdList{4029751945, 4029752010} /* stopIds */)};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
UNIT_TEST(SerDes_All)
|
||||
{
|
||||
TransitData plan = FillTestTransitData();
|
||||
TransitData fact;
|
||||
SerDesTransit(plan, fact, TransitUseCase::AllData);
|
||||
TestEqual(fact, plan, TransitUseCase::AllData);
|
||||
}
|
||||
|
||||
UNIT_TEST(SerDes_ForRouting)
|
||||
{
|
||||
TransitData plan = FillTestTransitData();
|
||||
TransitData fact;
|
||||
SerDesTransit(plan, fact, TransitUseCase::Routing);
|
||||
TestEqual(fact, plan, TransitUseCase::Routing);
|
||||
}
|
||||
|
||||
UNIT_TEST(SerDes_ForRendering)
|
||||
{
|
||||
TransitData plan = FillTestTransitData();
|
||||
TransitData fact;
|
||||
SerDesTransit(plan, fact, TransitUseCase::Rendering);
|
||||
TestEqual(fact, plan, TransitUseCase::Rendering);
|
||||
}
|
||||
|
||||
UNIT_TEST(SerDes_ForCrossMwm)
|
||||
{
|
||||
TransitData plan = FillTestTransitData();
|
||||
TransitData fact;
|
||||
SerDesTransit(plan, fact, TransitUseCase::CrossMwm);
|
||||
TestEqual(fact, plan, TransitUseCase::CrossMwm);
|
||||
}
|
||||
} // namespace experimental
|
||||
} // namespace transit
|
||||
564
libs/transit/transit_graph_data.cpp
Normal file
564
libs/transit/transit_graph_data.cpp
Normal file
|
|
@ -0,0 +1,564 @@
|
|||
#include "transit/transit_graph_data.hpp"
|
||||
|
||||
#include "transit/transit_serdes.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include <set>
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
using namespace routing;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct ClearVisitor
|
||||
{
|
||||
template <typename Cont>
|
||||
void operator()(Cont & c, char const * /* name */) const
|
||||
{
|
||||
c.clear();
|
||||
}
|
||||
};
|
||||
|
||||
struct SortVisitor
|
||||
{
|
||||
template <typename Cont>
|
||||
void operator()(Cont & c, char const * /* name */) const
|
||||
{
|
||||
sort(c.begin(), c.end());
|
||||
|
||||
auto const end = c.end();
|
||||
auto const it = unique(c.begin(), end, [](auto const & l, auto const & r)
|
||||
{
|
||||
if (l == r)
|
||||
{
|
||||
// Print _right_ element as next equal entry.
|
||||
LOG(LINFO, ("Duplicating entry:", r));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (it != end)
|
||||
{
|
||||
LOG(LINFO, ("Will be deleted", std::distance(it, end), "elements"));
|
||||
c.erase(it, end);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckValidVisitor
|
||||
{
|
||||
template <typename Cont>
|
||||
void operator()(Cont const & c, char const * name)
|
||||
{
|
||||
CheckValid(c, name);
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckUniqueVisitor
|
||||
{
|
||||
template <typename Cont>
|
||||
void operator()(Cont const & c, char const * name)
|
||||
{
|
||||
CheckUnique(c, name);
|
||||
}
|
||||
};
|
||||
|
||||
struct CheckSortedVisitor
|
||||
{
|
||||
template <typename Cont>
|
||||
void operator()(Cont const & c, char const * name)
|
||||
{
|
||||
CheckSorted(c, name);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void Append(vector<T> const & src, vector<T> & dst)
|
||||
{
|
||||
dst.insert(dst.end(), src.begin(), src.end());
|
||||
}
|
||||
|
||||
bool HasStop(vector<Stop> const & stops, StopId stopId)
|
||||
{
|
||||
return binary_search(stops.cbegin(), stops.cend(), Stop(stopId));
|
||||
}
|
||||
|
||||
/// \brief Removes from |items| all items which stop ids is not contained in |stops|.
|
||||
/// \note This method keeps relative order of |items|.
|
||||
template <class Item>
|
||||
void ClipItemsByStops(vector<Stop> const & stops, vector<Item> & items)
|
||||
{
|
||||
CHECK(is_sorted(stops.cbegin(), stops.cend()), ());
|
||||
|
||||
vector<Item> itemsToFill;
|
||||
for (auto const & item : items)
|
||||
{
|
||||
for (auto const stopId : item.GetStopIds())
|
||||
{
|
||||
if (HasStop(stops, stopId))
|
||||
{
|
||||
itemsToFill.push_back(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
items.swap(itemsToFill);
|
||||
}
|
||||
|
||||
/// \returns ref to an item at |items| by |id|.
|
||||
/// \note |items| must be sorted before a call of this method.
|
||||
template <class Id, class Item>
|
||||
Item const & FindById(vector<Item> const & items, Id id)
|
||||
{
|
||||
auto const s1Id = equal_range(items.cbegin(), items.cend(), Item(id));
|
||||
CHECK_EQUAL(distance(s1Id.first, s1Id.second), 1,
|
||||
("An item with id:", id, "is not unique or there's not such item. items:", items));
|
||||
return *s1Id.first;
|
||||
}
|
||||
|
||||
/// \brief Fills |items| with items which have ids from |ids|.
|
||||
/// \note |items| must be sorted before a call of this method.
|
||||
template <class Id, class Item>
|
||||
void UpdateItems(set<Id> const & ids, vector<Item> & items)
|
||||
{
|
||||
vector<Item> itemsToFill;
|
||||
itemsToFill.reserve(ids.size());
|
||||
for (auto const id : ids)
|
||||
itemsToFill.push_back(FindById(items, id));
|
||||
|
||||
SortVisitor{}(itemsToFill, nullptr /* name */);
|
||||
items.swap(itemsToFill);
|
||||
}
|
||||
|
||||
template <class Item>
|
||||
void ReadItems(uint32_t start, uint32_t end, string const & name, NonOwningReaderSource & src, vector<Item> & items)
|
||||
{
|
||||
Deserializer<NonOwningReaderSource> deserializer(src);
|
||||
CHECK_EQUAL(src.Pos(), start, ("Wrong", TRANSIT_FILE_TAG, "section format. Table name:", name));
|
||||
deserializer(items);
|
||||
CHECK_EQUAL(src.Pos(), end, ("Wrong", TRANSIT_FILE_TAG, "section format. Table name:", name));
|
||||
CheckValidSortedUnique(items, name);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// DeserializerFromJson ---------------------------------------------------------------------------
|
||||
DeserializerFromJson::DeserializerFromJson(json_t * node, OsmIdToFeatureIdsMap const & osmIdToFeatureIds)
|
||||
: m_node(node)
|
||||
, m_osmIdToFeatureIds(osmIdToFeatureIds)
|
||||
{}
|
||||
|
||||
void DeserializerFromJson::operator()(m2::PointD & p, char const * name)
|
||||
{
|
||||
// @todo(bykoianko) Instead of having a special operator() method for m2::PointD class it's
|
||||
// necessary to add Point class to transit_types.hpp and process it in DeserializerFromJson with
|
||||
// regular method.
|
||||
json_t * item = nullptr;
|
||||
if (name == nullptr)
|
||||
item = m_node; // Array item case
|
||||
else
|
||||
item = base::GetJSONObligatoryField(m_node, name);
|
||||
|
||||
CHECK(json_is_object(item), ("Item is not a json object:", name));
|
||||
FromJSONObject(item, "x", p.x);
|
||||
FromJSONObject(item, "y", p.y);
|
||||
}
|
||||
|
||||
void DeserializerFromJson::operator()(FeatureIdentifiers & id, char const * name)
|
||||
{
|
||||
// Conversion osm id to feature id.
|
||||
string osmIdStr;
|
||||
GetField(osmIdStr, name);
|
||||
uint64_t osmIdNum;
|
||||
CHECK(strings::to_uint64(osmIdStr, osmIdNum), ("Cann't convert osm id string:", osmIdStr, "to a number."));
|
||||
base::GeoObjectId const osmId(osmIdNum);
|
||||
auto const it = m_osmIdToFeatureIds.find(osmId);
|
||||
if (it != m_osmIdToFeatureIds.cend())
|
||||
{
|
||||
CHECK(!it->second.empty(), ("Osm id:", osmId, "(encoded", osmId.GetEncodedId(),
|
||||
") from transit graph does not correspond to any feature."));
|
||||
if (it->second.size() != 1)
|
||||
{
|
||||
// Note. |osmId| corresponds to several feature ids. It may happen in case of stops,
|
||||
// if a stop is present as a relation. It's a rare case.
|
||||
LOG(LWARNING,
|
||||
("Osm id:", osmId, "( encoded", osmId.GetEncodedId(), ") corresponds to", it->second.size(), "feature ids."));
|
||||
}
|
||||
id.SetFeatureId(it->second[0]);
|
||||
}
|
||||
id.SetOsmId(osmId.GetEncodedId());
|
||||
}
|
||||
|
||||
void DeserializerFromJson::operator()(EdgeFlags & edgeFlags, char const * name)
|
||||
{
|
||||
bool transfer = false;
|
||||
(*this)(transfer, name);
|
||||
// Note. Only |transfer| field of |edgeFlags| may be set at this point because the
|
||||
// other fields of |edgeFlags| are unknown.
|
||||
edgeFlags.SetFlags(0);
|
||||
edgeFlags.m_transfer = transfer;
|
||||
}
|
||||
|
||||
void DeserializerFromJson::operator()(StopIdRanges & rs, char const * name)
|
||||
{
|
||||
vector<StopId> stopIds;
|
||||
(*this)(stopIds, name);
|
||||
rs = StopIdRanges({stopIds});
|
||||
}
|
||||
|
||||
// GraphData --------------------------------------------------------------------------------------
|
||||
void GraphData::DeserializeFromJson(base::Json const & root, OsmIdToFeatureIdsMap const & mapping)
|
||||
{
|
||||
DeserializerFromJson deserializer(root.get(), mapping);
|
||||
Visit(deserializer);
|
||||
|
||||
// Removes equivalent edges from |m_edges|. If there are several equivalent edges only
|
||||
// the most lightweight edge is left.
|
||||
// Note. It's possible that two stops are connected with the same line several times
|
||||
// in the same direction. It happens in Oslo metro (T-banen):
|
||||
// https://en.wikipedia.org/wiki/Oslo_Metro#/media/File:Oslo_Metro_Map.svg branch 5.
|
||||
base::SortUnique(m_edges, [](Edge const & e1, Edge const & e2)
|
||||
{
|
||||
if (e1 != e2)
|
||||
return e1 < e2;
|
||||
return e1.GetWeight() < e2.GetWeight();
|
||||
}, [](Edge const & e1, Edge const & e2) { return e1 == e2; });
|
||||
}
|
||||
|
||||
void GraphData::Serialize(Writer & writer)
|
||||
{
|
||||
auto const startOffset = writer.Pos();
|
||||
Serializer<Writer> serializer(writer);
|
||||
FixedSizeSerializer<Writer> numberSerializer(writer);
|
||||
m_header.Reset();
|
||||
numberSerializer(m_header);
|
||||
|
||||
m_header.m_stopsOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_stops);
|
||||
|
||||
m_header.m_gatesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_gates);
|
||||
|
||||
m_header.m_edgesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_edges);
|
||||
|
||||
m_header.m_transfersOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_transfers);
|
||||
|
||||
m_header.m_linesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_lines);
|
||||
|
||||
m_header.m_shapesOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_shapes);
|
||||
|
||||
m_header.m_networksOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
serializer(m_networks);
|
||||
|
||||
m_header.m_endOffset = base::checked_cast<uint32_t>(writer.Pos() - startOffset);
|
||||
|
||||
// Rewriting header info.
|
||||
CHECK(m_header.IsValid(), (m_header));
|
||||
auto const endOffset = writer.Pos();
|
||||
writer.Seek(startOffset);
|
||||
numberSerializer(m_header);
|
||||
writer.Seek(endOffset);
|
||||
|
||||
LOG(LINFO, (TRANSIT_FILE_TAG, "section is ready. Header:", m_header));
|
||||
}
|
||||
|
||||
void GraphData::DeserializeAll(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
ReadGates(src);
|
||||
ReadEdges(src);
|
||||
ReadTransfers(src);
|
||||
ReadLines(src);
|
||||
ReadShapes(src);
|
||||
ReadNetworks(src);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphData::DeserializeForRouting(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
ReadGates(src);
|
||||
ReadEdges(src);
|
||||
src.Skip(m_header.m_linesOffset - src.Pos());
|
||||
ReadLines(src);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphData::DeserializeForRendering(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
src.Skip(m_header.m_transfersOffset - src.Pos());
|
||||
ReadTransfers(src);
|
||||
ReadLines(src);
|
||||
ReadShapes(src);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphData::DeserializeForCrossMwm(Reader & reader)
|
||||
{
|
||||
DeserializeWith(reader, [this](NonOwningReaderSource & src)
|
||||
{
|
||||
ReadStops(src);
|
||||
src.Skip(m_header.m_edgesOffset - src.Pos());
|
||||
ReadEdges(src);
|
||||
});
|
||||
}
|
||||
|
||||
void GraphData::AppendTo(GraphData const & rhs)
|
||||
{
|
||||
Append(rhs.m_stops, m_stops);
|
||||
Append(rhs.m_gates, m_gates);
|
||||
Append(rhs.m_edges, m_edges);
|
||||
Append(rhs.m_transfers, m_transfers);
|
||||
Append(rhs.m_lines, m_lines);
|
||||
Append(rhs.m_shapes, m_shapes);
|
||||
Append(rhs.m_networks, m_networks);
|
||||
}
|
||||
|
||||
void GraphData::Clear()
|
||||
{
|
||||
ClearVisitor const v{};
|
||||
Visit(v);
|
||||
}
|
||||
|
||||
void GraphData::CheckValidSortedUnique() const
|
||||
{
|
||||
{
|
||||
CheckSortedVisitor v;
|
||||
Visit(v);
|
||||
}
|
||||
|
||||
{
|
||||
CheckUniqueVisitor v;
|
||||
Visit(v);
|
||||
}
|
||||
|
||||
{
|
||||
CheckValidVisitor v;
|
||||
Visit(v);
|
||||
}
|
||||
}
|
||||
|
||||
bool GraphData::IsEmpty() const
|
||||
{
|
||||
// Note. |m_transfers| may be empty if GraphData instance is not empty.
|
||||
return m_stops.empty() || m_gates.empty() || m_edges.empty() || m_lines.empty() || m_shapes.empty() ||
|
||||
m_networks.empty();
|
||||
}
|
||||
|
||||
void GraphData::Sort()
|
||||
{
|
||||
SortVisitor const v{};
|
||||
Visit(v);
|
||||
}
|
||||
|
||||
void GraphData::ClipGraph(vector<m2::RegionD> const & borders)
|
||||
{
|
||||
Sort();
|
||||
CheckValidSortedUnique();
|
||||
ClipLines(borders);
|
||||
ClipStops();
|
||||
ClipNetworks();
|
||||
ClipGates();
|
||||
ClipTransfer();
|
||||
ClipEdges();
|
||||
ClipShapes();
|
||||
CheckValidSortedUnique();
|
||||
}
|
||||
|
||||
void GraphData::SetGateBestPedestrianSegment(size_t gateIdx, SingleMwmSegment const & s)
|
||||
{
|
||||
CHECK_LESS(gateIdx, m_gates.size(), ());
|
||||
m_gates[gateIdx].SetBestPedestrianSegment(s);
|
||||
}
|
||||
|
||||
void GraphData::ClipLines(vector<m2::RegionD> const & borders)
|
||||
{
|
||||
// Set with stop ids with stops which are inside |borders|.
|
||||
set<StopId> stopIdInside;
|
||||
for (auto const & stop : m_stops)
|
||||
if (m2::RegionsContain(borders, stop.GetPoint()))
|
||||
stopIdInside.insert(stop.GetId());
|
||||
|
||||
set<StopId> hasNeighborInside;
|
||||
for (auto const & edge : m_edges)
|
||||
{
|
||||
auto const stop1Inside = stopIdInside.count(edge.GetStop1Id()) != 0;
|
||||
auto const stop2Inside = stopIdInside.count(edge.GetStop2Id()) != 0;
|
||||
if (stop1Inside && !stop2Inside)
|
||||
hasNeighborInside.insert(edge.GetStop2Id());
|
||||
if (stop2Inside && !stop1Inside)
|
||||
hasNeighborInside.insert(edge.GetStop1Id());
|
||||
}
|
||||
|
||||
stopIdInside.insert(hasNeighborInside.cbegin(), hasNeighborInside.cend());
|
||||
|
||||
// Filling |lines| with stops inside |borders|.
|
||||
vector<Line> lines;
|
||||
for (auto const & line : m_lines)
|
||||
{
|
||||
// Note. |stopIdsToFill| will be filled with continuous sequences of stop ids.
|
||||
// In most cases only one sequence of stop ids should be placed to |stopIdsToFill|.
|
||||
// But if a line is split by |borders| several times then several
|
||||
// continuous groups of stop ids will be placed to |stopIdsToFill|.
|
||||
// The loop below goes through all the stop ids belong the line |line| and
|
||||
// keeps in |stopIdsToFill| continuous groups of stop ids which are inside |borders|.
|
||||
Ranges stopIdsToFill;
|
||||
Ranges const & ranges = line.GetStopIds();
|
||||
CHECK_EQUAL(ranges.size(), 1, ());
|
||||
vector<StopId> const & stopIds = ranges[0];
|
||||
auto it = stopIds.begin();
|
||||
while (it != stopIds.end())
|
||||
{
|
||||
while (it != stopIds.end() && stopIdInside.count(*it) == 0)
|
||||
++it;
|
||||
auto jt = it;
|
||||
while (jt != stopIds.end() && stopIdInside.count(*jt) != 0)
|
||||
++jt;
|
||||
if (it != jt)
|
||||
stopIdsToFill.emplace_back(it, jt);
|
||||
it = jt;
|
||||
}
|
||||
|
||||
if (!stopIdsToFill.empty())
|
||||
{
|
||||
lines.emplace_back(line.GetId(), line.GetNumber(), line.GetTitle(), line.GetType(), line.GetColor(),
|
||||
line.GetNetworkId(), stopIdsToFill, line.GetInterval());
|
||||
}
|
||||
}
|
||||
|
||||
m_lines.swap(lines);
|
||||
}
|
||||
|
||||
void GraphData::ClipStops()
|
||||
{
|
||||
CHECK(is_sorted(m_stops.cbegin(), m_stops.cend()), ());
|
||||
set<StopId> stopIds;
|
||||
for (auto const & line : m_lines)
|
||||
for (auto const & range : line.GetStopIds())
|
||||
stopIds.insert(range.cbegin(), range.cend());
|
||||
|
||||
UpdateItems(stopIds, m_stops);
|
||||
}
|
||||
|
||||
void GraphData::ClipNetworks()
|
||||
{
|
||||
CHECK(is_sorted(m_networks.cbegin(), m_networks.cend()), ());
|
||||
set<NetworkId> networkIds;
|
||||
for (auto const & line : m_lines)
|
||||
networkIds.insert(line.GetNetworkId());
|
||||
|
||||
UpdateItems(networkIds, m_networks);
|
||||
}
|
||||
|
||||
void GraphData::ClipGates()
|
||||
{
|
||||
ClipItemsByStops(m_stops, m_gates);
|
||||
}
|
||||
|
||||
void GraphData::ClipTransfer()
|
||||
{
|
||||
ClipItemsByStops(m_stops, m_transfers);
|
||||
}
|
||||
|
||||
void GraphData::ClipEdges()
|
||||
{
|
||||
CHECK(is_sorted(m_stops.cbegin(), m_stops.cend()), ());
|
||||
|
||||
vector<Edge> edges;
|
||||
for (auto const & edge : m_edges)
|
||||
if (HasStop(m_stops, edge.GetStop1Id()) && HasStop(m_stops, edge.GetStop2Id()))
|
||||
edges.push_back(edge);
|
||||
|
||||
SortVisitor{}(edges, nullptr /* name */);
|
||||
m_edges.swap(edges);
|
||||
}
|
||||
|
||||
void GraphData::ClipShapes()
|
||||
{
|
||||
CHECK(is_sorted(m_edges.cbegin(), m_edges.cend()), ());
|
||||
|
||||
// Set with shape ids contained in m_edges.
|
||||
set<ShapeId> shapeIdInEdges;
|
||||
for (auto const & edge : m_edges)
|
||||
{
|
||||
auto const & shapeIds = edge.GetShapeIds();
|
||||
shapeIdInEdges.insert(shapeIds.cbegin(), shapeIds.cend());
|
||||
}
|
||||
|
||||
vector<Shape> shapes;
|
||||
for (auto const & shape : m_shapes)
|
||||
if (shapeIdInEdges.count(shape.GetId()) != 0)
|
||||
shapes.push_back(shape);
|
||||
|
||||
m_shapes.swap(shapes);
|
||||
}
|
||||
|
||||
void GraphData::ReadHeader(NonOwningReaderSource & src)
|
||||
{
|
||||
FixedSizeDeserializer<NonOwningReaderSource> numberDeserializer(src);
|
||||
numberDeserializer(m_header);
|
||||
CHECK_EQUAL(src.Pos(), m_header.m_stopsOffset, ("Wrong", TRANSIT_FILE_TAG, "section format."));
|
||||
CHECK(m_header.IsValid(), ());
|
||||
}
|
||||
|
||||
void GraphData::ReadStops(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_stopsOffset, m_header.m_gatesOffset, "stops", src, m_stops);
|
||||
}
|
||||
|
||||
void GraphData::ReadGates(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_gatesOffset, m_header.m_edgesOffset, "gates", src, m_gates);
|
||||
}
|
||||
|
||||
void GraphData::ReadEdges(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_edgesOffset, m_header.m_transfersOffset, "edges", src, m_edges);
|
||||
}
|
||||
|
||||
void GraphData::ReadTransfers(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_transfersOffset, m_header.m_linesOffset, "transfers", src, m_transfers);
|
||||
}
|
||||
|
||||
void GraphData::ReadLines(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_linesOffset, m_header.m_shapesOffset, "lines", src, m_lines);
|
||||
}
|
||||
|
||||
void GraphData::ReadShapes(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_shapesOffset, m_header.m_networksOffset, "shapes", src, m_shapes);
|
||||
}
|
||||
|
||||
void GraphData::ReadNetworks(NonOwningReaderSource & src)
|
||||
{
|
||||
ReadItems(m_header.m_networksOffset, m_header.m_endOffset, "networks", src, m_networks);
|
||||
}
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
214
libs/transit/transit_graph_data.hpp
Normal file
214
libs/transit/transit_graph_data.hpp
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
#pragma once
|
||||
|
||||
#include "transit/transit_types.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
#include "geometry/region2d.hpp"
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/writer.hpp"
|
||||
|
||||
#include "base/exception.hpp"
|
||||
#include "base/geo_object_id.hpp"
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include "cppjansson/cppjansson.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
using OsmIdToFeatureIdsMap = std::map<base::GeoObjectId, std::vector<FeatureId>>;
|
||||
|
||||
class DeserializerFromJson
|
||||
{
|
||||
public:
|
||||
DeserializerFromJson(json_t * node, OsmIdToFeatureIdsMap const & osmIdToFeatureIds);
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_integral<T>::value || std::is_enum<T>::value || std::is_same<T, double>::value>::type
|
||||
operator()(T & t, char const * name = nullptr)
|
||||
{
|
||||
GetField(t, name);
|
||||
return;
|
||||
}
|
||||
|
||||
void operator()(std::string & s, char const * name = nullptr) { GetField(s, name); }
|
||||
void operator()(m2::PointD & p, char const * name = nullptr);
|
||||
void operator()(FeatureIdentifiers & id, char const * name = nullptr);
|
||||
void operator()(EdgeFlags & edgeFlags, char const * name = nullptr);
|
||||
void operator()(StopIdRanges & rs, char const * name = nullptr);
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_same<T, Edge::WrappedEdgeId>::value ||
|
||||
std::is_same<T, Stop::WrappedStopId>::value>::type
|
||||
operator()(T & t, char const * name = nullptr)
|
||||
{
|
||||
typename T::RepType id;
|
||||
operator()(id, name);
|
||||
t.Set(id);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator()(std::vector<T> & vs, char const * name = nullptr)
|
||||
{
|
||||
auto * arr = base::GetJSONOptionalField(m_node, name);
|
||||
if (arr == nullptr)
|
||||
return;
|
||||
|
||||
if (!json_is_array(arr))
|
||||
MYTHROW(base::Json::Exception, ("The field", name, "must contain a json array."));
|
||||
size_t const sz = json_array_size(arr);
|
||||
vs.resize(sz);
|
||||
for (size_t i = 0; i < sz; ++i)
|
||||
{
|
||||
DeserializerFromJson arrayItem(json_array_get(arr, i), m_osmIdToFeatureIds);
|
||||
arrayItem(vs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
typename std::enable_if<std::is_class<T>::value && !std::is_same<T, Edge::WrappedEdgeId>::value &&
|
||||
!std::is_same<T, Stop::WrappedStopId>::value>::type
|
||||
operator()(T & t, char const * name = nullptr)
|
||||
{
|
||||
if (name != nullptr && json_is_object(m_node))
|
||||
{
|
||||
json_t * dictNode = base::GetJSONOptionalField(m_node, name);
|
||||
if (dictNode == nullptr)
|
||||
return; // No such field in json.
|
||||
|
||||
DeserializerFromJson dict(dictNode, m_osmIdToFeatureIds);
|
||||
t.Visit(dict);
|
||||
return;
|
||||
}
|
||||
|
||||
t.Visit(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
void GetField(T & t, char const * name = nullptr)
|
||||
{
|
||||
if (name == nullptr)
|
||||
{
|
||||
// |name| is not set in case of array items
|
||||
FromJSON(m_node, t);
|
||||
return;
|
||||
}
|
||||
|
||||
json_t * field = base::GetJSONOptionalField(m_node, name);
|
||||
if (field == nullptr)
|
||||
{
|
||||
// No optional field |name| at |m_node|. In that case the default value should be set to |t|.
|
||||
// This default value is set at constructor of corresponding class which is filled with
|
||||
// |DeserializerFromJson|. And the value (|t|) is not changed at this method.
|
||||
return;
|
||||
}
|
||||
FromJSON(field, t);
|
||||
}
|
||||
|
||||
json_t * m_node;
|
||||
OsmIdToFeatureIdsMap const & m_osmIdToFeatureIds;
|
||||
};
|
||||
|
||||
/// \brief The class contains all the information to make TRANSIT_FILE_TAG section.
|
||||
class GraphData
|
||||
{
|
||||
public:
|
||||
void DeserializeFromJson(base::Json const & root, OsmIdToFeatureIdsMap const & mapping);
|
||||
/// \note This method changes only |m_header| and fills it with correct offsets.
|
||||
void Serialize(Writer & writer);
|
||||
void DeserializeAll(Reader & reader);
|
||||
void DeserializeForRouting(Reader & reader);
|
||||
void DeserializeForRendering(Reader & reader);
|
||||
void DeserializeForCrossMwm(Reader & reader);
|
||||
void AppendTo(GraphData const & rhs);
|
||||
void Clear();
|
||||
void CheckValidSortedUnique() const;
|
||||
bool IsEmpty() const;
|
||||
|
||||
/// \brief Sorts all class fields by their ids.
|
||||
void Sort();
|
||||
/// \brief Removes some items from all the class fields if they are outside |borders|.
|
||||
/// Please see description for the other Clip*() method for excact rules of clipping.
|
||||
/// \note Before call of the method every line in |m_stopIds| should contain |m_stopIds|
|
||||
/// with only one stop range.
|
||||
void ClipGraph(std::vector<m2::RegionD> const & borders);
|
||||
void SetGateBestPedestrianSegment(size_t gateIdx, SingleMwmSegment const & s);
|
||||
|
||||
std::vector<Stop> const & GetStops() const { return m_stops; }
|
||||
std::vector<Gate> const & GetGates() const { return m_gates; }
|
||||
std::vector<Edge> const & GetEdges() const { return m_edges; }
|
||||
std::vector<Transfer> const & GetTransfers() const { return m_transfers; }
|
||||
std::vector<Line> const & GetLines() const { return m_lines; }
|
||||
std::vector<Shape> const & GetShapes() const { return m_shapes; }
|
||||
std::vector<Network> const & GetNetworks() const { return m_networks; }
|
||||
|
||||
private:
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(GraphData, visitor(m_stops, "stops"), visitor(m_gates, "gates"),
|
||||
visitor(m_edges, "edges"), visitor(m_transfers, "transfers"),
|
||||
visitor(m_lines, "lines"), visitor(m_shapes, "shapes"),
|
||||
visitor(m_networks, "networks"))
|
||||
|
||||
/// \brief Clipping |m_lines| with |borders|.
|
||||
/// \details After a call of the method the following stop ids in |m_lines| are left:
|
||||
/// * stops inside |borders|
|
||||
/// * stops which are connected with an edge from |m_edges| with stops inside |borders|
|
||||
/// \note Lines without stops are removed from |m_lines|.
|
||||
/// \note Before call of the method every line in |m_lines| should contain |m_stopIds|
|
||||
/// with only one stop range.
|
||||
void ClipLines(std::vector<m2::RegionD> const & borders);
|
||||
/// \brief Removes all stops from |m_stops| which are not contained in |m_lines| at field |m_stopIds|.
|
||||
/// \note Only stops which stop ids contained in |m_lines| will left in |m_stops|
|
||||
/// after call of this method.
|
||||
void ClipStops();
|
||||
/// \brief Removes all networks from |m_networks| which are not contained in |m_lines|
|
||||
/// at field |m_networkId|.
|
||||
void ClipNetworks();
|
||||
/// \brief Removes gates from |m_gates| if there's no stop in |m_stops| with their stop ids.
|
||||
void ClipGates();
|
||||
/// \brief Removes transfers from |m_transfers| if there's no stop in |m_stops| with their stop ids.
|
||||
void ClipTransfer();
|
||||
/// \brief Removes edges from |m_edges| if their ends are not contained in |m_stops|.
|
||||
void ClipEdges();
|
||||
/// \brief Removes all shapes from |m_shapes| which are not reffered form |m_edges|.
|
||||
void ClipShapes();
|
||||
|
||||
/// \brief Read a transit table form |srs|.
|
||||
/// \note Before calling any of the method except for ReadHeader() |m_header| has to be filled.
|
||||
void ReadHeader(NonOwningReaderSource & src);
|
||||
void ReadStops(NonOwningReaderSource & src);
|
||||
void ReadGates(NonOwningReaderSource & src);
|
||||
void ReadEdges(NonOwningReaderSource & src);
|
||||
void ReadTransfers(NonOwningReaderSource & src);
|
||||
void ReadLines(NonOwningReaderSource & src);
|
||||
void ReadShapes(NonOwningReaderSource & src);
|
||||
void ReadNetworks(NonOwningReaderSource & src);
|
||||
|
||||
template <typename Fn>
|
||||
void DeserializeWith(Reader & reader, Fn && fn)
|
||||
{
|
||||
NonOwningReaderSource src(reader);
|
||||
ReadHeader(src);
|
||||
fn(src);
|
||||
}
|
||||
|
||||
TransitHeader m_header;
|
||||
std::vector<Stop> m_stops;
|
||||
std::vector<Gate> m_gates;
|
||||
std::vector<Edge> m_edges;
|
||||
std::vector<Transfer> m_transfers;
|
||||
std::vector<Line> m_lines;
|
||||
std::vector<Shape> m_shapes;
|
||||
std::vector<Network> m_networks;
|
||||
};
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
412
libs/transit/transit_schedule.cpp
Normal file
412
libs/transit/transit_schedule.cpp
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
#include "transit/transit_schedule.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
// Constant which is added to the year value while deserizlizing from the unsigned int.
|
||||
uint32_t constexpr kEpochStartYear = 2020;
|
||||
|
||||
uint32_t constexpr kMask4bits = 0xf;
|
||||
uint32_t constexpr kMask5bits = 0x1f;
|
||||
uint32_t constexpr kMask6bits = 0x3F;
|
||||
|
||||
std::tm ToCalendarTime(time_t const & ts)
|
||||
{
|
||||
std::tm tm;
|
||||
localtime_r(&ts, &tm);
|
||||
return tm;
|
||||
}
|
||||
|
||||
uint8_t GetRawStatus(gtfs::CalendarAvailability const & status)
|
||||
{
|
||||
return status == gtfs::CalendarAvailability::Available ? 1 : 0;
|
||||
}
|
||||
|
||||
uint8_t GetRawStatus(gtfs::CalendarDateException const & status)
|
||||
{
|
||||
return status == gtfs::CalendarDateException::Added ? 1 : 0;
|
||||
}
|
||||
|
||||
enum class DateTimeRelation
|
||||
{
|
||||
// First element is earlier in time, later or is equal.
|
||||
Earlier,
|
||||
Later,
|
||||
Equal
|
||||
};
|
||||
|
||||
DateTimeRelation GetDatesRelation(::transit::Date const & date1, ::transit::Date const & date2)
|
||||
{
|
||||
if (date1.m_year < date2.m_year)
|
||||
return DateTimeRelation::Earlier;
|
||||
if (date1.m_year > date2.m_year)
|
||||
return DateTimeRelation::Later;
|
||||
|
||||
if (date1.m_month < date2.m_month)
|
||||
return DateTimeRelation::Earlier;
|
||||
if (date1.m_month > date2.m_month)
|
||||
return DateTimeRelation::Later;
|
||||
|
||||
if (date1.m_day < date2.m_day)
|
||||
return DateTimeRelation::Earlier;
|
||||
if (date1.m_day > date2.m_day)
|
||||
return DateTimeRelation::Later;
|
||||
|
||||
return DateTimeRelation::Equal;
|
||||
}
|
||||
|
||||
DateTimeRelation GetTimesRelation(::transit::Time const & time1, ::transit::Time const & time2)
|
||||
{
|
||||
if (time1.m_hour < time2.m_hour)
|
||||
return DateTimeRelation::Earlier;
|
||||
if (time1.m_hour > time2.m_hour)
|
||||
return DateTimeRelation::Later;
|
||||
|
||||
if (time1.m_minute < time2.m_minute)
|
||||
return DateTimeRelation::Earlier;
|
||||
if (time1.m_minute > time2.m_minute)
|
||||
return DateTimeRelation::Later;
|
||||
|
||||
if (time1.m_second < time2.m_second)
|
||||
return DateTimeRelation::Earlier;
|
||||
if (time1.m_second > time2.m_second)
|
||||
return DateTimeRelation::Later;
|
||||
|
||||
return DateTimeRelation::Equal;
|
||||
}
|
||||
|
||||
::transit::Date GetDate(std::tm const & tm)
|
||||
{
|
||||
return ::transit::Date(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
|
||||
}
|
||||
|
||||
::transit::Time GetTime(std::tm const & tm)
|
||||
{
|
||||
return ::transit::Time(tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace transit
|
||||
{
|
||||
// Status ------------------------------------------------------------------------------------------
|
||||
std::string DebugPrint(Status const & status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case Status::Open: return "Open";
|
||||
case Status::Closed: return "Closed";
|
||||
case Status::Unknown: return "Unknown";
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
// DatesInterval -----------------------------------------------------------------------------------
|
||||
DatesInterval::DatesInterval(gtfs::CalendarItem const & calendarItem)
|
||||
{
|
||||
uint32_t y1 = 0;
|
||||
uint32_t m1 = 0;
|
||||
uint32_t d1 = 0;
|
||||
uint32_t y2 = 0;
|
||||
uint32_t m2 = 0;
|
||||
uint32_t d2 = 0;
|
||||
|
||||
std::tie(y1, m1, d1) = calendarItem.start_date.get_yyyy_mm_dd();
|
||||
std::tie(y2, m2, d2) = calendarItem.end_date.get_yyyy_mm_dd();
|
||||
|
||||
y1 -= kEpochStartYear;
|
||||
y2 -= kEpochStartYear;
|
||||
|
||||
uint8_t const yDelta = y2 - y1;
|
||||
|
||||
// From high bit to the least significant bit (from 31 to 0):
|
||||
// year1 (4 bits), month1 (4 bits), day1 (5 bits), year delta (3 bits), month2 (4 bits), day2 (5
|
||||
// bits), sunday, monday, ..., saturday - 7 bits, each week day is 1 bit.
|
||||
m_data = y1 << 28 | m1 << 24 | d1 << 19 | yDelta << 16 | m2 << 12 | d2 << 7 | GetRawStatus(calendarItem.sunday) << 6 |
|
||||
GetRawStatus(calendarItem.monday) << 5 | GetRawStatus(calendarItem.tuesday) << 4 |
|
||||
GetRawStatus(calendarItem.wednesday) << 3 | GetRawStatus(calendarItem.thursday) << 2 |
|
||||
GetRawStatus(calendarItem.friday) << 1 | GetRawStatus(calendarItem.saturday);
|
||||
}
|
||||
|
||||
DatesInterval::DatesInterval(uint32_t data) : m_data(data) {}
|
||||
|
||||
std::tuple<Date, Date, WeekSchedule> DatesInterval::Extract() const
|
||||
{
|
||||
Date date1;
|
||||
Date date2;
|
||||
|
||||
WeekSchedule week;
|
||||
|
||||
static uint32_t constexpr mask3bits = 0x7;
|
||||
|
||||
date1.m_year = (m_data >> 28) + kEpochStartYear;
|
||||
date1.m_month = (m_data >> 24) & kMask4bits;
|
||||
date1.m_day = (m_data >> 19) & kMask5bits;
|
||||
uint8_t yDelta = (m_data >> 16) & mask3bits;
|
||||
date2.m_year = date1.m_year + yDelta;
|
||||
date2.m_month = (m_data >> 12) & kMask4bits;
|
||||
date2.m_day = (m_data >> 7) & kMask5bits;
|
||||
|
||||
week[WeekDays::Sunday] = m_data & 0x40;
|
||||
week[WeekDays::Monday] = m_data & 0x20;
|
||||
week[WeekDays::Tuesday] = m_data & 0x10;
|
||||
week[WeekDays::Wednesday] = m_data & 0x8;
|
||||
week[WeekDays::Thursday] = m_data & 0x4;
|
||||
week[WeekDays::Friday] = m_data & 0x2;
|
||||
week[WeekDays::Saturday] = m_data & 0x1;
|
||||
|
||||
return {date1, date2, week};
|
||||
}
|
||||
|
||||
Status DatesInterval::GetStatusInInterval(Date const & date, uint8_t wdIndex) const
|
||||
{
|
||||
auto const & [date1, date2, wd] = Extract();
|
||||
|
||||
if (GetDatesRelation(date, date1) != DateTimeRelation::Earlier &&
|
||||
GetDatesRelation(date, date2) != DateTimeRelation::Later)
|
||||
{
|
||||
return wd[wdIndex] ? Status::Open : Status::Closed;
|
||||
}
|
||||
|
||||
return Status::Closed;
|
||||
}
|
||||
|
||||
bool DatesInterval::operator==(DatesInterval const & rhs) const
|
||||
{
|
||||
return m_data == rhs.m_data;
|
||||
}
|
||||
|
||||
// DateException -----------------------------------------------------------------------------------
|
||||
DateException::DateException(gtfs::Date const & date, gtfs::CalendarDateException const & dateException)
|
||||
{
|
||||
uint32_t y = 0;
|
||||
uint32_t m = 0;
|
||||
uint32_t d = 0;
|
||||
|
||||
std::tie(y, m, d) = date.get_yyyy_mm_dd();
|
||||
|
||||
y -= kEpochStartYear;
|
||||
|
||||
// From high bit - 1 to the least significant bit (from 14 to 0):
|
||||
// year1 (4 bits), month1 (4 bits), day1 (5 bits), exctption statys (1 bit).
|
||||
m_data = y << 10 | m << 6 | d << 1 | GetRawStatus(dateException);
|
||||
}
|
||||
|
||||
DateException::DateException(uint16_t data) : m_data(data) {}
|
||||
|
||||
bool DateException::operator==(DateException const & rhs) const
|
||||
{
|
||||
return m_data == rhs.m_data;
|
||||
}
|
||||
|
||||
std::tuple<Date, bool> DateException::Extract() const
|
||||
{
|
||||
Date date;
|
||||
|
||||
date.m_year = (m_data >> 10) + kEpochStartYear;
|
||||
date.m_month = (m_data >> 6) & kMask4bits;
|
||||
date.m_day = (m_data >> 1) & kMask5bits;
|
||||
bool const isOpen = m_data & 0x1;
|
||||
|
||||
return {date, isOpen};
|
||||
}
|
||||
|
||||
Status DateException::GetExceptionStatus(Date const & date) const
|
||||
{
|
||||
auto const & [dateExc, isOpen] = Extract();
|
||||
|
||||
if (GetDatesRelation(date, dateExc) == DateTimeRelation::Equal)
|
||||
return isOpen ? Status::Open : Status::Closed;
|
||||
|
||||
return Status::Unknown;
|
||||
}
|
||||
|
||||
// TimeInterval ------------------------------------------------------------------------------------
|
||||
TimeInterval::TimeInterval(gtfs::Time const & startTime, gtfs::Time const & endTime)
|
||||
{
|
||||
uint64_t h1 = 0;
|
||||
uint64_t m1 = 0;
|
||||
uint64_t s1 = 0;
|
||||
|
||||
uint64_t h2 = 0;
|
||||
uint64_t m2 = 0;
|
||||
uint64_t s2 = 0;
|
||||
|
||||
std::tie(h1, m1, s1) = startTime.get_hh_mm_ss();
|
||||
std::tie(h2, m2, s2) = endTime.get_hh_mm_ss();
|
||||
|
||||
// From 33 bit to 0 bit:
|
||||
// hour1 (5 bits), minute1 (6 bits), second1 (6 bits), hour2 (5 bits), minute2 (6 bits), second2
|
||||
// (6 bits).
|
||||
m_data = h1 << 29 | m1 << 23 | s1 << 17 | h2 << 12 | m2 << 6 | s2;
|
||||
}
|
||||
|
||||
TimeInterval::TimeInterval(uint64_t data) : m_data(data) {}
|
||||
|
||||
bool TimeInterval::operator<(TimeInterval const & rhs) const
|
||||
{
|
||||
return m_data < rhs.m_data;
|
||||
}
|
||||
|
||||
bool TimeInterval::operator==(TimeInterval const & rhs) const
|
||||
{
|
||||
return m_data == rhs.m_data;
|
||||
}
|
||||
|
||||
std::pair<Time, Time> TimeInterval::Extract() const
|
||||
{
|
||||
Time startTime;
|
||||
Time endTime;
|
||||
|
||||
startTime.m_hour = (m_data >> 29) & kMask5bits;
|
||||
startTime.m_minute = (m_data >> 23) & kMask6bits;
|
||||
startTime.m_second = (m_data >> 17) & kMask6bits;
|
||||
|
||||
endTime.m_hour = (m_data >> 12) & kMask5bits;
|
||||
endTime.m_minute = (m_data >> 6) & kMask6bits;
|
||||
endTime.m_second = m_data & kMask6bits;
|
||||
|
||||
return {startTime, endTime};
|
||||
}
|
||||
|
||||
Status TimeInterval::GetTimeStatus(Time const & time) const
|
||||
{
|
||||
auto const & [startTime, endTime] = Extract();
|
||||
if (GetTimesRelation(time, startTime) != DateTimeRelation::Earlier &&
|
||||
GetTimesRelation(time, endTime) != DateTimeRelation::Later)
|
||||
{
|
||||
return Status::Open;
|
||||
}
|
||||
|
||||
return Status::Closed;
|
||||
}
|
||||
|
||||
// FrequencyIntervals ------------------------------------------------------------------------------
|
||||
FrequencyIntervals::FrequencyIntervals(gtfs::Frequencies const & frequencies)
|
||||
{
|
||||
for (auto const & freq : frequencies)
|
||||
if (freq.headway_secs > 0)
|
||||
m_intervals.emplace(TimeInterval(freq.start_time, freq.end_time), freq.headway_secs);
|
||||
else
|
||||
LOG(LINFO, ("Bad headway_secs:", freq.headway_secs));
|
||||
}
|
||||
|
||||
bool FrequencyIntervals::operator==(FrequencyIntervals const & rhs) const
|
||||
{
|
||||
return m_intervals == rhs.m_intervals;
|
||||
}
|
||||
|
||||
void FrequencyIntervals::AddInterval(TimeInterval const & timeInterval, Frequency frequency)
|
||||
{
|
||||
m_intervals[timeInterval] = frequency;
|
||||
}
|
||||
|
||||
Frequency FrequencyIntervals::GetFrequency(Time const & time) const
|
||||
{
|
||||
for (auto const & [interval, freq] : m_intervals)
|
||||
if (interval.GetTimeStatus(time) == Status::Open)
|
||||
return freq;
|
||||
|
||||
return kDefaultFrequency;
|
||||
}
|
||||
|
||||
std::map<TimeInterval, Frequency> const & FrequencyIntervals::GetFrequencies() const
|
||||
{
|
||||
return m_intervals;
|
||||
}
|
||||
|
||||
// Schedule ----------------------------------------------------------------------------------------
|
||||
bool Schedule::operator==(Schedule const & rhs) const
|
||||
{
|
||||
return m_serviceIntervals == rhs.m_serviceIntervals && m_serviceExceptions == rhs.m_serviceExceptions &&
|
||||
m_defaultFrequency == rhs.m_defaultFrequency;
|
||||
}
|
||||
|
||||
void Schedule::AddDatesInterval(gtfs::CalendarItem const & calendarItem, gtfs::Frequencies const & frequencies)
|
||||
{
|
||||
m_serviceIntervals.emplace(DatesInterval(calendarItem), FrequencyIntervals(frequencies));
|
||||
}
|
||||
|
||||
void Schedule::AddDateException(gtfs::Date const & date, gtfs::CalendarDateException const & dateException,
|
||||
gtfs::Frequencies const & frequencies)
|
||||
{
|
||||
m_serviceExceptions.emplace(DateException(date, dateException), FrequencyIntervals(frequencies));
|
||||
}
|
||||
|
||||
DatesIntervals const & Schedule::GetServiceIntervals() const
|
||||
{
|
||||
return m_serviceIntervals;
|
||||
}
|
||||
|
||||
DatesExceptions const & Schedule::GetServiceExceptions() const
|
||||
{
|
||||
return m_serviceExceptions;
|
||||
}
|
||||
|
||||
void Schedule::AddDatesInterval(DatesInterval const & interval, FrequencyIntervals const & frequencies)
|
||||
{
|
||||
m_serviceIntervals[interval] = frequencies;
|
||||
}
|
||||
|
||||
void Schedule::AddDateException(DateException const & dateException, FrequencyIntervals const & frequencies)
|
||||
{
|
||||
m_serviceExceptions[dateException] = frequencies;
|
||||
}
|
||||
|
||||
Status Schedule::GetStatus(time_t const & time) const
|
||||
{
|
||||
auto const & [date, wdIndex] = GetDateAndWeekIndex(time);
|
||||
|
||||
for (auto const & [dateException, freq] : m_serviceExceptions)
|
||||
{
|
||||
Status const & status = dateException.GetExceptionStatus(date);
|
||||
|
||||
if (status != Status::Unknown)
|
||||
return status;
|
||||
}
|
||||
|
||||
Status res = Status::Unknown;
|
||||
|
||||
for (auto const & [datesInterval, freq] : m_serviceIntervals)
|
||||
{
|
||||
Status const & status = datesInterval.GetStatusInInterval(date, wdIndex);
|
||||
|
||||
if (status != Status::Unknown)
|
||||
res = status;
|
||||
|
||||
if (res == Status::Open)
|
||||
return res;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Frequency Schedule::GetFrequency(time_t const & time) const
|
||||
{
|
||||
auto const & [date, timeHms, wdIndex] = GetDateTimeAndWeekIndex(time);
|
||||
|
||||
for (auto const & [dateException, freqInts] : m_serviceExceptions)
|
||||
if (dateException.GetExceptionStatus(date) == Status::Open)
|
||||
return freqInts.GetFrequency(timeHms);
|
||||
|
||||
for (auto const & [datesInterval, freqInts] : m_serviceIntervals)
|
||||
if (datesInterval.GetStatusInInterval(date, wdIndex) == Status::Open)
|
||||
return freqInts.GetFrequency(timeHms);
|
||||
|
||||
LOG(LWARNING, ("No frequency for date", date, "time", timeHms));
|
||||
return m_defaultFrequency;
|
||||
}
|
||||
|
||||
std::pair<Date, uint8_t> Schedule::GetDateAndWeekIndex(time_t const & time) const
|
||||
{
|
||||
std::tm const tm = ToCalendarTime(time);
|
||||
return {GetDate(tm), tm.tm_wday};
|
||||
}
|
||||
|
||||
std::tuple<Date, Time, uint8_t> Schedule::GetDateTimeAndWeekIndex(time_t const & time) const
|
||||
{
|
||||
std::tm const tm = ToCalendarTime(time);
|
||||
return {GetDate(tm), GetTime(tm), tm.tm_wday};
|
||||
}
|
||||
} // namespace transit
|
||||
252
libs/transit/transit_schedule.hpp
Normal file
252
libs/transit/transit_schedule.hpp
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
#pragma once
|
||||
|
||||
#include "base/macros.hpp"
|
||||
#include "base/newtype.hpp"
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/just_gtfs/just_gtfs.h"
|
||||
|
||||
// This is the implementation of the Schedule class and its helpers for handling transit service
|
||||
// days and stops timetables.
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
template <class Sink>
|
||||
class Serializer;
|
||||
template <class Source>
|
||||
class Deserializer;
|
||||
template <typename Sink>
|
||||
class FixedSizeSerializer;
|
||||
template <typename Sink>
|
||||
class FixedSizeDeserializer;
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
|
||||
namespace transit
|
||||
{
|
||||
#define DECLARE_SCHEDULE_TYPES_FRIENDS \
|
||||
template <class Sink> \
|
||||
friend class routing::transit::Serializer; \
|
||||
template <class Source> \
|
||||
friend class routing::transit::Deserializer; \
|
||||
template <typename Sink> \
|
||||
friend class routing::transit::FixedSizeSerializer; \
|
||||
template <typename Sink> \
|
||||
friend class routing::transit::FixedSizeDeserializer;
|
||||
|
||||
// Status of some transit itinerary (e.g. line) in the moment of time.
|
||||
enum class Status : uint8_t
|
||||
{
|
||||
Open,
|
||||
Closed,
|
||||
Unknown
|
||||
};
|
||||
|
||||
std::string DebugPrint(Status const & status);
|
||||
|
||||
struct Date
|
||||
{
|
||||
Date() = default;
|
||||
Date(uint32_t year, uint8_t month, uint16_t day) : m_year(year), m_month(month), m_day(day) {}
|
||||
|
||||
DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Date, visitor(m_year, "y"), visitor(m_month, "m"), visitor(m_day, "d"))
|
||||
|
||||
uint32_t m_year = 0;
|
||||
uint8_t m_month = 0;
|
||||
uint16_t m_day = 0;
|
||||
};
|
||||
|
||||
struct Time
|
||||
{
|
||||
Time() = default;
|
||||
Time(uint8_t hour, uint8_t minute, uint8_t second) : m_hour(hour), m_minute(minute), m_second(second) {}
|
||||
|
||||
DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Time, visitor(m_hour, "h"), visitor(m_minute, "m"), visitor(m_second, "s"))
|
||||
|
||||
uint8_t m_hour = 0;
|
||||
uint8_t m_minute = 0;
|
||||
uint8_t m_second = 0;
|
||||
};
|
||||
|
||||
using WeekSchedule = std::array<bool, 7>;
|
||||
|
||||
enum WeekDays
|
||||
{
|
||||
Sunday = 0,
|
||||
Monday,
|
||||
Tuesday,
|
||||
Wednesday,
|
||||
Thursday,
|
||||
Friday,
|
||||
Saturday
|
||||
};
|
||||
|
||||
// Service dates specified using a weekly schedule with start and end dates. Dates range is
|
||||
// specified by the start_date and end_date fields in the GTFS calendar.txt.
|
||||
// Dates interval and open/closed states for week days are stored |m_data|.
|
||||
class DatesInterval
|
||||
{
|
||||
public:
|
||||
DatesInterval() = default;
|
||||
explicit DatesInterval(gtfs::CalendarItem const & calendarItem);
|
||||
explicit DatesInterval(uint32_t data);
|
||||
|
||||
bool operator==(DatesInterval const & rhs) const;
|
||||
|
||||
std::tuple<Date, Date, WeekSchedule> Extract() const;
|
||||
uint32_t GetRaw() const { return m_data; }
|
||||
|
||||
Status GetStatusInInterval(Date const & date, uint8_t wdIndex) const;
|
||||
|
||||
private:
|
||||
DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(DatesInterval, visitor(m_data, "m_data"))
|
||||
|
||||
// From greater indexes to smaller: year1, month1, day1, years delta (year2 - year1), month2,
|
||||
// day2, weekdays. Week indexes start from sunday = 0.
|
||||
uint32_t m_data = 0;
|
||||
};
|
||||
|
||||
struct DatesIntervalHasher
|
||||
{
|
||||
size_t operator()(DatesInterval const & key) const { return key.GetRaw(); }
|
||||
};
|
||||
|
||||
// Exceptions for the services defined in the calendar.txt. If calendar.txt is not used, it may
|
||||
// contain all dates of service. Exception date and its type (added/removed) are stored in |m_data|.
|
||||
class DateException
|
||||
{
|
||||
public:
|
||||
DateException() = default;
|
||||
DateException(gtfs::Date const & date, gtfs::CalendarDateException const & dateException);
|
||||
explicit DateException(uint16_t data);
|
||||
|
||||
bool operator==(DateException const & rhs) const;
|
||||
|
||||
std::tuple<Date, bool> Extract() const;
|
||||
uint32_t GetRaw() const { return m_data; }
|
||||
|
||||
Status GetExceptionStatus(Date const & date) const;
|
||||
|
||||
private:
|
||||
DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(DateException, visitor(m_data, "m_data"))
|
||||
|
||||
// From greater indexes to smaller: year, month, day, status
|
||||
uint16_t m_data = 0;
|
||||
};
|
||||
|
||||
struct DateExceptionHasher
|
||||
{
|
||||
size_t operator()(DateException const & key) const { return key.GetRaw(); }
|
||||
};
|
||||
|
||||
// Time range for lines frequencies or stop timetables. Start time and end time are stored in
|
||||
// m_data.
|
||||
class TimeInterval
|
||||
{
|
||||
public:
|
||||
TimeInterval() = default;
|
||||
TimeInterval(gtfs::Time const & startTime, gtfs::Time const & endTime);
|
||||
explicit TimeInterval(uint64_t data);
|
||||
|
||||
bool operator<(TimeInterval const & rhs) const;
|
||||
bool operator==(TimeInterval const & rhs) const;
|
||||
|
||||
std::pair<Time, Time> Extract() const;
|
||||
uint64_t GetRaw() const { return m_data; }
|
||||
|
||||
Status GetTimeStatus(Time const & time) const;
|
||||
|
||||
private:
|
||||
DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(TimeInterval, visitor(m_data, "m_data"))
|
||||
|
||||
// From greater indexes to smaller: start time, end time.
|
||||
uint64_t m_data = 0;
|
||||
};
|
||||
|
||||
using Frequency = uint32_t;
|
||||
inline Frequency constexpr kDefaultFrequency = 0;
|
||||
|
||||
// Headway (interval between times that a vehicle arrives at and departs from stops) for
|
||||
// headway-based service or a compressed representation of fixed-schedule service. For each time
|
||||
// range there is a frequency value.
|
||||
class FrequencyIntervals
|
||||
{
|
||||
public:
|
||||
FrequencyIntervals() = default;
|
||||
FrequencyIntervals(gtfs::Frequencies const & frequencies);
|
||||
|
||||
bool operator==(FrequencyIntervals const & rhs) const;
|
||||
|
||||
void AddInterval(TimeInterval const & timeInterval, Frequency frequency);
|
||||
|
||||
Frequency GetFrequency(Time const & time) const;
|
||||
std::map<TimeInterval, Frequency> const & GetFrequencies() const;
|
||||
|
||||
private:
|
||||
DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(FrequencyIntervals, visitor(m_intervals, "m_intervals"))
|
||||
|
||||
std::map<TimeInterval, Frequency> m_intervals;
|
||||
};
|
||||
|
||||
using DatesIntervals = std::unordered_map<DatesInterval, FrequencyIntervals, DatesIntervalHasher>;
|
||||
using DatesExceptions = std::unordered_map<DateException, FrequencyIntervals, DateExceptionHasher>;
|
||||
|
||||
// Line schedule with line service days (as DatesInterval ranges) and exceptions in service days
|
||||
// (as DateException items). For each date there are frequency intervals (time ranges with headway
|
||||
// in seconds). This schedule is useful while building transit routes based on particular route
|
||||
// start time.
|
||||
class Schedule
|
||||
{
|
||||
public:
|
||||
bool operator==(Schedule const & rhs) const;
|
||||
|
||||
void AddDatesInterval(gtfs::CalendarItem const & calendarItem, gtfs::Frequencies const & frequencies);
|
||||
void AddDateException(gtfs::Date const & date, gtfs::CalendarDateException const & dateException,
|
||||
gtfs::Frequencies const & frequencies);
|
||||
|
||||
Status GetStatus(time_t const & time) const;
|
||||
Frequency GetFrequency(time_t const & time) const;
|
||||
Frequency GetFrequency() const { return m_defaultFrequency; }
|
||||
|
||||
DatesIntervals const & GetServiceIntervals() const;
|
||||
DatesExceptions const & GetServiceExceptions() const;
|
||||
|
||||
void AddDatesInterval(DatesInterval const & interval, FrequencyIntervals const & frequencies);
|
||||
void AddDateException(DateException const & dateException, FrequencyIntervals const & frequencies);
|
||||
|
||||
void SetDefaultFrequency(Frequency const & frequency) { m_defaultFrequency = frequency; }
|
||||
|
||||
private:
|
||||
std::pair<Date, uint8_t> GetDateAndWeekIndex(time_t const & time) const;
|
||||
std::tuple<Date, Time, uint8_t> GetDateTimeAndWeekIndex(time_t const & time) const;
|
||||
|
||||
DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Schedule, visitor(m_serviceIntervals, "m_serviceIntervals"),
|
||||
visitor(m_serviceExceptions, "m_serviceExceptions"),
|
||||
visitor(m_defaultFrequency, "m_defaultFrequency"))
|
||||
|
||||
DatesIntervals m_serviceIntervals;
|
||||
DatesExceptions m_serviceExceptions;
|
||||
|
||||
Frequency m_defaultFrequency = kDefaultFrequency;
|
||||
};
|
||||
|
||||
#undef DECLARE_SCHEDULE_TYPES_FRIENDS
|
||||
} // namespace transit
|
||||
439
libs/transit/transit_serdes.hpp
Normal file
439
libs/transit/transit_serdes.hpp
Normal file
|
|
@ -0,0 +1,439 @@
|
|||
#pragma once
|
||||
|
||||
#include "transit/experimental/transit_types_experimental.hpp"
|
||||
#include "transit/transit_types.hpp"
|
||||
|
||||
#include "coding/geometry_coding.hpp"
|
||||
#include "coding/point_coding.hpp"
|
||||
#include "coding/read_write_utils.hpp"
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/varint.hpp"
|
||||
#include "coding/write_to_sink.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/newtype.hpp"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/opening_hours/opening_hours.hpp"
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
// Note. For the time being double in transit section is used only for saving weight of edges (in seconds).
|
||||
// Let us assume that it takes less than 10^7 seconds (115 days) to get from one station to a neighboring one.
|
||||
double constexpr kMinDoubleAtTransitSection = kInvalidWeight;
|
||||
double constexpr kMaxDoubleAtTransitSection = 10000000.0;
|
||||
uint8_t constexpr kDoubleBits = 32;
|
||||
|
||||
template <typename Sink>
|
||||
class Serializer
|
||||
{
|
||||
public:
|
||||
explicit Serializer(Sink & sink) : m_sink(sink) {}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<(std::is_integral<T>::value || std::is_enum<T>::value) && !std::is_same<T, uint32_t>::value &&
|
||||
!std::is_same<T, uint64_t>::value && !std::is_same<T, int32_t>::value &&
|
||||
!std::is_same<T, int64_t>::value>
|
||||
operator()(T const & t, char const * /* name */ = nullptr)
|
||||
{
|
||||
WriteToSink(m_sink, t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_same<T, uint32_t>::value || std::is_same<T, uint64_t>::value> operator()(
|
||||
T t, char const * /* name */ = nullptr) const
|
||||
{
|
||||
WriteVarUint(m_sink, t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_same<T, int32_t>::value || std::is_same<T, int64_t>::value> operator()(
|
||||
T t, char const * name = nullptr) const
|
||||
{
|
||||
WriteVarInt(m_sink, t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_same<T, double>::value || std::is_same<T, float>::value> operator()(
|
||||
T d, char const * name = nullptr)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(d, kMinDoubleAtTransitSection, ());
|
||||
CHECK_LESS_OR_EQUAL(d, kMaxDoubleAtTransitSection, ());
|
||||
(*this)(DoubleToUint32(d, kMinDoubleAtTransitSection, kMaxDoubleAtTransitSection, kDoubleBits), name);
|
||||
}
|
||||
|
||||
void operator()(std::string const & s, char const * /* name */ = nullptr) { rw::Write(m_sink, s); }
|
||||
|
||||
void operator()(m2::PointD const & p, char const * /* name */ = nullptr)
|
||||
{
|
||||
WriteVarInt(m_sink, PointToInt64Obsolete(p, kPointCoordBits));
|
||||
}
|
||||
|
||||
void operator()(std::vector<m2::PointD> const & vs, char const * /* name */ = nullptr)
|
||||
{
|
||||
WriteVarUint(m_sink, static_cast<uint64_t>(vs.size()));
|
||||
m2::PointU lastEncodedPoint(0, 0);
|
||||
for (auto const & p : vs)
|
||||
{
|
||||
m2::PointU const pointU = PointDToPointU(p, kPointCoordBits);
|
||||
WriteVarUint(m_sink, coding::EncodePointDeltaAsUint(pointU, lastEncodedPoint));
|
||||
lastEncodedPoint = pointU;
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(Edge::WrappedEdgeId const & id, char const * /* name */ = nullptr)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(id.Get(), m_lastWrappedEdgeId.Get(), ());
|
||||
WriteVarUint(m_sink, static_cast<uint64_t>(id.Get() - m_lastWrappedEdgeId.Get()));
|
||||
m_lastWrappedEdgeId = id;
|
||||
}
|
||||
|
||||
void operator()(Stop::WrappedStopId const & id, char const * /* name */ = nullptr)
|
||||
{
|
||||
CHECK_GREATER_OR_EQUAL(id.Get(), m_lastWrappedStopId.Get(), ());
|
||||
WriteVarUint(m_sink, static_cast<uint64_t>(id.Get() - m_lastWrappedStopId.Get()));
|
||||
m_lastWrappedStopId = id;
|
||||
}
|
||||
|
||||
void operator()(FeatureIdentifiers const & id, char const * name = nullptr)
|
||||
{
|
||||
if (id.IsSerializeFeatureIdOnly())
|
||||
(*this)(id.GetFeatureId(), name);
|
||||
else
|
||||
id.Visit(*this);
|
||||
}
|
||||
|
||||
void operator()(::transit::experimental::IdBundle const & id, char const * name = nullptr)
|
||||
{
|
||||
if (id.SerializeFeatureIdOnly())
|
||||
(*this)(id.GetFeatureId(), name);
|
||||
else
|
||||
id.Visit(*this);
|
||||
}
|
||||
|
||||
void operator()(osmoh::OpeningHours const & oh, char const * /* name */ = nullptr) { (*this)(ToString(oh)); }
|
||||
|
||||
void operator()(Edge const & e, char const * /* name */ = nullptr)
|
||||
{
|
||||
(*this)(e.m_stop1Id);
|
||||
(*this)(e.m_stop2Id);
|
||||
(*this)(e.m_weight);
|
||||
(*this)(e.m_lineId);
|
||||
// Note. |Edge::m_flags| is not filled fully after deserialization from json.
|
||||
// So it's necessary to fill it here.
|
||||
EdgeFlags const flags = GetEdgeFlags(e.GetTransfer(), e.GetStop1Id(), e.GetStop2Id(), e.GetShapeIds());
|
||||
(*this)(flags);
|
||||
|
||||
if (flags.m_isShapeIdsEmpty || flags.m_isShapeIdsSame || flags.m_isShapeIdsReversed)
|
||||
return;
|
||||
|
||||
if (flags.m_isShapeIdsSingle)
|
||||
{
|
||||
CHECK_EQUAL(e.GetShapeIds().size(), 1, ());
|
||||
(*this)(e.GetShapeIds()[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
(*this)(e.GetShapeIds());
|
||||
}
|
||||
|
||||
void operator()(EdgeFlags const & f, char const * /* name */ = nullptr) { (*this)(f.GetFlags()); }
|
||||
|
||||
template <typename T>
|
||||
void operator()(std::vector<T> const & vs, char const * /* name */ = nullptr)
|
||||
{
|
||||
WriteVarUint(m_sink, static_cast<uint64_t>(vs.size()));
|
||||
for (auto const & v : vs)
|
||||
(*this)(v);
|
||||
}
|
||||
|
||||
template <class K, class V, class H>
|
||||
void operator()(std::unordered_map<K, V, H> const & container, char const * /* name */ = nullptr)
|
||||
{
|
||||
WriteVarUint(m_sink, static_cast<uint64_t>(container.size()));
|
||||
for (auto const & [key, val] : container)
|
||||
{
|
||||
(*this)(key);
|
||||
(*this)(val);
|
||||
}
|
||||
}
|
||||
|
||||
template <class K, class V, class H>
|
||||
void operator()(std::map<K, V, H> const & container, char const * /* name */ = nullptr)
|
||||
{
|
||||
WriteVarUint(m_sink, static_cast<uint64_t>(container.size()));
|
||||
for (auto const & [key, val] : container)
|
||||
{
|
||||
(*this)(key);
|
||||
(*this)(val);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_class<T>::value> operator()(T const & t, char const * /* name */ = nullptr)
|
||||
{
|
||||
t.Visit(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
Sink & m_sink;
|
||||
Edge::WrappedEdgeId m_lastWrappedEdgeId = {};
|
||||
Stop::WrappedStopId m_lastWrappedStopId = {};
|
||||
};
|
||||
|
||||
template <typename Source>
|
||||
class Deserializer
|
||||
{
|
||||
public:
|
||||
explicit Deserializer(Source & source) : m_source(source) {}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<(std::is_integral<T>::value || std::is_enum<T>::value) && !std::is_same<T, uint32_t>::value &&
|
||||
!std::is_same<T, uint64_t>::value && !std::is_same<T, int32_t>::value &&
|
||||
!std::is_same<T, int64_t>::value>
|
||||
operator()(T & t, char const * name = nullptr)
|
||||
{
|
||||
ReadPrimitiveFromSource(m_source, t);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_same<T, uint32_t>::value || std::is_same<T, uint64_t>::value> operator()(
|
||||
T & t, char const * name = nullptr)
|
||||
{
|
||||
t = ReadVarUint<T>(m_source);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_same<T, int32_t>::value || std::is_same<T, int64_t>::value> operator()(
|
||||
T & t, char const * name = nullptr)
|
||||
{
|
||||
t = ReadVarInt<T>(m_source);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_same<T, double>::value || std::is_same<T, float>::value> operator()(
|
||||
T & d, char const * name = nullptr)
|
||||
{
|
||||
uint32_t ui;
|
||||
(*this)(ui, name);
|
||||
d = Uint32ToDouble(ui, kMinDoubleAtTransitSection, kMaxDoubleAtTransitSection, kDoubleBits);
|
||||
}
|
||||
|
||||
void operator()(std::string & s, char const * /* name */ = nullptr) { rw::Read(m_source, s); }
|
||||
|
||||
void operator()(m2::PointD & p, char const * /* name */ = nullptr)
|
||||
{
|
||||
p = Int64ToPointObsolete(ReadVarInt<int64_t>(m_source), kPointCoordBits);
|
||||
}
|
||||
|
||||
void operator()(Edge::WrappedEdgeId & id, char const * /* name */ = nullptr)
|
||||
{
|
||||
id = m_lastWrappedEdgeId + Edge::WrappedEdgeId(ReadVarUint<uint64_t>(m_source));
|
||||
m_lastWrappedEdgeId = id;
|
||||
}
|
||||
|
||||
void operator()(Stop::WrappedStopId & id, char const * /* name */ = nullptr)
|
||||
{
|
||||
id = m_lastWrappedStopId + Stop::WrappedStopId(ReadVarUint<uint64_t>(m_source));
|
||||
m_lastWrappedStopId = id;
|
||||
}
|
||||
|
||||
void operator()(FeatureIdentifiers & id, char const * name = nullptr)
|
||||
{
|
||||
if (id.IsSerializeFeatureIdOnly())
|
||||
{
|
||||
FeatureId featureId;
|
||||
operator()(featureId, name);
|
||||
id.SetOsmId(kInvalidOsmId);
|
||||
id.SetFeatureId(featureId);
|
||||
return;
|
||||
}
|
||||
|
||||
id.Visit(*this);
|
||||
}
|
||||
|
||||
void operator()(::transit::experimental::IdBundle & idBundle, char const * name = nullptr)
|
||||
{
|
||||
if (idBundle.SerializeFeatureIdOnly())
|
||||
{
|
||||
FeatureId featureId;
|
||||
operator()(featureId, name);
|
||||
idBundle.SetFeatureId(featureId);
|
||||
idBundle.SetOsmId(kInvalidOsmId);
|
||||
return;
|
||||
}
|
||||
|
||||
idBundle.Visit(*this);
|
||||
}
|
||||
|
||||
void operator()(Edge & e, char const * name = nullptr)
|
||||
{
|
||||
(*this)(e.m_stop1Id);
|
||||
(*this)(e.m_stop2Id);
|
||||
(*this)(e.m_weight);
|
||||
(*this)(e.m_lineId);
|
||||
(*this)(e.m_flags);
|
||||
|
||||
e.m_shapeIds.clear();
|
||||
if (e.m_flags.m_isShapeIdsEmpty)
|
||||
return;
|
||||
|
||||
if (e.m_flags.m_isShapeIdsSame)
|
||||
{
|
||||
e.m_shapeIds.emplace_back(e.GetStop1Id(), e.GetStop2Id());
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.m_flags.m_isShapeIdsReversed)
|
||||
{
|
||||
e.m_shapeIds.emplace_back(e.GetStop2Id(), e.GetStop1Id());
|
||||
return;
|
||||
}
|
||||
|
||||
if (e.m_flags.m_isShapeIdsSingle)
|
||||
{
|
||||
e.m_shapeIds.resize(1 /* single shape id */);
|
||||
(*this)(e.m_shapeIds.back());
|
||||
return;
|
||||
}
|
||||
|
||||
(*this)(e.m_shapeIds);
|
||||
}
|
||||
|
||||
void operator()(EdgeFlags & f, char const * /* name */ = nullptr)
|
||||
{
|
||||
uint8_t flags = 0;
|
||||
(*this)(flags);
|
||||
f.SetFlags(flags);
|
||||
}
|
||||
|
||||
void operator()(std::vector<m2::PointD> & vs, char const * /* name */ = nullptr)
|
||||
{
|
||||
auto const size = ReadVarUint<uint64_t>(m_source);
|
||||
m2::PointU lastDecodedPoint(0, 0);
|
||||
vs.resize(size);
|
||||
for (auto & p : vs)
|
||||
{
|
||||
m2::PointU const pointU = coding::DecodePointDeltaFromUint(ReadVarUint<uint64_t>(m_source), lastDecodedPoint);
|
||||
p = PointUToPointD(pointU, kPointCoordBits);
|
||||
lastDecodedPoint = pointU;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void operator()(std::vector<T> & vs, char const * /* name */ = nullptr)
|
||||
{
|
||||
auto const size = ReadVarUint<uint64_t>(m_source);
|
||||
vs.resize(size);
|
||||
for (auto & v : vs)
|
||||
(*this)(v);
|
||||
}
|
||||
|
||||
template <class K, class V, class H>
|
||||
void operator()(std::unordered_map<K, V, H> & container, char const * /* name */ = nullptr)
|
||||
{
|
||||
auto const size = static_cast<size_t>(ReadVarUint<uint64_t>(m_source));
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
K key;
|
||||
V val;
|
||||
|
||||
(*this)(key);
|
||||
(*this)(val);
|
||||
|
||||
container.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
template <class K, class V, class H>
|
||||
void operator()(std::map<K, V, H> & container, char const * /* name */ = nullptr)
|
||||
{
|
||||
auto const size = static_cast<size_t>(ReadVarUint<uint64_t>(m_source));
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
{
|
||||
K key;
|
||||
V val;
|
||||
|
||||
(*this)(key);
|
||||
(*this)(val);
|
||||
|
||||
container.emplace(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
void operator()(osmoh::OpeningHours & oh, char const * /* name */ = nullptr)
|
||||
{
|
||||
std::string ohStr;
|
||||
(*this)(ohStr);
|
||||
oh = osmoh::OpeningHours(ohStr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_class<T>::value> operator()(T & t, char const * /* name */ = nullptr)
|
||||
{
|
||||
t.Visit(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
Source & m_source;
|
||||
Edge::WrappedEdgeId m_lastWrappedEdgeId = {};
|
||||
Stop::WrappedStopId m_lastWrappedStopId = {};
|
||||
};
|
||||
|
||||
template <typename Sink>
|
||||
class FixedSizeSerializer
|
||||
{
|
||||
public:
|
||||
explicit FixedSizeSerializer(Sink & sink) : m_sink(sink) {}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value, void> operator()(
|
||||
T const & t, char const * /* name */ = nullptr)
|
||||
{
|
||||
WriteToSink(m_sink, t);
|
||||
}
|
||||
|
||||
void operator()(TransitHeader const & header) { header.Visit(*this); }
|
||||
|
||||
void operator()(::transit::experimental::TransitHeader const & headerExperimental)
|
||||
{
|
||||
headerExperimental.Visit(*this);
|
||||
}
|
||||
|
||||
private:
|
||||
Sink & m_sink;
|
||||
};
|
||||
|
||||
template <typename Source>
|
||||
class FixedSizeDeserializer
|
||||
{
|
||||
public:
|
||||
explicit FixedSizeDeserializer(Source & source) : m_source(source) {}
|
||||
|
||||
template <typename T>
|
||||
std::enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value, void> operator()(T & t,
|
||||
char const * name = nullptr)
|
||||
{
|
||||
ReadPrimitiveFromSource(m_source, t);
|
||||
}
|
||||
|
||||
void operator()(TransitHeader & header) { header.Visit(*this); }
|
||||
|
||||
void operator()(::transit::experimental::TransitHeader & headerExperimental) { headerExperimental.Visit(*this); }
|
||||
|
||||
private:
|
||||
Source & m_source;
|
||||
};
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
13
libs/transit/transit_tests/CMakeLists.txt
Normal file
13
libs/transit/transit_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
project(transit_tests)
|
||||
|
||||
set(SRC
|
||||
transit_graph_test.cpp
|
||||
transit_json_parsing_test.cpp
|
||||
transit_schedule_tests.cpp
|
||||
transit_test.cpp
|
||||
transit_tools.hpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} transit)
|
||||
775
libs/transit/transit_tests/transit_graph_test.cpp
Normal file
775
libs/transit/transit_tests/transit_graph_test.cpp
Normal file
|
|
@ -0,0 +1,775 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/transit_tests/transit_tools.hpp"
|
||||
|
||||
#include "transit/transit_graph_data.hpp"
|
||||
#include "transit/transit_types.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace routing;
|
||||
using namespace routing::transit;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct Graph
|
||||
{
|
||||
vector<Stop> m_stops;
|
||||
vector<Gate> m_gates;
|
||||
vector<Edge> m_edges;
|
||||
vector<Transfer> m_transfers;
|
||||
vector<Line> m_lines;
|
||||
vector<Shape> m_shapes;
|
||||
vector<Network> m_networks;
|
||||
};
|
||||
|
||||
unique_ptr<GraphData> CreateGraphFromJson()
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"stops": [{
|
||||
"id": 0,
|
||||
"line_ids": [1],
|
||||
"osm_id": "100",
|
||||
"point": { "x": -2.0, "y": 1.0 },
|
||||
"title_anchors": []
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"line_ids": [1],
|
||||
"osm_id": "101",
|
||||
"point": { "x": 0.0, "y": 1.0 },
|
||||
"title_anchors": []
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"line_ids": [1],
|
||||
"osm_id": "102",
|
||||
"point": { "x": 2.0, "y": 1.0 },
|
||||
"title_anchors": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"line_ids": [1],
|
||||
"osm_id": "103",
|
||||
"point": { "x": 4.0, "y": 1.0 },
|
||||
"title_anchors": []
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"line_ids": [1],
|
||||
"osm_id": "104",
|
||||
"point": { "x": 5.0, "y": 1.0 },
|
||||
"title_anchors": []
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"line_ids": [2],
|
||||
"osm_id": "105",
|
||||
"point": { "x": -1.0, "y": -1.0 },
|
||||
"title_anchors": []
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"line_ids": [2],
|
||||
"osm_id": "106",
|
||||
"point": { "x": 1.0, "y": -1.0 },
|
||||
"title_anchors": []
|
||||
}
|
||||
],
|
||||
"lines": [
|
||||
{
|
||||
"id": 1,
|
||||
"interval": 150,
|
||||
"network_id": 2,
|
||||
"number": "1",
|
||||
"stop_ids": [ 0, 1, 2, 3, 4 ],
|
||||
"title": "Московская линия",
|
||||
"type": "subway",
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"interval": 150,
|
||||
"network_id": 2,
|
||||
"number": "2",
|
||||
"stop_ids": [ 5, 6 ],
|
||||
"title": "Варшавская линия",
|
||||
"type": "subway",
|
||||
"color": "red"
|
||||
}
|
||||
],
|
||||
"transfers": [ ],
|
||||
"networks": [
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Минский метрополитен"
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"stop1_id": 0,
|
||||
"stop2_id": 1,
|
||||
"line_id": 1,
|
||||
"shape_ids": [ { "stop1_id": 0, "stop2_id": 1 }],
|
||||
"transfer": false,
|
||||
"weight" : 20
|
||||
},
|
||||
{
|
||||
"stop1_id": 1,
|
||||
"stop2_id": 2,
|
||||
"line_id": 1,
|
||||
"shape_ids": [ { "stop1_id": 1, "stop2_id": 2 }],
|
||||
"transfer": false,
|
||||
"weight" : 20
|
||||
},
|
||||
{
|
||||
"stop1_id": 2,
|
||||
"stop2_id": 3,
|
||||
"line_id": 1,
|
||||
"shape_ids": [ { "stop1_id": 2, "stop2_id": 3 }],
|
||||
"transfer": false,
|
||||
"weight" : 20
|
||||
},
|
||||
{
|
||||
"stop1_id": 3,
|
||||
"stop2_id": 4,
|
||||
"line_id": 1,
|
||||
"shape_ids": [ { "stop1_id": 3, "stop2_id": 4 }],
|
||||
"transfer": false,
|
||||
"weight" : 10
|
||||
},
|
||||
{
|
||||
"stop1_id": 3,
|
||||
"stop2_id": 4,
|
||||
"line_id": 1,
|
||||
"shape_ids": [ { "stop1_id": 3, "stop2_id": 4 }],
|
||||
"transfer": false,
|
||||
"weight" : 20
|
||||
},
|
||||
{
|
||||
"stop1_id": 3,
|
||||
"stop2_id": 4,
|
||||
"line_id": 1,
|
||||
"shape_ids": [ { "stop1_id": 3, "stop2_id": 4 }],
|
||||
"transfer": false,
|
||||
"weight" : 20
|
||||
},
|
||||
{
|
||||
"stop1_id": 5,
|
||||
"stop2_id": 6,
|
||||
"line_id": 2,
|
||||
"shape_ids": [ { "stop1_id": 5, "stop2_id": 6 }],
|
||||
"transfer": false,
|
||||
"weight" : 20
|
||||
}
|
||||
],
|
||||
"shapes": [
|
||||
{
|
||||
"id": { "stop1_id": 0, "stop2_id": 1 },
|
||||
"polyline": [
|
||||
{ "x": -2.0, "y": 1.0 },
|
||||
{ "x": 0.0, "y": 1.0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": { "stop1_id": 1, "stop2_id": 2 },
|
||||
"polyline": [
|
||||
{ "x": 0.0, "y": 1.0 },
|
||||
{ "x": 2.0, "y": 1.0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": { "stop1_id": 2, "stop2_id": 3 },
|
||||
"polyline": [
|
||||
{ "x": 2.0, "y": 1.0 },
|
||||
{ "x": 4.0, "y": 1.0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": { "stop1_id": 3, "stop2_id": 4 },
|
||||
"polyline": [
|
||||
{ "x": 4.0, "y": 1.0 },
|
||||
{ "x": 5.0, "y": 1.0 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": { "stop1_id": 5, "stop2_id": 6 },
|
||||
"polyline": [
|
||||
{ "x": -1.0, "y": -1.0 },
|
||||
{ "x": 1.0, "y": -1.0 }
|
||||
]
|
||||
}
|
||||
],
|
||||
"gates": [
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "100",
|
||||
"point": { "x": -2.0, "y": 1.0 },
|
||||
"stop_ids": [ 0 ],
|
||||
"weight": 0
|
||||
},
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "101",
|
||||
"point": { "x": 0.0, "y": 1.0 },
|
||||
"stop_ids": [ 1 ],
|
||||
"weight": 0
|
||||
},
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "102",
|
||||
"point": { "x": 2.0, "y": 1.0 },
|
||||
"stop_ids": [ 2 ],
|
||||
"weight": 0
|
||||
},
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "103",
|
||||
"point": { "x": 4.0, "y": 1.0 },
|
||||
"stop_ids": [ 3 ],
|
||||
"weight": 0
|
||||
},
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "104",
|
||||
"point": { "x": 5.0, "y": 1.0 },
|
||||
"stop_ids": [ 4 ],
|
||||
"weight": 0
|
||||
},
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "105",
|
||||
"point": { "x": -1.0, "y": -1.0 },
|
||||
"stop_ids": [ 5 ],
|
||||
"weight": 0
|
||||
},
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "106",
|
||||
"point": { "x": 1.0, "y": -1.0 },
|
||||
"stop_ids": [ 6 ],
|
||||
"weight": 0
|
||||
}
|
||||
]})";
|
||||
|
||||
auto graph = make_unique<GraphData>();
|
||||
|
||||
OsmIdToFeatureIdsMap mapping;
|
||||
mapping[base::GeoObjectId(100)] = vector<FeatureId>({10});
|
||||
mapping[base::GeoObjectId(101)] = vector<FeatureId>({11});
|
||||
mapping[base::GeoObjectId(102)] = vector<FeatureId>({12});
|
||||
mapping[base::GeoObjectId(103)] = vector<FeatureId>({13});
|
||||
mapping[base::GeoObjectId(104)] = vector<FeatureId>({14});
|
||||
mapping[base::GeoObjectId(105)] = vector<FeatureId>({15});
|
||||
mapping[base::GeoObjectId(106)] = vector<FeatureId>({16});
|
||||
|
||||
base::Json root(jsonBuffer.c_str());
|
||||
CHECK(root.get() != nullptr, ("Cannot parse the json."));
|
||||
graph->DeserializeFromJson(root, mapping);
|
||||
return graph;
|
||||
}
|
||||
|
||||
unique_ptr<Graph> MakeFullGraph()
|
||||
{
|
||||
auto graph = make_unique<Graph>();
|
||||
graph->m_stops = {{0 /* stop id */,
|
||||
100 /* osm id */,
|
||||
10 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(-2.0, 1.0),
|
||||
{}},
|
||||
{1 /* stop id */,
|
||||
101 /* osm id */,
|
||||
11 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(0.0, 1.0),
|
||||
{}},
|
||||
{2 /* stop id */,
|
||||
102 /* osm id */,
|
||||
12 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(2.0, 1.0),
|
||||
{}},
|
||||
{3 /* stop id */,
|
||||
103 /* osm id */,
|
||||
13 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(4.0, 1.0),
|
||||
{}},
|
||||
{4 /* stop id */,
|
||||
104 /* osm id */,
|
||||
14 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(5.0, 1.0),
|
||||
{}},
|
||||
{5 /* stop id */,
|
||||
105 /* osm id */,
|
||||
15 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{2} /* line ids */,
|
||||
m2::PointD(-1.0, -1.0),
|
||||
{}},
|
||||
{6 /* stop id */,
|
||||
106 /* osm id */,
|
||||
16 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{2} /* line ids */,
|
||||
m2::PointD(1.0, -1.0),
|
||||
{}}};
|
||||
|
||||
graph->m_gates = {
|
||||
{100 /* osm id */,
|
||||
10 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{0} /* stop ids */,
|
||||
m2::PointD(-2.0, 1.0)},
|
||||
{101 /* osm id */,
|
||||
11 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{1} /* stop ids */,
|
||||
m2::PointD(0.0, 1.0)},
|
||||
{102 /* osm id */,
|
||||
12 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{2} /* stop ids */,
|
||||
m2::PointD(2.0, 1.0)},
|
||||
{103 /* osm id */,
|
||||
13 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{3} /* stop ids */,
|
||||
m2::PointD(4.0, 1.0)},
|
||||
{104 /* osm id */,
|
||||
14 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{4} /* stop ids */,
|
||||
m2::PointD(5.0, 1.0)},
|
||||
{105 /* osm id */,
|
||||
15 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{5} /* stop ids */,
|
||||
m2::PointD(-1.0, -1.0)},
|
||||
{106 /* osm id */,
|
||||
16 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{6} /* stop ids */,
|
||||
m2::PointD(1.0, -1.0)},
|
||||
};
|
||||
|
||||
graph->m_edges = {{0 /* stop 1 id */,
|
||||
1 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{0, 1}} /* shape ids */},
|
||||
{1 /* stop 1 id */,
|
||||
2 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{1, 2}} /* shape ids */},
|
||||
{2 /* stop 1 id */,
|
||||
3 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{2, 3}} /* shape ids */},
|
||||
{3 /* stop 1 id */,
|
||||
4 /* stop 2 id */,
|
||||
10 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{3, 4}} /* shape ids */},
|
||||
{5 /* stop 1 id */,
|
||||
6 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
2 /* line id */,
|
||||
false /* transfer */,
|
||||
{{5, 6}} /* shape ids */}};
|
||||
|
||||
// |graph.m_transfers| should be empty.
|
||||
|
||||
graph->m_lines = {{1 /* line id */,
|
||||
"1" /* number */,
|
||||
"Московская линия" /* title */,
|
||||
"subway" /* type */,
|
||||
"green",
|
||||
2 /* network id */,
|
||||
{{0, 1, 2, 3, 4}} /* stop id */,
|
||||
150 /* interval */},
|
||||
{2 /* line id */,
|
||||
"2" /* number */,
|
||||
"Варшавская линия" /* title */,
|
||||
"subway" /* type */,
|
||||
"red",
|
||||
2 /* network id */,
|
||||
{{5, 6}} /* stop id */,
|
||||
150 /* interval */}};
|
||||
|
||||
graph->m_shapes = {{{0, 1} /* shape id */, {{-2.0, 1.0}, {0.0, 1.0}} /* polyline */},
|
||||
{{1, 2} /* shape id */, {{0.0, 1.0}, {2.0, 1.0}} /* polyline */},
|
||||
{{2, 3} /* shape id */, {{2.0, 1.0}, {4.0, 1.0}} /* polyline */},
|
||||
{{3, 4} /* shape id */, {{4.0, 1.0}, {5.0, 1.0}} /* polyline */},
|
||||
{{5, 6} /* shape id */, {{-1.0, -1.0}, {1.0, -1.0}} /* polyline */}};
|
||||
|
||||
graph->m_networks = {{2 /* network id */, "Минский метрополитен" /* title */}};
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
unique_ptr<Graph> MakeOneLineGraph()
|
||||
{
|
||||
auto graph = make_unique<Graph>();
|
||||
|
||||
graph->m_stops = {{0 /* stop id */,
|
||||
100 /* osm id */,
|
||||
10 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(-2.0, 1.0),
|
||||
{}},
|
||||
{1 /* stop id */,
|
||||
101 /* osm id */,
|
||||
11 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(0.0, 1.0),
|
||||
{}},
|
||||
{2 /* stop id */,
|
||||
102 /* osm id */,
|
||||
12 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(2.0, 1.0),
|
||||
{}},
|
||||
{3 /* stop id */,
|
||||
103 /* osm id */,
|
||||
13 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(4.0, 1.0),
|
||||
{}}};
|
||||
|
||||
graph->m_gates = {{100 /* osm id */,
|
||||
10 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{0} /* stop ids */,
|
||||
m2::PointD(-2.0, 1.0)},
|
||||
{101 /* osm id */,
|
||||
11 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{1} /* stop ids */,
|
||||
m2::PointD(0.0, 1.0)},
|
||||
{102 /* osm id */,
|
||||
12 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{2} /* stop ids */,
|
||||
m2::PointD(2.0, 1.0)},
|
||||
{103 /* osm id */,
|
||||
13 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{3} /* stop ids */,
|
||||
m2::PointD(4.0, 1.0)}};
|
||||
|
||||
graph->m_edges = {{0 /* stop 1 id */,
|
||||
1 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{0, 1}} /* shape ids */},
|
||||
{1 /* stop 1 id */,
|
||||
2 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{1, 2}} /* shape ids */},
|
||||
{2 /* stop 1 id */,
|
||||
3 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{2, 3}} /* shape ids */}};
|
||||
|
||||
// |graph.m_transfers| should be empty.
|
||||
|
||||
graph->m_lines = {{1 /* line id */,
|
||||
"1" /* number */,
|
||||
"Московская линия" /* title */,
|
||||
"subway" /* type */,
|
||||
"green",
|
||||
2 /* network id */,
|
||||
{{0, 1, 2, 3}} /* stop id */,
|
||||
150 /* interval */}};
|
||||
|
||||
graph->m_shapes = {{{0, 1} /* shape id */, {{-2.0, 1.0}, {0.0, 1.0}} /* polyline */},
|
||||
{{1, 2} /* shape id */, {{0.0, 1.0}, {2.0, 1.0}} /* polyline */},
|
||||
{{2, 3} /* shape id */, {{2.0, 1.0}, {4.0, 1.0}} /* polyline */}};
|
||||
|
||||
graph->m_networks = {{2 /* network id */, "Минский метрополитен" /* title */}};
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
unique_ptr<Graph> MakeTwoLinesGraph()
|
||||
{
|
||||
auto graph = make_unique<Graph>();
|
||||
|
||||
graph->m_stops = {{1 /* stop id */,
|
||||
101 /* osm id */,
|
||||
11 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(0.0, 1.0),
|
||||
{}},
|
||||
{2 /* stop id */,
|
||||
102 /* osm id */,
|
||||
12 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(2.0, 1.0),
|
||||
{}},
|
||||
{3 /* stop id */,
|
||||
103 /* osm id */,
|
||||
13 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{1} /* line ids */,
|
||||
m2::PointD(4.0, 1.0),
|
||||
{}},
|
||||
{5 /* stop id */,
|
||||
105 /* osm id */,
|
||||
15 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{2} /* line ids */,
|
||||
m2::PointD(-1.0, -1.0),
|
||||
{}},
|
||||
{6 /* stop id */,
|
||||
106 /* osm id */,
|
||||
16 /* feature id */,
|
||||
kInvalidTransferId,
|
||||
{2} /* line ids */,
|
||||
m2::PointD(1.0, -1.0),
|
||||
{}}};
|
||||
|
||||
graph->m_gates = {
|
||||
{101 /* osm id */,
|
||||
11 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{1} /* stop ids */,
|
||||
m2::PointD(0.0, 1.0)},
|
||||
{102 /* osm id */,
|
||||
12 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{2} /* stop ids */,
|
||||
m2::PointD(2.0, 1.0)},
|
||||
{103 /* osm id */,
|
||||
13 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{3} /* stop ids */,
|
||||
m2::PointD(4.0, 1.0)},
|
||||
{105 /* osm id */,
|
||||
15 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{5} /* stop ids */,
|
||||
m2::PointD(-1.0, -1.0)},
|
||||
{106 /* osm id */,
|
||||
16 /* feature id */,
|
||||
true /* entrance */,
|
||||
true /* exit */,
|
||||
0 /* weight */,
|
||||
{6} /* stop ids */,
|
||||
m2::PointD(1.0, -1.0)},
|
||||
};
|
||||
|
||||
graph->m_edges = {{1 /* stop 1 id */,
|
||||
2 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{1, 2}} /* shape ids */},
|
||||
{2 /* stop 1 id */,
|
||||
3 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
1 /* line id */,
|
||||
false /* transfer */,
|
||||
{{2, 3}} /* shape ids */},
|
||||
{5 /* stop 1 id */,
|
||||
6 /* stop 2 id */,
|
||||
20 /* weight */,
|
||||
2 /* line id */,
|
||||
false /* transfer */,
|
||||
{{5, 6}} /* shape ids */}};
|
||||
|
||||
// |graph.m_transfers| should be empty.
|
||||
|
||||
graph->m_lines = {{1 /* line id */,
|
||||
"1" /* number */,
|
||||
"Московская линия" /* title */,
|
||||
"subway" /* type */,
|
||||
"green",
|
||||
2 /* network id */,
|
||||
{{1, 2, 3}} /* stop id */,
|
||||
150 /* interval */},
|
||||
{2 /* line id */,
|
||||
"2" /* number */,
|
||||
"Варшавская линия" /* title */,
|
||||
"subway" /* type */,
|
||||
"red",
|
||||
2 /* network id */,
|
||||
{{5, 6}} /* stop id */,
|
||||
150 /* interval */}};
|
||||
|
||||
graph->m_shapes = {{{1, 2} /* shape id */, {{0.0, 1.0}, {2.0, 1.0}} /* polyline */},
|
||||
{{2, 3} /* shape id */, {{2.0, 1.0}, {4.0, 1.0}} /* polyline */},
|
||||
{{5, 6} /* shape id */, {{-1.0, -1.0}, {1.0, -1.0}} /* polyline */}};
|
||||
|
||||
graph->m_networks = {{2 /* network id */, "Минский метрополитен" /* title */}};
|
||||
|
||||
return graph;
|
||||
}
|
||||
|
||||
void TestGraph(GraphData const & actualGraph, Graph const & expectedGraph)
|
||||
{
|
||||
TestForEquivalence(actualGraph.GetStops(), expectedGraph.m_stops);
|
||||
TestForEquivalence(actualGraph.GetGates(), expectedGraph.m_gates);
|
||||
TestForEquivalence(actualGraph.GetEdges(), expectedGraph.m_edges);
|
||||
TestForEquivalence(actualGraph.GetTransfers(), expectedGraph.m_transfers);
|
||||
TestForEquivalence(actualGraph.GetLines(), expectedGraph.m_lines);
|
||||
TestForEquivalence(actualGraph.GetShapes(), expectedGraph.m_shapes);
|
||||
TestForEquivalence(actualGraph.GetNetworks(), expectedGraph.m_networks);
|
||||
}
|
||||
|
||||
void SerializeAndDeserializeGraph(GraphData & src, GraphData & dst)
|
||||
{
|
||||
vector<uint8_t> buffer;
|
||||
MemWriter<decltype(buffer)> writer(buffer);
|
||||
src.Serialize(writer);
|
||||
|
||||
MemReader reader(buffer.data(), buffer.size());
|
||||
dst.DeserializeAll(reader);
|
||||
dst.CheckValidSortedUnique();
|
||||
}
|
||||
|
||||
// ^
|
||||
// |
|
||||
// * 2 _______
|
||||
// | |
|
||||
// s0----------s1----------s2----------s3----s4 Line 1
|
||||
// |_____|
|
||||
// * * * * * * * * * -->
|
||||
// -3 -2 -1 0 1 2 3 4 5
|
||||
// s5----------s6 Line 2
|
||||
//
|
||||
UNIT_TEST(ClipGraph_SmokeTest)
|
||||
{
|
||||
auto graph = CreateGraphFromJson();
|
||||
graph->Sort();
|
||||
|
||||
auto expectedGraph = MakeFullGraph();
|
||||
TestGraph(*graph, *expectedGraph);
|
||||
|
||||
GraphData readedGraph;
|
||||
SerializeAndDeserializeGraph(*graph, readedGraph);
|
||||
TestGraph(*graph, *expectedGraph);
|
||||
}
|
||||
|
||||
// ^
|
||||
// |
|
||||
// * 4
|
||||
//
|
||||
// ------------------* 3-----------Border
|
||||
// | |
|
||||
// | * 2 | _______
|
||||
// | | | |
|
||||
// | s0----------s1----------s2----|-----s3----s4 Line 1
|
||||
// | | |_____|
|
||||
// *-----*-----*-----*-----*-----*-----* * * -->
|
||||
// -3 -2 -1 0 1 2 3 4 5
|
||||
// s5----------s6 Line 2
|
||||
//
|
||||
UNIT_TEST(ClipGraph_OneLineTest)
|
||||
{
|
||||
auto graph = CreateGraphFromJson();
|
||||
vector<m2::PointD> points = {{3.0, 3.0}, {3.0, 0.0}, {-3.0, 0.0}, {-3.0, 3.0}, {3.0, 3.0}};
|
||||
graph->ClipGraph({m2::RegionD(points)});
|
||||
auto expectedGraph = MakeOneLineGraph();
|
||||
TestGraph(*graph, *expectedGraph);
|
||||
|
||||
GraphData readedGraph;
|
||||
SerializeAndDeserializeGraph(*graph, readedGraph);
|
||||
TestGraph(*graph, *expectedGraph);
|
||||
}
|
||||
|
||||
// ^
|
||||
// |
|
||||
// * 3
|
||||
//
|
||||
// * 2----------Border _______
|
||||
// | | | |
|
||||
// s0----------s1-|--------s2-|--------s3----s4 Line 1
|
||||
// | | |_____|
|
||||
// * * * * | * * | * * * -->
|
||||
// -3 -2 -1 0 | 1 2 | 3 4 5
|
||||
// Line 2 s5-------|--s6 |
|
||||
// | |
|
||||
// -2 * -------------
|
||||
//
|
||||
UNIT_TEST(ClipGraph_TwoLinesTest)
|
||||
{
|
||||
auto graph = CreateGraphFromJson();
|
||||
vector<m2::PointD> points = {{2.5, 2.0}, {2.5, -2.0}, {0.5, -2.0}, {0.5, 2.0}, {2.5, 2.0}};
|
||||
graph->ClipGraph({m2::RegionD(points)});
|
||||
|
||||
auto expectedGraph = MakeTwoLinesGraph();
|
||||
TestGraph(*graph, *expectedGraph);
|
||||
|
||||
GraphData readedGraph;
|
||||
SerializeAndDeserializeGraph(*graph, readedGraph);
|
||||
TestGraph(*graph, *expectedGraph);
|
||||
}
|
||||
} // namespace
|
||||
337
libs/transit/transit_tests/transit_json_parsing_test.cpp
Normal file
337
libs/transit/transit_tests/transit_json_parsing_test.cpp
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/transit_tests/transit_tools.hpp"
|
||||
|
||||
#include "transit/transit_graph_data.hpp"
|
||||
#include "transit/transit_types.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
using namespace routing;
|
||||
using namespace routing::transit;
|
||||
using namespace std;
|
||||
|
||||
namespace
|
||||
{
|
||||
template <typename Obj>
|
||||
void TestDeserializerFromJson(string const & jsonBuffer, OsmIdToFeatureIdsMap const & osmIdToFeatureIds,
|
||||
string const & name, vector<Obj> const & expected)
|
||||
{
|
||||
base::Json root(jsonBuffer.c_str());
|
||||
CHECK(root.get() != nullptr, ("Cannot parse the json."));
|
||||
|
||||
DeserializerFromJson deserializer(root.get(), osmIdToFeatureIds);
|
||||
|
||||
vector<Obj> objects;
|
||||
deserializer(objects, name.c_str());
|
||||
|
||||
TEST_EQUAL(objects.size(), expected.size(), ());
|
||||
TestForEquivalence(objects, expected);
|
||||
}
|
||||
|
||||
template <typename Obj>
|
||||
void TestDeserializerFromJson(string const & jsonBuffer, string const & name, vector<Obj> const & expected)
|
||||
{
|
||||
return TestDeserializerFromJson(jsonBuffer, OsmIdToFeatureIdsMap(), name, expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_TitleAnchors)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"title_anchors": [
|
||||
{ "min_zoom": 11, "anchor": 4 },
|
||||
{ "min_zoom": 14, "anchor": 6 }
|
||||
]})";
|
||||
|
||||
vector<TitleAnchor> expected = {TitleAnchor(11 /* min zoom */, 4 /* anchor */),
|
||||
TitleAnchor(14 /* min zoom */, 6 /* anchor */)};
|
||||
TestDeserializerFromJson(jsonBuffer, "title_anchors", expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_Stops)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"stops": [{
|
||||
"id": 343259523,
|
||||
"line_ids": [
|
||||
19207936,
|
||||
19207937
|
||||
],
|
||||
"osm_id": "1234",
|
||||
"point": {
|
||||
"x": 27.4970954,
|
||||
"y": 64.20146835878187
|
||||
},
|
||||
"title_anchors": []
|
||||
},
|
||||
{
|
||||
"id": 266680843,
|
||||
"transfer_id" : 5,
|
||||
"line_ids": [
|
||||
19213568,
|
||||
19213569
|
||||
],
|
||||
"osm_id": "2345",
|
||||
"point": {
|
||||
"x": 27.5227942,
|
||||
"y": 64.25206634443111
|
||||
},
|
||||
"title_anchors": [
|
||||
{ "min_zoom": 12, "anchor": 0 },
|
||||
{ "min_zoom": 15, "anchor": 9 }]
|
||||
}
|
||||
]})";
|
||||
|
||||
vector<Stop> const expected = {
|
||||
Stop(343259523 /* id */, 1234 /* osm id */, 1 /* feature id */, kInvalidTransferId /* transfer id */,
|
||||
{19207936, 19207937} /* lineIds */, {27.4970954, 64.20146835878187} /* point */, {} /* anchors */),
|
||||
Stop(266680843 /* id */, 2345 /* osm id */, 2 /* feature id */, 5 /* transfer id */,
|
||||
{19213568, 19213569} /* line ids */, {27.5227942, 64.25206634443111} /* point */,
|
||||
{TitleAnchor(12 /* min zoom */, 0 /* anchor */), TitleAnchor(15, 9)})};
|
||||
|
||||
OsmIdToFeatureIdsMap mapping;
|
||||
mapping[base::GeoObjectId(1234)] = vector<FeatureId>({1});
|
||||
mapping[base::GeoObjectId(2345)] = vector<FeatureId>({2});
|
||||
TestDeserializerFromJson(jsonBuffer, mapping, "stops", expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_Gates)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"gates": [
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "46116860",
|
||||
"point": {
|
||||
"x": 43.8594864,
|
||||
"y": 68.33320554776377
|
||||
},
|
||||
"stop_ids": [ 442018474 ],
|
||||
"weight": 60
|
||||
},
|
||||
{
|
||||
"entrance": true,
|
||||
"exit": true,
|
||||
"osm_id": "18446744073709551615",
|
||||
"point": {
|
||||
"x": 43.9290544,
|
||||
"y": 68.41120791512581
|
||||
},
|
||||
"stop_ids": [ 442018465 ],
|
||||
"weight": 60
|
||||
}
|
||||
]})";
|
||||
|
||||
vector<Gate> const expected = {
|
||||
Gate(46116860 /* osm id */, 0 /* feature id */, true /* entrance */, true /* exit */, 60 /* weight */,
|
||||
{442018474} /* stop ids */, {43.8594864, 68.33320554776377} /* point */),
|
||||
Gate(18446744073709551615ULL /* osm id */, 2 /* feature id */, true /* entrance */, true /* exit */,
|
||||
60 /* weight */, {442018465} /* stop ids */, {43.9290544, 68.41120791512581} /* point */)};
|
||||
|
||||
OsmIdToFeatureIdsMap mapping;
|
||||
mapping[base::GeoObjectId(46116860)] = vector<FeatureId>({0});
|
||||
// Note. std::numeric_limits<uint64_t>::max() == 18446744073709551615
|
||||
mapping[base::GeoObjectId(18446744073709551615ULL)] = vector<FeatureId>({2});
|
||||
TestDeserializerFromJson(jsonBuffer, mapping, "gates", expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_Edges)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"edges": [
|
||||
{
|
||||
"stop2_id": 442018445,
|
||||
"line_id": 72551680,
|
||||
"shape_ids": [
|
||||
{
|
||||
"stop1_id": 209186407,
|
||||
"stop2_id": 209186410
|
||||
},
|
||||
{
|
||||
"stop1_id": 209186408,
|
||||
"stop2_id": 209186411
|
||||
}
|
||||
],
|
||||
"stop1_id": 442018444,
|
||||
"transfer": false
|
||||
},
|
||||
{
|
||||
"stop2_id": 442018446,
|
||||
"line_id": 72551680,
|
||||
"shape_ids": [],
|
||||
"weight" : 345,
|
||||
"stop1_id": 442018445,
|
||||
"transfer": false
|
||||
}
|
||||
]})";
|
||||
|
||||
vector<Edge> const expected = {
|
||||
Edge(442018444 /* stop 1 id */, 442018445 /* stop 2 id */, kInvalidWeight /* weight */, 72551680 /* line id */,
|
||||
false /* transfer */, {ShapeId(209186407, 209186410), ShapeId(209186408, 209186411)}),
|
||||
Edge(442018445 /* stop 1 id */, 442018446 /* stop 2 id */, 345 /* weight */, 72551680 /* line id */,
|
||||
false /* transfer */, {} /* shape ids */)};
|
||||
|
||||
TestDeserializerFromJson(jsonBuffer, "edges", expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_Transfers)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"transfers": [
|
||||
{
|
||||
"id": 922337203,
|
||||
"point": {
|
||||
"x": 27.5619844,
|
||||
"y": 64.24325959173672
|
||||
},
|
||||
"stop_ids": [
|
||||
209186416,
|
||||
277039518
|
||||
]
|
||||
}
|
||||
]})";
|
||||
|
||||
vector<Transfer> const expected = {Transfer(922337203 /* stop id */, {27.5619844, 64.24325959173672} /* point */,
|
||||
{209186416, 277039518} /* stopIds */, {} /* anchors */)};
|
||||
|
||||
TestDeserializerFromJson(jsonBuffer, "transfers", expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_Lines)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"lines": [
|
||||
{
|
||||
"id": 19207936,
|
||||
"interval": 150,
|
||||
"network_id": 2,
|
||||
"number": "1",
|
||||
"stop_ids": [
|
||||
343262691,
|
||||
343259523,
|
||||
343252898,
|
||||
209191847,
|
||||
2947858576
|
||||
],
|
||||
"title": "Московская линия",
|
||||
"type": "subway",
|
||||
"color": "green"
|
||||
},
|
||||
{
|
||||
"id": 19207937,
|
||||
"interval": 150,
|
||||
"network_id": 2,
|
||||
"number": "2",
|
||||
"stop_ids": [
|
||||
246659391,
|
||||
246659390,
|
||||
209191855,
|
||||
209191854,
|
||||
209191853,
|
||||
209191852,
|
||||
209191851
|
||||
],
|
||||
"title": "Московская линия",
|
||||
"type": "subway",
|
||||
"color": "red"
|
||||
}
|
||||
]})";
|
||||
|
||||
vector<Line> const expected = {
|
||||
Line(19207936 /* line id */, "1" /* number */, "Московская линия" /* title */, "subway" /* type */,
|
||||
"green" /* color */, 2 /* network id */,
|
||||
{{343262691, 343259523, 343252898, 209191847, 2947858576}} /* stop ids */, 150 /* interval */),
|
||||
Line(19207937 /* line id */, "2" /* number */, "Московская линия" /* title */, "subway" /* type */,
|
||||
"red" /* color */, 2 /* network id */,
|
||||
{{246659391, 246659390, 209191855, 209191854, 209191853, 209191852, 209191851}} /* stop ids */,
|
||||
150 /* interval */)};
|
||||
|
||||
TestDeserializerFromJson(jsonBuffer, "lines", expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_Shapes)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"shapes": [
|
||||
{
|
||||
"id": {
|
||||
"stop1_id": 209186424,
|
||||
"stop2_id": 248520179
|
||||
},
|
||||
"polyline": [
|
||||
{
|
||||
"x": 27.5762295,
|
||||
"y": 64.256768574044699
|
||||
},
|
||||
{
|
||||
"x": 27.576325736220355,
|
||||
"y": 64.256879325696005
|
||||
},
|
||||
{
|
||||
"x": 27.576420780761875,
|
||||
"y": 64.256990221238539
|
||||
},
|
||||
{
|
||||
"x": 27.576514659541523,
|
||||
"y": 64.257101255242176
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": {
|
||||
"stop1_id": 209191850,
|
||||
"stop2_id": 209191851
|
||||
},
|
||||
"polyline": [
|
||||
{
|
||||
"x": 27.554025800000002,
|
||||
"y": 64.250591911669844
|
||||
},
|
||||
{
|
||||
"x": 27.553906184631536,
|
||||
"y": 64.250633404586054
|
||||
}
|
||||
]
|
||||
}
|
||||
]})";
|
||||
|
||||
vector<Shape> const expected = {
|
||||
Shape(ShapeId(209186424 /* stop 1 id */, 248520179 /* stop 2 id */),
|
||||
{m2::PointD(27.5762295, 64.256768574044699), m2::PointD(27.576325736220355, 64.256879325696005),
|
||||
m2::PointD(27.576420780761875, 64.256990221238539),
|
||||
m2::PointD(27.576514659541523, 64.257101255242176)} /* polyline */),
|
||||
Shape(ShapeId(209191850 /* stop 1 id */, 209191851 /* stop 2 id */),
|
||||
{m2::PointD(27.554025800000002, 64.250591911669844),
|
||||
m2::PointD(27.553906184631536, 64.250633404586054)} /* polyline */)};
|
||||
|
||||
TestDeserializerFromJson(jsonBuffer, "shapes", expected);
|
||||
}
|
||||
|
||||
UNIT_TEST(DeserializerFromJson_Networks)
|
||||
{
|
||||
string const jsonBuffer = R"(
|
||||
{
|
||||
"networks": [
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Минский метрополитен"
|
||||
}
|
||||
]})";
|
||||
|
||||
vector<Network> const expected = {Network(2 /* network id */, "Минский метрополитен" /* title */)};
|
||||
TestDeserializerFromJson(jsonBuffer, "networks", expected);
|
||||
}
|
||||
} // namespace
|
||||
255
libs/transit/transit_tests/transit_schedule_tests.cpp
Normal file
255
libs/transit/transit_tests/transit_schedule_tests.cpp
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/transit_schedule.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "3party/just_gtfs/just_gtfs.h"
|
||||
|
||||
namespace transit_schedule_tests
|
||||
{
|
||||
using namespace ::transit;
|
||||
|
||||
// String dates are provided in GTFS date format YYYYMMDD.
|
||||
// String times are provided in GTFS time format HH:MM:SS.
|
||||
uint32_t GetYear(std::string const & date)
|
||||
{
|
||||
return std::stoi(date.substr(0, 4));
|
||||
}
|
||||
|
||||
uint32_t GetMonth(std::string const & date)
|
||||
{
|
||||
return std::stoi(date.substr(4, 2));
|
||||
}
|
||||
|
||||
uint32_t GetDay(std::string const & date)
|
||||
{
|
||||
return std::stoi(date.substr(6));
|
||||
}
|
||||
|
||||
uint32_t GetHour(std::string const & time)
|
||||
{
|
||||
return std::stoi(time.substr(0, 2));
|
||||
}
|
||||
|
||||
uint32_t GetMinute(std::string const & time)
|
||||
{
|
||||
return std::stoi(time.substr(3, 2));
|
||||
}
|
||||
|
||||
uint32_t GetSecond(std::string const & time)
|
||||
{
|
||||
return std::stoi(time.substr(6));
|
||||
}
|
||||
|
||||
gtfs::Frequency GetFrequency(std::string const & startTime, std::string const & endTime, Frequency headwayS)
|
||||
{
|
||||
gtfs::Frequency freq;
|
||||
freq.start_time = gtfs::Time(startTime);
|
||||
freq.end_time = gtfs::Time(endTime);
|
||||
freq.headway_secs = headwayS;
|
||||
|
||||
return freq;
|
||||
}
|
||||
|
||||
gtfs::CalendarAvailability GetAvailability(bool available)
|
||||
{
|
||||
return available ? gtfs::CalendarAvailability::Available : gtfs::CalendarAvailability::NotAvailable;
|
||||
}
|
||||
|
||||
void TestDatesInterval(std::string const & date1, std::string const & date2, WeekSchedule weekDays)
|
||||
{
|
||||
CHECK_EQUAL(date1.size(), 8, ());
|
||||
CHECK_EQUAL(date2.size(), 8, ());
|
||||
|
||||
gtfs::CalendarItem ci;
|
||||
|
||||
ci.start_date = gtfs::Date(date1);
|
||||
ci.end_date = gtfs::Date(date2);
|
||||
|
||||
ci.sunday = GetAvailability(weekDays[0]);
|
||||
ci.monday = GetAvailability(weekDays[1]);
|
||||
ci.tuesday = GetAvailability(weekDays[2]);
|
||||
ci.wednesday = GetAvailability(weekDays[3]);
|
||||
ci.thursday = GetAvailability(weekDays[4]);
|
||||
ci.friday = GetAvailability(weekDays[5]);
|
||||
ci.saturday = GetAvailability(weekDays[6]);
|
||||
|
||||
DatesInterval const interval(ci);
|
||||
|
||||
auto const & [intervalDate1, intervalDate2, wd] = interval.Extract();
|
||||
|
||||
TEST_EQUAL(intervalDate1.m_year, GetYear(date1), ());
|
||||
TEST_EQUAL(intervalDate1.m_month, GetMonth(date1), ());
|
||||
TEST_EQUAL(intervalDate1.m_day, GetDay(date1), ());
|
||||
|
||||
TEST_EQUAL(intervalDate2.m_year, GetYear(date2), ());
|
||||
TEST_EQUAL(intervalDate2.m_month, GetMonth(date2), ());
|
||||
TEST_EQUAL(intervalDate2.m_day, GetDay(date2), ());
|
||||
|
||||
for (size_t i = 0; i < wd.size(); ++i)
|
||||
TEST_EQUAL(wd[i], weekDays[i], ());
|
||||
}
|
||||
|
||||
void TestDateException(std::string const & date, bool isOpen)
|
||||
{
|
||||
gtfs::CalendarDateException const ex =
|
||||
isOpen ? gtfs::CalendarDateException::Added : gtfs::CalendarDateException::Removed;
|
||||
|
||||
DateException const dateException(gtfs::Date(date), ex);
|
||||
|
||||
auto const & [exDate, exStatus] = dateException.Extract();
|
||||
|
||||
TEST_EQUAL(exDate.m_year, GetYear(date), ());
|
||||
TEST_EQUAL(exDate.m_month, GetMonth(date), ());
|
||||
TEST_EQUAL(exDate.m_day, GetDay(date), ());
|
||||
|
||||
TEST_EQUAL(exStatus, isOpen, ());
|
||||
}
|
||||
|
||||
void TestTime(std::string const & timePlan, Time const & timeFact)
|
||||
{
|
||||
TEST_EQUAL(timeFact.m_hour, GetHour(timePlan), ());
|
||||
TEST_EQUAL(timeFact.m_minute, GetMinute(timePlan), ());
|
||||
TEST_EQUAL(timeFact.m_second, GetSecond(timePlan), ());
|
||||
}
|
||||
|
||||
void TestTimeInterval(std::string const & time1, std::string const & time2)
|
||||
{
|
||||
CHECK_EQUAL(time1.size(), 8, ());
|
||||
CHECK_EQUAL(time2.size(), 8, ());
|
||||
|
||||
auto const & [startTime, endTime] = TimeInterval(gtfs::Time(time1), gtfs::Time(time2)).Extract();
|
||||
|
||||
TestTime(time1, startTime);
|
||||
TestTime(time2, endTime);
|
||||
}
|
||||
|
||||
UNIT_TEST(TransitSchedule_DatesInterval)
|
||||
{
|
||||
TestDatesInterval("20200902", "20210531",
|
||||
{
|
||||
false /* sunday */, true /* monday */, false /* tuesday */, true /* wednesday */,
|
||||
false /* thursday */, false /* friday */, false /* saturday */
|
||||
});
|
||||
|
||||
TestDatesInterval("20201101", "20201130",
|
||||
{
|
||||
true /* sunday */, false /* monday */, false /* tuesday */, false /* wednesday */,
|
||||
false /* thursday */, false /* friday */, true /* saturday */
|
||||
});
|
||||
|
||||
TestDatesInterval("20210101", "20210228",
|
||||
{
|
||||
false /* sunday */, true /* monday */, true /* tuesday */, true /* wednesday */,
|
||||
true /* thursday */, true /* friday */, false /* saturday */
|
||||
});
|
||||
|
||||
TestDatesInterval("20220101", "20240101",
|
||||
{
|
||||
false /* sunday */, false /* monday */, false /* tuesday */, false /* wednesday */,
|
||||
true /* thursday */, true /* friday */, true /* saturday */
|
||||
});
|
||||
}
|
||||
|
||||
UNIT_TEST(TransitSchedule_DateException)
|
||||
{
|
||||
TestDateException("20210316", true);
|
||||
TestDateException("20210101", false);
|
||||
TestDateException("20221231", true);
|
||||
TestDateException("20221231", false);
|
||||
}
|
||||
|
||||
UNIT_TEST(TransitSchedule_TimeInterval)
|
||||
{
|
||||
TestTimeInterval("14:30:00", "14:30:30");
|
||||
TestTimeInterval("00:00:00", "21:45:40");
|
||||
TestTimeInterval("07:10:30", "11:25:00");
|
||||
TestTimeInterval("13:00:00", "13:50:00");
|
||||
TestTimeInterval("23:40:50", "23:59:59");
|
||||
}
|
||||
|
||||
UNIT_TEST(TransitSchedule_FrequencyIntervals)
|
||||
{
|
||||
gtfs::Frequencies const frequencies{GetFrequency("14:40:00", "15:55:30", 600),
|
||||
GetFrequency("15:55:40", "17:00:00", 1200),
|
||||
GetFrequency("21:10:20", "22:45:40", 300)};
|
||||
|
||||
FrequencyIntervals const intervals(frequencies);
|
||||
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(13, 15, 30)), kDefaultFrequency, ());
|
||||
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(14, 55, 0)), 600, ());
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(15, 0, 55)), 600, ());
|
||||
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(15, 55, 39)), kDefaultFrequency, ());
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(15, 55, 40)), 1200, ());
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(16, 0, 0)), 1200, ());
|
||||
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(21, 50, 0)), 300, ());
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(22, 14, 20)), 300, ());
|
||||
TEST_EQUAL(intervals.GetFrequency(Time(22, 50, 10)), kDefaultFrequency, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TransitSchedule_Schedule_DatesInterval_Status)
|
||||
{
|
||||
gtfs::CalendarItem calendarItem1;
|
||||
calendarItem1.start_date = gtfs::Date("20200401");
|
||||
calendarItem1.end_date = gtfs::Date("20201120");
|
||||
calendarItem1.friday = gtfs::CalendarAvailability::Available;
|
||||
calendarItem1.saturday = gtfs::CalendarAvailability::Available;
|
||||
|
||||
gtfs::CalendarItem calendarItem2;
|
||||
calendarItem2.start_date = gtfs::Date("20201121");
|
||||
calendarItem2.end_date = gtfs::Date("20201231");
|
||||
calendarItem2.wednesday = gtfs::CalendarAvailability::Available;
|
||||
|
||||
gtfs::Frequencies const frequencies1{GetFrequency("12:10:30", "16:00:40", 4500),
|
||||
GetFrequency("18:00:00", "19:00:00", 1800)};
|
||||
|
||||
gtfs::Frequencies const frequencies2{GetFrequency("07:25:00", "12:10:00", 2700)};
|
||||
|
||||
Schedule busSchedule;
|
||||
busSchedule.AddDatesInterval(calendarItem1, frequencies1);
|
||||
busSchedule.AddDatesInterval(calendarItem2, frequencies2);
|
||||
|
||||
// 11.04.2020, saturday.
|
||||
TEST_EQUAL(busSchedule.GetStatus(time_t(1586600488)), Status::Open, ());
|
||||
// 12.04.2020, sunday.
|
||||
TEST_EQUAL(busSchedule.GetStatus(time_t(1586686888)), Status::Closed, ());
|
||||
// 07.08.2020, friday.
|
||||
TEST_EQUAL(busSchedule.GetStatus(time_t(1596795688)), Status::Open, ());
|
||||
// 08.08.2020, saturday.
|
||||
TEST_EQUAL(busSchedule.GetStatus(time_t(1596882088)), Status::Open, ());
|
||||
// 09.08.2020, sunday.
|
||||
TEST_EQUAL(busSchedule.GetStatus(time_t(1596968488)), Status::Closed, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(TransitSchedule_Schedule_DateException_Status)
|
||||
{
|
||||
gtfs::Frequencies const emptyFrequencies;
|
||||
|
||||
Schedule busSchedule;
|
||||
busSchedule.AddDateException(gtfs::Date("20200606"), gtfs::CalendarDateException::Added, emptyFrequencies);
|
||||
busSchedule.AddDateException(gtfs::Date("20200607"), gtfs::CalendarDateException::Removed, emptyFrequencies);
|
||||
busSchedule.AddDateException(gtfs::Date("20211029"), gtfs::CalendarDateException::Added, emptyFrequencies);
|
||||
busSchedule.AddDateException(gtfs::Date("20211128"), gtfs::CalendarDateException::Removed, emptyFrequencies);
|
||||
|
||||
// 06.06.2020.
|
||||
TEST(busSchedule.GetStatus(time_t(1591438888)) == Status::Open, ());
|
||||
// 07.06.2020.
|
||||
TEST(busSchedule.GetStatus(time_t(1591525288)) == Status::Closed, ());
|
||||
// 29.10.2021.
|
||||
TEST(busSchedule.GetStatus(time_t(1635502888)) == Status::Open, ());
|
||||
// 28.11.2021.
|
||||
TEST(busSchedule.GetStatus(time_t(1638094888)) == Status::Closed, ());
|
||||
// 01.03.2021.
|
||||
TEST(busSchedule.GetStatus(time_t(1614594088)) == Status::Unknown, ());
|
||||
// 01.01.2020.
|
||||
TEST(busSchedule.GetStatus(time_t(1577874088)) == Status::Unknown, ());
|
||||
}
|
||||
} // namespace transit_schedule_tests
|
||||
308
libs/transit/transit_tests/transit_test.cpp
Normal file
308
libs/transit/transit_tests/transit_test.cpp
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/transit_serdes.hpp"
|
||||
#include "transit/transit_types.hpp"
|
||||
#include "transit/transit_version.hpp"
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
#include "coding/writer.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
using namespace routing;
|
||||
using namespace routing::transit;
|
||||
using namespace std;
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
auto constexpr kTransitHeaderVersion = static_cast<uint16_t>(::transit::TransitVersion::OnlySubway);
|
||||
|
||||
template <class S, class D, class Obj>
|
||||
void TestCommonSerialization(Obj const & obj)
|
||||
{
|
||||
vector<uint8_t> buffer;
|
||||
MemWriter<vector<uint8_t>> writer(buffer);
|
||||
|
||||
S serializer(writer);
|
||||
serializer(obj);
|
||||
|
||||
MemReader reader(buffer.data(), buffer.size());
|
||||
ReaderSource<MemReader> src(reader);
|
||||
Obj deserializedObj;
|
||||
D deserializer(src);
|
||||
deserializer(deserializedObj);
|
||||
|
||||
TEST(obj.IsEqualForTesting(deserializedObj), (obj, deserializedObj));
|
||||
}
|
||||
|
||||
void TestSerialization(TransitHeader const & header)
|
||||
{
|
||||
TestCommonSerialization<FixedSizeSerializer<MemWriter<vector<uint8_t>>>,
|
||||
FixedSizeDeserializer<ReaderSource<MemReader>>>(header);
|
||||
}
|
||||
|
||||
template <class Obj>
|
||||
void TestSerialization(Obj const & obj)
|
||||
{
|
||||
TestCommonSerialization<Serializer<MemWriter<vector<uint8_t>>>, Deserializer<ReaderSource<MemReader>>>(obj);
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_HeaderRewriting)
|
||||
{
|
||||
TransitHeader const bigHeader(kTransitHeaderVersion /* version */, 500 /* stopsOffset */, 1000 /* gatesOffset */,
|
||||
200000 /* edgesOffset */, 300000 /* transfersOffset */, 400000 /* linesOffset */,
|
||||
5000000 /* shapesOffset */, 6000000 /* networksOffset */, 700000000 /* endOffset */);
|
||||
|
||||
TransitHeader header;
|
||||
vector<uint8_t> buffer;
|
||||
MemWriter<vector<uint8_t>> writer(buffer);
|
||||
|
||||
// Writing.
|
||||
FixedSizeSerializer<MemWriter<vector<uint8_t>>> serializer(writer);
|
||||
serializer(header);
|
||||
auto const endOffset = writer.Pos();
|
||||
|
||||
// Rewriting.
|
||||
header = bigHeader;
|
||||
|
||||
writer.Seek(0 /* start offset */);
|
||||
serializer(header);
|
||||
TEST_EQUAL(writer.Pos(), endOffset, ());
|
||||
|
||||
// Reading.
|
||||
MemReader reader(buffer.data(), buffer.size());
|
||||
ReaderSource<MemReader> src(reader);
|
||||
TransitHeader deserializedHeader;
|
||||
FixedSizeDeserializer<ReaderSource<MemReader>> deserializer(src);
|
||||
deserializer(deserializedHeader);
|
||||
|
||||
TEST(deserializedHeader.IsEqualForTesting(bigHeader), (deserializedHeader, bigHeader));
|
||||
}
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
|
||||
namespace
|
||||
{
|
||||
UNIT_TEST(Transit_CheckValidSortedUnique)
|
||||
{
|
||||
vector<Network> const networks = {Network(1 /* id */, "Title 1"), Network(2 /* id */, "Title 2")};
|
||||
CheckValidSortedUnique(networks, "networks");
|
||||
|
||||
vector<ShapeId> const shapeIds = {ShapeId(1, 2), ShapeId(1, 3)};
|
||||
CheckValidSortedUnique(shapeIds, "shapeIds");
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_HeaderSerialization)
|
||||
{
|
||||
{
|
||||
TransitHeader header;
|
||||
TestSerialization(header);
|
||||
TEST(header.IsValid(), (header));
|
||||
}
|
||||
{
|
||||
TransitHeader header(kTransitHeaderVersion /* version */, 500 /* stopsOffset */, 1000 /* gatesOffset */,
|
||||
2000 /* edgesOffset */, 3000 /* transfersOffset */, 4000 /* linesOffset */,
|
||||
5000 /* shapesOffset */, 6000 /* networksOffset */, 7000 /* endOffset */);
|
||||
TestSerialization(header);
|
||||
TEST(header.IsValid(), (header));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_TransitHeaderValidity)
|
||||
{
|
||||
{
|
||||
TransitHeader header;
|
||||
TEST(header.IsValid(), (header));
|
||||
}
|
||||
{
|
||||
TransitHeader const header(kTransitHeaderVersion /* version */, 40 /* stopsOffset */, 44 /* gatesOffset */,
|
||||
48 /* edgesOffset */, 52 /* transfersOffset */, 56 /* linesOffset */,
|
||||
60 /* shapesOffset */, 64 /* networksOffset */, 68 /* endOffset */);
|
||||
TEST(header.IsValid(), (header));
|
||||
}
|
||||
{
|
||||
TransitHeader const header(kTransitHeaderVersion /* version */, 44 /* stopsOffset */, 40 /* gatesOffset */,
|
||||
48 /* edgesOffset */, 52 /* transfersOffset */, 56 /* linesOffset */,
|
||||
60 /* shapesOffset */, 64 /* networksOffset */, 68 /* endOffset */);
|
||||
TEST(!header.IsValid(), (header));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_TitleAnchorSerialization)
|
||||
{
|
||||
{
|
||||
TitleAnchor anchor(17 /* min zoom */, 0 /* anchor */);
|
||||
TestSerialization(anchor);
|
||||
TEST(anchor.IsValid(), (anchor));
|
||||
}
|
||||
{
|
||||
TitleAnchor anchor(10 /* min zoom */, 2 /* anchor */);
|
||||
TestSerialization(anchor);
|
||||
TEST(anchor.IsValid(), (anchor));
|
||||
}
|
||||
{
|
||||
TitleAnchor anchor(18 /* min zoom */, 7 /* anchor */);
|
||||
TestSerialization(anchor);
|
||||
TEST(anchor.IsValid(), (anchor));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_StopSerialization)
|
||||
{
|
||||
{
|
||||
Stop stop;
|
||||
TestSerialization(stop);
|
||||
TEST(!stop.IsValid(), (stop));
|
||||
}
|
||||
{
|
||||
Stop stop(1234 /* id */, 1234567 /* osm id */, 5678 /* feature id */, 7 /* transfer id */,
|
||||
{7, 8, 9, 10} /* line id */, {55.0, 37.0} /* point */, {} /* anchors */);
|
||||
TestSerialization(stop);
|
||||
TEST(stop.IsValid(), (stop));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_SingleMwmSegmentSerialization)
|
||||
{
|
||||
{
|
||||
SingleMwmSegment s(12344 /* feature id */, 0 /* segmentIdx */, true /* forward */);
|
||||
TestSerialization(s);
|
||||
TEST(s.IsValid(), (s));
|
||||
}
|
||||
{
|
||||
SingleMwmSegment s(12544 /* feature id */, 5 /* segmentIdx */, false /* forward */);
|
||||
TestSerialization(s);
|
||||
TEST(s.IsValid(), (s));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GateSerialization)
|
||||
{
|
||||
Gate gate(12345678 /* osm id */, 12345 /* feature id */, true /* entrance */, false /* exit */, 117 /* weight */,
|
||||
{1, 2, 3} /* stop ids */, {30.0, 50.0} /* point */);
|
||||
TestSerialization(gate);
|
||||
TEST(gate.IsValid(), (gate));
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GatesRelational)
|
||||
{
|
||||
vector<Gate> const gates = {{1234567 /* osm id */,
|
||||
123 /* feature id */,
|
||||
true /* entrance */,
|
||||
false /* exit */,
|
||||
1 /* weight */,
|
||||
{1, 2, 3} /* stops ids */,
|
||||
m2::PointD(0.0, 0.0)},
|
||||
{12345678 /* osm id */,
|
||||
1234 /* feature id */,
|
||||
true /* entrance */,
|
||||
false /* exit */,
|
||||
1 /* weight */,
|
||||
{1, 2, 3} /* stops ids */,
|
||||
m2::PointD(0.0, 0.0)},
|
||||
{12345678 /* osm id */,
|
||||
1234 /* feature id */,
|
||||
true /* entrance */,
|
||||
false /* exit */,
|
||||
1 /* weight */,
|
||||
{1, 2, 3, 4} /* stops ids */,
|
||||
m2::PointD(0.0, 0.0)}};
|
||||
TEST(is_sorted(gates.cbegin(), gates.cend()), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_EdgeSerialization)
|
||||
{
|
||||
{
|
||||
Edge edge(1 /* start stop id */, 2 /* finish stop id */, 123 /* weight */, 11 /* line id */, false /* transfer */,
|
||||
{ShapeId(1, 2), ShapeId(3, 4), ShapeId(5, 6)} /* shape ids */);
|
||||
TestSerialization(edge);
|
||||
TEST(edge.IsValid(), (edge));
|
||||
}
|
||||
{
|
||||
Edge edge(1 /* start stop id */, 2 /* finish stop id */, 123 /* weight */, 11 /* line id */, false /* transfer */,
|
||||
{ShapeId(1, 2)} /* shape ids */);
|
||||
TestSerialization(edge);
|
||||
TEST(edge.IsValid(), (edge));
|
||||
}
|
||||
{
|
||||
Edge edge(1 /* start stop id */, 2 /* finish stop id */, 123 /* weight */, 11 /* line id */, false /* transfer */,
|
||||
{ShapeId(2, 1)} /* shape ids */);
|
||||
TestSerialization(edge);
|
||||
TEST(edge.IsValid(), (edge));
|
||||
}
|
||||
{
|
||||
Edge edge(1 /* start stop id */, 2 /* finish stop id */, 123 /* weight */, kInvalidLineId, true /* transfer */,
|
||||
{} /* shape ids */);
|
||||
TestSerialization(edge);
|
||||
TEST(edge.IsValid(), (edge));
|
||||
}
|
||||
{
|
||||
Edge edge(1 /* start stop id */, 2 /* finish stop id */, 123 /* weight */, 11 /* line id */, true /* transfer */,
|
||||
{} /* shape ids */);
|
||||
TestSerialization(edge);
|
||||
// Note. A transfer edge (transfer == true) with a valid line id is not allowable.
|
||||
TEST(!edge.IsValid(), (edge));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_TransferSerialization)
|
||||
{
|
||||
Transfer transfer(1 /* id */, {40.0, 35.0} /* point */, {1, 2, 3} /* stop ids */, {TitleAnchor(16, 0 /* anchor */)});
|
||||
TestSerialization(transfer);
|
||||
TEST(transfer.IsValid(), (transfer));
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_LineSerialization)
|
||||
{
|
||||
{
|
||||
Line line(1 /* line id */, "2" /* number */, "Линия" /* title */, "subway" /* type */, "red" /* color */,
|
||||
3 /* network id */, {{1}} /* stop ids */, 10 /* interval */);
|
||||
TestSerialization(line);
|
||||
TEST(line.IsValid(), (line));
|
||||
}
|
||||
{
|
||||
Line line(10 /* line id */, "11" /* number */, "Линия" /* title */, "subway" /* type */, "green" /* color */,
|
||||
12 /* network id */, {{13, 14, 15}} /* stop ids */, 15 /* interval */);
|
||||
TestSerialization(line);
|
||||
TEST(line.IsValid(), (line));
|
||||
}
|
||||
{
|
||||
Line line(100 /* line id */, "101" /* number */, "Линия" /* title */, "subway" /* type */, "blue" /* color */,
|
||||
103 /* network id */, {{1, 2, 3}, {7, 8, 9}} /* stop ids */, 15 /* interval */);
|
||||
TestSerialization(line);
|
||||
TEST(line.IsValid(), (line));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_ShapeSerialization)
|
||||
{
|
||||
{
|
||||
Shape shape(ShapeId(10, 20), {m2::PointD(0.0, 20.0), m2::PointD(0.0, 0.0)} /* polyline */);
|
||||
TestSerialization(shape);
|
||||
TEST(shape.IsValid(), (shape));
|
||||
}
|
||||
{
|
||||
Shape shape(ShapeId(11, 21),
|
||||
{m2::PointD(20.0, 20.0), m2::PointD(21.0, 21.0), m2::PointD(22.0, 22.0)} /* polyline */);
|
||||
TestSerialization(shape);
|
||||
TEST(shape.IsValid(), (shape));
|
||||
}
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_ShapeIdRelational)
|
||||
{
|
||||
vector<ShapeId> const ids = {{0, 10}, {0, 11}, {1, 10}, {1, 11}};
|
||||
TEST(is_sorted(ids.cbegin(), ids.cend()), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_NetworkSerialization)
|
||||
{
|
||||
Network network(0 /* network id */, "Title" /* title */);
|
||||
TestSerialization(network);
|
||||
TEST(network.IsValid(), (network));
|
||||
}
|
||||
} // namespace
|
||||
107
libs/transit/transit_tests/transit_tools.hpp
Normal file
107
libs/transit/transit_tests/transit_tools.hpp
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
#pragma once
|
||||
|
||||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/experimental/transit_types_experimental.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
template <typename Obj>
|
||||
void TestForEquivalence(std::vector<Obj> const & actual, std::vector<Obj> const & expected)
|
||||
{
|
||||
TEST_EQUAL(actual.size(), expected.size(), ());
|
||||
for (size_t i = 0; i < actual.size(); ++i)
|
||||
TEST(actual[i].IsEqualForTesting(expected[i]), (i, actual[i], expected[i]));
|
||||
}
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
|
||||
namespace transit
|
||||
{
|
||||
namespace experimental
|
||||
{
|
||||
double constexpr kPointsEqualEpsilon = 1e-5;
|
||||
|
||||
inline bool Equal(SingleMwmSegment const & s1, SingleMwmSegment const & s2)
|
||||
{
|
||||
return std::make_tuple(s1.GetFeatureId(), s1.GetSegmentIdx(), s1.IsForward()) ==
|
||||
std::make_tuple(s2.GetFeatureId(), s2.GetSegmentIdx(), s2.IsForward());
|
||||
}
|
||||
|
||||
inline bool Equal(Network const & n1, Network const & n2)
|
||||
{
|
||||
return std::make_tuple(n1.GetId(), n1.GetTitle()) == std::make_tuple(n2.GetId(), n2.GetTitle());
|
||||
}
|
||||
|
||||
inline bool Equal(Route const & r1, Route const & r2)
|
||||
{
|
||||
return std::make_tuple(r1.GetId(), r1.GetNetworkId(), r1.GetType(), r1.GetTitle(), r1.GetColor()) ==
|
||||
std::make_tuple(r2.GetId(), r2.GetNetworkId(), r2.GetType(), r2.GetTitle(), r2.GetColor());
|
||||
}
|
||||
|
||||
inline bool Equal(Line const & l1, Line const & l2)
|
||||
{
|
||||
return std::make_tuple(l1.GetId(), l1.GetRouteId(), l1.GetTitle(), l1.GetStopIds(), l1.GetSchedule(),
|
||||
l1.GetShapeLink()) == std::make_tuple(l2.GetId(), l2.GetRouteId(), l2.GetTitle(),
|
||||
l2.GetStopIds(), l2.GetSchedule(), l2.GetShapeLink());
|
||||
}
|
||||
|
||||
inline bool Equal(LineMetadata const & lm1, LineMetadata const & lm2)
|
||||
{
|
||||
return std::make_tuple(lm1.GetId(), lm1.GetLineSegmentsOrder()) ==
|
||||
std::make_tuple(lm2.GetId(), lm2.GetLineSegmentsOrder());
|
||||
}
|
||||
|
||||
inline bool Equal(Stop const & s1, Stop const & s2)
|
||||
{
|
||||
return (std::make_tuple(s1.GetId(), s1.GetFeatureId(), s1.GetOsmId(), s1.GetTitle(), s1.GetTimeTable(),
|
||||
s1.GetTransferIds(), s1.GetBestPedestrianSegments()) ==
|
||||
std::make_tuple(s2.GetId(), s2.GetFeatureId(), s2.GetOsmId(), s2.GetTitle(), s2.GetTimeTable(),
|
||||
s2.GetTransferIds(), s2.GetBestPedestrianSegments())) &&
|
||||
AlmostEqualAbs(s1.GetPoint(), s2.GetPoint(), kPointsEqualEpsilon);
|
||||
}
|
||||
|
||||
inline bool Equal(Gate const & g1, Gate const & g2)
|
||||
{
|
||||
return (std::make_tuple(g1.GetId(), g1.GetFeatureId(), g1.GetOsmId(), g1.IsEntrance(), g1.IsExit(),
|
||||
g1.GetStopsWithWeight(), g1.GetBestPedestrianSegments()) ==
|
||||
std::make_tuple(g2.GetId(), g2.GetFeatureId(), g2.GetOsmId(), g2.IsEntrance(), g2.IsExit(),
|
||||
g2.GetStopsWithWeight(), g2.GetBestPedestrianSegments())) &&
|
||||
AlmostEqualAbs(g1.GetPoint(), g2.GetPoint(), kPointsEqualEpsilon);
|
||||
}
|
||||
|
||||
inline bool Equal(Edge const & e1, Edge const & e2)
|
||||
{
|
||||
return (std::make_tuple(e1.GetStop1Id(), e1.GetStop2Id(), e1.GetLineId(), e1.GetWeight(), e1.IsTransfer(),
|
||||
e1.GetShapeLink()) == std::make_tuple(e2.GetStop1Id(), e2.GetStop2Id(), e2.GetLineId(),
|
||||
e2.GetWeight(), e2.IsTransfer(), e2.GetShapeLink()));
|
||||
}
|
||||
|
||||
inline bool Equal(Transfer const & t1, Transfer const & t2)
|
||||
{
|
||||
return (std::make_tuple(t1.GetId(), t1.GetStopIds()) == std::make_tuple(t2.GetId(), t2.GetStopIds())) &&
|
||||
AlmostEqualAbs(t1.GetPoint(), t2.GetPoint(), kPointsEqualEpsilon);
|
||||
}
|
||||
|
||||
inline bool Equal(Shape const & s1, Shape const & s2)
|
||||
{
|
||||
return std::make_tuple(s1.GetId(), s1.GetPolyline()) == std::make_tuple(s2.GetId(), s2.GetPolyline());
|
||||
}
|
||||
|
||||
template <class Item>
|
||||
void TestEqual(std::vector<Item> const & actual, std::vector<Item> const & expected)
|
||||
{
|
||||
TEST_EQUAL(actual.size(), expected.size(), ());
|
||||
for (size_t i = 0; i < actual.size(); ++i)
|
||||
TEST(Equal(actual[i], expected[i]), (i, actual[i], expected[i]));
|
||||
}
|
||||
} // namespace experimental
|
||||
} // namespace transit
|
||||
374
libs/transit/transit_types.cpp
Normal file
374
libs/transit/transit_types.cpp
Normal file
|
|
@ -0,0 +1,374 @@
|
|||
#include "transit/transit_types.hpp"
|
||||
|
||||
#include "transit/transit_serdes.hpp"
|
||||
#include "transit/transit_version.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
double constexpr kPointsEqualEpsilon = 1e-6;
|
||||
} // namespace
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
// TransitHeader ----------------------------------------------------------------------------------
|
||||
TransitHeader::TransitHeader(uint16_t version, uint32_t stopsOffset, uint32_t gatesOffset, uint32_t edgesOffset,
|
||||
uint32_t transfersOffset, uint32_t linesOffset, uint32_t shapesOffset,
|
||||
uint32_t networksOffset, uint32_t endOffset)
|
||||
: m_version(version)
|
||||
, m_reserve(0)
|
||||
, m_stopsOffset(stopsOffset)
|
||||
, m_gatesOffset(gatesOffset)
|
||||
, m_edgesOffset(edgesOffset)
|
||||
, m_transfersOffset(transfersOffset)
|
||||
, m_linesOffset(linesOffset)
|
||||
, m_shapesOffset(shapesOffset)
|
||||
, m_networksOffset(networksOffset)
|
||||
, m_endOffset(endOffset)
|
||||
{}
|
||||
|
||||
void TransitHeader::Reset()
|
||||
{
|
||||
m_version = static_cast<uint16_t>(::transit::TransitVersion::OnlySubway);
|
||||
m_reserve = 0;
|
||||
m_stopsOffset = 0;
|
||||
m_gatesOffset = 0;
|
||||
m_edgesOffset = 0;
|
||||
m_transfersOffset = 0;
|
||||
m_linesOffset = 0;
|
||||
m_shapesOffset = 0;
|
||||
m_networksOffset = 0;
|
||||
m_endOffset = 0;
|
||||
}
|
||||
|
||||
bool TransitHeader::IsEqualForTesting(TransitHeader const & header) const
|
||||
{
|
||||
return m_version == header.m_version && m_reserve == header.m_reserve && m_stopsOffset == header.m_stopsOffset &&
|
||||
m_gatesOffset == header.m_gatesOffset && m_edgesOffset == header.m_edgesOffset &&
|
||||
m_transfersOffset == header.m_transfersOffset && m_linesOffset == header.m_linesOffset &&
|
||||
m_shapesOffset == header.m_shapesOffset && m_networksOffset == header.m_networksOffset &&
|
||||
m_endOffset == header.m_endOffset;
|
||||
}
|
||||
|
||||
bool TransitHeader::IsValid() const
|
||||
{
|
||||
return m_stopsOffset <= m_gatesOffset && m_gatesOffset <= m_edgesOffset && m_edgesOffset <= m_transfersOffset &&
|
||||
m_transfersOffset <= m_linesOffset && m_linesOffset <= m_shapesOffset && m_shapesOffset <= m_networksOffset &&
|
||||
m_networksOffset <= m_endOffset;
|
||||
}
|
||||
|
||||
// FeatureIdentifiers -----------------------------------------------------------------------------
|
||||
FeatureIdentifiers::FeatureIdentifiers(bool serializeFeatureIdOnly) : m_serializeFeatureIdOnly(serializeFeatureIdOnly)
|
||||
{}
|
||||
|
||||
FeatureIdentifiers::FeatureIdentifiers(OsmId osmId, FeatureId const & featureId, bool serializeFeatureIdOnly)
|
||||
: m_osmId(osmId)
|
||||
, m_featureId(featureId)
|
||||
, m_serializeFeatureIdOnly(serializeFeatureIdOnly)
|
||||
{}
|
||||
|
||||
bool FeatureIdentifiers::operator<(FeatureIdentifiers const & rhs) const
|
||||
{
|
||||
CHECK_EQUAL(m_serializeFeatureIdOnly, rhs.m_serializeFeatureIdOnly, ());
|
||||
if (m_serializeFeatureIdOnly)
|
||||
return m_featureId < rhs.m_featureId;
|
||||
|
||||
if (m_featureId != rhs.m_featureId)
|
||||
return m_featureId < rhs.m_featureId;
|
||||
return m_osmId < rhs.m_osmId;
|
||||
}
|
||||
|
||||
bool FeatureIdentifiers::operator==(FeatureIdentifiers const & rhs) const
|
||||
{
|
||||
CHECK_EQUAL(m_serializeFeatureIdOnly, rhs.m_serializeFeatureIdOnly, ());
|
||||
return m_serializeFeatureIdOnly ? m_featureId == rhs.m_featureId
|
||||
: m_osmId == rhs.m_osmId && m_featureId == rhs.m_featureId;
|
||||
}
|
||||
|
||||
bool FeatureIdentifiers::IsValid() const
|
||||
{
|
||||
return m_serializeFeatureIdOnly ? m_featureId != kInvalidFeatureId
|
||||
: m_osmId != kInvalidOsmId && m_featureId != kInvalidFeatureId;
|
||||
}
|
||||
|
||||
// TitleAnchor ------------------------------------------------------------------------------------
|
||||
TitleAnchor::TitleAnchor(uint8_t minZoom, Anchor anchor) : m_minZoom(minZoom), m_anchor(anchor) {}
|
||||
|
||||
bool TitleAnchor::operator==(TitleAnchor const & titleAnchor) const
|
||||
{
|
||||
return m_minZoom == titleAnchor.m_minZoom && m_anchor == titleAnchor.m_anchor;
|
||||
}
|
||||
|
||||
bool TitleAnchor::IsEqualForTesting(TitleAnchor const & titleAnchor) const
|
||||
{
|
||||
return *this == titleAnchor;
|
||||
}
|
||||
|
||||
bool TitleAnchor::IsValid() const
|
||||
{
|
||||
return m_anchor != kInvalidAnchor;
|
||||
}
|
||||
|
||||
// Stop -------------------------------------------------------------------------------------------
|
||||
Stop::Stop(StopId id, OsmId osmId, FeatureId featureId, TransferId transferId, std::vector<LineId> const & lineIds,
|
||||
m2::PointD const & point, std::vector<TitleAnchor> const & titleAnchors)
|
||||
: m_id(id)
|
||||
, m_featureIdentifiers(osmId, featureId, true /* serializeFeatureIdOnly */)
|
||||
, m_transferId(transferId)
|
||||
, m_lineIds(lineIds)
|
||||
, m_point(point)
|
||||
, m_titleAnchors(titleAnchors)
|
||||
{}
|
||||
|
||||
bool Stop::IsEqualForTesting(Stop const & stop) const
|
||||
{
|
||||
double constexpr kPointsEqualEpsilon = 1e-6;
|
||||
return m_id == stop.m_id && m_featureIdentifiers == stop.m_featureIdentifiers && m_transferId == stop.m_transferId &&
|
||||
m_lineIds == stop.m_lineIds && AlmostEqualAbs(m_point, stop.m_point, kPointsEqualEpsilon) &&
|
||||
m_titleAnchors == stop.m_titleAnchors;
|
||||
}
|
||||
|
||||
bool Stop::IsValid() const
|
||||
{
|
||||
return m_id.Get() != kInvalidStopId && !m_lineIds.empty();
|
||||
}
|
||||
|
||||
// SingleMwmSegment -------------------------------------------------------------------------------
|
||||
SingleMwmSegment::SingleMwmSegment(FeatureId featureId, uint32_t segmentIdx, bool forward)
|
||||
: m_featureId(featureId)
|
||||
, m_segmentIdx(segmentIdx)
|
||||
, m_forward(forward)
|
||||
{}
|
||||
|
||||
bool SingleMwmSegment::IsEqualForTesting(SingleMwmSegment const & s) const
|
||||
{
|
||||
return m_featureId == s.m_featureId && m_segmentIdx == s.m_segmentIdx && m_forward == s.m_forward;
|
||||
}
|
||||
|
||||
bool SingleMwmSegment::IsValid() const
|
||||
{
|
||||
return m_featureId != kInvalidFeatureId;
|
||||
}
|
||||
|
||||
// Gate -------------------------------------------------------------------------------------------
|
||||
Gate::Gate(OsmId osmId, FeatureId featureId, bool entrance, bool exit, Weight weight,
|
||||
std::vector<StopId> const & stopIds, m2::PointD const & point)
|
||||
: m_featureIdentifiers(osmId, featureId, false /* serializeFeatureIdOnly */)
|
||||
, m_entrance(entrance)
|
||||
, m_exit(exit)
|
||||
, m_weight(weight)
|
||||
, m_stopIds(stopIds)
|
||||
, m_point(point)
|
||||
{}
|
||||
|
||||
bool Gate::operator<(Gate const & rhs) const
|
||||
{
|
||||
if (m_featureIdentifiers != rhs.m_featureIdentifiers)
|
||||
return m_featureIdentifiers < rhs.m_featureIdentifiers;
|
||||
|
||||
if (m_entrance != rhs.m_entrance)
|
||||
return m_entrance < rhs.m_entrance;
|
||||
|
||||
if (m_exit != rhs.m_exit)
|
||||
return m_exit < rhs.m_exit;
|
||||
|
||||
return m_stopIds < rhs.m_stopIds;
|
||||
}
|
||||
|
||||
bool Gate::operator==(Gate const & rhs) const
|
||||
{
|
||||
return m_featureIdentifiers == rhs.m_featureIdentifiers && m_entrance == rhs.m_entrance && m_exit == rhs.m_exit &&
|
||||
m_stopIds == rhs.m_stopIds;
|
||||
}
|
||||
|
||||
bool Gate::IsEqualForTesting(Gate const & gate) const
|
||||
{
|
||||
return m_featureIdentifiers == gate.m_featureIdentifiers && m_entrance == gate.m_entrance && m_exit == gate.m_exit &&
|
||||
m_weight == gate.m_weight && m_stopIds == gate.m_stopIds &&
|
||||
AlmostEqualAbs(m_point, gate.m_point, kPointsEqualEpsilon);
|
||||
}
|
||||
|
||||
bool Gate::IsValid() const
|
||||
{
|
||||
return m_featureIdentifiers.GetOsmId() != kInvalidOsmId && m_weight != kInvalidWeight && (m_entrance || m_exit) &&
|
||||
!m_stopIds.empty();
|
||||
}
|
||||
|
||||
// ShapeId ----------------------------------------------------------------------------------------
|
||||
bool ShapeId::operator<(ShapeId const & rhs) const
|
||||
{
|
||||
if (m_stop1Id != rhs.m_stop1Id)
|
||||
return m_stop1Id < rhs.m_stop1Id;
|
||||
return m_stop2Id < rhs.m_stop2Id;
|
||||
}
|
||||
|
||||
bool ShapeId::operator==(ShapeId const & rhs) const
|
||||
{
|
||||
return m_stop1Id == rhs.m_stop1Id && m_stop2Id == rhs.m_stop2Id;
|
||||
}
|
||||
|
||||
bool ShapeId::IsValid() const
|
||||
{
|
||||
return m_stop1Id != kInvalidStopId && m_stop2Id != kInvalidStopId;
|
||||
}
|
||||
|
||||
// EdgeFlags --------------------------------------------------------------------------------------
|
||||
uint8_t EdgeFlags::GetFlags() const
|
||||
{
|
||||
return BoolToUint(m_transfer) * kTransferMask + BoolToUint(m_isShapeIdsEmpty) * kEmptyShapeIdsMask +
|
||||
BoolToUint(m_isShapeIdsSingle) * kSingleShapeIdMask + BoolToUint(m_isShapeIdsSame) * kShapeIdIsSameMask +
|
||||
BoolToUint(m_isShapeIdsReversed) * kShapeIdIsReversedMask;
|
||||
}
|
||||
|
||||
void EdgeFlags::SetFlags(uint8_t flags)
|
||||
{
|
||||
m_transfer = GetBit(flags, kTransferMask);
|
||||
m_isShapeIdsEmpty = GetBit(flags, kEmptyShapeIdsMask);
|
||||
m_isShapeIdsSingle = GetBit(flags, kSingleShapeIdMask);
|
||||
m_isShapeIdsSame = GetBit(flags, kShapeIdIsSameMask);
|
||||
m_isShapeIdsReversed = GetBit(flags, kShapeIdIsReversedMask);
|
||||
}
|
||||
|
||||
std::string DebugPrint(EdgeFlags const & f)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << "EdgeFlags [transfer:" << f.m_transfer;
|
||||
ss << ", isShapeIdsEmpty:" << f.m_isShapeIdsEmpty;
|
||||
ss << ", isShapeIdsSingle:" << f.m_isShapeIdsSingle;
|
||||
ss << ", isShapeIdsSame:" << f.m_isShapeIdsSame;
|
||||
ss << ", isShapeIdsReversed:" << f.m_isShapeIdsReversed << "]";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
// Edge -------------------------------------------------------------------------------------------
|
||||
Edge::Edge(StopId stop1Id, StopId stop2Id, Weight weight, LineId lineId, bool transfer,
|
||||
std::vector<ShapeId> const & shapeIds)
|
||||
: m_stop1Id(stop1Id)
|
||||
, m_stop2Id(stop2Id)
|
||||
, m_weight(weight)
|
||||
, m_lineId(lineId)
|
||||
, m_flags(GetEdgeFlags(transfer, stop1Id, stop2Id, shapeIds))
|
||||
, m_shapeIds(shapeIds)
|
||||
{}
|
||||
|
||||
bool Edge::operator<(Edge const & rhs) const
|
||||
{
|
||||
if (m_stop1Id != rhs.m_stop1Id)
|
||||
return m_stop1Id < rhs.m_stop1Id;
|
||||
if (m_stop2Id != rhs.m_stop2Id)
|
||||
return m_stop2Id < rhs.m_stop2Id;
|
||||
return m_lineId < rhs.m_lineId;
|
||||
}
|
||||
|
||||
bool Edge::operator==(Edge const & rhs) const
|
||||
{
|
||||
return m_stop1Id == rhs.m_stop1Id && m_stop2Id == rhs.m_stop2Id && m_lineId == rhs.m_lineId;
|
||||
}
|
||||
|
||||
bool Edge::IsEqualForTesting(Edge const & edge) const
|
||||
{
|
||||
return m_stop1Id == edge.m_stop1Id && m_stop2Id == edge.m_stop2Id && m_weight == edge.m_weight &&
|
||||
m_lineId == edge.m_lineId && GetTransfer() == edge.GetTransfer() && m_shapeIds == edge.m_shapeIds;
|
||||
}
|
||||
|
||||
bool Edge::IsValid() const
|
||||
{
|
||||
if (GetTransfer() && (m_lineId != kInvalidLineId || !m_shapeIds.empty()))
|
||||
return false;
|
||||
|
||||
if (!GetTransfer() && m_lineId == kInvalidLineId)
|
||||
return false;
|
||||
|
||||
return m_stop1Id.Get() != kInvalidStopId && m_stop2Id != kInvalidStopId && m_weight != kInvalidWeight;
|
||||
}
|
||||
|
||||
// Transfer ---------------------------------------------------------------------------------------
|
||||
Transfer::Transfer(StopId id, m2::PointD const & point, std::vector<StopId> const & stopIds,
|
||||
std::vector<TitleAnchor> const & titleAnchors)
|
||||
: m_id(id)
|
||||
, m_point(point)
|
||||
, m_stopIds(stopIds)
|
||||
, m_titleAnchors(titleAnchors)
|
||||
{}
|
||||
|
||||
bool Transfer::IsEqualForTesting(Transfer const & transfer) const
|
||||
{
|
||||
return m_id == transfer.m_id && AlmostEqualAbs(m_point, transfer.m_point, kPointsEqualEpsilon) &&
|
||||
m_stopIds == transfer.m_stopIds && m_titleAnchors == transfer.m_titleAnchors;
|
||||
}
|
||||
|
||||
bool Transfer::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidStopId && !m_stopIds.empty();
|
||||
}
|
||||
|
||||
// Line -------------------------------------------------------------------------------------------
|
||||
Line::Line(LineId id, std::string const & number, std::string const & title, std::string const & type,
|
||||
std::string const & color, NetworkId networkId, Ranges const & stopIds, Weight interval)
|
||||
: m_id(id)
|
||||
, m_number(number)
|
||||
, m_title(title)
|
||||
, m_type(type)
|
||||
, m_color(color)
|
||||
, m_networkId(networkId)
|
||||
, m_stopIds(stopIds)
|
||||
, m_interval(interval)
|
||||
{}
|
||||
|
||||
bool Line::IsEqualForTesting(Line const & line) const
|
||||
{
|
||||
return m_id == line.m_id && m_number == line.m_number && m_title == line.m_title && m_type == line.m_type &&
|
||||
m_color == line.m_color && m_networkId == line.m_networkId && m_stopIds == line.m_stopIds &&
|
||||
m_interval == line.m_interval;
|
||||
}
|
||||
|
||||
bool Line::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidLineId && m_color != kInvalidColor && m_networkId != kInvalidNetworkId &&
|
||||
m_stopIds.IsValid() && m_interval != kInvalidWeight;
|
||||
}
|
||||
|
||||
// Shape ------------------------------------------------------------------------------------------
|
||||
bool Shape::IsEqualForTesting(Shape const & shape) const
|
||||
{
|
||||
if (!m_id.IsEqualForTesting(shape.m_id) || m_polyline.size() != shape.m_polyline.size())
|
||||
return false;
|
||||
|
||||
for (size_t i = 0; i < m_polyline.size(); ++i)
|
||||
if (!AlmostEqualAbs(m_polyline[i], shape.m_polyline[i], kPointsEqualEpsilon))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Network ----------------------------------------------------------------------------------------
|
||||
Network::Network(NetworkId id, std::string const & title) : m_id(id), m_title(title) {}
|
||||
|
||||
bool Network::IsEqualForTesting(Network const & shape) const
|
||||
{
|
||||
return m_id == shape.m_id && m_title == shape.m_title;
|
||||
}
|
||||
|
||||
bool Network::IsValid() const
|
||||
{
|
||||
return m_id != kInvalidNetworkId;
|
||||
}
|
||||
|
||||
EdgeFlags GetEdgeFlags(bool transfer, StopId stopId1, StopId stopId2, std::vector<ShapeId> const & shapeIds)
|
||||
{
|
||||
EdgeFlags flags;
|
||||
flags.m_transfer = transfer;
|
||||
flags.m_isShapeIdsEmpty = shapeIds.empty();
|
||||
bool const singleShapeId = (shapeIds.size() == 1);
|
||||
flags.m_isShapeIdsSingle = singleShapeId;
|
||||
if (singleShapeId)
|
||||
{
|
||||
flags.m_isShapeIdsSame = stopId1 == shapeIds[0].GetStop1Id() && stopId2 == shapeIds[0].GetStop2Id();
|
||||
flags.m_isShapeIdsReversed = stopId1 == shapeIds[0].GetStop2Id() && stopId2 == shapeIds[0].GetStop1Id();
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
493
libs/transit/transit_types.hpp
Normal file
493
libs/transit/transit_types.hpp
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
#pragma once
|
||||
|
||||
#include "indexer/feature_decl.hpp"
|
||||
#include "indexer/scales.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/newtype.hpp"
|
||||
#include "base/visitor.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace routing
|
||||
{
|
||||
namespace transit
|
||||
{
|
||||
using Anchor = uint8_t;
|
||||
using FeatureId = uint32_t;
|
||||
using LineId = uint32_t;
|
||||
using NetworkId = uint32_t;
|
||||
using OsmId = uint64_t;
|
||||
using StopId = uint64_t;
|
||||
using TransferId = uint64_t;
|
||||
using Weight = int32_t;
|
||||
using Ranges = std::vector<std::vector<StopId>>;
|
||||
|
||||
Anchor constexpr kInvalidAnchor = std::numeric_limits<Anchor>::max();
|
||||
std::string const kInvalidColor = std::string("");
|
||||
LineId constexpr kInvalidLineId = std::numeric_limits<LineId>::max();
|
||||
NetworkId constexpr kInvalidNetworkId = std::numeric_limits<NetworkId>::max();
|
||||
OsmId constexpr kInvalidOsmId = std::numeric_limits<OsmId>::max();
|
||||
StopId constexpr kInvalidStopId = std::numeric_limits<StopId>::max();
|
||||
TransferId constexpr kInvalidTransferId = std::numeric_limits<TransferId>::max();
|
||||
Weight constexpr kInvalidWeight = std::numeric_limits<Weight>::max();
|
||||
|
||||
#define DECLARE_TRANSIT_TYPE_FRIENDS \
|
||||
template <class Sink> \
|
||||
friend class Serializer; \
|
||||
template <class Source> \
|
||||
friend class Deserializer; \
|
||||
friend class DeserializerFromJson; \
|
||||
template <typename Sink> \
|
||||
friend class FixedSizeSerializer; \
|
||||
template <typename Sink> \
|
||||
friend class FixedSizeDeserializer;
|
||||
|
||||
struct TransitHeader
|
||||
{
|
||||
TransitHeader() { Reset(); }
|
||||
TransitHeader(uint16_t version, uint32_t stopsOffset, uint32_t gatesOffset, uint32_t edgesOffset,
|
||||
uint32_t transfersOffset, uint32_t linesOffset, uint32_t shapesOffset, uint32_t networksOffset,
|
||||
uint32_t endOffset);
|
||||
|
||||
void Reset();
|
||||
bool IsEqualForTesting(TransitHeader const & header) const;
|
||||
bool IsValid() const;
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(TransitHeader, visitor(m_version, "version"), visitor(m_reserve, "reserve"),
|
||||
visitor(m_stopsOffset, "stops"), visitor(m_gatesOffset, "gatesOffset"),
|
||||
visitor(m_edgesOffset, "edgesOffset"), visitor(m_transfersOffset, "transfersOffset"),
|
||||
visitor(m_linesOffset, "linesOffset"), visitor(m_shapesOffset, "shapesOffset"),
|
||||
visitor(m_networksOffset, "networksOffset"), visitor(m_endOffset, "endOffset"))
|
||||
|
||||
public:
|
||||
uint16_t m_version;
|
||||
uint16_t m_reserve;
|
||||
uint32_t m_stopsOffset;
|
||||
uint32_t m_gatesOffset;
|
||||
uint32_t m_edgesOffset;
|
||||
uint32_t m_transfersOffset;
|
||||
uint32_t m_linesOffset;
|
||||
uint32_t m_shapesOffset;
|
||||
uint32_t m_networksOffset;
|
||||
uint32_t m_endOffset;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TransitHeader) == 36, "Wrong header size of transit section.");
|
||||
|
||||
/// \brief This class represents osm id and feature id of the same feature.
|
||||
class FeatureIdentifiers
|
||||
{
|
||||
public:
|
||||
explicit FeatureIdentifiers(bool serializeFeatureIdOnly);
|
||||
FeatureIdentifiers(OsmId osmId, FeatureId const & featureId, bool serializeFeatureIdOnly);
|
||||
|
||||
bool operator<(FeatureIdentifiers const & rhs) const;
|
||||
bool operator==(FeatureIdentifiers const & rhs) const;
|
||||
bool operator!=(FeatureIdentifiers const & rhs) const { return !(*this == rhs); }
|
||||
bool IsValid() const;
|
||||
void SetOsmId(OsmId osmId) { m_osmId = osmId; }
|
||||
void SetFeatureId(FeatureId featureId) { m_featureId = featureId; }
|
||||
|
||||
OsmId GetOsmId() const { return m_osmId; }
|
||||
FeatureId GetFeatureId() const { return m_featureId; }
|
||||
bool IsSerializeFeatureIdOnly() const { return m_serializeFeatureIdOnly; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(FeatureIdentifiers, visitor(m_osmId, "osm_id"), visitor(m_featureId, "feature_id"))
|
||||
|
||||
OsmId m_osmId = kInvalidOsmId;
|
||||
FeatureId m_featureId = kInvalidFeatureId;
|
||||
bool m_serializeFeatureIdOnly = true;
|
||||
};
|
||||
|
||||
class TitleAnchor
|
||||
{
|
||||
public:
|
||||
TitleAnchor() = default;
|
||||
TitleAnchor(uint8_t minZoom, Anchor anchor);
|
||||
|
||||
bool operator==(TitleAnchor const & titleAnchor) const;
|
||||
bool IsEqualForTesting(TitleAnchor const & titleAnchor) const;
|
||||
bool IsValid() const;
|
||||
|
||||
uint8_t GetMinZoom() const { return m_minZoom; }
|
||||
Anchor const & GetAnchor() const { return m_anchor; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(TitleAnchor, visitor(m_minZoom, "min_zoom"), visitor(m_anchor, "anchor"))
|
||||
|
||||
uint8_t m_minZoom = scales::UPPER_STYLE_SCALE;
|
||||
Anchor m_anchor = kInvalidAnchor;
|
||||
};
|
||||
|
||||
class Stop
|
||||
{
|
||||
public:
|
||||
NEWTYPE(StopId, WrappedStopId);
|
||||
|
||||
Stop() : m_featureIdentifiers(true /* serializeFeatureIdOnly */) {}
|
||||
Stop(StopId id, OsmId osmId, FeatureId featureId, TransferId transferId, std::vector<LineId> const & lineIds,
|
||||
m2::PointD const & point, std::vector<TitleAnchor> const & titleAnchors);
|
||||
explicit Stop(StopId id) : m_id(id), m_featureIdentifiers(true /* serializeFeatureIdOnly */) {}
|
||||
|
||||
bool operator<(Stop const & rhs) const { return m_id < rhs.m_id; }
|
||||
bool operator==(Stop const & rhs) const { return m_id == rhs.m_id; }
|
||||
bool IsEqualForTesting(Stop const & stop) const;
|
||||
bool IsValid() const;
|
||||
|
||||
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<LineId> const & GetLineIds() const { return m_lineIds; }
|
||||
m2::PointD const & GetPoint() const { return m_point; }
|
||||
std::vector<TitleAnchor> const & GetTitleAnchors() const { return m_titleAnchors; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Stop, visitor(m_id, "id"), visitor(m_featureIdentifiers, "osm_id"),
|
||||
visitor(m_transferId, "transfer_id"), visitor(m_lineIds, "line_ids"),
|
||||
visitor(m_point, "point"), visitor(m_titleAnchors, "title_anchors"))
|
||||
|
||||
WrappedStopId m_id = WrappedStopId(kInvalidStopId);
|
||||
FeatureIdentifiers m_featureIdentifiers;
|
||||
TransferId m_transferId = kInvalidTransferId;
|
||||
std::vector<LineId> m_lineIds;
|
||||
m2::PointD m_point{0.0, 0.0};
|
||||
std::vector<TitleAnchor> m_titleAnchors;
|
||||
};
|
||||
NEWTYPE_SIMPLE_OUTPUT(Stop::WrappedStopId)
|
||||
|
||||
class SingleMwmSegment
|
||||
{
|
||||
public:
|
||||
SingleMwmSegment() = default;
|
||||
SingleMwmSegment(FeatureId featureId, uint32_t segmentIdx, bool forward);
|
||||
bool IsEqualForTesting(SingleMwmSegment const & s) const;
|
||||
bool IsValid() const;
|
||||
|
||||
FeatureId GetFeatureId() const { return m_featureId; }
|
||||
uint32_t GetSegmentIdx() const { return m_segmentIdx; }
|
||||
bool GetForward() const { return m_forward; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(SingleMwmSegment, visitor(m_featureId, "feature_id"),
|
||||
visitor(m_segmentIdx, "segment_idx"), visitor(m_forward, "forward"))
|
||||
|
||||
FeatureId m_featureId = kInvalidFeatureId;
|
||||
uint32_t m_segmentIdx = 0;
|
||||
bool m_forward = false;
|
||||
};
|
||||
|
||||
class Gate
|
||||
{
|
||||
public:
|
||||
Gate() : m_featureIdentifiers(false /* serializeFeatureIdOnly */) {}
|
||||
Gate(OsmId osmId, FeatureId featureId, bool entrance, bool exit, Weight weight, std::vector<StopId> const & stopIds,
|
||||
m2::PointD const & point);
|
||||
|
||||
bool operator<(Gate const & rhs) const;
|
||||
bool operator==(Gate const & rhs) const;
|
||||
bool IsEqualForTesting(Gate const & gate) const;
|
||||
bool IsValid() const;
|
||||
void SetBestPedestrianSegment(SingleMwmSegment const & s) { m_bestPedestrianSegment = s; }
|
||||
|
||||
FeatureId GetFeatureId() const { return m_featureIdentifiers.GetFeatureId(); }
|
||||
OsmId GetOsmId() const { return m_featureIdentifiers.GetOsmId(); }
|
||||
SingleMwmSegment const & GetBestPedestrianSegment() const { return m_bestPedestrianSegment; }
|
||||
bool GetEntrance() const { return m_entrance; }
|
||||
bool GetExit() const { return m_exit; }
|
||||
Weight GetWeight() const { return m_weight; }
|
||||
std::vector<StopId> const & GetStopIds() const { return m_stopIds; }
|
||||
m2::PointD const & GetPoint() const { return m_point; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Gate, visitor(m_featureIdentifiers, "osm_id"),
|
||||
visitor(m_bestPedestrianSegment, "best_pedestrian_segment"),
|
||||
visitor(m_entrance, "entrance"), visitor(m_exit, "exit"), visitor(m_weight, "weight"),
|
||||
visitor(m_stopIds, "stop_ids"), visitor(m_point, "point"))
|
||||
|
||||
// |m_featureIdentifiers| contains feature id of a feature which represents gates. Usually it's a
|
||||
// point feature.
|
||||
FeatureIdentifiers m_featureIdentifiers;
|
||||
// |m_bestPedestrianSegment| is a segment which can be used for pedestrian routing to leave and
|
||||
// enter the gate. The segment may be invalid because of map date. If so there's no pedestrian
|
||||
// segment which can be used to reach the gate.
|
||||
SingleMwmSegment m_bestPedestrianSegment;
|
||||
bool m_entrance = true;
|
||||
bool m_exit = true;
|
||||
Weight m_weight = kInvalidWeight;
|
||||
std::vector<StopId> m_stopIds;
|
||||
m2::PointD m_point;
|
||||
};
|
||||
|
||||
class ShapeId
|
||||
{
|
||||
public:
|
||||
ShapeId() = default;
|
||||
ShapeId(StopId stop1Id, StopId stop2Id) : m_stop1Id(stop1Id), m_stop2Id(stop2Id) {}
|
||||
|
||||
bool operator<(ShapeId const & rhs) const;
|
||||
bool operator==(ShapeId const & rhs) const;
|
||||
bool IsEqualForTesting(ShapeId const & rhs) const { return *this == rhs; }
|
||||
|
||||
bool IsValid() const;
|
||||
StopId GetStop1Id() const { return m_stop1Id; }
|
||||
StopId GetStop2Id() const { return m_stop2Id; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(ShapeId, visitor(m_stop1Id, "stop1_id"), visitor(m_stop2Id, "stop2_id"))
|
||||
|
||||
StopId m_stop1Id = kInvalidStopId;
|
||||
StopId m_stop2Id = kInvalidStopId;
|
||||
};
|
||||
|
||||
class EdgeFlags
|
||||
{
|
||||
public:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
friend std::string DebugPrint(EdgeFlags const & f);
|
||||
|
||||
uint8_t GetFlags() const;
|
||||
void SetFlags(uint8_t flags);
|
||||
|
||||
// |m_transfer == true| if |Edge::m_shapeIds| is empty.
|
||||
bool m_transfer = false;
|
||||
/// |m_isShapeIdsEmpty == true| if |Edge::m_shapeIds| is empty.
|
||||
bool m_isShapeIdsEmpty = false;
|
||||
// |m_isShapeIdsSingle == true| if |Edge::m_shapeIds| contains only one item.
|
||||
bool m_isShapeIdsSingle = false;
|
||||
// Note. If |m_isShapeIdsSingle == true| |m_isShapeIdsSame| is set to
|
||||
// |Edge::m_stop1Id == m_shapeIds[0].m_stop1Id && Edge::m_stop2Id == m_shapeIds[0].m_stop2Id|.
|
||||
// |m_isShapeIdsSingle| is invalid otherwise.
|
||||
bool m_isShapeIdsSame = false;
|
||||
// Note. If |m_isShapeIdsSingle == true| |m_isShapeIdsReversed| is set to
|
||||
// |Edge::m_stop1Id == m_shapeIds[0].m_stop2Id && Edge::m_stop2Id == m_shapeIds[0].m_stop1Id|.
|
||||
// |m_isShapeIdsReversed| is invalid otherwise.
|
||||
bool m_isShapeIdsReversed = false;
|
||||
|
||||
private:
|
||||
uint8_t BoolToUint(bool b) const { return static_cast<uint8_t>(b ? 1 : 0); }
|
||||
uint8_t GetBit(uint8_t flags, uint8_t mask) const { return BoolToUint((flags & mask) != 0); }
|
||||
|
||||
static uint8_t constexpr kTransferMask = 1;
|
||||
static uint8_t constexpr kEmptyShapeIdsMask = (1 << 1);
|
||||
static uint8_t constexpr kSingleShapeIdMask = (1 << 2);
|
||||
static uint8_t constexpr kShapeIdIsSameMask = (1 << 3);
|
||||
static uint8_t constexpr kShapeIdIsReversedMask = (1 << 4);
|
||||
};
|
||||
|
||||
std::string DebugPrint(EdgeFlags const & f);
|
||||
|
||||
class Edge
|
||||
{
|
||||
public:
|
||||
NEWTYPE(StopId, WrappedEdgeId);
|
||||
|
||||
Edge() = default;
|
||||
Edge(StopId stop1Id, StopId stop2Id, Weight weight, LineId lineId, bool transfer,
|
||||
std::vector<ShapeId> const & shapeIds);
|
||||
|
||||
bool operator<(Edge const & rhs) const;
|
||||
bool operator==(Edge const & rhs) const;
|
||||
bool operator!=(Edge const & rhs) const { return !(*this == rhs); }
|
||||
bool IsEqualForTesting(Edge const & edge) const;
|
||||
bool IsValid() const;
|
||||
void SetWeight(Weight weight) { m_weight = weight; }
|
||||
|
||||
StopId GetStop1Id() const { return m_stop1Id.Get(); }
|
||||
StopId GetStop2Id() const { return m_stop2Id; }
|
||||
Weight GetWeight() const { return m_weight; }
|
||||
LineId GetLineId() const { return m_lineId; }
|
||||
bool GetTransfer() const { return m_flags.m_transfer; }
|
||||
std::vector<ShapeId> const & GetShapeIds() const { return m_shapeIds; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Edge, visitor(m_stop1Id, "stop1_id"), visitor(m_stop2Id, "stop2_id"),
|
||||
visitor(m_weight, "weight"), visitor(m_lineId, "line_id"),
|
||||
visitor(m_flags, "transfer"), visitor(m_shapeIds, "shape_ids"))
|
||||
|
||||
WrappedEdgeId m_stop1Id = WrappedEdgeId(kInvalidStopId);
|
||||
StopId m_stop2Id = kInvalidStopId;
|
||||
Weight m_weight = kInvalidWeight; // in seconds
|
||||
LineId m_lineId = kInvalidLineId;
|
||||
EdgeFlags m_flags;
|
||||
std::vector<ShapeId> m_shapeIds;
|
||||
};
|
||||
NEWTYPE_SIMPLE_OUTPUT(Edge::WrappedEdgeId)
|
||||
|
||||
class Transfer
|
||||
{
|
||||
public:
|
||||
Transfer() = default;
|
||||
Transfer(StopId id, m2::PointD const & point, std::vector<StopId> const & stopIds,
|
||||
std::vector<TitleAnchor> const & titleAnchors);
|
||||
|
||||
bool operator<(Transfer const & rhs) const { return m_id < rhs.m_id; }
|
||||
bool operator==(Transfer const & rhs) const { return m_id == rhs.m_id; }
|
||||
bool IsEqualForTesting(Transfer const & transfer) const;
|
||||
bool IsValid() const;
|
||||
|
||||
StopId GetId() const { return m_id; }
|
||||
m2::PointD const & GetPoint() const { return m_point; }
|
||||
std::vector<StopId> const & GetStopIds() const { return m_stopIds; }
|
||||
std::vector<TitleAnchor> const & GetTitleAnchors() const { return m_titleAnchors; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Transfer, visitor(m_id, "id"), visitor(m_point, "point"),
|
||||
visitor(m_stopIds, "stop_ids"), visitor(m_titleAnchors, "title_anchors"))
|
||||
|
||||
StopId m_id = kInvalidStopId;
|
||||
m2::PointD m_point;
|
||||
std::vector<StopId> m_stopIds;
|
||||
std::vector<TitleAnchor> m_titleAnchors;
|
||||
};
|
||||
|
||||
class StopIdRanges
|
||||
{
|
||||
public:
|
||||
StopIdRanges() = default;
|
||||
explicit StopIdRanges(Ranges const & ids) : m_ids(ids) {}
|
||||
|
||||
bool operator==(StopIdRanges const & rhs) const { return m_ids == rhs.m_ids; }
|
||||
bool IsValid() const { return !m_ids.empty(); }
|
||||
|
||||
Ranges const & GetIds() const { return m_ids; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(StopIdRanges, visitor(m_ids, "ids"))
|
||||
|
||||
Ranges m_ids;
|
||||
};
|
||||
|
||||
class Line
|
||||
{
|
||||
public:
|
||||
Line() = default;
|
||||
Line(LineId id, std::string const & number, std::string const & title, std::string const & type,
|
||||
std::string const & color, NetworkId networkId, Ranges const & stopIds, Weight interval);
|
||||
|
||||
bool operator<(Line const & rhs) const { return m_id < rhs.m_id; }
|
||||
bool operator==(Line const & rhs) const { return m_id == rhs.m_id; }
|
||||
bool IsEqualForTesting(Line const & line) const;
|
||||
bool IsValid() const;
|
||||
|
||||
LineId GetId() const { return m_id; }
|
||||
std::string const & GetNumber() const { return m_number; }
|
||||
std::string const & GetTitle() const { return m_title; }
|
||||
std::string const & GetType() const { return m_type; }
|
||||
std::string const & GetColor() const { return m_color; }
|
||||
NetworkId GetNetworkId() const { return m_networkId; }
|
||||
Ranges const & GetStopIds() const { return m_stopIds.GetIds(); }
|
||||
Weight GetInterval() const { return m_interval; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Line, visitor(m_id, "id"), visitor(m_number, "number"), visitor(m_title, "title"),
|
||||
visitor(m_type, "type"), visitor(m_color, "color"),
|
||||
visitor(m_networkId, "network_id"), visitor(m_stopIds, "stop_ids"),
|
||||
visitor(m_interval, "interval"))
|
||||
|
||||
LineId m_id = kInvalidLineId;
|
||||
std::string m_number;
|
||||
std::string m_title;
|
||||
std::string m_type;
|
||||
std::string m_color = kInvalidColor;
|
||||
NetworkId m_networkId = kInvalidNetworkId;
|
||||
StopIdRanges m_stopIds;
|
||||
Weight m_interval = kInvalidWeight;
|
||||
};
|
||||
|
||||
class Shape
|
||||
{
|
||||
public:
|
||||
Shape() = default;
|
||||
Shape(ShapeId const & id, std::vector<m2::PointD> const & polyline) : m_id(id), m_polyline(polyline) {}
|
||||
|
||||
bool operator<(Shape const & rhs) const { return m_id < rhs.m_id; }
|
||||
bool operator==(Shape const & rhs) const { return m_id == rhs.m_id; }
|
||||
bool IsEqualForTesting(Shape const & shape) const;
|
||||
bool IsValid() const { return m_id.IsValid() && m_polyline.size() > 1; }
|
||||
|
||||
ShapeId GetId() const { return m_id; }
|
||||
std::vector<m2::PointD> const & GetPolyline() const { return m_polyline; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Shape, visitor(m_id, "id"), visitor(m_polyline, "polyline"))
|
||||
|
||||
ShapeId m_id;
|
||||
std::vector<m2::PointD> m_polyline;
|
||||
};
|
||||
|
||||
class Network
|
||||
{
|
||||
public:
|
||||
Network() = default;
|
||||
Network(NetworkId id, std::string const & title);
|
||||
explicit Network(NetworkId id) : m_id(id), m_title("") {}
|
||||
|
||||
bool operator<(Network const & rhs) const { return m_id < rhs.m_id; }
|
||||
bool operator==(Network const & rhs) const { return m_id == rhs.m_id; }
|
||||
bool IsEqualForTesting(Network const & shape) const;
|
||||
bool IsValid() const;
|
||||
|
||||
NetworkId GetId() const { return m_id; }
|
||||
std::string const & GetTitle() const { return m_title; }
|
||||
|
||||
private:
|
||||
DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(Network, visitor(m_id, "id"), visitor(m_title, "title"))
|
||||
|
||||
NetworkId m_id = kInvalidNetworkId;
|
||||
std::string m_title;
|
||||
};
|
||||
|
||||
template <class Item>
|
||||
void CheckValid(std::vector<Item> const & items, std::string const & name)
|
||||
{
|
||||
for (auto const & i : items)
|
||||
CHECK(i.IsValid(), (i, "is not valid. Table name:", name));
|
||||
}
|
||||
|
||||
template <class Item>
|
||||
void CheckSorted(std::vector<Item> const & items, std::string const & name)
|
||||
{
|
||||
CHECK(std::is_sorted(items.cbegin(), items.cend()), ("Table is not sorted. Table name:", name));
|
||||
}
|
||||
|
||||
template <class Item>
|
||||
void CheckUnique(std::vector<Item> const & items, std::string const & name)
|
||||
{
|
||||
auto const it = std::adjacent_find(items.cbegin(), items.cend());
|
||||
CHECK(it == items.cend(), (*it, "is not unique. Table name:", name));
|
||||
}
|
||||
|
||||
template <class Item>
|
||||
void CheckValidSortedUnique(std::vector<Item> const & items, std::string const & name)
|
||||
{
|
||||
CheckValid(items, name);
|
||||
CheckSorted(items, name);
|
||||
CheckUnique(items, name);
|
||||
}
|
||||
|
||||
EdgeFlags GetEdgeFlags(bool transfer, StopId stopId1, StopId stopId2, std::vector<ShapeId> const & shapeIds);
|
||||
|
||||
#undef DECLARE_TRANSIT_TYPE_FRIENDS
|
||||
} // namespace transit
|
||||
} // namespace routing
|
||||
28
libs/transit/transit_version.cpp
Normal file
28
libs/transit/transit_version.cpp
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#include "transit/transit_version.hpp"
|
||||
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
TransitVersion GetVersion(Reader & reader)
|
||||
{
|
||||
try
|
||||
{
|
||||
NonOwningReaderSource src(reader);
|
||||
uint16_t headerVersion = std::numeric_limits<uint16_t>::max();
|
||||
ReadPrimitiveFromSource(src, headerVersion);
|
||||
CHECK_LESS(headerVersion, static_cast<uint16_t>(TransitVersion::Counter), ("Invalid transit header version."));
|
||||
|
||||
return static_cast<TransitVersion>(headerVersion);
|
||||
}
|
||||
catch (std::exception const & ex)
|
||||
{
|
||||
LOG(LERROR, ("Error reading header version from transit mwm section.", ex.what()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
} // namespace transit
|
||||
34
libs/transit/transit_version.hpp
Normal file
34
libs/transit/transit_version.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#pragma once
|
||||
|
||||
#include "coding/reader.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
enum class TransitVersion
|
||||
{
|
||||
OnlySubway = 0,
|
||||
AllPublicTransport = 1,
|
||||
Counter = 2
|
||||
};
|
||||
|
||||
// Reads version from header in the transit mwm section and returns it.
|
||||
TransitVersion GetVersion(Reader & reader);
|
||||
|
||||
inline std::string DebugPrint(TransitVersion version)
|
||||
{
|
||||
switch (version)
|
||||
{
|
||||
case TransitVersion::OnlySubway: return "OnlySubway";
|
||||
case TransitVersion::AllPublicTransport: return "AllPublicTransport";
|
||||
case TransitVersion::Counter: return "Counter";
|
||||
}
|
||||
|
||||
LOG(LERROR, ("Unknown version:", static_cast<std::underlying_type<TransitVersion>::type>(version)));
|
||||
UNREACHABLE();
|
||||
}
|
||||
} // namespace transit
|
||||
28
libs/transit/world_feed/CMakeLists.txt
Normal file
28
libs/transit/world_feed/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
project(world_feed)
|
||||
|
||||
set(SRC
|
||||
color_picker.cpp
|
||||
color_picker.hpp
|
||||
date_time_helpers.cpp
|
||||
date_time_helpers.hpp
|
||||
feed_helpers.cpp
|
||||
feed_helpers.hpp
|
||||
subway_converter.cpp
|
||||
subway_converter.hpp
|
||||
world_feed.cpp
|
||||
world_feed.hpp
|
||||
)
|
||||
|
||||
omim_add_library(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
generator # routing::transit::DeserializeFromJson
|
||||
transit
|
||||
drape_frontend # df::ToDrapeColor, df::LoadTransitColors
|
||||
opening_hours
|
||||
)
|
||||
|
||||
omim_add_tool_subdirectory(gtfs_converter)
|
||||
|
||||
omim_add_test_subdirectory(world_feed_tests)
|
||||
omim_add_test_subdirectory(world_feed_integration_tests)
|
||||
88
libs/transit/world_feed/color_picker.cpp
Normal file
88
libs/transit/world_feed/color_picker.cpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#include "transit/world_feed/color_picker.hpp"
|
||||
|
||||
#include "drape_frontend/apply_feature_functors.hpp"
|
||||
|
||||
#include "drape/color.hpp"
|
||||
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <tuple>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::tuple<double, double, double> GetColors(dp::Color const & color)
|
||||
{
|
||||
return {color.GetRedF(), color.GetGreenF(), color.GetBlueF()};
|
||||
}
|
||||
|
||||
double GetDistance(dp::Color const & color1, dp::Color const & color2)
|
||||
{
|
||||
auto [r1, g1, b1] = GetColors(color1);
|
||||
auto [r2, g2, b2] = GetColors(color2);
|
||||
|
||||
// We use the cmetric (Color metric) for calculating the distance between two colors.
|
||||
// https://en.wikipedia.org/wiki/Color_difference
|
||||
// It reflects human perception of closest match for a specific colour. The formula weights RGB
|
||||
// values to better fit eye perception and performs well at proper determinations of colors
|
||||
// contributions, brightness of these colors, and degree to which human vision has less tolerance
|
||||
// for these colors.
|
||||
double const redMean = (r1 + r2) / 2.0;
|
||||
|
||||
double const redDelta = r1 - r2;
|
||||
double const greenDelta = g1 - g2;
|
||||
double const blueDelta = b1 - b2;
|
||||
|
||||
return (2.0 + redMean / 256.0) * redDelta * redDelta + 4 * greenDelta * greenDelta +
|
||||
(2.0 + (255.0 - redMean) / 256.0) * blueDelta * blueDelta;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace transit
|
||||
{
|
||||
ColorPicker::ColorPicker()
|
||||
{
|
||||
df::LoadTransitColors();
|
||||
// We need only colors for route polylines, not for text. So we skip items like
|
||||
// 'transit_text_navy' and work only with items like 'transit_navy'.
|
||||
for (auto const & [name, color] : df::GetTransitClearColors())
|
||||
if (name.find(df::kTransitTextPrefix) == std::string::npos)
|
||||
m_drapeClearColors.emplace(name, color);
|
||||
}
|
||||
|
||||
std::string ColorPicker::GetNearestColor(std::string const & rgb)
|
||||
{
|
||||
static std::string const kDefaultColor = "default";
|
||||
if (rgb.empty())
|
||||
return kDefaultColor;
|
||||
|
||||
auto [it, inserted] = m_colorsToNames.emplace(rgb, kDefaultColor);
|
||||
if (!inserted)
|
||||
return it->second;
|
||||
|
||||
std::string nearestColor = kDefaultColor;
|
||||
|
||||
unsigned int intColor;
|
||||
// We do not need to add to the cache invalid color, so we just return.
|
||||
if (!strings::to_uint(rgb, intColor, 16))
|
||||
return nearestColor;
|
||||
|
||||
dp::Color const color = df::ToDrapeColor(static_cast<uint32_t>(intColor));
|
||||
double minDist = std::numeric_limits<double>::max();
|
||||
|
||||
for (auto const & [name, transitColor] : m_drapeClearColors)
|
||||
{
|
||||
if (double const dist = GetDistance(color, transitColor); dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
nearestColor = name;
|
||||
}
|
||||
}
|
||||
|
||||
if (nearestColor.find(df::kTransitColorPrefix + df::kTransitLinePrefix) == 0)
|
||||
nearestColor = nearestColor.substr(df::kTransitColorPrefix.size() + df::kTransitLinePrefix.size());
|
||||
|
||||
it->second = nearestColor;
|
||||
return nearestColor;
|
||||
}
|
||||
} // namespace transit
|
||||
22
libs/transit/world_feed/color_picker.hpp
Normal file
22
libs/transit/world_feed/color_picker.hpp
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
#pragma once
|
||||
|
||||
#include "drape_frontend/color_constants.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
class ColorPicker
|
||||
{
|
||||
public:
|
||||
ColorPicker();
|
||||
// Picks the closest match for the |rgb| color from our transit palette.
|
||||
std::string GetNearestColor(std::string const & rgb);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, std::string> m_colorsToNames;
|
||||
std::map<std::string, dp::Color> m_drapeClearColors;
|
||||
};
|
||||
} // namespace transit
|
||||
249
libs/transit/world_feed/date_time_helpers.cpp
Normal file
249
libs/transit/world_feed/date_time_helpers.cpp
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
#include "transit/world_feed/date_time_helpers.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <boost/date_time/gregorian/gregorian.hpp>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
osmoh::Time GetTimeOsmoh(gtfs::Time const & gtfsTime)
|
||||
{
|
||||
uint16_t hh;
|
||||
uint16_t mm;
|
||||
std::tie(hh, mm, std::ignore) = gtfsTime.get_hh_mm_ss();
|
||||
return osmoh::Time(osmoh::Time::THours(hh) + osmoh::Time::TMinutes(mm));
|
||||
}
|
||||
|
||||
osmoh::RuleSequence GetRuleSequenceOsmoh(gtfs::Time const & start, gtfs::Time const & end)
|
||||
{
|
||||
osmoh::RuleSequence ruleSeq;
|
||||
ruleSeq.SetModifier(osmoh::RuleSequence::Modifier::Open);
|
||||
osmoh::Timespan range(GetTimeOsmoh(start), GetTimeOsmoh(end));
|
||||
ruleSeq.SetTimes({range});
|
||||
return ruleSeq;
|
||||
}
|
||||
|
||||
osmoh::MonthdayRange GetMonthdayRangeFromDates(gtfs::Date const & start, gtfs::Date const & end)
|
||||
{
|
||||
osmoh::MonthdayRange range;
|
||||
SetOpeningHoursRange(range, start, true /* isStart */);
|
||||
SetOpeningHoursRange(range, end, false /* isStart */);
|
||||
return range;
|
||||
}
|
||||
|
||||
struct AccumExceptionDates
|
||||
{
|
||||
public:
|
||||
using GregorianInterval = std::pair<boost::gregorian::date, boost::gregorian::date>;
|
||||
using GtfsInterval = std::pair<gtfs::Date, gtfs::Date>;
|
||||
|
||||
void InitIntervals(boost::gregorian::date const & gregorianDate, gtfs::Date const & gtfsDate);
|
||||
void AddRange();
|
||||
bool IsInited() const;
|
||||
|
||||
GregorianInterval m_GregorianInterval;
|
||||
GtfsInterval m_GtfsInterval;
|
||||
osmoh::TMonthdayRanges m_ranges;
|
||||
|
||||
private:
|
||||
bool m_inited = false;
|
||||
};
|
||||
|
||||
void AccumExceptionDates::InitIntervals(boost::gregorian::date const & gregorianDate, gtfs::Date const & gtfsDate)
|
||||
{
|
||||
m_GregorianInterval = std::make_pair(gregorianDate, gregorianDate);
|
||||
m_GtfsInterval = std::make_pair(gtfsDate, gtfsDate);
|
||||
m_inited = true;
|
||||
}
|
||||
|
||||
void AccumExceptionDates::AddRange()
|
||||
{
|
||||
osmoh::MonthdayRange range = GetMonthdayRangeFromDates(m_GtfsInterval.first, m_GtfsInterval.second);
|
||||
m_ranges.push_back(range);
|
||||
m_inited = false;
|
||||
}
|
||||
|
||||
bool AccumExceptionDates::IsInited() const
|
||||
{
|
||||
return m_inited;
|
||||
}
|
||||
|
||||
osmoh::Weekday ConvertWeekDayIndexToOsmoh(size_t index)
|
||||
{
|
||||
// Monday index in osmoh is 2.
|
||||
index += 2;
|
||||
|
||||
if (index == 7)
|
||||
return osmoh::Weekday::Saturday;
|
||||
if (index == 8)
|
||||
return osmoh::Weekday::Sunday;
|
||||
|
||||
return osmoh::ToWeekday(index);
|
||||
}
|
||||
|
||||
std::vector<WeekdaysInterval> GetOpenCloseIntervals(std::vector<gtfs::CalendarAvailability> const & week)
|
||||
{
|
||||
std::vector<WeekdaysInterval> intervals;
|
||||
|
||||
WeekdaysInterval interval;
|
||||
for (size_t i = 0; i < week.size(); ++i)
|
||||
{
|
||||
osmoh::RuleSequence::Modifier const status = week[i] == gtfs::CalendarAvailability::Available
|
||||
? osmoh::RuleSequence::Modifier::DefaultOpen
|
||||
: osmoh::RuleSequence::Modifier::Closed;
|
||||
if (status == interval.m_status)
|
||||
{
|
||||
interval.m_end = i;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (i > 0)
|
||||
intervals.push_back(interval);
|
||||
interval.m_start = i;
|
||||
interval.m_end = i;
|
||||
interval.m_status = status;
|
||||
}
|
||||
if (i == week.size() - 1)
|
||||
intervals.push_back(interval);
|
||||
}
|
||||
|
||||
return intervals;
|
||||
}
|
||||
|
||||
void SetOpeningHoursRange(osmoh::MonthdayRange & range, gtfs::Date const & date, bool isStart)
|
||||
{
|
||||
if (!date.is_provided())
|
||||
{
|
||||
LOG(LINFO, ("Date is not provided in the calendar."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto const & [year, month, day] = date.get_yyyy_mm_dd();
|
||||
|
||||
osmoh::MonthDay monthDay;
|
||||
monthDay.SetYear(year);
|
||||
monthDay.SetMonth(static_cast<osmoh::MonthDay::Month>(month));
|
||||
monthDay.SetDayNum(day);
|
||||
|
||||
if (isStart)
|
||||
range.SetStart(monthDay);
|
||||
else
|
||||
range.SetEnd(monthDay);
|
||||
}
|
||||
|
||||
void GetServiceDaysOsmoh(gtfs::CalendarItem const & serviceDays, osmoh::TRuleSequences & rules)
|
||||
{
|
||||
osmoh::MonthdayRange range = GetMonthdayRangeFromDates(serviceDays.start_date, serviceDays.end_date);
|
||||
osmoh::TMonthdayRanges const rangesMonths{range};
|
||||
|
||||
std::vector<gtfs::CalendarAvailability> const weekDayStatuses = {
|
||||
serviceDays.monday, serviceDays.tuesday, serviceDays.wednesday, serviceDays.thursday,
|
||||
serviceDays.friday, serviceDays.saturday, serviceDays.sunday};
|
||||
|
||||
auto const & intervals = GetOpenCloseIntervals(weekDayStatuses);
|
||||
|
||||
osmoh::RuleSequence ruleSeqOpen;
|
||||
osmoh::RuleSequence ruleSeqClose;
|
||||
|
||||
for (auto const & interval : intervals)
|
||||
{
|
||||
osmoh::RuleSequence & ruleSeq =
|
||||
interval.m_status == osmoh::RuleSequence::Modifier::DefaultOpen ? ruleSeqOpen : ruleSeqClose;
|
||||
ruleSeq.SetMonths(rangesMonths);
|
||||
ruleSeq.SetModifier(interval.m_status);
|
||||
|
||||
osmoh::WeekdayRange weekDayRange;
|
||||
weekDayRange.SetStart(ConvertWeekDayIndexToOsmoh(interval.m_start));
|
||||
weekDayRange.SetEnd(ConvertWeekDayIndexToOsmoh(interval.m_end));
|
||||
|
||||
osmoh::TWeekdayRanges weekDayRanges;
|
||||
weekDayRanges.push_back(weekDayRange);
|
||||
|
||||
osmoh::Weekdays weekDays;
|
||||
weekDays.SetWeekdayRanges(weekDayRanges);
|
||||
ruleSeq.SetWeekdays(weekDays);
|
||||
}
|
||||
|
||||
if (ruleSeqOpen.HasWeekdays())
|
||||
rules.push_back(ruleSeqOpen);
|
||||
|
||||
if (ruleSeqClose.HasWeekdays())
|
||||
rules.push_back(ruleSeqClose);
|
||||
}
|
||||
|
||||
void AppendMonthRules(osmoh::RuleSequence::Modifier const & status, osmoh::TMonthdayRanges const & monthRanges,
|
||||
osmoh::TRuleSequences & rules)
|
||||
{
|
||||
osmoh::RuleSequence ruleSeq;
|
||||
ruleSeq.SetMonths(monthRanges);
|
||||
ruleSeq.SetModifier(status);
|
||||
rules.push_back(ruleSeq);
|
||||
}
|
||||
|
||||
void GetServiceDaysExceptionsOsmoh(gtfs::CalendarDates const & exceptionDays, osmoh::TRuleSequences & rules)
|
||||
{
|
||||
if (exceptionDays.empty())
|
||||
return;
|
||||
|
||||
AccumExceptionDates accumOpen;
|
||||
AccumExceptionDates accumClosed;
|
||||
|
||||
for (size_t i = 0; i < exceptionDays.size(); ++i)
|
||||
{
|
||||
AccumExceptionDates & curAccum =
|
||||
(exceptionDays[i].exception_type == gtfs::CalendarDateException::Added) ? accumOpen : accumClosed;
|
||||
|
||||
auto const [year, month, day] = exceptionDays[i].date.get_yyyy_mm_dd();
|
||||
boost::gregorian::date const date{year, month, day};
|
||||
if (!curAccum.IsInited())
|
||||
{
|
||||
curAccum.InitIntervals(date, exceptionDays[i].date);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto & prevDate = curAccum.m_GregorianInterval.second;
|
||||
boost::gregorian::date_duration duration = date - prevDate;
|
||||
CHECK(!duration.is_negative(), ());
|
||||
|
||||
if (duration.days() == 1)
|
||||
{
|
||||
prevDate = date;
|
||||
curAccum.m_GtfsInterval.second = exceptionDays[i].date;
|
||||
}
|
||||
else
|
||||
{
|
||||
curAccum.AddRange();
|
||||
curAccum.InitIntervals(date, exceptionDays[i].date);
|
||||
}
|
||||
}
|
||||
|
||||
AccumExceptionDates & prevAccum =
|
||||
(exceptionDays[i].exception_type == gtfs::CalendarDateException::Added) ? accumClosed : accumOpen;
|
||||
if (prevAccum.IsInited())
|
||||
prevAccum.AddRange();
|
||||
|
||||
if (i == exceptionDays.size() - 1)
|
||||
curAccum.AddRange();
|
||||
}
|
||||
|
||||
if (!accumOpen.m_ranges.empty())
|
||||
AppendMonthRules(osmoh::RuleSequence::Modifier::DefaultOpen, accumOpen.m_ranges, rules);
|
||||
|
||||
if (!accumClosed.m_ranges.empty())
|
||||
AppendMonthRules(osmoh::RuleSequence::Modifier::Closed, accumClosed.m_ranges, rules);
|
||||
}
|
||||
|
||||
void MergeRules(osmoh::TRuleSequences & dstRules, osmoh::TRuleSequences const & srcRules)
|
||||
{
|
||||
for (auto const & rule : srcRules)
|
||||
if (!base::IsExist(dstRules, rule))
|
||||
dstRules.push_back(rule);
|
||||
}
|
||||
} // namespace transit
|
||||
43
libs/transit/world_feed/date_time_helpers.hpp
Normal file
43
libs/transit/world_feed/date_time_helpers.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/just_gtfs/just_gtfs.h"
|
||||
#include "3party/opening_hours/opening_hours.hpp"
|
||||
|
||||
namespace transit
|
||||
{
|
||||
// Creates osmoh::Time object from GTFS Time |gtfsTime|.
|
||||
osmoh::Time GetTimeOsmoh(gtfs::Time const & gtfsTime);
|
||||
|
||||
// Creates osmoh::RuleSequence with Modifier::Open and osmoh::Timespan with |start| - |end|
|
||||
// interval.
|
||||
osmoh::RuleSequence GetRuleSequenceOsmoh(gtfs::Time const & start, gtfs::Time const & end);
|
||||
|
||||
// Converts week day |index| in range [0, 6] to the osmoh::Weekday object.
|
||||
osmoh::Weekday ConvertWeekDayIndexToOsmoh(size_t index);
|
||||
|
||||
// Inclusive interval of days and corresponding Open/Closed status.
|
||||
struct WeekdaysInterval
|
||||
{
|
||||
size_t m_start = 0;
|
||||
size_t m_end = 0;
|
||||
osmoh::RuleSequence::Modifier m_status = osmoh::RuleSequence::Modifier::DefaultOpen;
|
||||
};
|
||||
|
||||
// Calculates open/closed intervals for |week|.
|
||||
std::vector<WeekdaysInterval> GetOpenCloseIntervals(std::vector<gtfs::CalendarAvailability> const & week);
|
||||
|
||||
// Sets start or end |date| for |range|.
|
||||
void SetOpeningHoursRange(osmoh::MonthdayRange & range, gtfs::Date const & date, bool isStart);
|
||||
|
||||
// Extracts open/closed service days ranges from |serviceDays| to |rules|.
|
||||
void GetServiceDaysOsmoh(gtfs::CalendarItem const & serviceDays, osmoh::TRuleSequences & rules);
|
||||
|
||||
// Extracts open/closed exception service days ranges from |exceptionDays| to |rules|.
|
||||
void GetServiceDaysExceptionsOsmoh(gtfs::CalendarDates const & exceptionDays, osmoh::TRuleSequences & rules);
|
||||
|
||||
// Adds |srcRules| to |dstRules| if they are not present.
|
||||
void MergeRules(osmoh::TRuleSequences & dstRules, osmoh::TRuleSequences const & srcRules);
|
||||
} // namespace transit
|
||||
539
libs/transit/world_feed/feed_helpers.cpp
Normal file
539
libs/transit/world_feed/feed_helpers.cpp
Normal file
|
|
@ -0,0 +1,539 @@
|
|||
#include "transit/world_feed/feed_helpers.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/parametrized_segment.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
// Epsilon for m2::PointD comparison.
|
||||
double constexpr kEps = 1e-5;
|
||||
|
||||
struct ProjectionData
|
||||
{
|
||||
// Projection to polyline.
|
||||
m2::PointD m_proj;
|
||||
// Index before which the projection will be inserted.
|
||||
size_t m_indexOnShape = 0;
|
||||
// Distance from point to its projection.
|
||||
double m_distFromPoint = 0.0;
|
||||
// Distance from the first ending (start for forward direction, end for backward) point on
|
||||
// polyline to the projection.
|
||||
double m_distFromEnding = 0.0;
|
||||
// Point on polyline almost equal to the projection can already exist, so we don't need to
|
||||
// insert projection. Or we insert it to the polyline.
|
||||
bool m_needsInsertion = false;
|
||||
};
|
||||
|
||||
// Returns true if |p1| is much closer to the first ending (start for forward direction, end for
|
||||
// backward) then |p2| (parameter |distDeltaEnding|) and its distance to projections to polyline
|
||||
// |m_distFromPoint| is comparable.
|
||||
bool CloserToEndingAndOnSimilarDistToLine(ProjectionData const & p1, ProjectionData const & p2)
|
||||
{
|
||||
// Delta between two points distances from start point on polyline.
|
||||
double constexpr distDeltaStart = 100.0;
|
||||
// Delta between two points distances from their corresponding projections to polyline.
|
||||
double constexpr distDeltaProj = 90.0;
|
||||
|
||||
return (p1.m_distFromEnding + distDeltaStart < p2.m_distFromEnding &&
|
||||
std::abs(p2.m_distFromPoint - p1.m_distFromPoint) <= distDeltaProj);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace transit
|
||||
{
|
||||
ProjectionToShape ProjectStopOnTrack(m2::PointD const & stopPoint, m2::PointD const & point1, m2::PointD const & point2)
|
||||
{
|
||||
m2::PointD const stopProjection = m2::ParametrizedSegment<m2::PointD>(point1, point2).ClosestPointTo(stopPoint);
|
||||
double const distM = mercator::DistanceOnEarth(stopProjection, stopPoint);
|
||||
return {stopProjection, distM};
|
||||
}
|
||||
|
||||
ProjectionData GetProjection(std::vector<m2::PointD> const & polyline, size_t index, Direction direction,
|
||||
ProjectionToShape const & proj)
|
||||
{
|
||||
ProjectionData projData;
|
||||
projData.m_distFromPoint = proj.m_dist;
|
||||
projData.m_proj = proj.m_point;
|
||||
|
||||
int64_t const next = direction == Direction::Forward ? index + 1 : index - 1;
|
||||
CHECK_GREATER_OR_EQUAL(next, 0, ());
|
||||
CHECK_LESS(static_cast<size_t>(next), polyline.size(), ());
|
||||
|
||||
if (AlmostEqualAbs(proj.m_point, polyline[index], kEps))
|
||||
{
|
||||
projData.m_indexOnShape = index;
|
||||
projData.m_needsInsertion = false;
|
||||
}
|
||||
else if (AlmostEqualAbs(proj.m_point, polyline[next], kEps))
|
||||
{
|
||||
projData.m_indexOnShape = next;
|
||||
projData.m_needsInsertion = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
projData.m_indexOnShape = direction == Direction::Forward ? next : index;
|
||||
projData.m_needsInsertion = true;
|
||||
}
|
||||
|
||||
return projData;
|
||||
}
|
||||
|
||||
void FillProjections(std::vector<m2::PointD> & polyline, size_t startIndex, size_t endIndex, m2::PointD const & point,
|
||||
double distStopsM, Direction direction, std::vector<ProjectionData> & projections)
|
||||
{
|
||||
CHECK_LESS_OR_EQUAL(startIndex, endIndex, ());
|
||||
|
||||
double distTravelledM = 0.0;
|
||||
// Stop can't be further from its projection to line then |maxDistFromStopM|.
|
||||
double constexpr maxDistFromStopM = 1000;
|
||||
|
||||
size_t const from = direction == Direction::Forward ? startIndex : endIndex;
|
||||
|
||||
auto const endCriterion = [&](size_t i) { return direction == Direction::Forward ? i < endIndex : i > startIndex; };
|
||||
|
||||
auto const move = [&](size_t & i)
|
||||
{
|
||||
direction == Direction::Forward ? ++i : --i;
|
||||
CHECK_LESS_OR_EQUAL(i, polyline.size(), ());
|
||||
};
|
||||
|
||||
for (size_t i = from; endCriterion(i); move(i))
|
||||
{
|
||||
auto const current = i;
|
||||
auto const prev = direction == Direction::Forward ? i - 1 : i + 1;
|
||||
auto const next = direction == Direction::Forward ? i + 1 : i - 1;
|
||||
|
||||
if (i != from)
|
||||
distTravelledM += mercator::DistanceOnEarth(polyline[prev], polyline[current]);
|
||||
|
||||
auto proj =
|
||||
GetProjection(polyline, current, direction, ProjectStopOnTrack(point, polyline[current], polyline[next]));
|
||||
proj.m_distFromEnding = distTravelledM + mercator::DistanceOnEarth(polyline[current], proj.m_proj);
|
||||
|
||||
// The distance on the polyline between the projections of stops must not be less than the
|
||||
// shortest possible distance between the stops themselves.
|
||||
if (proj.m_distFromEnding < distStopsM)
|
||||
continue;
|
||||
|
||||
if (proj.m_distFromPoint < maxDistFromStopM)
|
||||
projections.emplace_back(proj);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<size_t, bool> PrepareNearestPointOnTrack(m2::PointD const & point,
|
||||
std::optional<m2::PointD> const & prevPoint, size_t prevIndex,
|
||||
Direction direction, std::vector<m2::PointD> & polyline)
|
||||
{
|
||||
// We skip 70% of the distance in a straight line between two stops for preventing incorrect
|
||||
// projection of the |point| to the polyline of complex shape.
|
||||
double const distStopsM = prevPoint ? mercator::DistanceOnEarth(point, *prevPoint) * 0.7 : 0.0;
|
||||
|
||||
std::vector<ProjectionData> projections;
|
||||
// Reserve space for points on polyline which are relatively close to the shape.
|
||||
// Approximately 1/4 of all points on shape.
|
||||
auto const size = direction == Direction::Forward ? polyline.size() - prevIndex : prevIndex;
|
||||
projections.reserve(size / 4);
|
||||
|
||||
auto const startIndex = direction == Direction::Forward ? prevIndex : 0;
|
||||
auto const endIndex = direction == Direction::Forward ? polyline.size() - 1 : prevIndex;
|
||||
FillProjections(polyline, startIndex, endIndex, point, distStopsM, direction, projections);
|
||||
|
||||
if (projections.empty())
|
||||
return {polyline.size() + 1, false};
|
||||
|
||||
// We find the most fitting projection of the stop to the polyline. For two different projections
|
||||
// with approximately equal distances to the stop the most preferable is the one that is closer
|
||||
// to the beginning of the polyline segment.
|
||||
auto const cmp = [](ProjectionData const & p1, ProjectionData const & p2)
|
||||
{
|
||||
if (CloserToEndingAndOnSimilarDistToLine(p1, p2))
|
||||
return true;
|
||||
|
||||
if (CloserToEndingAndOnSimilarDistToLine(p2, p1))
|
||||
return false;
|
||||
|
||||
if (p1.m_distFromPoint == p2.m_distFromPoint)
|
||||
return p1.m_distFromEnding < p2.m_distFromEnding;
|
||||
|
||||
return p1.m_distFromPoint < p2.m_distFromPoint;
|
||||
};
|
||||
|
||||
auto proj = std::min_element(projections.begin(), projections.end(), cmp);
|
||||
|
||||
// This case is possible not only for the first stop on the shape. We try to resolve situation
|
||||
// when two stops are projected to the same point on the shape.
|
||||
if (proj->m_indexOnShape == prevIndex)
|
||||
{
|
||||
proj = std::min_element(projections.begin(), projections.end(),
|
||||
[](ProjectionData const & p1, ProjectionData const & p2)
|
||||
{ return p1.m_distFromPoint < p2.m_distFromPoint; });
|
||||
}
|
||||
|
||||
if (proj->m_needsInsertion)
|
||||
polyline.insert(polyline.begin() + proj->m_indexOnShape, proj->m_proj);
|
||||
|
||||
return {proj->m_indexOnShape, proj->m_needsInsertion};
|
||||
}
|
||||
|
||||
bool IsRelevantType(gtfs::RouteType const & routeType)
|
||||
{
|
||||
// All types and constants are described in GTFS:
|
||||
// https://developers.google.com/transit/gtfs/reference
|
||||
|
||||
auto const isSubway = [](gtfs::RouteType const & routeType)
|
||||
{
|
||||
return routeType == gtfs::RouteType::Subway || routeType == gtfs::RouteType::MetroService ||
|
||||
routeType == gtfs::RouteType::UndergroundService;
|
||||
};
|
||||
|
||||
// We skip all subways because we extract subway data from OSM, not from GTFS.
|
||||
if (isSubway(routeType))
|
||||
return false;
|
||||
|
||||
auto const val = static_cast<size_t>(routeType);
|
||||
// "Classic" GTFS route types.
|
||||
if (val < 8 || (val > 10 && val < 13))
|
||||
return true;
|
||||
|
||||
// Extended GTFS route types.
|
||||
// We do not handle taxi services.
|
||||
if (val >= 1500)
|
||||
return false;
|
||||
|
||||
// Other not relevant types - school buses, lorry services etc.
|
||||
static std::vector<gtfs::RouteType> const kNotRelevantTypes{gtfs::RouteType::CarTransportRailService,
|
||||
gtfs::RouteType::LorryTransportRailService,
|
||||
gtfs::RouteType::VehicleTransportRailService,
|
||||
gtfs::RouteType::PostBusService,
|
||||
gtfs::RouteType::SpecialNeedsBus,
|
||||
gtfs::RouteType::MobilityBusService,
|
||||
gtfs::RouteType::MobilityBusForRegisteredDisabled,
|
||||
gtfs::RouteType::SchoolBus,
|
||||
gtfs::RouteType::SchoolAndPublicServiceBus};
|
||||
|
||||
return !base::IsExist(kNotRelevantTypes, routeType);
|
||||
}
|
||||
|
||||
std::string ToString(gtfs::RouteType const & routeType)
|
||||
{
|
||||
// GTFS route types.
|
||||
switch (routeType)
|
||||
{
|
||||
case gtfs::RouteType::Tram: return "tram";
|
||||
case gtfs::RouteType::Subway: return "subway";
|
||||
case gtfs::RouteType::Rail: return "rail";
|
||||
case gtfs::RouteType::Bus: return "bus";
|
||||
case gtfs::RouteType::Ferry: return "ferry";
|
||||
case gtfs::RouteType::CableTram: return "cable_tram";
|
||||
case gtfs::RouteType::AerialLift: return "aerial_lift";
|
||||
case gtfs::RouteType::Funicular: return "funicular";
|
||||
case gtfs::RouteType::Trolleybus: return "trolleybus";
|
||||
case gtfs::RouteType::Monorail: return "monorail";
|
||||
default:
|
||||
// Extended GTFS route types.
|
||||
return ToStringExtendedType(routeType);
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToStringExtendedType(gtfs::RouteType const & routeType)
|
||||
{
|
||||
// These constants refer to extended GTFS routes types.
|
||||
auto const val = static_cast<size_t>(routeType);
|
||||
if (val >= 100 && val < 200)
|
||||
return "rail";
|
||||
|
||||
if (val >= 200 && val < 300)
|
||||
return "bus";
|
||||
|
||||
if (val == 405)
|
||||
return "monorail";
|
||||
|
||||
if (val >= 400 && val < 500)
|
||||
return "rail";
|
||||
|
||||
if (val >= 700 && val < 800)
|
||||
return "bus";
|
||||
|
||||
if (val == 800)
|
||||
return "trolleybus";
|
||||
|
||||
if (val >= 900 && val < 1000)
|
||||
return "tram";
|
||||
|
||||
if (val == 1000)
|
||||
return "water_service";
|
||||
|
||||
if (val == 1100)
|
||||
return "air_service";
|
||||
|
||||
if (val == 1200)
|
||||
return "ferry";
|
||||
|
||||
if (val == 1300)
|
||||
return "aerial_lift";
|
||||
|
||||
if (val == 1400)
|
||||
return "funicular";
|
||||
|
||||
LOG(LINFO, ("Unrecognized route type", val));
|
||||
return {};
|
||||
}
|
||||
|
||||
gtfs::StopTimes GetStopTimesForTrip(gtfs::StopTimes const & allStopTimes, std::string const & tripId)
|
||||
{
|
||||
gtfs::StopTime reference;
|
||||
reference.trip_id = tripId;
|
||||
|
||||
auto itStart =
|
||||
std::lower_bound(allStopTimes.begin(), allStopTimes.end(), reference,
|
||||
[](gtfs::StopTime const & t1, gtfs::StopTime const & t2) { return t1.trip_id < t2.trip_id; });
|
||||
|
||||
if (itStart == allStopTimes.end())
|
||||
return {};
|
||||
auto itEnd = itStart;
|
||||
while (itEnd != allStopTimes.end() && itEnd->trip_id == tripId)
|
||||
++itEnd;
|
||||
|
||||
gtfs::StopTimes res(itStart, itEnd);
|
||||
std::sort(res.begin(), res.end(),
|
||||
[](gtfs::StopTime const & t1, gtfs::StopTime const & t2) { return t1.stop_sequence < t2.stop_sequence; });
|
||||
return res;
|
||||
}
|
||||
|
||||
void UpdateLinePart(LineParts & lineParts, LineSegment const & segment, m2::PointD const & startPoint,
|
||||
TransitId commonLineId, m2::PointD const & startPointParallel)
|
||||
{
|
||||
if (auto it = FindLinePart(lineParts, segment); it == lineParts.end())
|
||||
{
|
||||
LinePart lp;
|
||||
lp.m_segment = segment;
|
||||
lp.m_commonLines[commonLineId] = startPointParallel;
|
||||
lp.m_firstPoint = startPoint;
|
||||
lineParts.push_back(lp);
|
||||
}
|
||||
else
|
||||
{
|
||||
it->m_commonLines[commonLineId] = startPointParallel;
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<LineSegments, LineSegments> FindIntersections(std::vector<m2::PointD> const & line1,
|
||||
std::vector<m2::PointD> const & line2)
|
||||
{
|
||||
double constexpr eps = 1e-5;
|
||||
size_t constexpr minIntersection = 2;
|
||||
|
||||
CHECK_GREATER_OR_EQUAL(line1.size(), minIntersection, ());
|
||||
CHECK_GREATER_OR_EQUAL(line2.size(), minIntersection, ());
|
||||
|
||||
std::pair<LineSegments, LineSegments> intersections;
|
||||
|
||||
// Find start indexes of line1 and line2 intersections.
|
||||
size_t i = 0;
|
||||
|
||||
while (i < line1.size() - minIntersection + 1)
|
||||
{
|
||||
size_t j = 0;
|
||||
size_t delta = 1;
|
||||
|
||||
while (j < line2.size() - minIntersection + 1)
|
||||
{
|
||||
size_t intersection = 0;
|
||||
size_t const len = std::min(line1.size() - i, line2.size() - j);
|
||||
|
||||
for (size_t k = 0; k < len; ++k)
|
||||
{
|
||||
if (!AlmostEqualAbs(line1[i + k], line2[j + k], eps))
|
||||
break;
|
||||
++intersection;
|
||||
}
|
||||
|
||||
if (intersection >= minIntersection)
|
||||
{
|
||||
intersections.first.emplace_back(i, i + intersection - 1);
|
||||
intersections.second.emplace_back(j, j + intersection - 1);
|
||||
delta = intersection;
|
||||
break;
|
||||
}
|
||||
|
||||
++j;
|
||||
}
|
||||
|
||||
i += delta;
|
||||
}
|
||||
|
||||
CHECK_EQUAL(intersections.first.size(), intersections.second.size(), ());
|
||||
|
||||
return intersections;
|
||||
}
|
||||
|
||||
LineParts::iterator FindLinePart(LineParts & lineParts, LineSegment const & segment)
|
||||
{
|
||||
return std::find_if(lineParts.begin(), lineParts.end(),
|
||||
[&segment](LinePart const & linePart) { return linePart.m_segment == segment; });
|
||||
}
|
||||
|
||||
std::optional<LineSegment> GetIntersection(size_t start1, size_t finish1, size_t start2, size_t finish2)
|
||||
{
|
||||
int const maxStart = static_cast<int>(std::max(start1, start2));
|
||||
int const minFinish = static_cast<int>(std::min(finish1, finish2));
|
||||
|
||||
size_t const intersectionLen = std::max(minFinish - maxStart, 0);
|
||||
|
||||
if (intersectionLen == 0)
|
||||
return std::nullopt;
|
||||
|
||||
return LineSegment(maxStart, static_cast<uint32_t>(maxStart + intersectionLen));
|
||||
}
|
||||
|
||||
int CalcSegmentOrder(size_t segIndex, size_t totalSegCount)
|
||||
{
|
||||
int constexpr shapeOffsetIncrement = 2;
|
||||
|
||||
int const shapeOffset = -static_cast<int>(totalSegCount / 2) * 2 - static_cast<int>(totalSegCount % 2) + 1;
|
||||
int const curSegOffset = shapeOffset + shapeOffsetIncrement * static_cast<int>(segIndex);
|
||||
|
||||
return curSegOffset;
|
||||
}
|
||||
|
||||
bool StopIndexIsSet(size_t stopIndex)
|
||||
{
|
||||
return stopIndex != std::numeric_limits<size_t>::max();
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> GetStopsRange(IdList const & lineStopIds, IdSet const & stopIdsInRegion)
|
||||
{
|
||||
size_t first = std::numeric_limits<size_t>::max();
|
||||
size_t last = std::numeric_limits<size_t>::max();
|
||||
|
||||
for (size_t i = 0; i < lineStopIds.size(); ++i)
|
||||
{
|
||||
auto const & stopId = lineStopIds[i];
|
||||
if (stopIdsInRegion.count(stopId) != 0)
|
||||
{
|
||||
if (!StopIndexIsSet(first))
|
||||
first = i;
|
||||
last = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (StopIndexIsSet(first))
|
||||
{
|
||||
if (first > 0)
|
||||
--first;
|
||||
|
||||
if (last < lineStopIds.size() - 1)
|
||||
++last;
|
||||
}
|
||||
|
||||
CHECK_GREATER_OR_EQUAL(last, first, ());
|
||||
return {first, last};
|
||||
}
|
||||
|
||||
// Returns indexes of nearest to the |point| elements in |shape|.
|
||||
std::vector<size_t> GetMinDistIndexes(std::vector<m2::PointD> const & shape, m2::PointD const & point)
|
||||
{
|
||||
double minDist = std::numeric_limits<double>::max();
|
||||
|
||||
std::vector<size_t> indexes;
|
||||
|
||||
for (size_t i = 0; i < shape.size(); ++i)
|
||||
{
|
||||
double dist = mercator::DistanceOnEarth(shape[i], point);
|
||||
|
||||
if (AlmostEqualAbs(dist, minDist, kEps))
|
||||
{
|
||||
indexes.push_back(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
indexes.clear();
|
||||
indexes.push_back(i);
|
||||
}
|
||||
}
|
||||
|
||||
CHECK(std::is_sorted(indexes.begin(), indexes.end()), ());
|
||||
return indexes;
|
||||
}
|
||||
|
||||
// Returns minimal distance between |val| and element in |vals| and the nearest element value.
|
||||
std::pair<size_t, size_t> FindMinDist(size_t val, std::vector<size_t> const & vals)
|
||||
{
|
||||
size_t minDist = std::numeric_limits<size_t>::max();
|
||||
size_t minVal;
|
||||
|
||||
CHECK(!vals.empty(), ());
|
||||
|
||||
for (size_t curVal : vals)
|
||||
{
|
||||
auto const & [min, max] = std::minmax(val, curVal);
|
||||
size_t const dist = max - min;
|
||||
|
||||
if (dist < minDist)
|
||||
{
|
||||
minVal = curVal;
|
||||
minDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
return {minDist, minVal};
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> FindSegmentOnShape(std::vector<m2::PointD> const & shape,
|
||||
std::vector<m2::PointD> const & segment)
|
||||
{
|
||||
auto const & intersectionsShape = FindIntersections(shape, segment).first;
|
||||
|
||||
if (intersectionsShape.empty())
|
||||
return {0, 0};
|
||||
|
||||
auto const & firstIntersection = intersectionsShape.front();
|
||||
return {firstIntersection.m_startIdx, firstIntersection.m_endIdx};
|
||||
}
|
||||
|
||||
std::pair<size_t, size_t> FindPointsOnShape(std::vector<m2::PointD> const & shape, m2::PointD const & p1,
|
||||
m2::PointD const & p2)
|
||||
{
|
||||
// We find indexes of nearest points in |shape| to |p1| and |p2| correspondingly.
|
||||
std::vector<size_t> const & indexes1 = GetMinDistIndexes(shape, p1);
|
||||
std::vector<size_t> const & indexes2 = GetMinDistIndexes(shape, p2);
|
||||
|
||||
// We fill mapping of distance (between p1 and p2 on the shape) to pairs of indexes of p1 and p2.
|
||||
std::map<size_t, std::pair<size_t, size_t>> distToIndexes;
|
||||
|
||||
for (size_t i1 : indexes1)
|
||||
{
|
||||
auto [minDist, i2] = FindMinDist(i1, indexes2);
|
||||
distToIndexes.emplace(minDist, std::make_pair(i1, i2));
|
||||
}
|
||||
|
||||
CHECK(!distToIndexes.empty(), ());
|
||||
|
||||
// If index of |p1| equals index of |p2| on the |shape| we return the next pair of the nearest
|
||||
// indexes. It is possible in case if |p1| and |p2| are ends of the edge which represents the loop
|
||||
// on the route.
|
||||
auto const & [first, last] = distToIndexes.begin()->second;
|
||||
if (first == last)
|
||||
{
|
||||
LOG(LINFO, ("Edge with equal indexes of first and last points on the shape. Index on the shape:", first));
|
||||
CHECK_GREATER(distToIndexes.size(), 1, ());
|
||||
|
||||
auto const & nextPair = std::next(distToIndexes.begin());
|
||||
CHECK_NOT_EQUAL(nextPair->second.first, nextPair->second.second, ());
|
||||
|
||||
return nextPair->second;
|
||||
}
|
||||
|
||||
return distToIndexes.begin()->second;
|
||||
}
|
||||
} // namespace transit
|
||||
150
libs/transit/world_feed/feed_helpers.hpp
Normal file
150
libs/transit/world_feed/feed_helpers.hpp
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
#pragma once
|
||||
#include "transit/transit_entities.hpp"
|
||||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/just_gtfs/just_gtfs.h"
|
||||
|
||||
namespace transit
|
||||
{
|
||||
// Projection point and mercator distance to it.
|
||||
struct ProjectionToShape
|
||||
{
|
||||
m2::PointD m_point;
|
||||
double m_dist;
|
||||
};
|
||||
|
||||
enum class Direction
|
||||
{
|
||||
Forward,
|
||||
Backward
|
||||
};
|
||||
|
||||
/// \returns |stopPoint| projection to the track segment [|point1|, |point2|] and
|
||||
/// distance from the |stopPoint| to its projection.
|
||||
ProjectionToShape ProjectStopOnTrack(m2::PointD const & stopPoint, m2::PointD const & point1,
|
||||
m2::PointD const & point2);
|
||||
|
||||
/// \returns index of the nearest track point to the |point| and flag if it was inserted to the
|
||||
/// shape. If this index doesn't match already existent points, the stop projection is inserted to
|
||||
/// the |polyline| and the flag is set to true. New point should follow prevPoint in the direction
|
||||
/// |direction|.
|
||||
std::pair<size_t, bool> PrepareNearestPointOnTrack(m2::PointD const & point,
|
||||
std::optional<m2::PointD> const & prevPoint, size_t prevIndex,
|
||||
Direction direction, std::vector<m2::PointD> & polyline);
|
||||
|
||||
/// \returns true if we should not skip routes with this GTFS |routeType|.
|
||||
bool IsRelevantType(gtfs::RouteType const & routeType);
|
||||
|
||||
/// \return string representation of the GTFS |routeType|.
|
||||
std::string ToString(gtfs::RouteType const & routeType);
|
||||
|
||||
/// \return string representation of the extended GTFS |routeType|.
|
||||
std::string ToStringExtendedType(gtfs::RouteType const & routeType);
|
||||
|
||||
/// \return stop times for trip with |tripId|.
|
||||
gtfs::StopTimes GetStopTimesForTrip(gtfs::StopTimes const & allStopTimes, std::string const & tripId);
|
||||
|
||||
// Delete item from the |container| by its key.
|
||||
template <class C, class K>
|
||||
void DeleteIfExists(C & container, K const & key)
|
||||
{
|
||||
auto it = container.find(key);
|
||||
if (it != container.end())
|
||||
container.erase(it);
|
||||
}
|
||||
|
||||
template <class K>
|
||||
void DeleteIfExists(std::vector<K> & container, K const & key)
|
||||
{
|
||||
auto it = std::find(container.begin(), container.end(), key);
|
||||
if (it != container.end())
|
||||
container.erase(it);
|
||||
}
|
||||
|
||||
// Delete items by keys in |keysForDel| from the |container|.
|
||||
template <class C, class S>
|
||||
void DeleteAllEntriesByIds(C & container, S const & keysForDel)
|
||||
{
|
||||
for (auto const & key : keysForDel)
|
||||
DeleteIfExists(container, key);
|
||||
}
|
||||
|
||||
// We have routes with multiple lines. Each line corresponds to the geometric polyline. Lines may
|
||||
// be parallel in some segments. |LinePart| represents these operlapping segments for each line.
|
||||
struct LinePart
|
||||
{
|
||||
// Start and end indexes on polyline.
|
||||
LineSegment m_segment;
|
||||
// Parallel line ids to its start points on the segment.
|
||||
std::map<TransitId, m2::PointD> m_commonLines;
|
||||
// First coordinate of current line on the segment. It is used for determining if the line is
|
||||
// co-directional or reversed regarding the main line on the segment.
|
||||
m2::PointD m_firstPoint;
|
||||
};
|
||||
|
||||
using LineParts = std::vector<LinePart>;
|
||||
|
||||
// Returns iterator to the line part with equivalent segment.
|
||||
LineParts::iterator FindLinePart(LineParts & lineParts, LineSegment const & segment);
|
||||
|
||||
// Data required for finding parallel polyline segments and calculating offsets for each line on the
|
||||
// segment.
|
||||
struct LineSchemeData
|
||||
{
|
||||
TransitId m_lineId = 0;
|
||||
std::string m_color;
|
||||
ShapeLink m_shapeLink;
|
||||
|
||||
LineParts m_lineParts;
|
||||
};
|
||||
|
||||
// Returns overlapping segments between two polylines.
|
||||
std::pair<LineSegments, LineSegments> FindIntersections(std::vector<m2::PointD> const & line1,
|
||||
std::vector<m2::PointD> const & line2);
|
||||
|
||||
// Finds item in |lineParts| equal to |segment| and updates it. If it doesn't exist it is added to
|
||||
// the |lineParts|.
|
||||
void UpdateLinePart(LineParts & lineParts, LineSegment const & segment, m2::PointD const & startPoint,
|
||||
TransitId commonLineId, m2::PointD const & startPointParallel);
|
||||
|
||||
// Calculates start and end indexes of intersection of two segments: [start1, finish1] and [start2,
|
||||
// finish2].
|
||||
std::optional<LineSegment> GetIntersection(size_t start1, size_t finish1, size_t start2, size_t finish2);
|
||||
|
||||
// Calculates line order on segment based on two parameters: line index between all parallel lines,
|
||||
// total parallel lines count. Line order must be symmetrical with respect to the сentral axis of
|
||||
// the polyline.
|
||||
int CalcSegmentOrder(size_t segIndex, size_t totalSegCount);
|
||||
|
||||
// Returns true if |stopIndex| doesn't equal size_t maximum value.
|
||||
bool StopIndexIsSet(size_t stopIndex);
|
||||
|
||||
// Gets interval of stop indexes on the line with |lineStopIds| which belong to the region and
|
||||
// its vicinity. |stopIdsInRegion| is set of all stop ids in the region.
|
||||
std::pair<size_t, size_t> GetStopsRange(IdList const & lineStopIds, IdSet const & stopIdsInRegion);
|
||||
|
||||
// Returns indexes of points |p1| and |p2| on the |shape| polyline. If there are more then 1
|
||||
// occurrences of |p1| or |p2| in |shape|, indexes with minimum distance are returned.
|
||||
std::pair<size_t, size_t> FindPointsOnShape(std::vector<m2::PointD> const & shape, m2::PointD const & p1,
|
||||
m2::PointD const & p2);
|
||||
|
||||
// Returns indexes of first and last points of |segment| in the |shape| polyline. If |edgeShape|
|
||||
// is not found returns pair of zeroes.
|
||||
std::pair<size_t, size_t> FindSegmentOnShape(std::vector<m2::PointD> const & shape,
|
||||
std::vector<m2::PointD> const & segment);
|
||||
|
||||
// Returns reversed vector.
|
||||
template <class T>
|
||||
std::vector<T> GetReversed(std::vector<T> vec)
|
||||
{
|
||||
std::reverse(vec.begin(), vec.end());
|
||||
return vec;
|
||||
}
|
||||
} // namespace transit
|
||||
10
libs/transit/world_feed/gtfs_converter/CMakeLists.txt
Normal file
10
libs/transit/world_feed/gtfs_converter/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
project(gtfs_converter)
|
||||
|
||||
omim_add_executable(${PROJECT_NAME} gtfs_converter.cpp)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
world_feed
|
||||
generator
|
||||
platform
|
||||
gflags::gflags
|
||||
)
|
||||
322
libs/transit/world_feed/gtfs_converter/gtfs_converter.cpp
Normal file
322
libs/transit/world_feed/gtfs_converter/gtfs_converter.cpp
Normal file
|
|
@ -0,0 +1,322 @@
|
|||
#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"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/logging.hpp"
|
||||
#include "base/timer.hpp"
|
||||
|
||||
#include <gflags/gflags.h>
|
||||
|
||||
DEFINE_string(path_mapping, "",
|
||||
"Path to the mapping file of TransitId to GTFS hash for all transit entities except edges");
|
||||
DEFINE_string(path_mapping_edges, "", "Path to the mapping file of TransitId to GTFS hash for edges");
|
||||
// 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, "", "OMaps json file with subway data from OSM");
|
||||
DEFINE_string(path_json, "", "Output directory for dumping json files");
|
||||
DEFINE_string(path_resources, "", "OMaps resources directory");
|
||||
DEFINE_string(start_feed, "", "Optional. Feed directory from which the process continues");
|
||||
DEFINE_string(stop_feed, "", "Optional. Feed directory on which to stop the process");
|
||||
|
||||
// Finds subdirectories with feeds.
|
||||
Platform::FilesList GetGtfsFeedsInDirectory(std::string const & path)
|
||||
{
|
||||
Platform::FilesList res;
|
||||
Platform::TFilesWithType gtfsList;
|
||||
Platform::GetFilesByType(path, Platform::EFileType::Directory, gtfsList);
|
||||
|
||||
for (auto const & item : gtfsList)
|
||||
{
|
||||
auto const & gtfsFeedDir = item.first;
|
||||
if (gtfsFeedDir != "." && gtfsFeedDir != "..")
|
||||
res.push_back(base::JoinPath(path, gtfsFeedDir));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Handles the case when the directory consists of a single subdirectory with GTFS files.
|
||||
void ExtendPath(std::string & path)
|
||||
{
|
||||
Platform::TFilesWithType csvFiles;
|
||||
Platform::GetFilesByType(path, Platform::EFileType::Regular, csvFiles);
|
||||
if (!csvFiles.empty())
|
||||
return;
|
||||
|
||||
Platform::TFilesWithType subdirs;
|
||||
Platform::GetFilesByType(path, Platform::EFileType::Directory, subdirs);
|
||||
|
||||
// If there are more subdirectories then ".", ".." and directory with feed, the feed is most
|
||||
// likely corrupted.
|
||||
if (subdirs.size() > 3)
|
||||
return;
|
||||
|
||||
for (auto const & item : subdirs)
|
||||
{
|
||||
auto const & subdir = item.first;
|
||||
if (subdir != "." && subdir != "..")
|
||||
{
|
||||
path = base::JoinPath(path, subdir);
|
||||
LOG(LDEBUG, ("Found subdirectory with feed", path));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SkipFeed(std::string const & feedPath, bool & pass)
|
||||
{
|
||||
if (!FLAGS_start_feed.empty() && pass)
|
||||
{
|
||||
if (base::FileNameFromFullPath(feedPath) != FLAGS_start_feed)
|
||||
return true;
|
||||
pass = false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool StopOnFeed(std::string const & feedPath)
|
||||
{
|
||||
if (!FLAGS_stop_feed.empty() && base::FileNameFromFullPath(feedPath) == FLAGS_stop_feed)
|
||||
{
|
||||
LOG(LINFO, ("Stop on", feedPath));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enum class FeedStatus
|
||||
{
|
||||
OK = 0,
|
||||
CORRUPTED,
|
||||
NO_SHAPES
|
||||
};
|
||||
|
||||
FeedStatus ReadFeed(gtfs::Feed & feed)
|
||||
{
|
||||
// First we read shapes. If there are no shapes in feed we do not need to read all the required
|
||||
// files - agencies, stops, etc.
|
||||
if (auto res = feed.read_shapes(); res != gtfs::ResultCode::OK)
|
||||
{
|
||||
LOG(LWARNING, ("Could not get shapes.", res.message));
|
||||
return FeedStatus::NO_SHAPES;
|
||||
}
|
||||
|
||||
if (feed.get_shapes().empty())
|
||||
return FeedStatus::NO_SHAPES;
|
||||
|
||||
// We try to parse required for json files and return error in case of invalid file content.
|
||||
if (auto res = feed.read_agencies(); res != gtfs::ResultCode::OK)
|
||||
{
|
||||
LOG(LWARNING, ("Could not parse agencies.", res.message));
|
||||
return FeedStatus::CORRUPTED;
|
||||
}
|
||||
|
||||
if (auto res = feed.read_routes(); res != gtfs::ResultCode::OK)
|
||||
{
|
||||
LOG(LWARNING, ("Could not parse routes.", res.message));
|
||||
return FeedStatus::CORRUPTED;
|
||||
}
|
||||
|
||||
if (auto res = feed.read_trips(); res != gtfs::ResultCode::OK)
|
||||
{
|
||||
LOG(LWARNING, ("Could not parse trips.", res.message));
|
||||
return FeedStatus::CORRUPTED;
|
||||
}
|
||||
|
||||
if (auto res = feed.read_stops(); res != gtfs::ResultCode::OK)
|
||||
{
|
||||
LOG(LWARNING, ("Could not parse stops.", res.message));
|
||||
return FeedStatus::CORRUPTED;
|
||||
}
|
||||
|
||||
if (auto res = feed.read_stop_times(); res != gtfs::ResultCode::OK)
|
||||
{
|
||||
LOG(LWARNING, ("Could not parse stop times.", res.message));
|
||||
return FeedStatus::CORRUPTED;
|
||||
}
|
||||
|
||||
// We try to parse optional for json files and do not return error in case of invalid file
|
||||
// content, only log warning message.
|
||||
if (auto res = feed.read_calendar(); gtfs::ErrorParsingOptionalFile(res))
|
||||
LOG(LINFO, ("Could not parse calendar.", res.message));
|
||||
|
||||
if (auto res = feed.read_calendar_dates(); gtfs::ErrorParsingOptionalFile(res))
|
||||
LOG(LINFO, ("Could not parse calendar dates.", res.message));
|
||||
|
||||
if (auto res = feed.read_frequencies(); gtfs::ErrorParsingOptionalFile(res))
|
||||
LOG(LINFO, ("Could not parse frequencies.", res.message));
|
||||
|
||||
if (auto res = feed.read_transfers(); gtfs::ErrorParsingOptionalFile(res))
|
||||
LOG(LINFO, ("Could not parse transfers.", res.message));
|
||||
|
||||
if (feed.read_feed_info() == gtfs::ResultCode::OK)
|
||||
LOG(LINFO, ("Feed info is present."));
|
||||
|
||||
return FeedStatus::OK;
|
||||
}
|
||||
|
||||
// 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::IdGenerator & generatorEdges,
|
||||
transit::ColorPicker & colorPicker, feature::CountriesFilesAffiliation & mwmMatcher)
|
||||
{
|
||||
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 false;
|
||||
}
|
||||
|
||||
std::vector<std::string> invalidFeeds;
|
||||
|
||||
size_t feedsWithNoShapesCount = 0;
|
||||
size_t feedsNotDumpedCount = 0;
|
||||
size_t feedsDumped = 0;
|
||||
size_t feedsTotal = gtfsFeeds.size();
|
||||
bool pass = true;
|
||||
|
||||
for (size_t i = 0; i < gtfsFeeds.size(); ++i)
|
||||
{
|
||||
base::Timer feedTimer;
|
||||
auto feedPath = gtfsFeeds[i];
|
||||
|
||||
if (SkipFeed(feedPath, pass))
|
||||
{
|
||||
++feedsTotal;
|
||||
LOG(LINFO, ("Skipped", feedPath));
|
||||
continue;
|
||||
}
|
||||
|
||||
bool stop = StopOnFeed(feedPath);
|
||||
if (stop)
|
||||
feedsTotal -= (gtfsFeeds.size() - i - 1);
|
||||
|
||||
ExtendPath(feedPath);
|
||||
LOG(LINFO, ("Handling feed", feedPath));
|
||||
|
||||
gtfs::Feed feed(feedPath);
|
||||
|
||||
if (auto const res = ReadFeed(feed); res != FeedStatus::OK)
|
||||
{
|
||||
if (res == FeedStatus::NO_SHAPES)
|
||||
feedsWithNoShapesCount++;
|
||||
else
|
||||
invalidFeeds.push_back(feedPath);
|
||||
|
||||
if (stop)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
transit::WorldFeed globalFeed(generator, generatorEdges, colorPicker, mwmMatcher);
|
||||
|
||||
if (!globalFeed.SetFeed(std::move(feed)))
|
||||
{
|
||||
LOG(LINFO, ("Error transforming feed for json representation."));
|
||||
++feedsNotDumpedCount;
|
||||
if (stop)
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool const saved = globalFeed.Save(FLAGS_path_json, i == 0 /* overwrite */);
|
||||
if (saved)
|
||||
++feedsDumped;
|
||||
else
|
||||
++feedsNotDumpedCount;
|
||||
|
||||
LOG(LINFO, ("Merged:", saved ? "yes" : "no", "time", feedTimer.ElapsedSeconds(), "s"));
|
||||
|
||||
if (stop)
|
||||
break;
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Corrupted feeds paths:", invalidFeeds));
|
||||
LOG(LINFO, ("Corrupted feeds:", invalidFeeds.size(), "/", feedsTotal));
|
||||
LOG(LINFO, ("Feeds with no shapes:", feedsWithNoShapesCount, "/", feedsTotal));
|
||||
LOG(LINFO, ("Feeds parsed but not dumped:", feedsNotDumpedCount, "/", feedsTotal));
|
||||
LOG(LINFO, ("Total dumped feeds:", feedsDumped, "/", feedsTotal));
|
||||
|
||||
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::IdGenerator & generatorEdges,
|
||||
transit::ColorPicker & colorPicker, feature::CountriesFilesAffiliation & mwmMatcher, bool overwrite)
|
||||
{
|
||||
transit::WorldFeed globalFeed(generator, generatorEdges, 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)
|
||||
{
|
||||
gflags::SetUsageMessage("Reads GTFS feeds or subway transit.json, produces json with global ids for generator.");
|
||||
gflags::ParseCommandLineFlags(&argc, &argv, true);
|
||||
auto const toolName = base::FileNameFromFullPath(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."));
|
||||
gflags::ShowUsageWithFlagsRestrict(argv[0], toolName.c_str());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (FLAGS_path_mapping.empty() || FLAGS_path_mapping_edges.empty() || FLAGS_path_json.empty())
|
||||
{
|
||||
LOG(LWARNING, ("Some of the required options are not present."));
|
||||
gflags::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));
|
||||
gflags::ShowUsageWithFlagsRestrict(argv[0], toolName.c_str());
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
transit::IdGenerator generator(FLAGS_path_mapping);
|
||||
transit::IdGenerator generatorEdges(FLAGS_path_mapping_edges);
|
||||
|
||||
GetPlatform().SetResourceDir(FLAGS_path_resources);
|
||||
transit::ColorPicker colorPicker;
|
||||
|
||||
feature::CountriesFilesAffiliation mwmMatcher(GetPlatform().ResourcesDir(), false /* haveBordersForWholeWorld */);
|
||||
|
||||
// We convert GTFS feeds to the json format suitable for generator_tool and save it to the
|
||||
// corresponding directory.
|
||||
if (!FLAGS_path_gtfs_feeds.empty() && !ConvertFeeds(generator, generatorEdges, colorPicker, mwmMatcher))
|
||||
return EXIT_FAILURE;
|
||||
|
||||
// We mixin data in our "old transit" (in fact subway-only) json format to the resulting files
|
||||
// in experimental line-by-line json format which is processed by generator_tool for building
|
||||
// experimental transit section. We use the same id |generator| so ids of subway and GTFS
|
||||
// itineraries will not conflict.
|
||||
if (!FLAGS_path_subway_json.empty() &&
|
||||
!ConvertSubway(generator, generatorEdges, colorPicker, mwmMatcher, FLAGS_path_gtfs_feeds.empty() /* overwrite */))
|
||||
{
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
generator.Save();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
810
libs/transit/world_feed/subway_converter.cpp
Normal file
810
libs/transit/world_feed/subway_converter.cpp
Normal file
|
|
@ -0,0 +1,810 @@
|
|||
#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 <algorithm>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
|
||||
namespace transit
|
||||
{
|
||||
std::string const kHashPrefix = "mapsme_transit";
|
||||
std::string const kDefaultLang = "default";
|
||||
std::string const kSubwayRouteType = "subway";
|
||||
|
||||
namespace
|
||||
{
|
||||
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)
|
||||
{
|
||||
return static_cast<uint32_t>(lineId >> 4);
|
||||
}
|
||||
|
||||
// Increments |lineSegment| indexes by |shapeLink| start index.
|
||||
void ShiftSegmentOnShape(transit::LineSegment & lineSegment, transit::ShapeLink const & shapeLink)
|
||||
{
|
||||
lineSegment.m_startIdx += shapeLink.m_startIndex;
|
||||
lineSegment.m_endIdx += shapeLink.m_startIndex;
|
||||
}
|
||||
|
||||
// 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), ());
|
||||
|
||||
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
|
||||
|
||||
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();
|
||||
MinimizeReversedLinesCount();
|
||||
|
||||
if (!ConvertStops())
|
||||
return false;
|
||||
|
||||
ConvertTransfers();
|
||||
|
||||
// In contrast to the GTFS gates OSM gates for subways shouldn't be empty.
|
||||
if (!ConvertGates())
|
||||
return false;
|
||||
|
||||
if (!ConvertEdges())
|
||||
return false;
|
||||
|
||||
m_feed.SplitFeedIntoRegions();
|
||||
|
||||
PrepareLinesMetadata();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
m_feed.m_networks.m_data.emplace(networkId, networkSubway.GetTitle());
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
auto & edgesSubway = m_graphData.GetEdges();
|
||||
|
||||
for (size_t i = 0; i < edgesSubway.size(); ++i)
|
||||
{
|
||||
auto const & edgeSubway = edgesSubway[i];
|
||||
|
||||
if (edgeSubway.GetTransfer())
|
||||
m_edgesTransferSubway.emplace(edgeSubway, i);
|
||||
else
|
||||
m_edgesSubway.emplace(edgeSubway, i);
|
||||
}
|
||||
|
||||
return !m_edgesSubway.empty() && !m_edgesTransferSubway.empty();
|
||||
}
|
||||
|
||||
std::pair<TransitId, RouteData> SubwayConverter::MakeRoute(routing::transit::Line const & lineSubway)
|
||||
{
|
||||
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 = lineSubway.GetNumber();
|
||||
routeData.m_routeType = kSubwayRouteType;
|
||||
routeData.m_networkId = lineSubway.GetNetworkId();
|
||||
routeData.m_color = lineSubway.GetColor();
|
||||
|
||||
return {routeId, routeData};
|
||||
}
|
||||
|
||||
std::pair<TransitId, GateData> 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<TransitId, TransferData> 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<TransitId, LineData> 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 = lineSubway.GetTitle();
|
||||
lineData.m_schedule.SetDefaultFrequency(lineSubway.GetInterval());
|
||||
|
||||
return {lineId, lineData};
|
||||
}
|
||||
|
||||
std::pair<EdgeId, EdgeData> SubwayConverter::MakeEdge(routing::transit::Edge const & edgeSubway, uint32_t index)
|
||||
{
|
||||
auto const lineId = edgeSubway.GetLineId();
|
||||
EdgeId const edgeId(m_stopIdMapping[edgeSubway.GetStop1Id()], m_stopIdMapping[edgeSubway.GetStop2Id()], lineId);
|
||||
EdgeData edgeData;
|
||||
edgeData.m_weight = edgeSubway.GetWeight();
|
||||
edgeData.m_featureId = index;
|
||||
|
||||
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;
|
||||
|
||||
return {edgeId, edgeData};
|
||||
}
|
||||
|
||||
std::pair<EdgeTransferId, EdgeData> SubwayConverter::MakeEdgeTransfer(routing::transit::Edge const & edgeSubway,
|
||||
uint32_t index)
|
||||
{
|
||||
EdgeTransferId const edgeTransferId(m_stopIdMapping[edgeSubway.GetStop1Id()] /* fromStopId */,
|
||||
m_stopIdMapping[edgeSubway.GetStop2Id()] /* toStopId */);
|
||||
EdgeData edgeData;
|
||||
edgeData.m_weight = edgeSubway.GetWeight();
|
||||
edgeData.m_featureId = index;
|
||||
|
||||
return {edgeTransferId, edgeData};
|
||||
}
|
||||
|
||||
std::pair<TransitId, StopData> 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();
|
||||
|
||||
if (stopSubway.GetFeatureId() != kInvalidFeatureId)
|
||||
stopData.m_featureId = stopSubway.GetFeatureId();
|
||||
|
||||
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 & stopIdsSubway = lineSubway.GetStopIds().front();
|
||||
|
||||
CHECK_GREATER(stopIdsSubway.size(), 1, ("Range must include at least two stops."));
|
||||
|
||||
for (size_t i = 0; i < stopIdsSubway.size(); ++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 = stopIdsSubway[i - 1];
|
||||
CHECK(stopIdSubwayPrev != stopIdSubway, (stopIdSubway));
|
||||
|
||||
auto const & edge = FindEdge(stopIdSubwayPrev, stopIdSubway, lineId);
|
||||
|
||||
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())
|
||||
{
|
||||
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);
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
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."));
|
||||
|
||||
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."));
|
||||
|
||||
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);
|
||||
|
||||
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)
|
||||
if (base::IsExist(lineData.m_stopIds, stopId))
|
||||
routeToStops[lineData.m_routeId].insert(stopId);
|
||||
}
|
||||
|
||||
// We don't count as transfers transfer 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.
|
||||
for (auto stopId : transferData.m_stopsIds)
|
||||
LinkTransferIdToStop(m_feed.m_stops.m_data.at(stopId), transferId);
|
||||
}
|
||||
|
||||
LOG(LINFO, ("Converted", m_feed.m_transfers.m_data.size(), "transfers."));
|
||||
|
||||
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."));
|
||||
|
||||
return !m_feed.m_gates.m_data.empty();
|
||||
}
|
||||
|
||||
bool SubwayConverter::ConvertEdges()
|
||||
{
|
||||
for (auto const & [edgeSubway, index] : m_edgesSubway)
|
||||
m_feed.m_edges.m_data.emplace(MakeEdge(edgeSubway, index));
|
||||
|
||||
LOG(LINFO, ("Converted", m_feed.m_edges.m_data.size(), "edges."));
|
||||
|
||||
for (auto const & [edgeTransferSubway, index] : m_edgesTransferSubway)
|
||||
m_feed.m_edgesTransfers.m_data.emplace(MakeEdgeTransfer(edgeTransferSubway, index));
|
||||
|
||||
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 (!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 = 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, ("Preparing metadata for", 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((AlmostEqualAbs(startPoint1, startPoint2, kEps) && AlmostEqualAbs(endPoint1, endPoint2, kEps)) ||
|
||||
(AlmostEqualAbs(startPoint1, endPoint2, kEps) && 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
|
||||
{
|
||||
routing::transit::Edge edge(stop1Id, stop2Id, 0 /* weight */, lineId, false /* transfer */, {} /* shapeIds */);
|
||||
|
||||
auto const itEdge = m_edgesSubway.find(edge);
|
||||
|
||||
CHECK(itEdge != m_edgesSubway.end(), (stop1Id, stop2Id, lineId));
|
||||
|
||||
return itEdge->first;
|
||||
}
|
||||
} // namespace transit
|
||||
88
libs/transit/world_feed/subway_converter.hpp
Normal file
88
libs/transit/world_feed/subway_converter.hpp
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#pragma once
|
||||
|
||||
#include "generator/affiliation.hpp"
|
||||
|
||||
#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"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/opening_hours/opening_hours.hpp"
|
||||
|
||||
namespace transit
|
||||
{
|
||||
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
|
||||
{
|
||||
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();
|
||||
// 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 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<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);
|
||||
static std::pair<TransitId, LineData> MakeLine(routing::transit::Line const & lineSubway, TransitId routeId);
|
||||
std::pair<EdgeId, EdgeData> MakeEdge(routing::transit::Edge const & edgeSubway, uint32_t index);
|
||||
std::pair<EdgeTransferId, EdgeData> MakeEdgeTransfer(routing::transit::Edge const & edgeSubway, uint32_t index);
|
||||
std::pair<TransitId, StopData> 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<routing::transit::StopId, TransitId> m_stopIdMapping;
|
||||
// Subset of the |m_graphData| edges with no transfers.
|
||||
std::map<routing::transit::Edge, uint32_t> m_edgesSubway;
|
||||
// Subset of the |m_graphData| edges with transfers.
|
||||
std::map<routing::transit::Edge, uint32_t> m_edgesTransferSubway;
|
||||
};
|
||||
} // namespace transit
|
||||
1977
libs/transit/world_feed/world_feed.cpp
Normal file
1977
libs/transit/world_feed/world_feed.cpp
Normal file
File diff suppressed because it is too large
Load diff
434
libs/transit/world_feed/world_feed.hpp
Normal file
434
libs/transit/world_feed/world_feed.hpp
Normal file
|
|
@ -0,0 +1,434 @@
|
|||
#pragma once
|
||||
|
||||
#include "generator/affiliation.hpp"
|
||||
|
||||
#include "transit/transit_entities.hpp"
|
||||
#include "transit/transit_schedule.hpp"
|
||||
#include "transit/world_feed/color_picker.hpp"
|
||||
#include "transit/world_feed/feed_helpers.hpp"
|
||||
|
||||
#include "geometry/mercator.hpp"
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/just_gtfs/just_gtfs.h"
|
||||
|
||||
namespace transit
|
||||
{
|
||||
static std::string const kDelimiter = "_";
|
||||
// Generates globally unique TransitIds mapped to the GTFS entities hashes.
|
||||
class IdGenerator
|
||||
{
|
||||
public:
|
||||
IdGenerator() = default;
|
||||
explicit IdGenerator(std::string const & idMappingPath);
|
||||
void Save();
|
||||
|
||||
TransitId MakeId(std::string const & hash);
|
||||
|
||||
private:
|
||||
std::unordered_map<std::string, TransitId> m_hashToId;
|
||||
TransitId m_curId = 0;
|
||||
std::string m_idMappingPath;
|
||||
};
|
||||
|
||||
// Here are OMaps representations for GTFS entities, e.g. networks for GTFS agencies.
|
||||
// https://developers.google.com/transit/gtfs/reference
|
||||
|
||||
struct Networks
|
||||
{
|
||||
void Write(IdSet const & ids, std::ofstream & stream) const;
|
||||
|
||||
// Id to agency name mapping.
|
||||
std::unordered_map<TransitId, std::string> m_data;
|
||||
};
|
||||
|
||||
struct RouteData
|
||||
{
|
||||
TransitId m_networkId = 0;
|
||||
std::string m_routeType;
|
||||
std::string m_title;
|
||||
std::string m_color;
|
||||
};
|
||||
|
||||
struct Routes
|
||||
{
|
||||
void Write(IdSet const & ids, std::ofstream & stream) const;
|
||||
|
||||
std::unordered_map<TransitId, RouteData> m_data;
|
||||
};
|
||||
|
||||
struct LineData
|
||||
{
|
||||
TransitId m_routeId = 0;
|
||||
ShapeLink m_shapeLink;
|
||||
std::string m_title;
|
||||
// Sequence of stops along the line from first to last.
|
||||
IdList m_stopIds;
|
||||
|
||||
// Monthdays and weekdays ranges on which the line is at service.
|
||||
// Exceptions in service schedule. Explicitly activates or disables service by dates.
|
||||
// Transport intervals depending on the day timespans.
|
||||
Schedule m_schedule;
|
||||
|
||||
// Fields not intended to be exported to json.
|
||||
TransitId m_shapeId = 0;
|
||||
std::string m_gtfsTripId;
|
||||
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, LineSegmentInRegion> const & ids, std::ofstream & stream) const;
|
||||
|
||||
std::unordered_map<TransitId, LineData> m_data;
|
||||
};
|
||||
|
||||
struct LinesMetadata
|
||||
{
|
||||
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;
|
||||
};
|
||||
|
||||
struct ShapeData
|
||||
{
|
||||
ShapeData() = default;
|
||||
explicit ShapeData(std::vector<m2::PointD> const & points);
|
||||
|
||||
std::vector<m2::PointD> m_points;
|
||||
// Field not for dumping to json:
|
||||
IdSet m_lineIds;
|
||||
};
|
||||
|
||||
using ShapesIter = std::unordered_map<TransitId, ShapeData>::iterator;
|
||||
|
||||
struct Shapes
|
||||
{
|
||||
void Write(IdSet const & ids, std::ofstream & stream) const;
|
||||
|
||||
std::unordered_map<TransitId, ShapeData> m_data;
|
||||
};
|
||||
|
||||
struct StopData
|
||||
{
|
||||
void UpdateTimetable(TransitId lineId, gtfs::StopTime const & stopTime);
|
||||
|
||||
m2::PointD m_point;
|
||||
std::string m_title;
|
||||
// If arrival time at a specific stop for a specific trip on a route is not available,
|
||||
// |m_timetable| can be left empty.
|
||||
TimeTable m_timetable;
|
||||
|
||||
// Ids of transfer nodes containing this stop.
|
||||
IdList m_transferIds;
|
||||
|
||||
uint64_t m_osmId = 0;
|
||||
uint32_t m_featureId = 0;
|
||||
|
||||
// Field not intended for dumping to json:
|
||||
std::string m_gtfsParentId;
|
||||
};
|
||||
|
||||
struct Stops
|
||||
{
|
||||
void Write(IdSet const & ids, std::ofstream & stream) const;
|
||||
|
||||
std::unordered_map<TransitId, StopData> m_data;
|
||||
};
|
||||
|
||||
struct Edges
|
||||
{
|
||||
void Write(IdEdgeSet const & ids, std::ofstream & stream) const;
|
||||
|
||||
std::unordered_map<EdgeId, EdgeData, EdgeIdHasher> m_data;
|
||||
};
|
||||
|
||||
struct EdgeTransferId
|
||||
{
|
||||
EdgeTransferId() = default;
|
||||
EdgeTransferId(TransitId fromStopId, TransitId toStopId);
|
||||
|
||||
bool operator==(EdgeTransferId const & other) const;
|
||||
|
||||
TransitId m_fromStopId = 0;
|
||||
TransitId m_toStopId = 0;
|
||||
};
|
||||
|
||||
struct EdgeTransferIdHasher
|
||||
{
|
||||
size_t operator()(EdgeTransferId const & key) const;
|
||||
};
|
||||
|
||||
using IdEdgeTransferSet = std::unordered_set<EdgeTransferId, EdgeTransferIdHasher>;
|
||||
|
||||
struct EdgesTransfer
|
||||
{
|
||||
void Write(IdEdgeTransferSet const & ids, std::ofstream & stream) const;
|
||||
// Key is pair of stops and value is |EdgeData|, containing weight (in seconds).
|
||||
std::unordered_map<EdgeTransferId, EdgeData, EdgeTransferIdHasher> m_data;
|
||||
};
|
||||
|
||||
struct TransferData
|
||||
{
|
||||
m2::PointD m_point;
|
||||
IdList m_stopsIds;
|
||||
};
|
||||
|
||||
struct Transfers
|
||||
{
|
||||
void Write(IdSet const & ids, std::ofstream & stream) const;
|
||||
|
||||
std::unordered_map<TransitId, TransferData> m_data;
|
||||
};
|
||||
|
||||
struct GateData
|
||||
{
|
||||
bool m_isEntrance = false;
|
||||
bool m_isExit = false;
|
||||
m2::PointD m_point;
|
||||
std::vector<TimeFromGateToStop> m_weights;
|
||||
|
||||
uint64_t m_osmId = 0;
|
||||
// Field not intended for dumping to json:
|
||||
std::string m_gtfsId;
|
||||
};
|
||||
|
||||
struct Gates
|
||||
{
|
||||
void Write(IdSet const & ids, std::ofstream & stream) const;
|
||||
|
||||
std::unordered_map<TransitId, GateData> m_data;
|
||||
};
|
||||
|
||||
// Indexes for WorldFeed |m_gtfsIdToHash| field. For each type of GTFS entity, e.g. agency or stop,
|
||||
// there is distinct mapping located by its own |FieldIdx| index in the |m_gtfsIdToHash|.
|
||||
enum FieldIdx
|
||||
{
|
||||
AgencyIdx = 0,
|
||||
StopsIdx,
|
||||
RoutesIdx,
|
||||
TripsIdx,
|
||||
ShapesIdx,
|
||||
IdxCount
|
||||
};
|
||||
|
||||
using GtfsIdToHash = std::unordered_map<std::string, std::string>;
|
||||
|
||||
struct StopsOnLines
|
||||
{
|
||||
explicit StopsOnLines(IdList const & ids);
|
||||
|
||||
IdList m_stopSeq;
|
||||
IdSet m_lines;
|
||||
bool m_isValid = true;
|
||||
transit::Direction m_direction = Direction::Forward;
|
||||
};
|
||||
|
||||
using IdsInRegion = std::unordered_map<std::string, IdSet>;
|
||||
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>;
|
||||
|
||||
using Regions = std::vector<std::string>;
|
||||
|
||||
struct TransitByRegion
|
||||
{
|
||||
IdsInRegion m_networks;
|
||||
IdsInRegion m_routes;
|
||||
LinesInRegion m_lines;
|
||||
IdsInRegion m_shapes;
|
||||
IdsInRegion m_stops;
|
||||
EdgeIdsInRegion m_edges;
|
||||
EdgeTransferIdsInRegion m_edgesTransfers;
|
||||
IdsInRegion m_transfers;
|
||||
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,
|
||||
// |ColorPicker| for choosing route colors from our palette, |CountriesFilesAffiliation| for
|
||||
// splitting result feed into regions.
|
||||
// 2) Call SetFeed(...) method to convert GTFS entities into objects convenient for dumping to json.
|
||||
// 3) Call Save(...) to save result data as a set of json files in the specified directory.
|
||||
class WorldFeed
|
||||
{
|
||||
public:
|
||||
WorldFeed(IdGenerator & generator, IdGenerator & generatorEdges, ColorPicker & colorPicker,
|
||||
feature::CountriesFilesAffiliation & mwmMatcher);
|
||||
// Transforms GTFS feed into the global feed.
|
||||
bool SetFeed(gtfs::Feed && feed);
|
||||
|
||||
// Dumps global feed to |world_feed_path|.
|
||||
bool Save(std::string const & worldFeedDir, bool overwrite);
|
||||
|
||||
private:
|
||||
friend class WorldFeedIntegrationTests;
|
||||
friend class SubwayConverterTests;
|
||||
friend class SubwayConverter;
|
||||
|
||||
void SaveRegions(std::string const & worldFeedDir, std::string const & region, bool overwrite);
|
||||
|
||||
bool SetFeedLanguage();
|
||||
// Fills networks from GTFS agencies data.
|
||||
bool FillNetworks();
|
||||
// Fills routes from GTFS routes data.
|
||||
bool FillRoutes();
|
||||
// Fills lines and corresponding shapes from GTFS trips and shapes.
|
||||
bool FillLinesAndShapes();
|
||||
// Deletes shapes which are sub-shapes and refreshes corresponding links in lines.
|
||||
void ModifyLinesAndShapes();
|
||||
// Gets service monthday open/closed ranges, weekdays and exceptions in schedule.
|
||||
void FillLinesSchedule();
|
||||
// Gets frequencies of trips from GTFS.
|
||||
|
||||
// Adds shape with mercator points instead of WGS84 lat/lon.
|
||||
void AddShape(GtfsIdToHash::iterator & iter, gtfs::Shape const & shapeItems, TransitId lineId);
|
||||
// Fills stops data, corresponding fields in |m_lines| and builds edges for the road graph.
|
||||
bool FillStopsEdges();
|
||||
|
||||
// Generates globally unique id and hash for the stop by its |stopGtfsId|.
|
||||
std::pair<TransitId, std::string> GetStopIdAndHash(std::string const & stopGtfsId);
|
||||
|
||||
// Adds new stop with |stopId| and fills it with GTFS data by |gtfsId| or just
|
||||
// links to it |lineId|.
|
||||
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. Returns number of
|
||||
// invalid and valid shapes.
|
||||
std::pair<size_t, size_t> ModifyShapes();
|
||||
// Fills transfers based on GTFS transfers.
|
||||
void FillTransfers();
|
||||
// Fills gates based on GTFS stops.
|
||||
void FillGates();
|
||||
// Recalculates 0-weights of edges based on the shape length.
|
||||
bool UpdateEdgeWeights();
|
||||
|
||||
std::optional<Direction> ProjectStopsToShape(ShapesIter & itShape, StopsOnLines const & stopsOnLines,
|
||||
std::unordered_map<TransitId, std::vector<size_t>> & stopsToIndexes);
|
||||
|
||||
// Splits data into regions.
|
||||
void SplitFeedIntoRegions();
|
||||
// Splits |m_stops|, |m_edges| and |m_edgesTransfer| into regions. The following stops are
|
||||
// added to corresponding regions: stops inside borders; stops which are connected with an edge
|
||||
// from |m_edges| or |m_edgesTransfer| with stops inside borders. Edge from |m_edges| or
|
||||
// |m_edgesTransfer| is added to the region if one of its stop ids lies inside mwm.
|
||||
void SplitStopsBasedData();
|
||||
// Splits |m_lines|, |m_shapes|, |m_routes| and |m_networks| into regions. If one of the line
|
||||
// stops (|m_stopIds|) lies inside region, then this line is added to this region. But only stops
|
||||
// whose stop ids are contained in this region will be attached to the line in this region. Shape,
|
||||
// route or network is added to the region if it is linked to the line in this region.
|
||||
void SplitLinesBasedData();
|
||||
// Splits |m_transfers| and |m_gates| into regions. Transfer is added to the region if there is
|
||||
// stop in |m_stopsIds| which is inside this region. Gate is added to the region if there is stop
|
||||
// in |m_weights| which is inside the region.
|
||||
void SplitSupplementalData();
|
||||
// Extend existing ids containers by appending to them |fromId| and |toId|. If one of the ids is
|
||||
// present in region, then the other is also added.
|
||||
std::pair<Regions, Regions> ExtendRegionsByPair(TransitId fromId, TransitId toId);
|
||||
// Returns true if edge weight between two stops (stop ids are contained in |edgeId|)
|
||||
// contradicts maximum transit speed.
|
||||
bool SpeedExceedsMaxVal(EdgeId const & edgeId, EdgeData const & edgeData);
|
||||
// Removes entities from feed which are linked only to the |corruptedLineIds|.
|
||||
bool ClearFeedByLineIds(std::unordered_set<TransitId> const & corruptedLineIds);
|
||||
// Current GTFS feed which is being merged to the global feed.
|
||||
gtfs::Feed m_feed;
|
||||
|
||||
// Entities for json'izing and feeding to the generator_tool (Not split by regions).
|
||||
Networks m_networks;
|
||||
Routes m_routes;
|
||||
Lines m_lines;
|
||||
LinesMetadata m_linesMetadata;
|
||||
Shapes m_shapes;
|
||||
Stops m_stops;
|
||||
Edges m_edges;
|
||||
EdgesTransfer m_edgesTransfers;
|
||||
Transfers m_transfers;
|
||||
Gates m_gates;
|
||||
|
||||
// Mapping of the edge to its 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;
|
||||
|
||||
// Generator of ids, globally unique and constant between re-runs.
|
||||
IdGenerator & m_idGenerator;
|
||||
// Generator of ids for edges only.
|
||||
IdGenerator & m_idGeneratorEdges;
|
||||
// Color name picker of the nearest color for route RBG from our constant list of transfer colors.
|
||||
ColorPicker & m_colorPicker;
|
||||
// Mwm matcher for m2:Points representing stops and other entities.
|
||||
feature::CountriesFilesAffiliation & m_affiliation;
|
||||
|
||||
// GTFS id -> entity hash mapping. Maps GTFS id string (unique only for current feed) to the
|
||||
// globally unique hash.
|
||||
std::vector<GtfsIdToHash> m_gtfsIdToHash;
|
||||
|
||||
// Unique hash characterizing each GTFS feed.
|
||||
std::string m_gtfsHash;
|
||||
|
||||
// Unique hashes of all agencies handled by WorldFeed.
|
||||
static std::unordered_set<std::string> m_agencyHashes;
|
||||
// Count of corrupted stops sequences which could not be projected to the shape polyline.
|
||||
static size_t m_badStopSeqCount;
|
||||
// Agencies which are already handled by WorldFeed and should be copied to the resulting jsons.
|
||||
std::unordered_set<std::string> m_agencySkipList;
|
||||
|
||||
// If the feed explicitly specifies its language, we use its value. Otherwise set to default.
|
||||
std::string m_feedLanguage;
|
||||
|
||||
bool m_feedIsSplitIntoRegions = false;
|
||||
};
|
||||
|
||||
// Creates concatenation of |values| separated by delimiter.
|
||||
template <typename... Values>
|
||||
auto BuildHash(Values... values)
|
||||
{
|
||||
size_t constexpr paramsCount = sizeof...(Values);
|
||||
size_t const delimitersSize = (paramsCount - 1) * kDelimiter.size();
|
||||
size_t const totalSize = (delimitersSize + ... + values.size());
|
||||
|
||||
std::string hash;
|
||||
hash.reserve(totalSize);
|
||||
(hash.append(values + kDelimiter), ...);
|
||||
hash.pop_back();
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Inserts |transferId| into the |stop| m_transferIds if it isn't already present there.
|
||||
void LinkTransferIdToStop(StopData & stop, TransitId transferId);
|
||||
} // namespace transit
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
project(world_feed_integration_tests)
|
||||
|
||||
set(SRC world_feed_integration_tests.cpp)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
world_feed
|
||||
generator
|
||||
)
|
||||
|
||||
# This test requires additional data from the other repository
|
||||
if(NOT TEST_DATA_REPO_URL)
|
||||
set(TEST_DATA_REPO_URL "https://github.com/organicmaps/world_feed_integration_tests_data.git")
|
||||
endif()
|
||||
set(DESTINATION_FOLDER "${OMIM_DATA_DIR}/test_data/world_feed_integration_tests_data/")
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${PROJECT_NAME}
|
||||
POST_BUILD
|
||||
COMMAND test -d ${DESTINATION_FOLDER} || (git clone ${TEST_DATA_REPO_URL} ${DESTINATION_FOLDER})
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "generator/affiliation.hpp"
|
||||
|
||||
#include "transit/world_feed/world_feed.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
#include "base/string_utils.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string const kFeedsSubdir = "test_data/world_feed_integration_tests_data";
|
||||
} // namespace
|
||||
|
||||
namespace transit
|
||||
{
|
||||
class WorldFeedIntegrationTests
|
||||
{
|
||||
public:
|
||||
WorldFeedIntegrationTests()
|
||||
: m_mwmMatcher(GetPlatform().ResourcesDir(), false /* haveBordersForWholeWorld */)
|
||||
, m_globalFeed(m_generator, m_generatorEdges, m_colorPicker, m_mwmMatcher)
|
||||
{
|
||||
m_testPath = base::JoinPath(GetPlatform().WritableDir(), kFeedsSubdir);
|
||||
CHECK(GetPlatform().IsFileExistsByFullPath(m_testPath), ());
|
||||
|
||||
m_generator = IdGenerator(base::JoinPath(m_testPath, "mapping.txt"));
|
||||
m_generatorEdges = IdGenerator(base::JoinPath(m_testPath, "mapping_edges.txt"));
|
||||
}
|
||||
|
||||
void ReadMinimalisticFeed()
|
||||
{
|
||||
gtfs::Feed feed(base::JoinPath(m_testPath, "minimalistic_feed"));
|
||||
TEST_EQUAL(feed.read_feed().code, gtfs::ResultCode::OK, ());
|
||||
TEST(m_globalFeed.SetFeed(std::move(feed)), ());
|
||||
|
||||
TEST_EQUAL(m_globalFeed.m_networks.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_routes.m_data.size(), 2, ());
|
||||
// We check that lines with no entries in stop_times.txt are not added.
|
||||
TEST_EQUAL(m_globalFeed.m_lines.m_data.size(), 2, ());
|
||||
TEST_EQUAL(m_globalFeed.m_stops.m_data.size(), 7, ());
|
||||
TEST_EQUAL(m_globalFeed.m_shapes.m_data.size(), 2, ());
|
||||
// There are 2 lines with 3 and 4 stops correspondingly. So we have 5 edges.
|
||||
TEST_EQUAL(m_globalFeed.m_edges.m_data.size(), 5, ());
|
||||
}
|
||||
|
||||
void ReadRealLifeFeed()
|
||||
{
|
||||
gtfs::Feed feed(base::JoinPath(m_testPath, "real_life_feed"));
|
||||
TEST_EQUAL(feed.read_feed().code, gtfs::ResultCode::OK, ());
|
||||
TEST(m_globalFeed.SetFeed(std::move(feed)), ());
|
||||
|
||||
TEST_EQUAL(m_globalFeed.m_networks.m_data.size(), 21, ());
|
||||
TEST_EQUAL(m_globalFeed.m_routes.m_data.size(), 87, ());
|
||||
// All trips have unique service_id so each line corresponds to some trip.
|
||||
TEST_EQUAL(m_globalFeed.m_lines.m_data.size(), 392, ());
|
||||
TEST_EQUAL(m_globalFeed.m_stops.m_data.size(), 1008, ());
|
||||
// 64 shapes contained in other shapes should be skipped.
|
||||
TEST_EQUAL(m_globalFeed.m_shapes.m_data.size(), 329, ());
|
||||
TEST_EQUAL(m_globalFeed.m_gates.m_data.size(), 0, ());
|
||||
TEST_EQUAL(m_globalFeed.m_transfers.m_data.size(), 0, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edges.m_data.size(), 3999, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edgesTransfers.m_data.size(), 0, ());
|
||||
}
|
||||
|
||||
void ReadFeedWithMultipleShapeProjections()
|
||||
{
|
||||
gtfs::Feed feed(base::JoinPath(m_testPath, "feed_with_multiple_shape_projections"));
|
||||
TEST_EQUAL(feed.read_feed().code, gtfs::ResultCode::OK, ());
|
||||
TEST(m_globalFeed.SetFeed(std::move(feed)), ());
|
||||
|
||||
TEST_EQUAL(m_globalFeed.m_networks.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_routes.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_lines.m_data.size(), 2, ());
|
||||
TEST_EQUAL(m_globalFeed.m_stops.m_data.size(), 16, ());
|
||||
TEST_EQUAL(m_globalFeed.m_shapes.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_gates.m_data.size(), 0, ());
|
||||
TEST_EQUAL(m_globalFeed.m_transfers.m_data.size(), 2, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edges.m_data.size(), 27, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edgesTransfers.m_data.size(), 2, ());
|
||||
}
|
||||
|
||||
void ReadFeedWithWrongStopsOrder()
|
||||
{
|
||||
gtfs::Feed feed(base::JoinPath(m_testPath, "feed_with_wrong_stops_order"));
|
||||
TEST_EQUAL(feed.read_feed().code, gtfs::ResultCode::OK, ());
|
||||
// Feed has wrong stops order (impossible for trip shape) and should be rejected.
|
||||
TEST(!m_globalFeed.SetFeed(std::move(feed)), ());
|
||||
}
|
||||
|
||||
void ReadFeedWithBackwardOrder()
|
||||
{
|
||||
gtfs::Feed feed(base::JoinPath(m_testPath, "feed_with_backward_order"));
|
||||
TEST_EQUAL(feed.read_feed().code, gtfs::ResultCode::OK, ());
|
||||
TEST(m_globalFeed.SetFeed(std::move(feed)), ());
|
||||
|
||||
TEST_EQUAL(m_globalFeed.m_networks.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_routes.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_lines.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_stops.m_data.size(), 16, ());
|
||||
TEST_EQUAL(m_globalFeed.m_shapes.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_gates.m_data.size(), 0, ());
|
||||
TEST_EQUAL(m_globalFeed.m_transfers.m_data.size(), 2, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edges.m_data.size(), 16, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edgesTransfers.m_data.size(), 2, ());
|
||||
}
|
||||
|
||||
// Test for train itinerary that passes through 4 regions in Europe and consists of 4 stops
|
||||
// (each in separate mwm) and 1 route with 1 line. This line passes through 4 stops:
|
||||
// [1] Switzerland_Ticino -> [2] Switzerland_Eastern ->
|
||||
// [3] Italy_Lombardy_Como -> [4] Italy_Lombardy_Monza and Brianza
|
||||
void SplitFeedIntoMultipleRegions()
|
||||
{
|
||||
gtfs::Feed feed(base::JoinPath(m_testPath, "feed_long_itinerary"));
|
||||
TEST_EQUAL(feed.read_feed().code, gtfs::ResultCode::OK, ());
|
||||
TEST(m_globalFeed.SetFeed(std::move(feed)), ());
|
||||
|
||||
TEST_EQUAL(m_globalFeed.m_lines.m_data.size(), 1, ());
|
||||
TEST_EQUAL(m_globalFeed.m_stops.m_data.size(), 4, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edges.m_data.size(), 3, ());
|
||||
|
||||
m_globalFeed.SplitFeedIntoRegions();
|
||||
|
||||
size_t const mwmCount = 4;
|
||||
// We check that count of keys in each regions-to-ids mapping corresponds to the mwms count.
|
||||
TEST_EQUAL(m_globalFeed.m_splitting.m_networks.size(), mwmCount, ());
|
||||
TEST_EQUAL(m_globalFeed.m_splitting.m_routes.size(), mwmCount, ());
|
||||
TEST_EQUAL(m_globalFeed.m_splitting.m_lines.size(), mwmCount, ());
|
||||
TEST_EQUAL(m_globalFeed.m_splitting.m_stops.size(), mwmCount, ());
|
||||
|
||||
auto & stopsInRegions = m_globalFeed.m_splitting.m_stops;
|
||||
auto & edgesInRegions = m_globalFeed.m_splitting.m_edges;
|
||||
|
||||
// First and last stops are connected through 1 edge with 1 nearest stop.
|
||||
// Stops in the middle are connected through 2 edges with 2 nearest stops.
|
||||
TEST_EQUAL(stopsInRegions["Switzerland_Ticino"].size(), 3, ());
|
||||
TEST_EQUAL(edgesInRegions["Switzerland_Ticino"].size(), 1, ());
|
||||
|
||||
TEST_EQUAL(stopsInRegions["Switzerland_Eastern"].size(), 4, ());
|
||||
TEST_EQUAL(edgesInRegions["Switzerland_Eastern"].size(), 2, ());
|
||||
|
||||
TEST_EQUAL(stopsInRegions["Italy_Lombardy_Como"].size(), 4, ());
|
||||
TEST_EQUAL(edgesInRegions["Italy_Lombardy_Como"].size(), 2, ());
|
||||
|
||||
TEST_EQUAL(stopsInRegions["Italy_Lombardy_Monza and Brianza"].size(), 3, ());
|
||||
TEST_EQUAL(edgesInRegions["Italy_Lombardy_Monza and Brianza"].size(), 1, ());
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_testPath;
|
||||
IdGenerator m_generator;
|
||||
IdGenerator m_generatorEdges;
|
||||
transit::ColorPicker m_colorPicker;
|
||||
feature::CountriesFilesAffiliation m_mwmMatcher;
|
||||
WorldFeed m_globalFeed;
|
||||
};
|
||||
|
||||
UNIT_CLASS_TEST(WorldFeedIntegrationTests, MinimalisticFeed)
|
||||
{
|
||||
ReadMinimalisticFeed();
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(WorldFeedIntegrationTests, RealLifeFeed)
|
||||
{
|
||||
ReadRealLifeFeed();
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(WorldFeedIntegrationTests, FeedWithLongItinerary)
|
||||
{
|
||||
SplitFeedIntoMultipleRegions();
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(WorldFeedIntegrationTests, FeedWithMultipleShapeProjections)
|
||||
{
|
||||
ReadFeedWithMultipleShapeProjections();
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(WorldFeedIntegrationTests, FeedWithWrongStopsOrder)
|
||||
{
|
||||
ReadFeedWithWrongStopsOrder();
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(WorldFeedIntegrationTests, FeedWithBackwardOrder)
|
||||
{
|
||||
ReadFeedWithBackwardOrder();
|
||||
}
|
||||
} // namespace transit
|
||||
14
libs/transit/world_feed/world_feed_tests/CMakeLists.txt
Normal file
14
libs/transit/world_feed/world_feed_tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
project(world_feed_tests)
|
||||
|
||||
set(SRC
|
||||
subway_converter_tests.cpp
|
||||
world_feed_tests.cpp
|
||||
)
|
||||
|
||||
omim_add_test(${PROJECT_NAME} ${SRC})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
generator
|
||||
world_feed
|
||||
transit
|
||||
)
|
||||
|
|
@ -0,0 +1,388 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/world_feed/subway_converter.hpp"
|
||||
#include "transit/world_feed/world_feed.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/file_name_utils.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string const kSubwayTestsDir = "transit_subway_converter_tests";
|
||||
std::string const kSubwayJsonFile = "subways.json";
|
||||
std::string const kMappingFile = "mapping.txt";
|
||||
std::string const kMappingEdgesFile = "mapping_edges.txt";
|
||||
|
||||
void WriteStringToFile(std::string const & fileName, std::string const & data)
|
||||
{
|
||||
std::ofstream file;
|
||||
file.open(fileName);
|
||||
CHECK(file.is_open(), ("Could not open file", fileName));
|
||||
file << data;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace transit
|
||||
{
|
||||
class SubwayConverterTests
|
||||
{
|
||||
public:
|
||||
SubwayConverterTests() : m_mwmMatcher(GetPlatform().ResourcesDir(), false /* haveBordersForWholeWorld */)
|
||||
{
|
||||
CHECK(Platform::MkDirChecked(kSubwayTestsDir), ("Could not create directory for test data:", kSubwayTestsDir));
|
||||
m_generator = transit::IdGenerator(base::JoinPath(kSubwayTestsDir, kMappingFile));
|
||||
m_generatorEdges = transit::IdGenerator(base::JoinPath(kSubwayTestsDir, kMappingEdgesFile));
|
||||
}
|
||||
|
||||
~SubwayConverterTests() { Platform::RmDirRecursively(kSubwayTestsDir); }
|
||||
|
||||
void ParseEmptySubway()
|
||||
{
|
||||
std::string const emptySubway = R"({
|
||||
"networks":[],
|
||||
"lines":[],
|
||||
"edges":[],
|
||||
"gates":[],
|
||||
"shapes":[],
|
||||
"stops":[],
|
||||
"transfers":[]
|
||||
})";
|
||||
|
||||
auto const & filePath = base::JoinPath(kSubwayTestsDir, kSubwayJsonFile);
|
||||
WriteStringToFile(filePath, emptySubway);
|
||||
transit::WorldFeed feed(m_generator, m_generatorEdges, m_colorPicker, m_mwmMatcher);
|
||||
transit::SubwayConverter converter(filePath, feed);
|
||||
TEST(!converter.Convert(), ());
|
||||
}
|
||||
|
||||
// Subway consists of two lines with one transfer.
|
||||
// 65687110 ---------> 76638582 ---------> 61447662 ---------> 61447702 | Line 244075840
|
||||
// | /
|
||||
// 61447663 ---------->/ | Line 244075841
|
||||
void ParseValidSubway()
|
||||
{
|
||||
std::string const validSubway = R"({
|
||||
"networks":[
|
||||
{
|
||||
"id":108,
|
||||
"title":"Belo Horizonte"
|
||||
}
|
||||
],
|
||||
"lines":[
|
||||
{
|
||||
"color":"orange_light",
|
||||
"id":244075840,
|
||||
"interval":150,
|
||||
"network_id":108,
|
||||
"number":"L1",
|
||||
"stop_ids":[
|
||||
65687110,
|
||||
76638582,
|
||||
61447662,
|
||||
61447702
|
||||
],
|
||||
"title":"Metro L1R: Place des Martyrs - Ain Naâdja",
|
||||
"type":"subway"
|
||||
},
|
||||
{
|
||||
"color":"amber_dark",
|
||||
"id":244075841,
|
||||
"interval":320,
|
||||
"network_id":108,
|
||||
"number":"L1",
|
||||
"stop_ids":[
|
||||
61447663,
|
||||
61447702
|
||||
],
|
||||
"title":"Metro L1R: Place des Martyrs - Ain Naâdja",
|
||||
"type":"subway"
|
||||
}
|
||||
],
|
||||
"stops":[
|
||||
{
|
||||
"id":65687110,
|
||||
"line_ids":[
|
||||
244075840
|
||||
],
|
||||
"osm_id":"4611686021817678228",
|
||||
"point":{
|
||||
"x":21.056,
|
||||
"y":61.316
|
||||
},
|
||||
"title_anchors":[]
|
||||
},
|
||||
{
|
||||
"id":76638582,
|
||||
"line_ids":[
|
||||
244075840
|
||||
],
|
||||
"osm_id":"4611686021817678229",
|
||||
"point":{
|
||||
"x":21.057,
|
||||
"y":61.317
|
||||
},
|
||||
"title_anchors":[]
|
||||
},
|
||||
{
|
||||
"id":61447662,
|
||||
"line_ids":[
|
||||
244075840
|
||||
],
|
||||
"osm_id":"4611686021817678230",
|
||||
"point":{
|
||||
"x":21.058,
|
||||
"y":61.319
|
||||
},
|
||||
"title_anchors":[]
|
||||
},
|
||||
{
|
||||
"id":61447702,
|
||||
"line_ids":[
|
||||
244075840,
|
||||
244075841
|
||||
],
|
||||
"osm_id":"4611686021817678231",
|
||||
"point":{
|
||||
"x":21.059,
|
||||
"y":61.321
|
||||
},
|
||||
"title_anchors":[]
|
||||
},
|
||||
{
|
||||
"id":61447663,
|
||||
"line_ids":[
|
||||
244075841
|
||||
],
|
||||
"osm_id":"4611686021817678231",
|
||||
"point":{
|
||||
"x":21.068,
|
||||
"y":61.399
|
||||
},
|
||||
"title_anchors":[]
|
||||
}
|
||||
],
|
||||
"edges":[
|
||||
{
|
||||
"stop1_id":61447662,
|
||||
"stop2_id":61447663,
|
||||
"transfer":true,
|
||||
"weight":225
|
||||
},
|
||||
{
|
||||
"line_id":244075840,
|
||||
"shape_ids":[
|
||||
{
|
||||
"stop1_id":65687110,
|
||||
"stop2_id":76638582
|
||||
}
|
||||
],
|
||||
"stop1_id":65687110,
|
||||
"stop2_id":76638582,
|
||||
"transfer":false,
|
||||
"weight":33
|
||||
},
|
||||
{
|
||||
"line_id":244075840,
|
||||
"shape_ids":[
|
||||
{
|
||||
"stop1_id":76638582,
|
||||
"stop2_id":61447662
|
||||
}
|
||||
],
|
||||
"stop1_id":76638582,
|
||||
"stop2_id":61447662,
|
||||
"transfer":false,
|
||||
"weight":46
|
||||
},
|
||||
{
|
||||
"line_id":244075840,
|
||||
"shape_ids":[
|
||||
{
|
||||
"stop1_id":61447662,
|
||||
"stop2_id":61447702
|
||||
}
|
||||
],
|
||||
"stop1_id":61447662,
|
||||
"stop2_id":61447702,
|
||||
"transfer":false,
|
||||
"weight":32
|
||||
},
|
||||
{
|
||||
"line_id":244075841,
|
||||
"shape_ids":[
|
||||
{
|
||||
"stop1_id":61447662,
|
||||
"stop2_id":61447702
|
||||
}
|
||||
],
|
||||
"stop1_id":61447663,
|
||||
"stop2_id":61447702,
|
||||
"transfer":false,
|
||||
"weight":40
|
||||
}
|
||||
],
|
||||
"gates":[
|
||||
{
|
||||
"entrance":true,
|
||||
"exit":true,
|
||||
"osm_id":"4611686018706540014",
|
||||
"point":{
|
||||
"x":21.055938,
|
||||
"y":61.31740794838219
|
||||
},
|
||||
"stop_ids":[
|
||||
76638582
|
||||
],
|
||||
"weight":129
|
||||
}
|
||||
],
|
||||
"shapes":[
|
||||
{
|
||||
"id":{
|
||||
"stop1_id":65687110,
|
||||
"stop2_id":76638582
|
||||
},
|
||||
"polyline":[
|
||||
{
|
||||
"x":3.062,
|
||||
"y":39.616
|
||||
},
|
||||
{
|
||||
"x":3.063,
|
||||
"y":39.61
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":{
|
||||
"stop1_id":76638582,
|
||||
"stop2_id":61447662
|
||||
},
|
||||
"polyline":[
|
||||
{
|
||||
"x":3.063,
|
||||
"y":39.61
|
||||
},
|
||||
{
|
||||
"x":3.0624,
|
||||
"y":39.614
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":{
|
||||
"stop1_id":61447662,
|
||||
"stop2_id":61447702
|
||||
},
|
||||
"polyline":[
|
||||
{
|
||||
"x":3.062,
|
||||
"y":39.616
|
||||
},
|
||||
{
|
||||
"x":3.065,
|
||||
"y":39.61
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id":{
|
||||
"stop1_id":61447663,
|
||||
"stop2_id":61447702
|
||||
},
|
||||
"polyline":[
|
||||
{
|
||||
"x":3.068,
|
||||
"y":39.64
|
||||
},
|
||||
{
|
||||
"x":3.063,
|
||||
"y":39.674
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"transfers":[
|
||||
{
|
||||
"id":4611686018489133646,
|
||||
"point":{
|
||||
"x":-3.6761538,
|
||||
"y":44.26760612521531
|
||||
},
|
||||
"stop_ids":[
|
||||
61447662,
|
||||
61447663
|
||||
],
|
||||
"title_anchors":[
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
})";
|
||||
|
||||
auto const & filePath = base::JoinPath(kSubwayTestsDir, kSubwayJsonFile);
|
||||
WriteStringToFile(filePath, validSubway);
|
||||
transit::WorldFeed feed(m_generator, m_generatorEdges, m_colorPicker, m_mwmMatcher);
|
||||
transit::SubwayConverter converter(filePath, feed);
|
||||
|
||||
// We check that the conversion between old and new formats is successful.
|
||||
TEST(converter.Convert(), ());
|
||||
|
||||
// We check that transit entities are converted correctly.
|
||||
TEST_EQUAL(feed.m_networks.m_data.size(), 1, ());
|
||||
TEST_EQUAL(feed.m_routes.m_data.size(), 1, ());
|
||||
TEST_EQUAL(feed.m_lines.m_data.size(), 2, ());
|
||||
TEST_EQUAL(feed.m_stops.m_data.size(), 5, ());
|
||||
TEST_EQUAL(feed.m_edges.m_data.size(), 4, ());
|
||||
TEST_EQUAL(feed.m_edgesTransfers.m_data.size(), 1, ());
|
||||
TEST_EQUAL(feed.m_transfers.m_data.size(), 0, ());
|
||||
TEST_EQUAL(feed.m_gates.m_data.size(), 1, ());
|
||||
|
||||
// Two initial shapes must be merged into one.
|
||||
TEST_EQUAL(feed.m_shapes.m_data.size(), 1, ());
|
||||
|
||||
// Shape does not contain duplicate points (we have 5 points instead of 6).
|
||||
TEST_EQUAL(feed.m_shapes.m_data.begin()->second.m_points.size(), 5, ());
|
||||
|
||||
// We check main relations consistency.
|
||||
auto const networkIt = feed.m_networks.m_data.begin();
|
||||
auto const routeIt = feed.m_routes.m_data.begin();
|
||||
|
||||
TEST_EQUAL(routeIt->second.m_networkId, networkIt->first, ());
|
||||
|
||||
for (auto const & [lineId, lineData] : feed.m_lines.m_data)
|
||||
{
|
||||
TEST_EQUAL(lineData.m_routeId, routeIt->first, ());
|
||||
|
||||
for (auto const stopId : lineData.m_stopIds)
|
||||
{
|
||||
auto const stopIt = feed.m_stops.m_data.find(stopId);
|
||||
TEST(stopIt != feed.m_stops.m_data.end(), (stopId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
transit::IdGenerator m_generator;
|
||||
transit::IdGenerator m_generatorEdges;
|
||||
transit::ColorPicker m_colorPicker;
|
||||
feature::CountriesFilesAffiliation m_mwmMatcher;
|
||||
};
|
||||
|
||||
UNIT_CLASS_TEST(SubwayConverterTests, SubwayConverter_ParseInvalidJson)
|
||||
{
|
||||
ParseEmptySubway();
|
||||
}
|
||||
|
||||
UNIT_CLASS_TEST(SubwayConverterTests, SubwayConverter_ParseValidJson)
|
||||
{
|
||||
ParseValidSubway();
|
||||
}
|
||||
} // namespace transit
|
||||
541
libs/transit/world_feed/world_feed_tests/world_feed_tests.cpp
Normal file
541
libs/transit/world_feed/world_feed_tests/world_feed_tests.cpp
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
#include "testing/testing.hpp"
|
||||
|
||||
#include "transit/world_feed/color_picker.hpp"
|
||||
#include "transit/world_feed/date_time_helpers.hpp"
|
||||
#include "transit/world_feed/feed_helpers.hpp"
|
||||
#include "transit/world_feed/world_feed.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/just_gtfs/just_gtfs.h"
|
||||
#include "3party/opening_hours/opening_hours.hpp"
|
||||
|
||||
namespace world_feed_tests
|
||||
{
|
||||
using namespace transit;
|
||||
|
||||
std::vector<gtfs::CalendarAvailability> GetCalendarAvailability(std::vector<size_t> const & data)
|
||||
{
|
||||
CHECK_EQUAL(data.size(), 7, ());
|
||||
std::vector<gtfs::CalendarAvailability> res;
|
||||
|
||||
for (auto val : data)
|
||||
if (val == 0)
|
||||
res.push_back(gtfs::CalendarAvailability::NotAvailable);
|
||||
else
|
||||
res.push_back(gtfs::CalendarAvailability::Available);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
gtfs::StopTimes GetFakeStopTimes(std::vector<std::string> const & transitIds)
|
||||
{
|
||||
auto ids = transitIds;
|
||||
std::sort(ids.begin(), ids.end());
|
||||
gtfs::StopTimes res;
|
||||
for (size_t i = 0; i < ids.size(); ++i)
|
||||
{
|
||||
gtfs::StopTime st;
|
||||
st.trip_id = ids[i];
|
||||
st.stop_sequence = i;
|
||||
res.emplace_back(st);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void TestInterval(WeekdaysInterval const & interval, size_t start, size_t end, osmoh::RuleSequence::Modifier status)
|
||||
{
|
||||
TEST_EQUAL(interval.m_start, start, ());
|
||||
TEST_EQUAL(interval.m_end, end, ());
|
||||
TEST_EQUAL(interval.m_status, status, ());
|
||||
}
|
||||
|
||||
void TestExceptionIntervals(gtfs::CalendarDates const & dates, size_t intervalsCount,
|
||||
std::string const & resOpeningHoursStr)
|
||||
{
|
||||
osmoh::TRuleSequences rules;
|
||||
GetServiceDaysExceptionsOsmoh(dates, rules);
|
||||
// TEST_EQUAL(rules.size(), intervalsCount, ());
|
||||
auto const openingHours = ToString(osmoh::OpeningHours(rules));
|
||||
TEST_EQUAL(openingHours, resOpeningHoursStr, ());
|
||||
}
|
||||
|
||||
void TestStopsRange(IdList const & stopsOnLine, IdSet const & stopsInRegion, size_t firstIdxPlan, size_t lastIdxPlan)
|
||||
{
|
||||
auto const & [firstIdxFact, lastIdxFact] = GetStopsRange(stopsOnLine, stopsInRegion);
|
||||
TEST_EQUAL(firstIdxFact, firstIdxPlan, ());
|
||||
TEST_EQUAL(lastIdxFact, lastIdxPlan, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_OpenCloseInterval1)
|
||||
{
|
||||
auto const & intervals = GetOpenCloseIntervals(GetCalendarAvailability({1, 1, 1, 1, 1, 0, 0}));
|
||||
TEST_EQUAL(intervals.size(), 2, ());
|
||||
|
||||
TestInterval(intervals[0], 0, 4, osmoh::RuleSequence::Modifier::DefaultOpen);
|
||||
TestInterval(intervals[1], 5, 6, osmoh::RuleSequence::Modifier::Closed);
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_OpenCloseInterval2)
|
||||
{
|
||||
auto const & intervals = GetOpenCloseIntervals(GetCalendarAvailability({0, 0, 0, 0, 0, 1, 0}));
|
||||
TEST_EQUAL(intervals.size(), 3, ());
|
||||
|
||||
TestInterval(intervals[0], 0, 4, osmoh::RuleSequence::Modifier::Closed);
|
||||
TestInterval(intervals[1], 5, 5, osmoh::RuleSequence::Modifier::DefaultOpen);
|
||||
TestInterval(intervals[2], 6, 6, osmoh::RuleSequence::Modifier::Closed);
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_OpenCloseInterval3)
|
||||
{
|
||||
auto const & intervals = GetOpenCloseIntervals(GetCalendarAvailability({0, 0, 0, 0, 0, 0, 1}));
|
||||
TEST_EQUAL(intervals.size(), 2, ());
|
||||
|
||||
TestInterval(intervals[0], 0, 5, osmoh::RuleSequence::Modifier::Closed);
|
||||
TestInterval(intervals[1], 6, 6, osmoh::RuleSequence::Modifier::DefaultOpen);
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_GetTimeOsmoh)
|
||||
{
|
||||
size_t const hours = 21;
|
||||
size_t const minutes = 5;
|
||||
size_t const seconds = 30;
|
||||
gtfs::Time const timeGtfs(hours, minutes, seconds);
|
||||
|
||||
auto const timeOsmoh = GetTimeOsmoh(timeGtfs);
|
||||
TEST_EQUAL(timeOsmoh.GetMinutesCount(), minutes, ());
|
||||
TEST_EQUAL(timeOsmoh.GetHoursCount(), hours, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_ServiceDaysExceptions1)
|
||||
{
|
||||
gtfs::CalendarDates const exceptionDays{
|
||||
{"serviceId1", gtfs::Date(2015, 01, 30), gtfs::CalendarDateException::Removed},
|
||||
{"serviceId1", gtfs::Date(2015, 01, 31), gtfs::CalendarDateException::Removed},
|
||||
{"serviceId1", gtfs::Date(2015, 02, 01), gtfs::CalendarDateException::Removed},
|
||||
{"serviceId1", gtfs::Date(2015, 04, 03), gtfs::CalendarDateException::Added}};
|
||||
TestExceptionIntervals(exceptionDays, 2 /* intervalsCount */,
|
||||
"2015 Apr 03-2015 Apr 03; 2015 Jan 30-2015 Feb 01 closed" /* resOpeningHoursStr */);
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_ServiceDaysExceptions2)
|
||||
{
|
||||
gtfs::CalendarDates const exceptionDays{
|
||||
{"serviceId2", gtfs::Date(1999, 11, 14), gtfs::CalendarDateException::Removed}};
|
||||
TestExceptionIntervals(exceptionDays, 1 /* intervalsCount */,
|
||||
"1999 Nov 14-1999 Nov 14 closed" /* resOpeningHoursStr */);
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_ServiceDaysExceptions3)
|
||||
{
|
||||
gtfs::CalendarDates const exceptionDays{
|
||||
{"serviceId2", gtfs::Date(2005, 8, 01), gtfs::CalendarDateException::Added},
|
||||
{"serviceId2", gtfs::Date(2005, 8, 12), gtfs::CalendarDateException::Added},
|
||||
{"serviceId2", gtfs::Date(2005, 10, 11), gtfs::CalendarDateException::Removed},
|
||||
{"serviceId2", gtfs::Date(2005, 10, 12), gtfs::CalendarDateException::Removed},
|
||||
{"serviceId2", gtfs::Date(2005, 10, 13), gtfs::CalendarDateException::Added},
|
||||
{"serviceId2", gtfs::Date(1999, 10, 14), gtfs::CalendarDateException::Removed}};
|
||||
TestExceptionIntervals(exceptionDays, 2 /* intervalsCount */,
|
||||
"2005 Aug 01-2005 Aug 01, 2005 Aug 12-2005 Aug 12, 2005 Oct 13-2005 Oct 13; 2005 Oct 11-2005 "
|
||||
"Oct 12, 1999 Oct 14-1999 Oct 14 closed" /* resOpeningHoursStr */);
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_FindStopTimesByTransitId)
|
||||
{
|
||||
auto const allStopTimes = GetFakeStopTimes({"4", "5", "6", "2", "10", "2", "2", "6"});
|
||||
auto const stopTimes1 = GetStopTimesForTrip(allStopTimes, "2");
|
||||
TEST_EQUAL(stopTimes1.size(), 3, ());
|
||||
|
||||
auto const stopTimes10 = GetStopTimesForTrip(allStopTimes, "10");
|
||||
TEST_EQUAL(stopTimes10.size(), 1, ());
|
||||
|
||||
auto const stopTimes6 = GetStopTimesForTrip(allStopTimes, "6");
|
||||
TEST_EQUAL(stopTimes6.size(), 2, ());
|
||||
|
||||
auto const stopTimesNonExistent1 = GetStopTimesForTrip(allStopTimes, "11");
|
||||
TEST(stopTimesNonExistent1.empty(), ());
|
||||
|
||||
auto const stopTimesNonExistent2 = GetStopTimesForTrip(allStopTimes, "1");
|
||||
TEST(stopTimesNonExistent1.empty(), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_GTFS_FindStopTimesByTransitId2)
|
||||
{
|
||||
auto const allStopTimes = GetFakeStopTimes({"28", "28", "28", "28"});
|
||||
auto const stopTimes = GetStopTimesForTrip(allStopTimes, "28");
|
||||
TEST_EQUAL(stopTimes.size(), 4, ());
|
||||
|
||||
auto const stopTimesNonExistent = GetStopTimesForTrip(allStopTimes, "3");
|
||||
TEST(stopTimesNonExistent.empty(), ());
|
||||
}
|
||||
|
||||
// Stops are marked as *, points on polyline as +. Points have indexes, stops have letters.
|
||||
//
|
||||
// *A
|
||||
//
|
||||
// +----+---------------+----------------------+
|
||||
// 0 1 2 3
|
||||
//
|
||||
// *B *C
|
||||
//
|
||||
UNIT_TEST(Transit_GTFS_ProjectStopToLine_Simple)
|
||||
{
|
||||
// index, was inserted
|
||||
using ResT = std::pair<size_t, bool>;
|
||||
|
||||
double const y = 0.0002;
|
||||
std::vector<m2::PointD> shape{{0.001, y}, {0.0015, y}, {0.004, y}, {0.005, y}};
|
||||
|
||||
m2::PointD const point_A{0.0012, 0.0003};
|
||||
m2::PointD const point_B{0.00499, 0.0001};
|
||||
m2::PointD const point_C{0.005, 0.0001};
|
||||
|
||||
// Test that point_A is projected between two existing polyline points and the new point is
|
||||
// added in the place of its projection.
|
||||
TEST_EQUAL(ResT(1, true),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* prevIndex */, Direction::Forward, shape), ());
|
||||
|
||||
TEST_EQUAL(shape.size(), 5, ());
|
||||
TEST_EQUAL(shape[1 /* expectedIndex */], m2::PointD(point_A.x, y), ());
|
||||
|
||||
// Test that repeated point_A projection to the polyline doesn't lead to the second insertion.
|
||||
// Expected point projection index is the same.
|
||||
// But this projection is not inserted (it is already present).
|
||||
TEST_EQUAL(ResT(1, false),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* prevIndex */, Direction::Forward, shape), ());
|
||||
// So the shape size remains the same.
|
||||
TEST_EQUAL(shape.size(), 5, ());
|
||||
|
||||
// Test that point_B insertion leads to addition of the new projection to the shape.
|
||||
TEST_EQUAL(ResT(4, true),
|
||||
PrepareNearestPointOnTrack(point_B, std::nullopt, 1 /* prevIndex */, Direction::Forward, shape), ());
|
||||
|
||||
// Test that point_C insertion does not lead to the addition of the new projection.
|
||||
TEST_EQUAL(ResT(5, false),
|
||||
PrepareNearestPointOnTrack(point_C, std::nullopt, 4 /* prevIndex */, Direction::Forward, shape), ());
|
||||
|
||||
// Test point_C projection in backward direction.
|
||||
TEST_EQUAL(
|
||||
ResT(5, false),
|
||||
PrepareNearestPointOnTrack(point_C, std::nullopt, shape.size() - 1 /* prevIndex */, Direction::Backward, shape),
|
||||
());
|
||||
|
||||
// Test point_B projection in backward direction.
|
||||
TEST_EQUAL(ResT(4, false),
|
||||
PrepareNearestPointOnTrack(point_B, std::nullopt, 5 /* prevIndex */, Direction::Backward, shape), ());
|
||||
|
||||
// Test point_A projection in backward direction.
|
||||
TEST_EQUAL(ResT(1, false),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 4 /* prevIndex */, Direction::Backward, shape), ());
|
||||
}
|
||||
|
||||
// Stop is on approximately the same distance from the segment (0, 1) and segment (1, 2).
|
||||
// Its projection index and projection coordinate depend on the |startIndex| parameter.
|
||||
//
|
||||
// 1 +----------+ 2
|
||||
// |
|
||||
// | *A
|
||||
// |
|
||||
// 0 +
|
||||
//
|
||||
UNIT_TEST(Transit_GTFS_ProjectStopToLine_DifferentStartIndexes)
|
||||
{
|
||||
// index, was inserted
|
||||
using ResT = std::pair<size_t, bool>;
|
||||
|
||||
std::vector<m2::PointD> const referenceShape{{0.001, 0.001}, {0.001, 0.002}, {0.003, 0.002}};
|
||||
m2::PointD const point_A{0.0015, 0.0015};
|
||||
|
||||
// Test for |startIndex| = 0.
|
||||
{
|
||||
auto shape = referenceShape;
|
||||
TEST_EQUAL(ResT(1, true),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* prevIndex */, Direction::Forward, shape), ());
|
||||
TEST_EQUAL(shape.size(), 4, ());
|
||||
TEST_EQUAL(shape[1 /* expectedIndex */], m2::PointD(0.001, point_A.y), ());
|
||||
}
|
||||
|
||||
// Test for |startIndex| = 1.
|
||||
{
|
||||
auto shape = referenceShape;
|
||||
TEST_EQUAL(ResT(2, true),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 1 /* prevIndex */, Direction::Forward, shape), ());
|
||||
TEST_EQUAL(shape.size(), 4, ());
|
||||
TEST_EQUAL(shape[2 /* expectedIndex */], m2::PointD(point_A.x, 0.002), ());
|
||||
}
|
||||
}
|
||||
|
||||
// Real-life example of stop being closer to the other side of the route (4, 5) then to its real
|
||||
// destination (0, 1).
|
||||
// We handle this type of situations by using constant max distance of departing from this stop
|
||||
// on the polyline in |PrepareNearestPointOnTrack()|.
|
||||
//
|
||||
// 5 4
|
||||
// +--------------------------------+---------------------------------------+ 3
|
||||
// |
|
||||
// /+-------------------------------------------------+ 2
|
||||
// *A / 1
|
||||
// /
|
||||
// + 0
|
||||
//
|
||||
UNIT_TEST(Transit_GTFS_ProjectStopToLine_MaxDistance)
|
||||
{
|
||||
// index, was inserted
|
||||
using ResT = std::pair<size_t, bool>;
|
||||
|
||||
std::vector<m2::PointD> shape{{0.002, 0.001}, {0.003, 0.003}, {0.010, 0.003},
|
||||
{0.010, 0.0031}, {0.005, 0.0031}, {0.001, 0.0031}};
|
||||
m2::PointD const point_A{0.0028, 0.0029};
|
||||
TEST_EQUAL(ResT(1, true),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* prevIndex */, Direction::Forward, shape), ());
|
||||
}
|
||||
|
||||
/* Complex shape with multiple points on it and multiple stops for projection.
|
||||
*
|
||||
* +-----+
|
||||
* C* / \
|
||||
* /+\ / \ *D
|
||||
* + / \+/ \
|
||||
* / +
|
||||
* / | *E
|
||||
* + +-----+
|
||||
* | |
|
||||
* | |
|
||||
* +---+\ +-----+
|
||||
* \ |
|
||||
* B* + |
|
||||
* A* \ +---------+
|
||||
* + |
|
||||
* | +
|
||||
* + *F
|
||||
*/
|
||||
UNIT_TEST(Transit_GTFS_ProjectStopToLine_NearCircle)
|
||||
{
|
||||
// index, was inserted
|
||||
using ResT = std::pair<size_t, bool>;
|
||||
|
||||
double constexpr kEps = 1e-5;
|
||||
std::vector<m2::PointD> const initialShape{{0.003, 0.001}, {0.003, 0.0015}, {0.0025, 0.002}, {0.002, 0.0025},
|
||||
{0.001, 0.0025}, {0.001, 0.0035}, {0.0015, 0.0045}, {0.0025, 0.005},
|
||||
{0.0035, 0.0045}, {0.004, 0.0055}, {0.0055, 0.0055}, {0.0065, 0.0045},
|
||||
{0.0065, 0.0035}, {0.0075, 0.0035}, {0.0075, 0.0025}, {0.0065, 0.0025},
|
||||
{0.0065, 0.0015}, {0.004, 0.0015}, {0.004, 0.001}};
|
||||
|
||||
m2::PointD const point_A{0.0024, 0.0018};
|
||||
m2::PointD const point_B{0.002499, 0.00199};
|
||||
m2::PointD const point_C{0.0036, 0.0049};
|
||||
m2::PointD const point_D{0.0063, 0.005};
|
||||
m2::PointD const point_E{0.008, 0.004};
|
||||
m2::PointD const point_F{0.0047, 0.0005};
|
||||
|
||||
// Forward
|
||||
auto shape = initialShape;
|
||||
TEST_EQUAL(ResT(2, true),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* prevIndex */, Direction::Forward, shape), ());
|
||||
auto const coordA = shape[2];
|
||||
|
||||
TEST_EQUAL(ResT(3, false),
|
||||
PrepareNearestPointOnTrack(point_B, std::nullopt, 2 /* prevIndex */, Direction::Forward, shape), ());
|
||||
auto const coordB = shape[3];
|
||||
|
||||
TEST_EQUAL(ResT(10, true),
|
||||
PrepareNearestPointOnTrack(point_C, std::nullopt, 3 /* prevIndex */, Direction::Forward, shape), ());
|
||||
auto const coordC = shape[10];
|
||||
|
||||
TEST_EQUAL(ResT(12, false),
|
||||
PrepareNearestPointOnTrack(point_D, std::nullopt, 10 /* prevIndex */, Direction::Forward, shape), ());
|
||||
auto const coordD = shape[12];
|
||||
|
||||
TEST_EQUAL(ResT(14, true),
|
||||
PrepareNearestPointOnTrack(point_E, std::nullopt, 12 /* prevIndex */, Direction::Forward, shape), ());
|
||||
auto const coordE = shape[14];
|
||||
|
||||
TEST_EQUAL(ResT(20, true),
|
||||
PrepareNearestPointOnTrack(point_F, std::nullopt, 14 /* prevIndex */, Direction::Forward, shape), ());
|
||||
|
||||
// Backward processing of reversed shape
|
||||
shape = initialShape;
|
||||
reverse(shape.begin(), shape.end());
|
||||
TEST_EQUAL(
|
||||
ResT(17, true),
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, shape.size() - 1 /* prevIndex */, Direction::Backward, shape),
|
||||
());
|
||||
TEST(AlmostEqualAbs(coordA, shape[17], kEps), (coordA, shape[17]));
|
||||
|
||||
TEST_EQUAL(ResT(16, false),
|
||||
PrepareNearestPointOnTrack(point_B, std::nullopt, 17 /* prevIndex */, Direction::Backward, shape), ());
|
||||
TEST(AlmostEqualAbs(coordB, shape[16], kEps), (coordA, shape[17]));
|
||||
|
||||
TEST_EQUAL(ResT(10, true),
|
||||
PrepareNearestPointOnTrack(point_C, std::nullopt, 16 /* prevIndex */, Direction::Backward, shape), ());
|
||||
TEST(AlmostEqualAbs(coordC, shape[10], kEps), (coordA, shape[17]));
|
||||
|
||||
TEST_EQUAL(ResT(8, false),
|
||||
PrepareNearestPointOnTrack(point_D, std::nullopt, 10 /* prevIndex */, Direction::Backward, shape), ());
|
||||
TEST(AlmostEqualAbs(coordD, shape[8], kEps), (coordA, shape[17]));
|
||||
|
||||
TEST_EQUAL(ResT(7, true),
|
||||
PrepareNearestPointOnTrack(point_E, std::nullopt, 8 /* prevIndex */, Direction::Backward, shape), ());
|
||||
TEST(AlmostEqualAbs(coordE, shape[7], kEps), (coordA, shape[17]));
|
||||
|
||||
// point_F has different position because we do not insert before point 0.
|
||||
TEST_EQUAL(ResT(2, true),
|
||||
PrepareNearestPointOnTrack(point_F, std::nullopt, 7 /* prevIndex */, Direction::Backward, shape), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_ColorPicker)
|
||||
{
|
||||
ColorPicker colorPicker;
|
||||
|
||||
// We check that we don't match with the 'text' colors subset. This is the color of transit
|
||||
// text lime_light and we expect not to pick it.
|
||||
TEST_EQUAL(colorPicker.GetNearestColor("827717"), "lime_dark", ());
|
||||
|
||||
// We check the default color for invalid input.
|
||||
TEST_EQUAL(colorPicker.GetNearestColor("94141230"), "default", ());
|
||||
|
||||
// We check that we really find nearest colors. This input is really close to pink light.
|
||||
TEST_EQUAL(colorPicker.GetNearestColor("d18aa2"), "pink_light", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_BuildHash1Arg)
|
||||
{
|
||||
TEST_EQUAL(BuildHash(std::string("Title")), "Title", ());
|
||||
TEST_EQUAL(BuildHash(std::string("Id1"), std::string("Id2")), "Id1_Id2", ());
|
||||
TEST_EQUAL(BuildHash(std::string("A"), std::string("B"), std::string("C")), "A_B_C", ());
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectionSimple)
|
||||
{
|
||||
auto const & factRes = FindIntersections({{1.0, 1.0}, {2.0, 2.0}, {3.0, 3.0}},
|
||||
{{4.0, 4.0}, {1.0, 1.0}, {2.0, 2.0}, {3.0, 3.0}, {4.0, 4.0}});
|
||||
|
||||
std::pair<LineSegments, LineSegments> planRes{{LineSegment(0, 2)}, {LineSegment(1, 3)}};
|
||||
TEST(factRes == planRes, ());
|
||||
}
|
||||
|
||||
std::vector<m2::PointD> Get2DVector(std::vector<size_t> const & v)
|
||||
{
|
||||
std::vector<m2::PointD> res;
|
||||
res.reserve(v.size());
|
||||
|
||||
for (size_t val : v)
|
||||
res.emplace_back(static_cast<double>(val), 1.0);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::pair<LineSegments, LineSegments> GetIntersections(std::vector<size_t> const & line1,
|
||||
std::vector<size_t> const & line2)
|
||||
{
|
||||
return FindIntersections(Get2DVector(line1), Get2DVector(line2));
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectionShortest)
|
||||
{
|
||||
auto const & factRes = GetIntersections({10, 15}, {10, 15});
|
||||
std::pair<LineSegments, LineSegments> planRes{{LineSegment(0, 1)}, {LineSegment(0, 1)}};
|
||||
TEST(factRes == planRes, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectionNone)
|
||||
{
|
||||
auto const & factRes = GetIntersections({100, 105}, {101, 110});
|
||||
std::pair<LineSegments, LineSegments> planRes{{}, {}};
|
||||
TEST(factRes == planRes, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectionDouble)
|
||||
{
|
||||
auto const & factRes = GetIntersections({1, 2, 3, 4, 5, 6, 7, 8}, {3, 4, 5, 100, 7, 8});
|
||||
|
||||
std::pair<LineSegments, LineSegments> planRes{{LineSegment(2, 4), LineSegment(6, 7)},
|
||||
{LineSegment(0, 2), LineSegment(4, 5)}};
|
||||
TEST(factRes == planRes, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(IntersectionTriple)
|
||||
{
|
||||
auto const & factRes = GetIntersections({1, 2, 3, 6, 6, 6, 7, 8, 6, 6, 9, 10}, {0, 0, 1, 2, 3, 0, 7, 8, 0, 9, 10, 0});
|
||||
|
||||
std::pair<LineSegments, LineSegments> planRes{{LineSegment(0, 2), LineSegment(6, 7), LineSegment(10, 11)},
|
||||
{LineSegment(2, 4), LineSegment(6, 7), LineSegment(9, 10)}};
|
||||
TEST(factRes == planRes, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GetIntersectionInner)
|
||||
{
|
||||
auto inter = GetIntersection(0, 5, 2, 4);
|
||||
TEST(inter, ());
|
||||
TEST_EQUAL(inter->m_startIdx, 2, ());
|
||||
TEST_EQUAL(inter->m_endIdx, 4, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GetIntersectionNone)
|
||||
{
|
||||
auto inter = GetIntersection(1, 10, 20, 40);
|
||||
TEST(!inter, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GetIntersectionLeft)
|
||||
{
|
||||
auto inter = GetIntersection(10, 100, 5, 15);
|
||||
TEST(inter, ());
|
||||
TEST_EQUAL(inter->m_startIdx, 10, ());
|
||||
TEST_EQUAL(inter->m_endIdx, 15, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GetIntersectionRight)
|
||||
{
|
||||
auto inter = GetIntersection(0, 8, 5, 10);
|
||||
TEST(inter, ());
|
||||
TEST_EQUAL(inter->m_startIdx, 5, ());
|
||||
TEST_EQUAL(inter->m_endIdx, 8, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(GetIntersectionSingle)
|
||||
{
|
||||
auto inter = GetIntersection(0, 8, 8, 10);
|
||||
TEST(!inter, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(CalcSegmentOrder)
|
||||
{
|
||||
TEST_EQUAL(CalcSegmentOrder(0 /* segIndex */, 1 /* totalSegCount */), 0, ());
|
||||
|
||||
TEST_EQUAL(CalcSegmentOrder(0 /* segIndex */, 2 /* totalSegCount */), -1, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(1 /* segIndex */, 2 /* totalSegCount */), 1, ());
|
||||
|
||||
TEST_EQUAL(CalcSegmentOrder(0 /* segIndex */, 3 /* totalSegCount */), -2, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(1 /* segIndex */, 3 /* totalSegCount */), 0, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(2 /* segIndex */, 3 /* totalSegCount */), 2, ());
|
||||
|
||||
TEST_EQUAL(CalcSegmentOrder(0 /* segIndex */, 4 /* totalSegCount */), -3, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(1 /* segIndex */, 4 /* totalSegCount */), -1, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(2 /* segIndex */, 4 /* totalSegCount */), 1, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(3 /* segIndex */, 4 /* totalSegCount */), 3, ());
|
||||
|
||||
TEST_EQUAL(CalcSegmentOrder(0 /* segIndex */, 5 /* totalSegCount */), -4, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(1 /* segIndex */, 5 /* totalSegCount */), -2, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(2 /* segIndex */, 5 /* totalSegCount */), 0, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(3 /* segIndex */, 5 /* totalSegCount */), 2, ());
|
||||
TEST_EQUAL(CalcSegmentOrder(4 /* segIndex */, 5 /* totalSegCount */), 4, ());
|
||||
}
|
||||
|
||||
UNIT_TEST(SplitLineToRegions)
|
||||
{
|
||||
TestStopsRange({1, 2, 3, 4, 5} /* stopsOnLine */, {1, 2, 3, 4, 5} /* stopsInRegion */, 0 /* firstIdxPlan */,
|
||||
4 /* lastIdxPlan */);
|
||||
TestStopsRange({1, 2, 3, 4, 5, 6, 7} /* stopsOnLine */, {1, 2, 3} /* stopsInRegion */, 0 /* firstIdxPlan */,
|
||||
3 /* lastIdxPlan */);
|
||||
TestStopsRange({1, 2, 3, 4, 5, 6, 7} /* stopsOnLine */, {3, 4} /* stopsInRegion */, 1 /* firstIdxPlan */,
|
||||
4 /* lastIdxPlan */);
|
||||
}
|
||||
} // namespace world_feed_tests
|
||||
Loading…
Add table
Add a link
Reference in a new issue