diff --git a/generator/routing_index_generator.cpp b/generator/routing_index_generator.cpp index 4c1b590dc1..ba57a1d1b6 100644 --- a/generator/routing_index_generator.cpp +++ b/generator/routing_index_generator.cpp @@ -27,6 +27,7 @@ #include "base/checked_cast.hpp" #include "base/logging.hpp" +#include #include #include #include @@ -172,10 +173,42 @@ bool RegionsContain(vector const & regions, m2::PointD const & poin return false; } +double CalcClockwiseDistance(vector const & borders, + CrossMwmConnectorSerializer::Transition const & transition) +{ + double distance = 0.0; + + for (m2::RegionD const & region : borders) + { + vector points = region.Data(); + + m2::PointD const * prev = &points.back(); + for (m2::PointD const & curr : points) + { + m2::PointD intersection; + if (region.IsIntersect(transition.GetBackPoint(), transition.GetFrontPoint(), *prev, curr, + intersection)) + { + distance += prev->Length(intersection); + return distance; + } + + distance += prev->Length(curr); + prev = &curr; + } + } + + CHECK(false, ("Intersection not found, feature:", transition.GetFeatureId(), "segment:", + transition.GetSegmentIdx(), "back:", transition.GetBackPoint(), "front:", + transition.GetFrontPoint())); + return distance; +} + void CalcCrossMwmTransitions(string const & path, string const & mwmFile, string const & country, vector & transitions, CrossMwmConnectorPerVehicleType & connectors) { + my::Timer timer; string const polyFile = my::JoinPath(path, BORDERS_DIR, country + BORDERS_EXTENSION); vector borders; osm::LoadBorders(polyFile, borders); @@ -206,20 +239,36 @@ void CalcCrossMwmTransitions(string const & path, string const & mwmFile, string transitions.emplace_back(featureId, segmentIdx, roadMask, oneWayMask, currPointIn, f.GetPoint(i - 1), f.GetPoint(i)); - for (size_t j = 0; j < connectors.size(); ++j) - { - VehicleMask const mask = GetVehicleMask(static_cast(j)); - CrossMwmConnectorSerializer::AddTransition(transitions.back(), mask, connectors[j]); - } - prevPointIn = currPointIn; } }); + LOG(LINFO, ("Transitions finished, transitions:", transitions.size(), ", elapsed:", + timer.ElapsedSeconds(), "seconds")); + + timer.Reset(); + sort(transitions.begin(), transitions.end(), + [&](CrossMwmConnectorSerializer::Transition const & lhs, + CrossMwmConnectorSerializer::Transition const & rhs) { + return CalcClockwiseDistance(borders, lhs) < CalcClockwiseDistance(borders, rhs); + }); + + LOG(LINFO, ("Transition sorted in", timer.ElapsedSeconds(), "seconds")); + + for (auto const & transition : transitions) + { + for (size_t j = 0; j < connectors.size(); ++j) + { + VehicleMask const mask = GetVehicleMask(static_cast(j)); + CrossMwmConnectorSerializer::AddTransition(transition, mask, connectors[j]); + } + } } void FillWeights(string const & path, string const & mwmFile, string const & country, CrossMwmConnector & connector) { + my::Timer timer; + shared_ptr vehicleModel = CarModelFactory().GetVehicleModelForCountry(country); IndexGraph graph( GeometryLoader::CreateFromFile(mwmFile, vehicleModel), @@ -231,6 +280,8 @@ void FillWeights(string const & path, string const & mwmFile, string const & cou map> weights; map distanceMap; auto const numEnters = connector.GetEnters().size(); + size_t foundCount = 0; + size_t notFoundCount = 0; for (size_t i = 0; i < numEnters; ++i) { if ((i % 10 == 0) && (i != 0)) @@ -250,7 +301,14 @@ void FillWeights(string const & path, string const & mwmFile, string const & cou { auto it = distanceMap.find(exit); if (it != distanceMap.end()) + { weights[enter][exit] = it->second; + ++foundCount; + } + else + { + ++notFoundCount; + } } } @@ -265,6 +323,9 @@ void FillWeights(string const & path, string const & mwmFile, string const & cou return it1->second; }); + + LOG(LINFO, ("Leaps finished, elapsed:", timer.ElapsedSeconds(), "seconds, routes found:", + foundCount, ", not found:", notFoundCount)); } serial::CodingParams LoadCodingParams(string const & mwmFile) @@ -308,15 +369,12 @@ bool BuildRoutingIndex(string const & filename, string const & country) void BuildCrossMwmSection(string const & path, string const & mwmFile, string const & country) { LOG(LINFO, ("Building cross mwm section for", country)); - my::Timer timer; CrossMwmConnectorPerVehicleType connectors; vector transitions; CalcCrossMwmTransitions(path, mwmFile, country, transitions, connectors); - LOG(LINFO, ("Transitions finished, transitions:", transitions.size(), ", elapsed:", - timer.ElapsedSeconds(), "seconds")); for (size_t i = 0; i < connectors.size(); ++i) { VehicleType const vehicleType = static_cast(i); @@ -324,10 +382,8 @@ void BuildCrossMwmSection(string const & path, string const & mwmFile, string co LOG(LINFO, (vehicleType, "model: enters:", connector.GetEnters().size(), ", exits:", connector.GetExits().size())); } - timer.Reset(); FillWeights(path, mwmFile, country, connectors[static_cast(VehicleType::Car)]); - LOG(LINFO, ("Leaps finished, elapsed:", timer.ElapsedSeconds(), "seconds")); serial::CodingParams const codingParams = LoadCodingParams(mwmFile); FilesContainerW cont(mwmFile, FileWriter::OP_WRITE_EXISTING); diff --git a/routing/cross_mwm_connector.hpp b/routing/cross_mwm_connector.hpp index 2de2a25a96..ab61296d7f 100644 --- a/routing/cross_mwm_connector.hpp +++ b/routing/cross_mwm_connector.hpp @@ -147,6 +147,7 @@ private: std::unordered_map m_transitions; WeightsLoadState m_weightsLoadState = WeightsLoadState::Unknown; uint64_t m_weightsOffset = 0; + Weight m_accuracy = 0; std::vector m_weights; }; } // namespace routing diff --git a/routing/cross_mwm_connector_serialization.cpp b/routing/cross_mwm_connector_serialization.cpp index cdc6c2c4ec..1e951417da 100644 --- a/routing/cross_mwm_connector_serialization.cpp +++ b/routing/cross_mwm_connector_serialization.cpp @@ -19,12 +19,25 @@ void CrossMwmConnectorSerializer::WriteTransitions(vector const & tr } // static -void CrossMwmConnectorSerializer::WriteWeights(vector const & weights, +void CrossMwmConnectorSerializer::WriteWeights(vector const & weights, vector & buffer) { MemWriter> memWriter(buffer); + BitWriter>> writer(memWriter); - for (auto weight : weights) - WriteToSink(memWriter, weight); + CrossMwmConnector::Weight prevWeight = 1; + for (auto const weight : weights) + { + if (weight == CrossMwmConnector::kNoRoute) + { + writer.Write(kNoRouteBit, 1); + continue; + } + + writer.Write(kRouteBit, 1); + auto const storedWeight = (weight + kAccuracy - 1) / kAccuracy; + WriteDelta(writer, EncodePositivesDelta(prevWeight, storedWeight)); + prevWeight = storedWeight; + } } } // namespace routing diff --git a/routing/cross_mwm_connector_serialization.hpp b/routing/cross_mwm_connector_serialization.hpp index f21bc74a7d..1b82dcbafc 100644 --- a/routing/cross_mwm_connector_serialization.hpp +++ b/routing/cross_mwm_connector_serialization.hpp @@ -8,6 +8,7 @@ #include "indexer/geometry_serialization.hpp" #include "coding/bit_streams.hpp" +#include "coding/elias_coder.hpp" #include "coding/reader.hpp" #include "coding/write_to_sink.hpp" #include "coding/writer.hpp" @@ -17,6 +18,7 @@ #include #include #include +#include #include namespace routing @@ -49,12 +51,12 @@ public: void Serialize(serial::CodingParams const & codingParams, uint8_t bitsPerMask, Sink & sink) const { - WriteToSink(sink, m_featureId); - WriteToSink(sink, m_segmentIdx); serial::SavePoint(sink, m_backPoint, codingParams); serial::SavePoint(sink, m_frontPoint, codingParams); BitWriter writer(sink); + WriteDelta(writer, m_featureId + 1); + WriteDelta(writer, m_segmentIdx + 1); writer.WriteAtMost32Bits(base::asserted_cast(m_roadMask), bitsPerMask); writer.WriteAtMost32Bits(base::asserted_cast(m_oneWayMask), bitsPerMask); writer.Write(m_forwardIsEnter ? 0 : 1, 1); @@ -63,12 +65,12 @@ public: template void Deserialize(serial::CodingParams const & codingParams, uint8_t bitsPerMask, Source & src) { - m_featureId = ReadPrimitiveFromSource(src); - m_segmentIdx = ReadPrimitiveFromSource(src); m_backPoint = serial::LoadPoint(src, codingParams); m_frontPoint = serial::LoadPoint(src, codingParams); BitReader reader(src); + m_featureId = ReadDelta(reader) - 1; + m_segmentIdx = ReadDelta(reader) - 1; m_roadMask = base::asserted_cast(reader.ReadAtMost32Bits(bitsPerMask)); m_oneWayMask = base::asserted_cast(reader.ReadAtMost32Bits(bitsPerMask)); m_forwardIsEnter = reader.Read(1) == 0; @@ -115,7 +117,6 @@ public: std::vector & buffer = weightBuffers[i]; WriteWeights(connector.m_weights, buffer); - auto const numEnters = base::checked_cast(connector.GetEnters().size()); auto const numExits = base::checked_cast(connector.GetExits().size()); auto const vehicleType = static_cast(i); @@ -133,7 +134,7 @@ public: static void DeserializeTransitions(VehicleType requiredVehicle, CrossMwmConnector & connector, Source & src) { - CHECK(connector.m_weightsLoadState == CrossMwmConnector::WeightsLoadState::Unknown, ()); + CHECK(connector.m_weightsLoadState == WeightsLoadState::Unknown, ()); Header header; header.Deserialize(src); @@ -180,30 +181,44 @@ public: } connector.m_weightsOffset = weightsOffset; - connector.m_weightsLoadState = CrossMwmConnector::WeightsLoadState::ReadyToLoad; + connector.m_accuracy = header.GetAccuracy(); + connector.m_weightsLoadState = WeightsLoadState::ReadyToLoad; return; } - connector.m_weightsLoadState = CrossMwmConnector::WeightsLoadState::NotExists; + connector.m_weightsLoadState = WeightsLoadState::NotExists; } template static void DeserializeWeights(VehicleType requiredVehicle, CrossMwmConnector & connector, Source & src) { - CHECK(connector.m_weightsLoadState == CrossMwmConnector::WeightsLoadState::ReadyToLoad, ()); + CHECK(connector.m_weightsLoadState == WeightsLoadState::ReadyToLoad, ()); + CHECK_GREATER(connector.m_accuracy, 0, ()); src.Skip(connector.m_weightsOffset); size_t const amount = connector.GetEnters().size() * connector.GetExits().size(); connector.m_weights.reserve(amount); + + BitReader reader(src); + + Weight prev = 1; for (size_t i = 0; i < amount; ++i) { - auto const weight = ReadPrimitiveFromSource(src); - connector.m_weights.push_back(static_cast(weight)); + if (reader.Read(1) == kNoRouteBit) + { + connector.m_weights.push_back(CrossMwmConnector::kNoRoute); + continue; + } + + Weight const delta = ReadDelta(reader); + Weight const current = DecodePositivesDelta(prev, delta); + connector.m_weights.push_back(current * connector.m_accuracy); + prev = current; } - connector.m_weightsLoadState = CrossMwmConnector::WeightsLoadState::Loaded; + connector.m_weightsLoadState = WeightsLoadState::Loaded; } static void AddTransition(Transition const & transition, VehicleMask requiredMask, @@ -219,7 +234,17 @@ public: } private: + using Weight = CrossMwmConnector::Weight; + using WeightsLoadState = CrossMwmConnector::WeightsLoadState; + static uint32_t constexpr kLastVersion = 0; + static uint8_t constexpr kNoRouteBit = 0; + static uint8_t constexpr kRouteBit = 1; + // Accuracy is integral unit for storing Weight in section. + // Accuracy measured in seconds as well as the Weight. + // Accuracy is the way to tune section size. + static Weight constexpr kAccuracy = 4; + static_assert(kAccuracy > 0, "kAccuracy should be > 0"); class Section final { @@ -281,6 +306,7 @@ private: WriteToSink(sink, m_version); WriteToSink(sink, m_numTransitions); WriteToSink(sink, m_sizeTransitions); + WriteToSink(sink, m_accuracy); m_codingParams.Save(sink); WriteToSink(sink, m_bitsPerMask); @@ -301,6 +327,7 @@ private: m_numTransitions = ReadPrimitiveFromSource(src); m_sizeTransitions = ReadPrimitiveFromSource(src); + m_accuracy = ReadPrimitiveFromSource(src); m_codingParams.Load(src); m_bitsPerMask = ReadPrimitiveFromSource(src); @@ -314,6 +341,7 @@ private: uint32_t GetNumTransitions() const { return m_numTransitions; } uint64_t GetSizeTransitions() const { return m_sizeTransitions; } + Weight GetAccuracy() const { return m_accuracy; } serial::CodingParams const & GetCodingParams() const { return m_codingParams; } uint8_t GetBitsPerMask() const { return m_bitsPerMask; } std::vector
const & GetSections() const { return m_sections; } @@ -322,11 +350,74 @@ private: uint32_t m_version = kLastVersion; uint32_t m_numTransitions = 0; uint64_t m_sizeTransitions = 0; + Weight m_accuracy = kAccuracy; serial::CodingParams m_codingParams; uint8_t m_bitsPerMask = 0; std::vector
m_sections; }; + template + static void WriteDelta(BitWriter & writer, T value) + { + static_assert(std::is_integral::value, "T should be integral type"); + ASSERT_GREATER(value, 0, ()); + + bool const success = coding::DeltaCoder::Encode(writer, static_cast(value)); + ASSERT(success, ()); + UNUSED_VALUE(success); + } + + template + static T ReadDelta(BitReader & reader) + { + static_assert(std::is_integral::value, "T should be integral type"); + uint64_t const decoded = coding::DeltaCoder::Decode(reader); + if (decoded > numeric_limits::max()) + MYTHROW(CorruptedDataException, + ("Decoded value", decoded, "out of limit", numeric_limits::max())); + + return static_cast(decoded); + } + + // Encodes current as delta compared with prev. + // + // Example: + // prev = 3 + // current = 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 + // delta = 5, 3, 1, 2, 4, 6, 7, 8, 9, 10 + template + static T EncodePositivesDelta(T prev, T current) + { + static_assert(std::is_integral::value, "T should be integral type"); + ASSERT_GREATER(prev, 0, ()); + ASSERT_GREATER(current, 0, ()); + + if (current <= prev) + return (prev - current) * 2 + 1; + + if (current < prev * 2) + return (current - prev) * 2; + + return current; + } + + // Reverse function for EncodePositivesDelta. + template + static T DecodePositivesDelta(T prev, T delta) + { + static_assert(std::is_integral::value, "T should be integral type"); + ASSERT_GREATER(prev, 0, ()); + ASSERT_GREATER(delta, 0, ()); + + if (delta >= prev * 2) + return delta; + + if (delta % 2 == 0) + return prev + delta / 2; + + return prev - delta / 2; + } + template static T GetBitsPerMask() { @@ -347,7 +438,6 @@ private: serial::CodingParams const & codingParams, uint8_t bitsPerMask, std::vector & buffer); - static void WriteWeights(std::vector const & weights, - std::vector & buffer); + static void WriteWeights(std::vector const & weights, std::vector & buffer); }; } // namespace routing diff --git a/routing/routing_tests/cross_mwm_connector_test.cpp b/routing/routing_tests/cross_mwm_connector_test.cpp index 7973ed088c..c2930ad392 100644 --- a/routing/routing_tests/cross_mwm_connector_test.cpp +++ b/routing/routing_tests/cross_mwm_connector_test.cpp @@ -139,7 +139,7 @@ UNIT_TEST(TwoWayExit) UNIT_TEST(Serialization) { - float constexpr kEdgesWeight = 333; + float constexpr kEdgesWeight = 4444; vector buffer; { @@ -236,4 +236,76 @@ UNIT_TEST(Serialization) {{Segment(mwmId, 10, 1, true /* forward */), kEdgesWeight}, {Segment(mwmId, 20, 2, true /* forward */), kEdgesWeight}}); } + +UNIT_TEST(WeightsSerialization) +{ + size_t constexpr kNumTransitions = 3; + std::vector const weights = { + 4.0, 20.0, CrossMwmConnector::kNoRoute, 12.0, CrossMwmConnector::kNoRoute, 40.0, 48.0, + 24.0, 12.0}; + TEST_EQUAL(weights.size(), kNumTransitions * kNumTransitions, ()); + + vector buffer; + { + vector transitions; + for (uint32_t featureId = 0; featureId < kNumTransitions; ++featureId) + { + transitions.emplace_back(featureId, 1 /* segmentIdx */, kCarMask, 0 /* oneWayMask */, + true /* forwardIsEnter */, m2::PointD::Zero(), m2::PointD::Zero()); + } + + CrossMwmConnectorPerVehicleType connectors; + CrossMwmConnector & carConnector = connectors[static_cast(VehicleType::Car)]; + for (auto const & transition : transitions) + CrossMwmConnectorSerializer::AddTransition(transition, kCarMask, carConnector); + + int weightIdx = 0; + carConnector.FillWeights( + [&](Segment const & enter, Segment const & exit) { return weights[weightIdx++]; }); + + serial::CodingParams const codingParams; + MemWriter> writer(buffer); + CrossMwmConnectorSerializer::Serialize(transitions, connectors, codingParams, writer); + } + + CrossMwmConnector connector(mwmId); + { + MemReader reader(buffer.data(), buffer.size()); + ReaderSource source(reader); + CrossMwmConnectorSerializer::DeserializeTransitions(VehicleType::Car, connector, source); + } + + TestConnectorConsistency(connector); + + TEST_EQUAL(connector.GetEnters().size(), kNumTransitions, ()); + TEST_EQUAL(connector.GetExits().size(), kNumTransitions, ()); + + TEST(!connector.WeightsWereLoaded(), ()); + TEST(!connector.HasWeights(), ()); + + { + MemReader reader(buffer.data(), buffer.size()); + ReaderSource source(reader); + CrossMwmConnectorSerializer::DeserializeWeights(VehicleType::Car, connector, source); + } + TEST(connector.WeightsWereLoaded(), ()); + TEST(connector.HasWeights(), ()); + + int weightIdx = 0; + + for (uint32_t enterId = 0; enterId < kNumTransitions; ++enterId) + { + Segment const enter(mwmId, enterId, 1, true /* forward */); + vector expectedEdges; + for (uint32_t exitId = 0; exitId < kNumTransitions; ++exitId) + { + auto const weight = weights[weightIdx]; + if (weight != CrossMwmConnector::kNoRoute) + expectedEdges.emplace_back(Segment(mwmId, exitId, 1, false /* forward */), weight); + ++weightIdx; + } + + TestEdges(connector, enter, true /* isOutgoing */, expectedEdges); + } +} } // namespace routing_test