Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
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