diff --git a/routing/CMakeLists.txt b/routing/CMakeLists.txt index ac3d06ce66..93e3bd9bdc 100644 --- a/routing/CMakeLists.txt +++ b/routing/CMakeLists.txt @@ -28,6 +28,8 @@ set( city_roads.hpp city_roads_serialization.hpp coding.hpp + cross_border_graph.cpp + cross_border_graph.hpp cross_mwm_connector.cpp cross_mwm_connector.hpp cross_mwm_connector_serialization.cpp diff --git a/routing/cross_border_graph.cpp b/routing/cross_border_graph.cpp new file mode 100644 index 0000000000..edcd59872f --- /dev/null +++ b/routing/cross_border_graph.cpp @@ -0,0 +1,62 @@ +#include "routing/cross_border_graph.hpp" + +namespace routing +{ +void CrossBorderGraph::AddCrossBorderSegment(RegionSegmentId segId, + CrossBorderSegment const & segment) +{ + m_segments.emplace(segId, segment); + + auto addEndingToMwms = [&](CrossBorderSegmentEnding const & ending) { + auto const & [it, inserted] = + m_mwms.emplace(ending.m_numMwmId, std::vector{segId}); + if (!inserted) + it->second.push_back(segId); + }; + + addEndingToMwms(segment.m_start); + addEndingToMwms(segment.m_end); +} + +// static +double CrossBorderGraphSerializer::SerDesVals::GetMin() { return ms::LatLon::kMinLat; } +// static +double CrossBorderGraphSerializer::SerDesVals::GetMax() { return ms::LatLon::kMaxLat; } + +CrossBorderSegmentEnding::CrossBorderSegmentEnding(m2::PointD const & point, NumMwmId const & mwmId) + : m_point(mercator::ToLatLon(point), geometry::kDefaultAltitudeMeters), m_numMwmId(mwmId) +{ +} + +CrossBorderSegmentEnding::CrossBorderSegmentEnding(ms::LatLon const & point, NumMwmId const & mwmId) + : m_point(point, 0), m_numMwmId(mwmId) +{ +} + +CrossBorderGraphSerializer::Header::Header(CrossBorderGraph const & graph, uint32_t version) + : m_numRegions(static_cast(graph.m_mwms.size())) + , m_numRoads(static_cast(graph.m_segments.size())) + , m_version(version) +{ +} + +// static +uint32_t CrossBorderGraphSerializer::Hash(std::string const & s) +{ + // We use RSHash (Real Simple Hash) variation. RSHash is a standard hash calculating function + // described in Robert Sedgwick's "Algorithms in C". The difference between this implementation + // and the original algorithm is that we return hash, not (hash & & 0x7FFFFFFF). + uint32_t constexpr b = 378551; + uint32_t a = 63689; + + uint32_t hash = 0; + + for (auto c : s) + { + hash = hash * a + c; + a *= b; + } + + return hash; +} +} // namespace routing diff --git a/routing/cross_border_graph.hpp b/routing/cross_border_graph.hpp new file mode 100644 index 0000000000..05155b03d3 --- /dev/null +++ b/routing/cross_border_graph.hpp @@ -0,0 +1,250 @@ +#pragma once + +#include "routing/latlon_with_altitude.hpp" + +#include "routing_common/num_mwm_id.hpp" + +#include "coding/point_coding.hpp" +#include "coding/reader.hpp" +#include "coding/write_to_sink.hpp" + +#include "geometry/mercator.hpp" +#include "geometry/point2d.hpp" +#include "geometry/point_with_altitude.hpp" + +#include "base/assert.hpp" +#include "base/logging.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace routing +{ +uint32_t constexpr kLatestVersionRoutingWorldSection = 0; + +using RegionSegmentId = uint32_t; + +struct CrossBorderSegmentEnding +{ + CrossBorderSegmentEnding() = default; + CrossBorderSegmentEnding(m2::PointD const & point, NumMwmId const & mwmId); + CrossBorderSegmentEnding(ms::LatLon const & point, NumMwmId const & mwmId); + + LatLonWithAltitude m_point; + NumMwmId m_numMwmId = std::numeric_limits::max(); +}; + +struct CrossBorderSegment +{ + CrossBorderSegmentEnding m_start; + CrossBorderSegmentEnding m_end; + + double m_weight = 0.0; +}; + +using CrossBorderSegments = std::unordered_map; +using MwmIdToSegmentIds = std::unordered_map>; + +struct CrossBorderGraph +{ + void AddCrossBorderSegment(RegionSegmentId segId, CrossBorderSegment const & segment); + + CrossBorderSegments m_segments; + MwmIdToSegmentIds m_mwms; +}; + +class CrossBorderGraphSerializer +{ +public: + CrossBorderGraphSerializer() = delete; + + template + static void Serialize(CrossBorderGraph const & graph, Sink & sink, + std::shared_ptr numMwmIds); + + template + static void Deserialize(CrossBorderGraph & graph, Source & src, + std::shared_ptr numMwmIds); + +private: + struct Header + { + Header() = default; + explicit Header(CrossBorderGraph const & graph, + uint32_t version = kLatestVersionRoutingWorldSection); + + template + void Serialize(Sink & sink) const; + + template + void Deserialize(Source & src); + + uint32_t m_numRegions = 0; + uint32_t m_numRoads = 0; + + uint32_t m_version = kLatestVersionRoutingWorldSection; + }; + + // Constants for point-coding of double fields (coordinates and edge weight). + class SerDesVals + { + public: + static double GetMin(); + static double GetMax(); + static constexpr size_t GetBitsCount() { return kBitsForDouble; } + + private: + static size_t constexpr kBitsForDouble = 30; + }; + + static uint32_t Hash(std::string const & s); +}; + +// static +template +void CrossBorderGraphSerializer::Serialize(CrossBorderGraph const & graph, Sink & sink, + std::shared_ptr numMwmIds) +{ + Header header(graph); + header.Serialize(sink); + + std::set mwmNameHashes; + + for (auto it = graph.m_mwms.begin(); it != graph.m_mwms.end(); ++it) + { + auto const & mwmId = it->first; + std::string const & mwmName = numMwmIds->GetFile(mwmId).GetName(); + auto const hash = Hash(mwmName); + + // Triggering of this check during the maps build means that the mwm set has been changed and + // current hash function Hash(mwmName) no longer suits it and should be replaced. + CHECK(mwmNameHashes.emplace(hash).second, (mwmId, mwmName, hash)); + } + + for (auto hash : mwmNameHashes) + WriteToSink(sink, hash); + + auto writeVal = [&sink](double val, bool isCoord = true) { + WriteToSink(sink, DoubleToUint32(val, SerDesVals::GetMin(), SerDesVals::GetMax(), + SerDesVals::GetBitsCount())); + }; + + auto writeSegEnding = [&](CrossBorderSegmentEnding const & ending) { + auto const & coord = ending.m_point.GetLatLon(); + writeVal(coord.m_lat); + writeVal(coord.m_lon); + + auto const & mwmNameHash = Hash(numMwmIds->GetFile(ending.m_numMwmId).GetName()); + auto it = mwmNameHashes.find(mwmNameHash); + CHECK(it != mwmNameHashes.end(), (ending.m_numMwmId, mwmNameHash)); + + NumMwmId id = std::distance(mwmNameHashes.begin(), it); + WriteToSink(sink, id); + }; + + for (auto const & [segId, seg] : graph.m_segments) + { + WriteToSink(sink, segId); + + double const weightRounded = std::ceil(seg.m_weight); + CHECK_LESS(weightRounded, std::numeric_limits::max(), (weightRounded)); + + WriteToSink(sink, static_cast(weightRounded)); + writeSegEnding(seg.m_start); + writeSegEnding(seg.m_end); + } +} + +// static +template +void CrossBorderGraphSerializer::Deserialize(CrossBorderGraph & graph, Source & src, + std::shared_ptr numMwmIds) +{ + Header header; + header.Deserialize(src); + + graph.m_mwms.reserve(header.m_numRegions); + graph.m_segments.reserve(header.m_numRoads); + + std::map hashToMwmId; + + numMwmIds->ForEachId([&](NumMwmId id) { + std::string const & region = numMwmIds->GetFile(id).GetName(); + auto const & mwmNameHash = Hash(region); + // Triggering of this check in runtime means that the latest built mwm set differs from + // the previous one ("classic" mwm set which is constant for many years). The solution is to + // replace current hash function Hash() and rebuild World.mwm. + CHECK(hashToMwmId.emplace(mwmNameHash, id).second, (id, region, mwmNameHash)); + }); + + std::set mwmNameHashes; + + for (size_t i = 0; i < header.m_numRegions; ++i) + { + auto const mwmNameHash = ReadPrimitiveFromSource(src); + mwmNameHashes.emplace(mwmNameHash); + } + + auto readVal = [&src]() { + return Uint32ToDouble(ReadPrimitiveFromSource(src), SerDesVals::GetMin(), + SerDesVals::GetMax(), SerDesVals::GetBitsCount()); + }; + + auto readSegEnding = [&](CrossBorderSegmentEnding & ending) { + double const lat = readVal(); + double const lon = readVal(); + ending.m_point = LatLonWithAltitude(ms::LatLon(lat, lon), geometry::kDefaultAltitudeMeters); + + NumMwmId index = ReadPrimitiveFromSource(src); + CHECK_LESS(index, mwmNameHashes.size(), ()); + + auto it = mwmNameHashes.begin(); + std::advance(it, index); + + auto const & mwmHash = *it; + auto itHash = hashToMwmId.find(mwmHash); + CHECK(itHash != hashToMwmId.end(), (mwmHash)); + + ending.m_numMwmId = itHash->second; + }; + + for (size_t i = 0; i < header.m_numRoads; ++i) + { + RegionSegmentId segId = ReadPrimitiveFromSource(src); + + CrossBorderSegment seg; + + seg.m_weight = static_cast(ReadPrimitiveFromSource(src)); + readSegEnding(seg.m_start); + readSegEnding(seg.m_end); + + graph.AddCrossBorderSegment(segId, seg); + } +} + +template +void CrossBorderGraphSerializer::Header::Serialize(Sink & sink) const +{ + WriteToSink(sink, m_version); + WriteToSink(sink, m_numRegions); + WriteToSink(sink, m_numRoads); +} + +template +void CrossBorderGraphSerializer::Header::Deserialize(Source & src) +{ + m_version = ReadPrimitiveFromSource(src); + CHECK_EQUAL(m_version, kLatestVersionRoutingWorldSection, ("Unknown CrossRegionGraph version.")); + + m_numRegions = ReadPrimitiveFromSource(src); + m_numRoads = ReadPrimitiveFromSource(src); +} +} // namespace routing