Repo created
This commit is contained in:
parent
4af19165ec
commit
68073add76
12458 changed files with 12350765 additions and 2 deletions
510
libs/routing/opening_hours_serdes.hpp
Normal file
510
libs/routing/opening_hours_serdes.hpp
Normal file
|
|
@ -0,0 +1,510 @@
|
|||
#pragma once
|
||||
|
||||
#include "coding/bit_streams.hpp"
|
||||
#include "coding/varint.hpp"
|
||||
|
||||
#include "base/checked_cast.hpp"
|
||||
#include "base/stl_helpers.hpp"
|
||||
|
||||
#include <limits>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "3party/opening_hours/opening_hours.hpp"
|
||||
|
||||
// TODO (@gmoryes)
|
||||
// opening_hours does not support such string: 2019 Apr - 2020 May,
|
||||
// but 2019 Arp 14 - 2020 May 31 supports.
|
||||
|
||||
namespace routing
|
||||
{
|
||||
/// \brief This class serialise opening hours from OSM format to some binary data.
|
||||
/// Each opening hours has one, two, ... rules. Each rule has some data, like: Year, Month,
|
||||
/// Weekdays etc. (look to |Header::Bits| for supported features). This class has two mods:
|
||||
/// 1) (default) Serialize opening hours only if all rules have same headers. In this case
|
||||
/// we serialize in such format:
|
||||
/// <number of rules><header><rule 1>...<rule N>
|
||||
/// 2) (Turned on by |SetSerializeEverything| method) For each rule serialize its' header.
|
||||
/// Format:
|
||||
/// <number of rules><header 1><rule 1><header 2><rule 2>...<header N><rule N>
|
||||
///
|
||||
/// First format can be used in routing for access:conditional because it has more strong filtering
|
||||
/// of opening hours and the more trash will be dropped.
|
||||
/// Second format can be used in opening hours for POI because it support more kinds of opening
|
||||
/// hours and in case of invalid data we can show raw string and user will able to understand what
|
||||
/// it's supposed to be.
|
||||
///
|
||||
/// This class also drops rules with old year range, like:
|
||||
/// "2010 - 2011"
|
||||
/// "2010 Arp 19 - 2014 May 20"
|
||||
/// etc.
|
||||
/// In case of mixed data:
|
||||
/// "2010 Arp 19 - 2014 May 20 ; 2020 Mo-Su"
|
||||
/// "2010 Arp 19 - 2014 May 20 ; Mo-Su 10:00-15:00"
|
||||
/// It leaves only fresh rules:
|
||||
/// "2020 Mo-Su" and "Mo-Su 10:00-15:00"
|
||||
class OpeningHoursSerDes
|
||||
{
|
||||
public:
|
||||
class Header
|
||||
{
|
||||
public:
|
||||
using HeaderType = uint16_t;
|
||||
enum class Bits : HeaderType
|
||||
{
|
||||
Year = 1U << 0U,
|
||||
Month = 1U << 1U,
|
||||
MonthDay = 1U << 2U,
|
||||
WeekDay = 1U << 3U,
|
||||
Hours = 1U << 4U,
|
||||
Minutes = 1U << 5U,
|
||||
Off = 1U << 6U,
|
||||
Holiday = 1U << 7U,
|
||||
|
||||
Max = 1U << 8U
|
||||
};
|
||||
|
||||
static inline uint8_t constexpr kBitsSize = 8;
|
||||
static_assert(base::Underlying(Bits::Max) == 1U << kBitsSize,
|
||||
"It's seems header's size has been changed. Need to change |kBitsSize| member.");
|
||||
|
||||
static_assert(kBitsSize <= 8, "Header's size is more than 1 byte, be careful about section weight.");
|
||||
|
||||
/// \brief Returns true if feature takes only two values (0 or 1).
|
||||
static bool IsBinaryFeature(Header::Bits feature);
|
||||
|
||||
template <typename Writer>
|
||||
static void Serialize(BitWriter<Writer> & writer, Header header);
|
||||
template <typename Reader>
|
||||
static Header Deserialize(BitReader<Reader> & reader);
|
||||
|
||||
static uint8_t GetBitsSize() { return kBitsSize; }
|
||||
|
||||
Header() = default;
|
||||
explicit Header(HeaderType header) : m_header(header) {}
|
||||
|
||||
bool operator==(Header const & rhs) const { return m_header == rhs.m_header; }
|
||||
bool operator!=(Header const & rhs) const { return !(*this == rhs); }
|
||||
|
||||
void Set(Bits bit) { m_header |= base::Underlying(bit); }
|
||||
bool IsSet(Bits bit) const { return m_header & base::Underlying(bit); }
|
||||
|
||||
HeaderType GetValue() const { return m_header; }
|
||||
|
||||
private:
|
||||
HeaderType m_header = 0;
|
||||
};
|
||||
|
||||
OpeningHoursSerDes();
|
||||
|
||||
void Enable(Header::Bits bit);
|
||||
bool IsEnabled(Header::Bits bit) const;
|
||||
|
||||
void SetSerializeEverything() { m_serializeEverything = true; }
|
||||
|
||||
template <typename Writer>
|
||||
bool Serialize(BitWriter<Writer> & writer, osmoh::OpeningHours const & openingHours);
|
||||
|
||||
template <typename Writer>
|
||||
bool Serialize(BitWriter<Writer> & writer, std::string const & openingHoursString);
|
||||
|
||||
template <typename Reader>
|
||||
osmoh::OpeningHours Deserialize(BitReader<Reader> & reader);
|
||||
|
||||
private:
|
||||
inline static auto constexpr kMaxRulesCount = std::numeric_limits<uint8_t>::max();
|
||||
inline static uint16_t constexpr kYearBias = 2000;
|
||||
|
||||
bool IsTwentyFourHourRule(osmoh::RuleSequence const & rule) const;
|
||||
|
||||
template <typename Writer>
|
||||
bool SerializeImpl(BitWriter<Writer> & writer, osmoh::OpeningHours const & openingHours);
|
||||
|
||||
bool IsSerializable(osmoh::OpeningHours const & openingHours);
|
||||
|
||||
std::vector<osmoh::RuleSequence> DecomposeOh(osmoh::OpeningHours const & oh);
|
||||
Header CreateHeader(osmoh::RuleSequence const & rule) const;
|
||||
bool NotSupported(osmoh::RuleSequence const & rule);
|
||||
bool ExistsFeatureInOpeningHours(Header::Bits feature, osmoh::RuleSequence const & rule) const;
|
||||
bool CheckSupportedFeatures() const;
|
||||
bool HaveSameHeaders(std::vector<osmoh::RuleSequence> const & decomposedRules) const;
|
||||
|
||||
template <typename Writer>
|
||||
bool Serialize(BitWriter<Writer> & writer, Header::Bits type, osmoh::RuleSequence const & rule);
|
||||
|
||||
template <typename Reader>
|
||||
void Deserialize(BitReader<Reader> & reader, Header::Bits type, osmoh::RuleSequence & rule);
|
||||
|
||||
template <typename Writer>
|
||||
bool SerializeValue(BitWriter<Writer> & writer, Header::Bits type, uint8_t value);
|
||||
|
||||
template <typename Writer>
|
||||
void SerializeRuleHeader(BitWriter<Writer> & writer, osmoh::RuleSequence const & rule);
|
||||
|
||||
uint8_t GetBitsNumber(Header::Bits type) const;
|
||||
bool CheckYearRange(osmoh::MonthDay::TYear start, osmoh::MonthDay::TYear end) const;
|
||||
|
||||
uint16_t m_currentYear;
|
||||
bool m_serializeEverything = false;
|
||||
std::vector<Header::Bits> m_supportedFeatures;
|
||||
std::vector<Header::Bits> m_unsupportedFeatures;
|
||||
};
|
||||
|
||||
// OpeningHoursSerDes::Header ----------------------------------------------------------------------
|
||||
template <typename Writer>
|
||||
void OpeningHoursSerDes::Header::Serialize(BitWriter<Writer> & writer, OpeningHoursSerDes::Header header)
|
||||
{
|
||||
writer.Write(header.GetValue(), Header::GetBitsSize());
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
OpeningHoursSerDes::Header OpeningHoursSerDes::Header::Deserialize(BitReader<Reader> & reader)
|
||||
{
|
||||
return Header(reader.Read(Header::GetBitsSize()));
|
||||
}
|
||||
|
||||
// OpeningHoursSerDes ------------------------------------------------------------------------------
|
||||
template <typename Writer>
|
||||
bool OpeningHoursSerDes::Serialize(BitWriter<Writer> & writer, osmoh::OpeningHours const & openingHours)
|
||||
{
|
||||
if (!IsSerializable(openingHours))
|
||||
return false;
|
||||
|
||||
return SerializeImpl(writer, openingHours);
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
bool OpeningHoursSerDes::Serialize(BitWriter<Writer> & writer, std::string const & openingHoursString)
|
||||
{
|
||||
osmoh::OpeningHours const oh(openingHoursString);
|
||||
if (!oh.IsValid())
|
||||
return false;
|
||||
|
||||
return OpeningHoursSerDes::Serialize(writer, oh);
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
bool OpeningHoursSerDes::SerializeImpl(BitWriter<Writer> & writer, osmoh::OpeningHours const & openingHours)
|
||||
{
|
||||
CheckSupportedFeatures();
|
||||
|
||||
std::vector<osmoh::RuleSequence> decomposedRules = DecomposeOh(openingHours);
|
||||
if (decomposedRules.empty())
|
||||
return false;
|
||||
|
||||
if (!m_serializeEverything && !HaveSameHeaders(decomposedRules))
|
||||
return false;
|
||||
|
||||
if (decomposedRules.size() > kMaxRulesCount)
|
||||
return false;
|
||||
|
||||
auto const rulesCount = base::checked_cast<uint8_t>(decomposedRules.size());
|
||||
writer.Write(rulesCount /* bits */, 8 /* bitsNumber */);
|
||||
|
||||
if (!m_serializeEverything)
|
||||
{
|
||||
// We assume that all rules have same headers (checked by |HaveSameHeaders| method).
|
||||
SerializeRuleHeader(writer, decomposedRules.back());
|
||||
}
|
||||
|
||||
for (auto const & rule : decomposedRules)
|
||||
{
|
||||
if (m_serializeEverything)
|
||||
SerializeRuleHeader(writer, rule);
|
||||
|
||||
for (auto const supportedFeature : m_supportedFeatures)
|
||||
{
|
||||
if (ExistsFeatureInOpeningHours(supportedFeature, rule))
|
||||
{
|
||||
if (!Serialize(writer, supportedFeature, rule))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
osmoh::OpeningHours OpeningHoursSerDes::Deserialize(BitReader<Reader> & reader)
|
||||
{
|
||||
osmoh::TRuleSequences rules;
|
||||
uint8_t const rulesCount = reader.Read(8 /* bitsNumber */);
|
||||
Header header;
|
||||
if (!m_serializeEverything)
|
||||
header = Header::Deserialize(reader);
|
||||
|
||||
for (size_t i = 0; i < rulesCount; ++i)
|
||||
{
|
||||
if (m_serializeEverything)
|
||||
header = Header::Deserialize(reader);
|
||||
|
||||
osmoh::RuleSequence rule;
|
||||
for (auto const supportedFeature : m_supportedFeatures)
|
||||
if (header.IsSet(supportedFeature))
|
||||
Deserialize(reader, supportedFeature, rule);
|
||||
|
||||
rules.emplace_back(std::move(rule));
|
||||
}
|
||||
|
||||
if (rules.size() == 1 && IsTwentyFourHourRule(rules.back()))
|
||||
{
|
||||
rules.back().SetTimes({});
|
||||
rules.back().SetTwentyFourHours(true /* on */);
|
||||
}
|
||||
|
||||
return osmoh::OpeningHours(rules);
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
bool OpeningHoursSerDes::Serialize(BitWriter<Writer> & writer, Header::Bits type, osmoh::RuleSequence const & rule)
|
||||
{
|
||||
uint8_t start = 0;
|
||||
uint8_t end = 0;
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Header::Bits::Year:
|
||||
{
|
||||
uint16_t startYear = 0;
|
||||
uint16_t endYear = 0;
|
||||
if (rule.GetYears().size() == 1)
|
||||
{
|
||||
startYear = rule.GetYears().back().GetStart();
|
||||
endYear = rule.GetYears().back().GetEnd();
|
||||
}
|
||||
else if (rule.GetMonths().size() == 1)
|
||||
{
|
||||
startYear = rule.GetMonths().back().GetStart().GetYear();
|
||||
endYear = rule.GetMonths().back().GetEnd().GetYear();
|
||||
}
|
||||
else
|
||||
{
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
if (!CheckYearRange(startYear, endYear))
|
||||
return false;
|
||||
|
||||
start = base::checked_cast<uint8_t>(startYear - kYearBias);
|
||||
end = base::checked_cast<uint8_t>(endYear - kYearBias);
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Month:
|
||||
{
|
||||
CHECK_EQUAL(rule.GetMonths().size(), 1, ());
|
||||
auto const startMonth = rule.GetMonths().back().GetStart().GetMonth();
|
||||
auto const endMonth = rule.GetMonths().back().GetEnd().GetMonth();
|
||||
start = base::checked_cast<uint8_t>(base::Underlying(startMonth));
|
||||
end = base::checked_cast<uint8_t>(base::Underlying(endMonth));
|
||||
break;
|
||||
}
|
||||
case Header::Bits::MonthDay:
|
||||
{
|
||||
CHECK_EQUAL(rule.GetMonths().size(), 1, ());
|
||||
start = rule.GetMonths().back().GetStart().GetDayNum();
|
||||
end = rule.GetMonths().back().GetEnd().GetDayNum();
|
||||
break;
|
||||
}
|
||||
case Header::Bits::WeekDay:
|
||||
{
|
||||
CHECK_EQUAL(rule.GetWeekdays().GetWeekdayRanges().size(), 1, ());
|
||||
auto const startWeekday = rule.GetWeekdays().GetWeekdayRanges().back().GetStart();
|
||||
auto const endWeekday = rule.GetWeekdays().GetWeekdayRanges().back().GetEnd();
|
||||
start = base::checked_cast<uint8_t>(base::Underlying(startWeekday));
|
||||
end = base::checked_cast<uint8_t>(base::Underlying(endWeekday));
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Hours:
|
||||
{
|
||||
CHECK(rule.GetTimes().size() == 1 || rule.IsTwentyFourHours(), ());
|
||||
if (rule.IsTwentyFourHours())
|
||||
{
|
||||
start = 0;
|
||||
end = 24;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = rule.GetTimes().back().GetStart().GetHoursCount();
|
||||
end = rule.GetTimes().back().GetEnd().GetHoursCount();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Minutes:
|
||||
{
|
||||
CHECK(rule.GetTimes().size() == 1 || rule.IsTwentyFourHours(), ());
|
||||
if (rule.IsTwentyFourHours())
|
||||
break;
|
||||
start = rule.GetTimes().back().GetStart().GetMinutesCount();
|
||||
end = rule.GetTimes().back().GetEnd().GetMinutesCount();
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Off:
|
||||
{
|
||||
start = rule.GetModifier() == osmoh::RuleSequence::Modifier::Closed;
|
||||
end = 0;
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Holiday:
|
||||
{
|
||||
CHECK_EQUAL(rule.GetWeekdays().GetHolidays().size(), 1, ());
|
||||
start = rule.GetWeekdays().GetHolidays().back().IsPlural();
|
||||
end = 0;
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Max: UNREACHABLE();
|
||||
}
|
||||
|
||||
if (!SerializeValue(writer, type, start))
|
||||
return false;
|
||||
|
||||
if (!Header::IsBinaryFeature(type))
|
||||
return SerializeValue(writer, type, end);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Reader>
|
||||
void OpeningHoursSerDes::Deserialize(BitReader<Reader> & reader, OpeningHoursSerDes::Header::Bits type,
|
||||
osmoh::RuleSequence & rule)
|
||||
{
|
||||
uint8_t const start = reader.Read(GetBitsNumber(type));
|
||||
uint8_t end = 0;
|
||||
if (!Header::IsBinaryFeature(type))
|
||||
end = reader.Read(GetBitsNumber(type));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
case Header::Bits::Year:
|
||||
{
|
||||
osmoh::YearRange range;
|
||||
range.SetStart(start + kYearBias);
|
||||
range.SetEnd(end + kYearBias);
|
||||
rule.SetYears({range});
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Month:
|
||||
{
|
||||
osmoh::MonthDay startMonth;
|
||||
startMonth.SetMonth(static_cast<osmoh::MonthDay::Month>(start));
|
||||
|
||||
osmoh::MonthDay endMonth;
|
||||
endMonth.SetMonth(static_cast<osmoh::MonthDay::Month>(end));
|
||||
|
||||
if (rule.HasYears())
|
||||
{
|
||||
CHECK_EQUAL(rule.GetYears().size(), 1, ());
|
||||
startMonth.SetYear(rule.GetYears().back().GetStart());
|
||||
endMonth.SetYear(rule.GetYears().back().GetEnd());
|
||||
rule.SetYears({});
|
||||
}
|
||||
|
||||
osmoh::MonthdayRange range;
|
||||
range.SetStart(startMonth);
|
||||
range.SetEnd(endMonth);
|
||||
rule.SetMonths({range});
|
||||
break;
|
||||
}
|
||||
case Header::Bits::MonthDay:
|
||||
{
|
||||
CHECK_EQUAL(rule.GetMonths().size(), 1, ());
|
||||
auto range = rule.GetMonths().back();
|
||||
|
||||
auto startMonth = range.GetStart();
|
||||
auto endMonth = range.GetEnd();
|
||||
|
||||
startMonth.SetDayNum(start);
|
||||
endMonth.SetDayNum(end);
|
||||
|
||||
range.SetStart(startMonth);
|
||||
range.SetEnd(endMonth);
|
||||
|
||||
rule.SetMonths({range});
|
||||
break;
|
||||
}
|
||||
case Header::Bits::WeekDay:
|
||||
{
|
||||
osmoh::WeekdayRange range;
|
||||
|
||||
auto const startWeekday = static_cast<osmoh::Weekday>(start);
|
||||
range.SetStart(startWeekday);
|
||||
|
||||
auto const endWeekday = static_cast<osmoh::Weekday>(end);
|
||||
range.SetEnd(endWeekday);
|
||||
|
||||
osmoh::Weekdays weekdays;
|
||||
weekdays.SetWeekdayRanges({range});
|
||||
rule.SetWeekdays(weekdays);
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Hours:
|
||||
{
|
||||
osmoh::Timespan range;
|
||||
auto hm = range.GetStart().GetHourMinutes();
|
||||
hm.SetHours(osmoh::HourMinutes::THours(start));
|
||||
range.GetStart().SetHourMinutes(hm);
|
||||
|
||||
hm = range.GetEnd().GetHourMinutes();
|
||||
hm.SetHours(osmoh::HourMinutes::THours(end));
|
||||
range.GetEnd().SetHourMinutes(hm);
|
||||
|
||||
rule.SetTimes({range});
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Minutes:
|
||||
{
|
||||
CHECK_EQUAL(rule.GetTimes().size(), 1, ());
|
||||
osmoh::Timespan range = rule.GetTimes().back();
|
||||
|
||||
range.GetStart().GetHourMinutes().SetMinutes(osmoh::HourMinutes::TMinutes(start));
|
||||
range.GetEnd().GetHourMinutes().SetMinutes(osmoh::HourMinutes::TMinutes(end));
|
||||
rule.SetTimes({range});
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Off:
|
||||
{
|
||||
CHECK(start == 0 || start == 1, (start));
|
||||
CHECK_EQUAL(end, 0, ());
|
||||
if (start)
|
||||
rule.SetModifier(osmoh::RuleSequence::Modifier::Closed);
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Holiday:
|
||||
{
|
||||
CHECK(start == 0 || start == 1, (start));
|
||||
CHECK_EQUAL(end, 0, ());
|
||||
auto const plural = static_cast<bool>(start);
|
||||
|
||||
osmoh::Holiday holiday;
|
||||
holiday.SetPlural(plural);
|
||||
|
||||
osmoh::Weekdays weekdays;
|
||||
weekdays.SetHolidays({holiday});
|
||||
|
||||
rule.SetWeekdays({weekdays});
|
||||
break;
|
||||
}
|
||||
case Header::Bits::Max: UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
bool OpeningHoursSerDes::SerializeValue(BitWriter<Writer> & writer, Header::Bits type, uint8_t value)
|
||||
{
|
||||
uint8_t const bitsNumber = GetBitsNumber(type);
|
||||
if (value >= 1U << bitsNumber)
|
||||
return false;
|
||||
|
||||
writer.Write(value, bitsNumber);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename Writer>
|
||||
void OpeningHoursSerDes::SerializeRuleHeader(BitWriter<Writer> & writer, osmoh::RuleSequence const & rule)
|
||||
{
|
||||
Header const header = CreateHeader(rule);
|
||||
Header::Serialize(writer, header);
|
||||
}
|
||||
} // namespace routing
|
||||
Loading…
Add table
Add a link
Reference in a new issue