forked from organicmaps/organicmaps
[transit] Validate edge weight in gtfs_converter.
This commit is contained in:
parent
ec0f786f23
commit
f0915ec153
8 changed files with 296 additions and 60 deletions
|
@ -57,6 +57,7 @@ struct TimeFromGateToStop
|
|||
|
||||
DECLARE_VISITOR_AND_DEBUG_PRINT(TimeFromGateToStop, visitor(m_stopId, "stopId"),
|
||||
visitor(m_timeSeconds, "timeSeconds"))
|
||||
|
||||
TransitId m_stopId = kInvalidTransitId;
|
||||
size_t m_timeSeconds = 0;
|
||||
};
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include "base/assert.hpp"
|
||||
#include "base/logging.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
|
@ -82,7 +81,8 @@ ProjectionData GetProjection(std::vector<m2::PointD> const & polyline, size_t in
|
|||
}
|
||||
|
||||
void FillProjections(std::vector<m2::PointD> & polyline, size_t startIndex, size_t endIndex,
|
||||
m2::PointD const & point, std::vector<ProjectionData> & projections)
|
||||
m2::PointD const & point, double distStopsM,
|
||||
std::vector<ProjectionData> & projections)
|
||||
{
|
||||
double distTravelledM = 0.0;
|
||||
// Stop can't be further from its projection to line then |maxDistFromStopM|.
|
||||
|
@ -96,20 +96,31 @@ void FillProjections(std::vector<m2::PointD> & polyline, size_t startIndex, size
|
|||
auto proj = GetProjection(polyline, i, ProjectStopOnTrack(point, polyline[i], polyline[i + 1]));
|
||||
proj.m_distFromStart = distTravelledM + mercator::DistanceOnEarth(polyline[i], 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_distFromStart < distStopsM)
|
||||
continue;
|
||||
|
||||
if (proj.m_distFromPoint < maxDistFromStopM)
|
||||
projections.emplace_back(proj);
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<size_t, bool> PrepareNearestPointOnTrack(m2::PointD const & point, size_t startIndex,
|
||||
std::pair<size_t, bool> PrepareNearestPointOnTrack(m2::PointD const & point,
|
||||
std::optional<m2::PointD> const & prevPoint,
|
||||
size_t startIndex,
|
||||
std::vector<m2::PointD> & polyline)
|
||||
{
|
||||
std::vector<ProjectionData> projections;
|
||||
// Reserve space for points on polyline which are relatively close to the polyline.
|
||||
// Approximately 1/4 of all points on shape.
|
||||
projections.reserve(polyline.size() / 4);
|
||||
// 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;
|
||||
|
||||
FillProjections(polyline, startIndex, polyline.size() - 1, point, projections);
|
||||
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.
|
||||
projections.reserve((polyline.size() - startIndex) / 4);
|
||||
|
||||
FillProjections(polyline, startIndex, polyline.size() - 1, point, distStopsM, projections);
|
||||
|
||||
if (projections.empty())
|
||||
return {polyline.size() + 1, false};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "geometry/point2d.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -25,7 +26,9 @@ ProjectionToShape ProjectStopOnTrack(m2::PointD const & stopPoint, m2::PointD co
|
|||
/// \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.
|
||||
std::pair<size_t, bool> PrepareNearestPointOnTrack(m2::PointD const & point, size_t startIndex,
|
||||
std::pair<size_t, bool> PrepareNearestPointOnTrack(m2::PointD const & point,
|
||||
std::optional<m2::PointD> const & prevPoint,
|
||||
size_t startIndex,
|
||||
std::vector<m2::PointD> & polyline);
|
||||
|
||||
/// \returns true if we should not skip routes with this GTFS |routeType|.
|
||||
|
@ -40,4 +43,32 @@ 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);
|
||||
}
|
||||
|
||||
inline double KmphToMps(double kmph) { return kmph * 1'000.0 / (60.0 * 60.0); }
|
||||
inline double MpsToKmph(double mps) { return mps / 1'000.0 * 60.0 * 60.0; }
|
||||
} // namespace transit
|
||||
|
|
|
@ -238,7 +238,6 @@ bool ConvertFeeds(transit::IdGenerator & generator, transit::ColorPicker & color
|
|||
|
||||
LOG(LINFO, ("Corrupted feeds paths:", invalidFeeds));
|
||||
LOG(LINFO, ("Corrupted feeds:", invalidFeeds.size(), "/", feedsTotal));
|
||||
LOG(LINFO, ("Bad stop sequences:", transit::WorldFeed::GetCorruptedStopSequenceCount()));
|
||||
LOG(LINFO, ("Feeds with no shapes:", feedsWithNoShapesCount, "/", feedsTotal));
|
||||
LOG(LINFO, ("Feeds parsed but not dumped:", feedsNotDumpedCount, "/", feedsTotal));
|
||||
LOG(LINFO, ("Total dumped feeds:", feedsDumped, "/", feedsTotal));
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
#include "transit/world_feed/world_feed.hpp"
|
||||
|
||||
#include "routing/fake_feature_ids.hpp"
|
||||
|
||||
#include "transit/transit_entities.hpp"
|
||||
#include "transit/world_feed/date_time_helpers.hpp"
|
||||
#include "transit/world_feed/feed_helpers.hpp"
|
||||
|
||||
#include "indexer/fake_feature_ids.hpp"
|
||||
|
||||
#include "platform/platform.hpp"
|
||||
|
||||
#include "coding/string_utf8_multilang.hpp"
|
||||
|
@ -20,6 +17,7 @@
|
|||
#include <cmath>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
|
@ -33,6 +31,9 @@ namespace
|
|||
// TODO(o.khlopkova) Set average speed for each type of transit separately - trains, buses, etc.
|
||||
// Average transit speed. Approximately 40 km/h.
|
||||
static double constexpr kAvgTransitSpeedMpS = 11.1;
|
||||
// If count of corrupted shapes in feed exceeds this value we skip feed and don't save it. The shape
|
||||
// is corrupted if we cant't properly project all stops from the trip to its polyline.
|
||||
static size_t constexpr kMaxInvalidShapesCount = 5;
|
||||
|
||||
template <class C, class ID>
|
||||
void AddToRegions(C & container, ID const & id, transit::Regions const & regions)
|
||||
|
@ -200,7 +201,6 @@ namespace transit
|
|||
{
|
||||
// Static fields.
|
||||
std::unordered_set<std::string> WorldFeed::m_agencyHashes;
|
||||
size_t WorldFeed::m_badStopSeqCount = 0;
|
||||
|
||||
EdgeId::EdgeId(TransitId fromStopId, TransitId toStopId, TransitId lineId)
|
||||
: m_fromStopId(fromStopId), m_toStopId(toStopId), m_lineId(lineId)
|
||||
|
@ -241,8 +241,7 @@ size_t EdgeTransferIdHasher::operator()(EdgeTransferId const & key) const
|
|||
|
||||
ShapeData::ShapeData(std::vector<m2::PointD> const & points) : m_points(points) {}
|
||||
|
||||
IdGenerator::IdGenerator(std::string const & idMappingPath)
|
||||
: m_curId(routing::FakeFeatureIds::kTransitGraphFeaturesStart), m_idMappingPath(idMappingPath)
|
||||
IdGenerator::IdGenerator(std::string const & idMappingPath) : m_idMappingPath(idMappingPath)
|
||||
{
|
||||
LOG(LINFO, ("Inited generator with", m_curId, "start id and path to mappings", m_idMappingPath));
|
||||
CHECK(!m_idMappingPath.empty(), ());
|
||||
|
@ -319,14 +318,9 @@ void IdGenerator::Save()
|
|||
CHECK(mappingFile.is_open(), ("Path to the mapping file does not exist:", m_idMappingPath));
|
||||
|
||||
mappingFile << m_curId << std::endl;
|
||||
CHECK(routing::FakeFeatureIds::IsTransitFeature(m_curId), (m_curId));
|
||||
|
||||
for (auto const & [hash, id] : m_hashToId)
|
||||
{
|
||||
mappingFile << id << std::endl;
|
||||
CHECK(routing::FakeFeatureIds::IsTransitFeature(id), (id));
|
||||
mappingFile << hash << std::endl;
|
||||
}
|
||||
mappingFile << id << std::endl << hash << std::endl;
|
||||
}
|
||||
catch (std::ofstream::failure const & se)
|
||||
{
|
||||
|
@ -873,9 +867,14 @@ bool WorldFeed::FillLinesSchedule()
|
|||
}
|
||||
|
||||
bool WorldFeed::ProjectStopsToShape(
|
||||
TransitId shapeId, std::vector<m2::PointD> & shape, IdList const & stopIds,
|
||||
ShapesIter & itShape, StopsOnLines const & stopsOnLines,
|
||||
std::unordered_map<TransitId, std::vector<size_t>> & stopsToIndexes)
|
||||
{
|
||||
IdList const & stopIds = stopsOnLines.m_stopSeq;
|
||||
TransitId const shapeId = itShape->first;
|
||||
auto & shape = itShape->second.m_points;
|
||||
std::optional<m2::PointD> prevPoint = std::nullopt;
|
||||
|
||||
for (size_t i = 0; i < stopIds.size(); ++i)
|
||||
{
|
||||
auto const & stopId = stopIds[i];
|
||||
|
@ -884,10 +883,22 @@ bool WorldFeed::ProjectStopsToShape(
|
|||
auto const & stop = itStop->second;
|
||||
|
||||
size_t const startIdx = i == 0 ? 0 : stopsToIndexes[stopIds[i - 1]].back();
|
||||
auto const [curIdx, pointInserted] = PrepareNearestPointOnTrack(stop.m_point, startIdx, shape);
|
||||
auto const [curIdx, pointInserted] =
|
||||
PrepareNearestPointOnTrack(stop.m_point, prevPoint, startIdx, shape);
|
||||
|
||||
if (curIdx > shape.size())
|
||||
{
|
||||
CHECK(!itShape->second.m_lineIds.empty(), (shapeId));
|
||||
TransitId const lineId = *stopsOnLines.m_lines.begin();
|
||||
|
||||
LOG(LWARNING,
|
||||
("Error projecting stops to the shape. GTFS trip id", m_lines.m_data[lineId].m_gtfsTripId,
|
||||
"shapeId", shapeId, "stopId", stopId, "i", i, "start index on shape", startIdx,
|
||||
"trips count", stopsOnLines.m_lines.size()));
|
||||
return false;
|
||||
}
|
||||
|
||||
prevPoint = std::optional<m2::PointD>(stop.m_point);
|
||||
|
||||
if (pointInserted)
|
||||
{
|
||||
|
@ -962,23 +973,29 @@ size_t WorldFeed::ModifyShapes()
|
|||
{
|
||||
CHECK(!stopsLists.empty(), (shapeId));
|
||||
|
||||
auto it = m_shapes.m_data.find(shapeId);
|
||||
CHECK(it != m_shapes.m_data.end(), (shapeId));
|
||||
auto & shape = it->second;
|
||||
auto itShape = m_shapes.m_data.find(shapeId);
|
||||
CHECK(itShape != m_shapes.m_data.end(), (shapeId));
|
||||
|
||||
std::unordered_map<TransitId, std::vector<size_t>> stopToShapeIndex;
|
||||
|
||||
for (auto & stopsOnLines : stopsLists)
|
||||
{
|
||||
if (stopsOnLines.m_stopSeq.size() < 2 ||
|
||||
!ProjectStopsToShape(shapeId, shape.m_points, stopsOnLines.m_stopSeq, stopToShapeIndex))
|
||||
if (stopsOnLines.m_stopSeq.size() < 2)
|
||||
{
|
||||
TransitId const lineId = *stopsOnLines.m_lines.begin();
|
||||
LOG(LWARNING, ("Error in stops count. Lines count:", stopsOnLines.m_stopSeq.size(),
|
||||
"GTFS trip id:", m_lines.m_data[lineId].m_gtfsTripId));
|
||||
stopsOnLines.m_isValid = false;
|
||||
++invalidStopSequences;
|
||||
}
|
||||
else if (!ProjectStopsToShape(itShape, stopsOnLines, stopToShapeIndex))
|
||||
{
|
||||
stopsOnLines.m_isValid = false;
|
||||
++invalidStopSequences;
|
||||
LOG(LINFO,
|
||||
("Error projecting stops to shape. trips count:", stopsOnLines.m_lines.size(),
|
||||
"first trip GTFS id:", m_lines.m_data[*stopsOnLines.m_lines.begin()].m_gtfsTripId));
|
||||
}
|
||||
|
||||
if (invalidStopSequences > kMaxInvalidShapesCount)
|
||||
return invalidStopSequences;
|
||||
}
|
||||
|
||||
for (auto const & stopsOnLines : stopsLists)
|
||||
|
@ -1117,13 +1134,142 @@ void WorldFeed::FillGates()
|
|||
}
|
||||
}
|
||||
|
||||
bool WorldFeed::SpeedExceedsMaxVal(EdgeId const & edgeId, EdgeData const & edgeData)
|
||||
{
|
||||
m2::PointD const & stop1 = m_stops.m_data.at(edgeId.m_fromStopId).m_point;
|
||||
m2::PointD const & stop2 = m_stops.m_data.at(edgeId.m_toStopId).m_point;
|
||||
|
||||
static double const maxSpeedMpS = KmphToMps(routing::kTransitMaxSpeedKMpH);
|
||||
double const speedMpS = mercator::DistanceOnEarth(stop1, stop2) / edgeData.m_weight;
|
||||
|
||||
bool speedExceedsMaxVal = speedMpS > maxSpeedMpS;
|
||||
if (speedExceedsMaxVal)
|
||||
{
|
||||
LOG(LWARNING,
|
||||
("Invalid edge weight conflicting with kTransitMaxSpeedKMpH:", edgeId.m_fromStopId,
|
||||
edgeId.m_toStopId, edgeId.m_lineId, "speed (km/h):", MpsToKmph(speedMpS),
|
||||
"maxSpeed (km/h):", routing::kTransitMaxSpeedKMpH));
|
||||
}
|
||||
|
||||
return speedExceedsMaxVal;
|
||||
}
|
||||
|
||||
bool WorldFeed::ClearFeedByLineIds(std::unordered_set<TransitId> const & corruptedLineIds)
|
||||
{
|
||||
std::unordered_set<TransitId> corruptedRouteIds;
|
||||
std::unordered_set<TransitId> corruptedShapeIds;
|
||||
std::unordered_set<TransitId> corruptedNetworkIds;
|
||||
|
||||
for (auto lineId : corruptedLineIds)
|
||||
{
|
||||
LineData const & lineData = m_lines.m_data[lineId];
|
||||
corruptedRouteIds.emplace(lineData.m_routeId);
|
||||
corruptedNetworkIds.emplace(m_routes.m_data.at(lineData.m_routeId).m_networkId);
|
||||
corruptedShapeIds.emplace(lineData.m_shapeId);
|
||||
}
|
||||
|
||||
for (auto const & [lineId, lineData] : m_lines.m_data)
|
||||
{
|
||||
if (corruptedLineIds.find(lineId) != corruptedLineIds.end())
|
||||
continue;
|
||||
|
||||
// We keep in lists for deletion only ids which are not linked to valid entities.
|
||||
DeleteIfExists(corruptedRouteIds, lineData.m_routeId);
|
||||
DeleteIfExists(corruptedShapeIds, lineData.m_shapeId);
|
||||
}
|
||||
|
||||
for (auto const & [routeId, routeData] : m_routes.m_data)
|
||||
{
|
||||
if (corruptedRouteIds.find(routeId) == corruptedRouteIds.end())
|
||||
DeleteIfExists(corruptedNetworkIds, routeData.m_networkId);
|
||||
}
|
||||
|
||||
DeleteAllEntriesByIds(m_shapes.m_data, corruptedShapeIds);
|
||||
DeleteAllEntriesByIds(m_routes.m_data, corruptedRouteIds);
|
||||
DeleteAllEntriesByIds(m_networks.m_data, corruptedNetworkIds);
|
||||
DeleteAllEntriesByIds(m_lines.m_data, corruptedLineIds);
|
||||
|
||||
std::unordered_set<TransitId> corruptedStopIds;
|
||||
|
||||
// We fill |corruptedStopIds| and delete corresponding edges from |m_edges|.
|
||||
for (auto it = m_edges.m_data.begin(); it != m_edges.m_data.end();)
|
||||
{
|
||||
if (corruptedLineIds.find(it->first.m_lineId) != corruptedLineIds.end())
|
||||
{
|
||||
corruptedStopIds.emplace(it->first.m_fromStopId);
|
||||
corruptedStopIds.emplace(it->first.m_toStopId);
|
||||
it = m_edges.m_data.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// We remove transfer edges linked to the corrupted stop ids.
|
||||
for (auto it = m_edgesTransfers.m_data.begin(); it != m_edgesTransfers.m_data.end();)
|
||||
{
|
||||
if (corruptedStopIds.find(it->first.m_fromStopId) != corruptedStopIds.end() ||
|
||||
corruptedStopIds.find(it->first.m_toStopId) != corruptedStopIds.end())
|
||||
{
|
||||
it = m_edgesTransfers.m_data.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// We remove transfers linked to the corrupted stop ids.
|
||||
for (auto it = m_transfers.m_data.begin(); it != m_transfers.m_data.end();)
|
||||
{
|
||||
auto & transferData = it->second;
|
||||
DeleteAllEntriesByIds(transferData.m_stopsIds, corruptedStopIds);
|
||||
|
||||
if (transferData.m_stopsIds.size() < 2)
|
||||
it = m_transfers.m_data.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
// We remove gates linked to the corrupted stop ids.
|
||||
for (auto it = m_gates.m_data.begin(); it != m_gates.m_data.end();)
|
||||
{
|
||||
auto & gateData = it->second;
|
||||
|
||||
for (auto itW = gateData.m_weights.begin(); itW != gateData.m_weights.end();)
|
||||
{
|
||||
if (corruptedStopIds.find(itW->m_stopId) != corruptedStopIds.end())
|
||||
itW = gateData.m_weights.erase(itW);
|
||||
else
|
||||
++itW;
|
||||
}
|
||||
|
||||
if (gateData.m_weights.empty())
|
||||
it = m_gates.m_data.erase(it);
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
DeleteAllEntriesByIds(m_stops.m_data, corruptedStopIds);
|
||||
|
||||
LOG(LINFO, ("Count of lines linked to the corrupted edges:", corruptedLineIds.size(),
|
||||
", routes:", corruptedRouteIds.size(), ", networks:", corruptedNetworkIds.size(),
|
||||
", shapes:", corruptedShapeIds.size()));
|
||||
|
||||
return !m_networks.m_data.empty() && !m_routes.m_data.empty() && !m_lines.m_data.empty() &&
|
||||
!m_stops.m_data.empty() && !m_edges.m_data.empty();
|
||||
}
|
||||
|
||||
bool WorldFeed::UpdateEdgeWeights()
|
||||
{
|
||||
std::unordered_set<TransitId> corruptedLineIds;
|
||||
|
||||
for (auto & [edgeId, edgeData] : m_edges.m_data)
|
||||
{
|
||||
if (edgeData.m_weight == 0)
|
||||
{
|
||||
auto const polyLine = m_shapes.m_data.at(edgeData.m_shapeLink.m_shapeId).m_points;
|
||||
auto const & polyLine = m_shapes.m_data.at(edgeData.m_shapeLink.m_shapeId).m_points;
|
||||
|
||||
bool const isInverted = edgeData.m_shapeLink.m_startIndex > edgeData.m_shapeLink.m_endIndex;
|
||||
|
||||
|
@ -1134,7 +1280,14 @@ bool WorldFeed::UpdateEdgeWeights()
|
|||
|
||||
auto const edgePolyLine =
|
||||
std::vector<m2::PointD>(polyLine.begin() + startIndex, polyLine.begin() + endIndex + 1);
|
||||
CHECK_GREATER(edgePolyLine.size(), 1, ());
|
||||
|
||||
if (edgePolyLine.size() < 1)
|
||||
{
|
||||
LOG(LWARNING, ("Invalid edge with too short shape polyline:", edgeId.m_fromStopId,
|
||||
edgeId.m_toStopId, edgeId.m_lineId));
|
||||
corruptedLineIds.emplace(edgeId.m_lineId);
|
||||
continue;
|
||||
}
|
||||
|
||||
double edgeLengthM = 0.0;
|
||||
|
||||
|
@ -1142,12 +1295,24 @@ bool WorldFeed::UpdateEdgeWeights()
|
|||
edgeLengthM += mercator::DistanceOnEarth(edgePolyLine[i], edgePolyLine[i + 1]);
|
||||
|
||||
if (edgeLengthM == 0.0)
|
||||
return false;
|
||||
{
|
||||
LOG(LWARNING, ("Invalid edge with 0 length:", edgeId.m_fromStopId, edgeId.m_toStopId,
|
||||
edgeId.m_lineId));
|
||||
corruptedLineIds.emplace(edgeId.m_lineId);
|
||||
continue;
|
||||
}
|
||||
|
||||
edgeData.m_weight = std::ceil(edgeLengthM / kAvgTransitSpeedMpS);
|
||||
}
|
||||
|
||||
// We check that edge weight doesn't violate A* invariant in routing runtime.
|
||||
if (SpeedExceedsMaxVal(edgeId, edgeData))
|
||||
corruptedLineIds.emplace(edgeId.m_lineId);
|
||||
}
|
||||
|
||||
if (!corruptedLineIds.empty())
|
||||
return ClearFeedByLineIds(corruptedLineIds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1199,9 +1364,15 @@ bool WorldFeed::SetFeed(gtfs::Feed && feed)
|
|||
}
|
||||
LOG(LINFO, ("Filled stop timetables and road graph edges."));
|
||||
|
||||
m_badStopSeqCount += ModifyShapes();
|
||||
size_t const badShapesCount = ModifyShapes();
|
||||
LOG(LINFO, ("Modified shapes."));
|
||||
|
||||
if (badShapesCount > kMaxInvalidShapesCount)
|
||||
{
|
||||
LOG(LINFO, ("Corrupted shapes count exceeds allowable limit."));
|
||||
return false;
|
||||
}
|
||||
|
||||
FillTransfers();
|
||||
LOG(LINFO, ("Filled transfers."));
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
|
||||
private:
|
||||
std::unordered_map<std::string, TransitId> m_hashToId;
|
||||
TransitId m_curId;
|
||||
TransitId m_curId = 0;
|
||||
std::string m_idMappingPath;
|
||||
};
|
||||
|
||||
|
@ -104,6 +104,8 @@ struct ShapeData
|
|||
IdSet m_lineIds;
|
||||
};
|
||||
|
||||
using ShapesIter = std::unordered_map<TransitId, ShapeData>::iterator;
|
||||
|
||||
struct Shapes
|
||||
{
|
||||
void Write(IdSet const & ids, std::ofstream & stream) const;
|
||||
|
@ -285,8 +287,6 @@ public:
|
|||
// Dumps global feed to |world_feed_path|.
|
||||
bool Save(std::string const & worldFeedDir, bool overwrite);
|
||||
|
||||
inline static size_t GetCorruptedStopSequenceCount() { return m_badStopSeqCount; }
|
||||
|
||||
private:
|
||||
friend class WorldFeedIntegrationTests;
|
||||
friend class SubwayConverterTests;
|
||||
|
@ -307,7 +307,7 @@ private:
|
|||
bool FillLinesSchedule();
|
||||
// Gets frequencies of trips from GTFS.
|
||||
|
||||
// Adds shape with mercator points instead of WGS83 lat/lon.
|
||||
// Adds shape with mercator points instead of WGS84 lat/lon.
|
||||
bool AddShape(GtfsIdToHash::iterator & iter, std::string const & gtfsShapeId, TransitId lineId);
|
||||
// Fills stops data, corresponding fields in |m_lines| and builds edges for the road graph.
|
||||
bool FillStopsEdges();
|
||||
|
@ -331,8 +331,7 @@ private:
|
|||
// Recalculates 0-weights of edges based on the shape length.
|
||||
bool UpdateEdgeWeights();
|
||||
|
||||
bool ProjectStopsToShape(TransitId shapeId, std::vector<m2::PointD> & shape,
|
||||
IdList const & stopIds,
|
||||
bool ProjectStopsToShape(ShapesIter & itShape, StopsOnLines const & stopsOnLines,
|
||||
std::unordered_map<TransitId, std::vector<size_t>> & stopsToIndexes);
|
||||
|
||||
// Extracts data from GTFS calendar for lines.
|
||||
|
@ -364,7 +363,11 @@ private:
|
|||
// 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;
|
||||
|
||||
|
|
|
@ -81,13 +81,13 @@ public:
|
|||
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(), 981, ());
|
||||
TEST_EQUAL(m_globalFeed.m_stops.m_data.size(), 1021, ());
|
||||
TEST_EQUAL(m_globalFeed.m_lines.m_data.size(), 980, ());
|
||||
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(), 10091, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edges.m_data.size(), 10079, ());
|
||||
TEST_EQUAL(m_globalFeed.m_edgesTransfers.m_data.size(), 0, ());
|
||||
}
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
#include "platform/platform.hpp"
|
||||
|
||||
#include "base/assert.hpp"
|
||||
#include "base/math.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
@ -201,7 +203,7 @@ UNIT_TEST(Transit_GTFS_ProjectStopToLine_Simple)
|
|||
// Test that point_A is projected between two existing polyline points and the new point is
|
||||
// added in the place of its projection.
|
||||
TestPlanFact(1 /* planIndex */, true /* planInsert */,
|
||||
PrepareNearestPointOnTrack(point_A, 0 /* startIndex */, shape));
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* startIndex */, shape));
|
||||
|
||||
TEST_EQUAL(shape.size(), 5, ());
|
||||
TEST_EQUAL(shape[1 /* expectedIndex */], m2::PointD(point_A.x, y), ());
|
||||
|
@ -210,15 +212,17 @@ UNIT_TEST(Transit_GTFS_ProjectStopToLine_Simple)
|
|||
// Expected point projection index is the same.
|
||||
// But this projection is not inserted (it is already present).
|
||||
TestPlanFact(1 /* planIndex */, false /* planInsert */,
|
||||
PrepareNearestPointOnTrack(point_A, 0 /* startIndex */, shape));
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* startIndex */, 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.
|
||||
TestPlanFact(4, true, PrepareNearestPointOnTrack(point_B, 1 /* startIndex */, shape));
|
||||
TestPlanFact(4, true,
|
||||
PrepareNearestPointOnTrack(point_B, std::nullopt, 1 /* startIndex */, shape));
|
||||
|
||||
// Test that point_C insertion does not lead to the addition of the new projection.
|
||||
TestPlanFact(5, false, PrepareNearestPointOnTrack(point_C, 4 /* startIndex */, shape));
|
||||
TestPlanFact(5, false,
|
||||
PrepareNearestPointOnTrack(point_C, std::nullopt, 4 /* startIndex */, shape));
|
||||
}
|
||||
|
||||
// Stop is on approximately the same distance from the segment (0, 1) and segment (1, 2).
|
||||
|
@ -238,7 +242,8 @@ UNIT_TEST(Transit_GTFS_ProjectStopToLine_DifferentStartIndexes)
|
|||
// Test for |startIndex| = 0.
|
||||
{
|
||||
auto shape = referenceShape;
|
||||
TestPlanFact(1, true, PrepareNearestPointOnTrack(point_A, 0 /* startIndex */, shape));
|
||||
TestPlanFact(1, true,
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* startIndex */, shape));
|
||||
TEST_EQUAL(shape.size(), 4, ());
|
||||
TEST_EQUAL(shape[1 /* expectedIndex */], m2::PointD(0.001, point_A.y), ());
|
||||
}
|
||||
|
@ -246,7 +251,8 @@ UNIT_TEST(Transit_GTFS_ProjectStopToLine_DifferentStartIndexes)
|
|||
// Test for |startIndex| = 1.
|
||||
{
|
||||
auto shape = referenceShape;
|
||||
TestPlanFact(2, true, PrepareNearestPointOnTrack(point_A, 1 /* startIndex */, shape));
|
||||
TestPlanFact(2, true,
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 1 /* startIndex */, shape));
|
||||
TEST_EQUAL(shape.size(), 4, ());
|
||||
TEST_EQUAL(shape[2 /* expectedIndex */], m2::PointD(point_A.x, 0.002), ());
|
||||
}
|
||||
|
@ -270,7 +276,8 @@ UNIT_TEST(Transit_GTFS_ProjectStopToLine_MaxDistance)
|
|||
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};
|
||||
TestPlanFact(1, true, PrepareNearestPointOnTrack(point_A, 0 /* startIndex */, shape));
|
||||
TestPlanFact(1, true,
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* startIndex */, shape));
|
||||
}
|
||||
|
||||
// Complex shape with multiple points on it and multiple stops for projection.
|
||||
|
@ -306,12 +313,18 @@ UNIT_TEST(Transit_GTFS_ProjectStopToLine_NearCircle)
|
|||
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};
|
||||
TestPlanFact(2, true, PrepareNearestPointOnTrack(point_A, 0 /* startIndex */, shape));
|
||||
TestPlanFact(3, false, PrepareNearestPointOnTrack(point_B, 2 /* startIndex */, shape));
|
||||
TestPlanFact(10, true, PrepareNearestPointOnTrack(point_C, 3 /* startIndex */, shape));
|
||||
TestPlanFact(12, false, PrepareNearestPointOnTrack(point_D, 10 /* startIndex */, shape));
|
||||
TestPlanFact(14, true, PrepareNearestPointOnTrack(point_E, 12 /* startIndex */, shape));
|
||||
TestPlanFact(20, true, PrepareNearestPointOnTrack(point_F, 14 /* startIndex */, shape));
|
||||
TestPlanFact(2, true,
|
||||
PrepareNearestPointOnTrack(point_A, std::nullopt, 0 /* startIndex */, shape));
|
||||
TestPlanFact(3, false,
|
||||
PrepareNearestPointOnTrack(point_B, std::nullopt, 2 /* startIndex */, shape));
|
||||
TestPlanFact(10, true,
|
||||
PrepareNearestPointOnTrack(point_C, std::nullopt, 3 /* startIndex */, shape));
|
||||
TestPlanFact(12, false,
|
||||
PrepareNearestPointOnTrack(point_D, std::nullopt, 10 /* startIndex */, shape));
|
||||
TestPlanFact(14, true,
|
||||
PrepareNearestPointOnTrack(point_E, std::nullopt, 12 /* startIndex */, shape));
|
||||
TestPlanFact(20, true,
|
||||
PrepareNearestPointOnTrack(point_F, std::nullopt, 14 /* startIndex */, shape));
|
||||
}
|
||||
|
||||
UNIT_TEST(Transit_ColorPicker)
|
||||
|
@ -338,4 +351,11 @@ UNIT_TEST(Transit_BuildHash1Arg)
|
|||
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(Measurement_Utils)
|
||||
{
|
||||
double constexpr eps = 1e-5;
|
||||
TEST(base::AlmostEqualAbs(KmphToMps(1.0), 0.27777, eps), ());
|
||||
TEST(base::AlmostEqualAbs(MpsToKmph(1.0), 3.6, eps), ());
|
||||
}
|
||||
} // namespace
|
||||
|
|
Loading…
Add table
Reference in a new issue