From 7772d02bb5de60de94f7f4d4101ee74156c69bfc Mon Sep 17 00:00:00 2001 From: Olga Khlopkova Date: Wed, 2 Sep 2020 10:38:02 +0300 Subject: [PATCH] [transit] Schedule for ser/des of PT timetables. --- transit/CMakeLists.txt | 2 + transit/transit_schedule.cpp | 360 +++++++++++++++++++++++++++++++++++ transit/transit_schedule.hpp | 218 +++++++++++++++++++++ 3 files changed, 580 insertions(+) create mode 100644 transit/transit_schedule.cpp create mode 100644 transit/transit_schedule.hpp diff --git a/transit/CMakeLists.txt b/transit/CMakeLists.txt index 9c5b04f938..885bdba41f 100644 --- a/transit/CMakeLists.txt +++ b/transit/CMakeLists.txt @@ -17,6 +17,8 @@ set( transit_serdes.hpp transit_types.cpp transit_types.hpp + transit_schedule.cpp + transit_schedule.hpp transit_version.cpp transit_version.hpp ) diff --git a/transit/transit_schedule.cpp b/transit/transit_schedule.cpp new file mode 100644 index 0000000000..0a48a445cd --- /dev/null +++ b/transit/transit_schedule.cpp @@ -0,0 +1,360 @@ +#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); +} + +std::tuple 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[0] = m_data & 0x40; + week[1] = m_data & 0x20; + week[2] = m_data & 0x10; + week[3] = m_data & 0x8; + week[4] = m_data & 0x4; + week[5] = m_data & 0x2; + week[6] = 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 & exception) +{ + 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(exception); +} + +bool DateException::operator==(DateException const & rhs) const { return m_data == rhs.m_data; } + +std::tuple 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; +} + +bool TimeInterval::operator<(TimeInterval const & rhs) const { return m_data < rhs.m_data; } + +std::pair 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) + { + CHECK_GREATER(freq.headway_secs, 0, ()); + m_intervals.emplace(TimeInterval(freq.start_time, freq.end_time), freq.headway_secs); + } +} + +Frequency FrequencyIntervals::GetFrequency(Time const & time) const +{ + for (auto const & [interval, freq] : m_intervals) + { + if (interval.GetTimeStatus(time) == Status::Open) + return freq; + } + + return kDefaultFrequency; +} + +// Schedule ---------------------------------------------------------------------------------------- +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 & exception, + gtfs::Frequencies const & frequencies) +{ + m_serviceExceptions.emplace(DateException(date, exception), FrequencyIntervals(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 Schedule::GetDateAndWeekIndex(time_t const & time) const +{ + std::tm const tm = ToCalendarTime(time); + return {GetDate(tm), tm.tm_wday}; +} + +std::tuple Schedule::GetDateTimeAndWeekIndex(time_t const & time) const +{ + std::tm const tm = ToCalendarTime(time); + return {GetDate(tm), GetTime(tm), tm.tm_wday}; +} +} // namespace transit diff --git a/transit/transit_schedule.hpp b/transit/transit_schedule.hpp new file mode 100644 index 0000000000..816d95f8b4 --- /dev/null +++ b/transit/transit_schedule.hpp @@ -0,0 +1,218 @@ +#pragma once + +#include "base/macros.hpp" +#include "base/newtype.hpp" +#include "base/visitor.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 Serializer; +template +class Deserializer; +template +class FixedSizeSerializer; +template +class FixedSizeDeserializer; +} // namespace transit +} // namespace routing + +namespace transit +{ +#define DECLARE_SCHEDULE_TYPES_FRIENDS \ + template \ + friend class routing::transit::Serializer; \ + template \ + friend class routing::transit::Deserializer; \ + template \ + friend class routing::transit::FixedSizeSerializer; \ + template \ + 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; +// 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); + + bool operator==(DatesInterval const & rhs) const; + + std::tuple 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 & exception); + + bool operator==(DateException const & rhs) const; + + std::tuple 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); + + bool operator<(TimeInterval const & rhs) const; + + std::pair 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); + + Frequency GetFrequency(Time const & time) const; + +private: + std::map m_intervals; +}; + +// 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: + void AddDatesInterval(gtfs::CalendarItem const & calendarItem, + gtfs::Frequencies const & frequencies); + void AddDateException(gtfs::Date const & date, gtfs::CalendarDateException const & exception, + gtfs::Frequencies const & frequencies); + + Status GetStatus(time_t const & time) const; + Frequency GetFrequency(time_t const & time) const; + Frequency GetFrequency() const { return m_defaultFrequency; } + + void SetDefaultFrequency(Frequency const & frequency) { m_defaultFrequency = frequency; } + +private: + std::pair GetDateAndWeekIndex(time_t const & time) const; + std::tuple GetDateTimeAndWeekIndex(time_t const & time) const; + + std::unordered_map m_serviceIntervals; + std::unordered_map m_serviceExceptions; + + Frequency m_defaultFrequency = kDefaultFrequency; +}; + +#undef DECLARE_SCHEDULE_TYPES_FRIENDS +} // namespace transit