diff --git a/defines.hpp b/defines.hpp index 99a4d69207..a982e36870 100644 --- a/defines.hpp +++ b/defines.hpp @@ -24,6 +24,7 @@ #define INDEX_FILE_TAG "idx" #define SEARCH_INDEX_FILE_TAG "sdx" #define SEARCH_ADDRESS_FILE_TAG "addr" +#define CITIES_BOUNDARIES_FILE_TAG "cities_boundaries" #define HEADER_FILE_TAG "header" #define VERSION_FILE_TAG "version" #define METADATA_FILE_TAG "meta" diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index 5815608c6b..184b125201 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt @@ -20,6 +20,8 @@ set(SRC centers_table_builder.hpp check_model.cpp check_model.hpp + cities_boundaries_builder.cpp + cities_boundaries_builder.hpp coastlines_generator.cpp coastlines_generator.hpp dumper.cpp diff --git a/generator/booking_quality_check/booking_quality_check.cpp b/generator/booking_quality_check/booking_quality_check.cpp index c12cefcb5b..1621179460 100644 --- a/generator/booking_quality_check/booking_quality_check.cpp +++ b/generator/booking_quality_check/booking_quality_check.cpp @@ -117,6 +117,11 @@ public: m_features.emplace(fb.GetMostGenericOsmId(), fb); } + void EmitCityBoundary(FeatureBuilder1 const & /* fb */, + FeatureParams const & /* params */) override + { + } + void GetNames(vector & names) const override { names.clear(); diff --git a/generator/cities_boundaries_builder.cpp b/generator/cities_boundaries_builder.cpp new file mode 100644 index 0000000000..c59cf3cb46 --- /dev/null +++ b/generator/cities_boundaries_builder.cpp @@ -0,0 +1,106 @@ +#include "generator/cities_boundaries_builder.hpp" + +#include "generator/utils.hpp" + +#include "search/categories_cache.hpp" +#include "search/cbv.hpp" +#include "search/mwm_context.hpp" + +#include "indexer/cities_boundaries_serdes.hpp" +#include "indexer/city_boundary.hpp" +#include "indexer/classificator.hpp" +#include "indexer/index.hpp" +#include "indexer/mwm_set.hpp" + +#include "platform/local_country_file.hpp" + +#include "coding/compressed_bit_vector.hpp" + +#include "base/assert.hpp" +#include "base/checked_cast.hpp" + +#include +#include + +#include "defines.hpp" + +using namespace indexer; +using namespace search; +using namespace std; + +namespace generator +{ +namespace +{ +bool ParseFeatureIdToOsmIdMapping(string const & path, map> & mapping) +{ + return ForEachOsmId2FeatureId(path, [&](osm::Id const & osmId, uint32_t const featureId) { + mapping[featureId].push_back(osmId); + }); +} + +struct LocalitiesSource +{ + LocalitiesSource() + { + auto & c = classif(); + m_city = c.GetTypeByPath({"place", "city"}); + m_town = c.GetTypeByPath({"place", "town"}); + } + + template + void ForEachType(Fn && fn) const + { + fn(m_city); + fn(m_town); + } + + uint32_t m_city = 0; + uint32_t m_town = 0; +}; + +CBV GetLocalities(string const & dataPath) +{ + Index index; + auto const result = index.Register(platform::LocalCountryFile::MakeTemporary(dataPath)); + CHECK_EQUAL(result.second, MwmSet::RegResult::Success, ("Can't register", dataPath)); + + search::MwmContext context(index.GetMwmHandleById(result.first)); + return search::CategoriesCache(LocalitiesSource{}, my::Cancellable{}).Get(context); +} +} // namespace + +bool BuildCitiesBoundaries(string const & dataPath, string const & osmToFeaturePath, + OsmIdToBoundariesTable & table) +{ + auto const localities = GetLocalities(dataPath); + + map> mapping; + if (!ParseFeatureIdToOsmIdMapping(dataPath + OSM2FEATURE_FILE_EXTENSION, mapping)) + return false; + + vector> all; + + localities.ForEach([&](uint64_t fid) { + vector bs; + + auto it = mapping.find(base::asserted_cast(fid)); + if (it != mapping.end()) + { + for (auto const & osmId : it->second) + { + auto const & b = table.Get(osmId); + bs.insert(bs.end(), b.begin(), b.end()); + } + } + + all.emplace_back(move(bs)); + }); + + FilesContainerW container(dataPath, FileWriter::OP_WRITE_EXISTING); + FileWriter sink = container.GetWriter(CITIES_BOUNDARIES_FILE_TAG); + indexer::CitiesBoundariesSerDes::Serialize(sink, all); + + return true; +} +} // namespace generator diff --git a/generator/cities_boundaries_builder.hpp b/generator/cities_boundaries_builder.hpp new file mode 100644 index 0000000000..581d2faaca --- /dev/null +++ b/generator/cities_boundaries_builder.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include "generator/osm_id.hpp" + +#include "indexer/city_boundary.hpp" + +#include "base/clustering_map.hpp" + +#include + +namespace generator +{ +using OsmIdToBoundariesTable = + base::ClusteringMap; + +bool BuildCitiesBoundaries(std::string const & dataPath, std::string const & osmToFeaturePath, + OsmIdToBoundariesTable & table); +} // namespace generator diff --git a/generator/generate_info.hpp b/generator/generate_info.hpp index 0de215dee9..ed53c53e37 100644 --- a/generator/generate_info.hpp +++ b/generator/generate_info.hpp @@ -1,11 +1,14 @@ #pragma once -#include "defines.hpp" - -#include "base/logging.hpp" +#include "generator/cities_boundaries_builder.hpp" #include "coding/file_name_utils.hpp" +#include "base/logging.hpp" + +#include "defines.hpp" + +#include #include #include @@ -46,6 +49,8 @@ struct GenerateInfo std::string m_opentableReferenceDir; std::string m_viatorDatafileName; + std::shared_ptr m_boundariesTable; + uint32_t m_versionDate = 0; std::vector m_bucketNames; diff --git a/generator/generator.pro b/generator/generator.pro index d115e083e6..7aa16a251a 100644 --- a/generator/generator.pro +++ b/generator/generator.pro @@ -22,6 +22,7 @@ SOURCES += \ borders_loader.cpp \ centers_table_builder.cpp \ check_model.cpp \ + cities_boundaries_builder.cpp \ coastlines_generator.cpp \ dumper.cpp \ feature_builder.cpp \ @@ -65,6 +66,7 @@ HEADERS += \ borders_loader.hpp \ centers_table_builder.hpp \ check_model.hpp \ + cities_boundaries_builder.hpp \ coastlines_generator.hpp \ dumper.hpp \ feature_builder.hpp \ diff --git a/generator/generator_tool/generator_tool.cpp b/generator/generator_tool/generator_tool.cpp index b672da6415..70a3bbc81d 100644 --- a/generator/generator_tool/generator_tool.cpp +++ b/generator/generator_tool/generator_tool.cpp @@ -3,6 +3,7 @@ #include "generator/borders_loader.hpp" #include "generator/centers_table_builder.hpp" #include "generator/check_model.hpp" +#include "generator/cities_boundaries_builder.hpp" #include "generator/dumper.hpp" #include "generator/feature_generator.hpp" #include "generator/feature_sorter.hpp" @@ -72,6 +73,7 @@ DEFINE_bool(generate_geometry, false, "3rd pass - split and simplify geometry and triangles for features."); DEFINE_bool(generate_index, false, "4rd pass - generate index."); DEFINE_bool(generate_search_index, false, "5th pass - generate search index."); +DEFINE_bool(generate_cities_boundaries, false, "Generate section with cities boundaries"); DEFINE_bool(generate_world, false, "Generate separate world file."); DEFINE_bool(split_by_polygons, false, "Use countries borders to split planet by regions and countries."); @@ -153,6 +155,9 @@ int main(int argc, char ** argv) genInfo.m_opentableReferenceDir = FLAGS_opentable_reference_path; genInfo.m_viatorDatafileName = FLAGS_viator_data; + if (FLAGS_generate_cities_boundaries) + genInfo.m_boundariesTable = make_shared(); + genInfo.m_versionDate = static_cast(FLAGS_planet_version); if (!FLAGS_node_storage.empty()) @@ -175,8 +180,8 @@ int main(int argc, char ** argv) // Load classificator only when necessary. if (FLAGS_make_coasts || FLAGS_generate_features || FLAGS_generate_geometry || - FLAGS_generate_index || FLAGS_generate_search_index || FLAGS_calc_statistics || - FLAGS_type_statistics || FLAGS_dump_types || FLAGS_dump_prefixes || + FLAGS_generate_index || FLAGS_generate_search_index || FLAGS_generate_cities_boundaries || + FLAGS_calc_statistics || FLAGS_type_statistics || FLAGS_dump_types || FLAGS_dump_prefixes || FLAGS_dump_feature_names != "" || FLAGS_check_mwm || FLAGS_srtm_path != "" || FLAGS_make_routing_index || FLAGS_make_cross_mwm || FLAGS_generate_traffic_keys || FLAGS_transit_path != "") @@ -282,6 +287,17 @@ int main(int argc, char ** argv) LOG(LCRITICAL, ("Error generating centers table.")); } + if (FLAGS_generate_cities_boundaries) + { + LOG(LINFO, ("Generating cities boundaries for", datFile)); + CHECK(genInfo.m_boundariesTable, ()); + if (!generator::BuildCitiesBoundaries(datFile, osmToFeatureFilename, + *genInfo.m_boundariesTable)) + { + LOG(LCRITICAL, ("Error generating cities boundaries.")); + } + } + if (!FLAGS_srtm_path.empty()) routing::BuildRoadAltitudes(datFile, FLAGS_srtm_path); diff --git a/generator/osm_id.hpp b/generator/osm_id.hpp index f67e7bf29e..703cd18560 100644 --- a/generator/osm_id.hpp +++ b/generator/osm_id.hpp @@ -1,9 +1,9 @@ #pragma once #include +#include #include - namespace osm { @@ -34,5 +34,10 @@ public: bool operator==(uint64_t other) const { return OsmId() == other; } }; +struct HashId : private std::hash +{ + size_t operator()(Id const & id) const { return std::hash::operator()(id.OsmId()); } +}; + std::string DebugPrint(osm::Id const & id); } // namespace osm diff --git a/generator/osm_source.cpp b/generator/osm_source.cpp index 90c67357c8..0b6406a60b 100644 --- a/generator/osm_source.cpp +++ b/generator/osm_source.cpp @@ -1,3 +1,4 @@ +#include "generator/cities_boundaries_builder.hpp" #include "generator/coastlines_generator.hpp" #include "generator/feature_generator.hpp" #include "generator/intermediate_data.hpp" @@ -16,6 +17,7 @@ #include "generator/opentable_dataset.hpp" #include "generator/viator_dataset.hpp" +#include "indexer/city_boundary.hpp" #include "indexer/classificator.hpp" #include "platform/platform.hpp" @@ -28,6 +30,8 @@ #include "coding/file_name_utils.hpp" #include "coding/parse_xml.hpp" +#include + #include "defines.hpp" using namespace std; @@ -244,7 +248,7 @@ public: // Assume that area places has better priority than point places at the very end ... /// @todo It was usefull when place=XXX type has any area fill style. /// Need to review priority logic here (leave the native osm label). - return !IsPoint(); + return !IsPoint() && r.IsPoint(); } private: @@ -285,6 +289,7 @@ class MainFeaturesEmitter : public EmitterBase generator::BookingDataset m_bookingDataset; generator::OpentableDataset m_opentableDataset; generator::ViatorDataset m_viatorDataset; + shared_ptr m_boundariesTable; /// Used to prepare a list of cities to serve as a list of nodes /// for building a highway graph with OSRM for low zooms. @@ -301,7 +306,25 @@ class MainFeaturesEmitter : public EmitterBase }; uint32_t m_types[TYPES_COUNT]; - inline uint32_t Type(TypeIndex i) const { return m_types[i]; } + uint32_t Type(TypeIndex i) const { return m_types[i]; } + + uint32_t GetPlaceType(FeatureParams const & params) const + { + static uint32_t const placeType = classif().GetTypeByPath({"place"}); + return params.FindType(placeType, 1); + } + + void UnionEqualPlacesIds(Place const & place) + { + if (!m_boundariesTable) + return; + + auto const id = place.GetFeature().GetLastOsmId(); + m_places.ForEachInRect(place.GetLimitRect(), [&](Place const & p) { + if (p.IsEqual(place)) + m_boundariesTable->Union(p.GetFeature().GetLastOsmId(), id); + }); + } public: MainFeaturesEmitter(feature::GenerateInfo const & info) @@ -310,6 +333,7 @@ public: , m_bookingDataset(info.m_bookingDatafileName, info.m_bookingReferenceDir) , m_opentableDataset(info.m_opentableDatafileName, info.m_opentableReferenceDir) , m_viatorDataset(info.m_viatorDatafileName) + , m_boundariesTable(info.m_boundariesTable) { Classificator const & c = classif(); @@ -348,8 +372,7 @@ public: void operator()(FeatureBuilder1 & fb) override { - static uint32_t const placeType = classif().GetTypeByPath({"place"}); - uint32_t const type = fb.GetParams().FindType(placeType, 1); + uint32_t const type = GetPlaceType(fb.GetParams()); // TODO(mgserigio): Would it be better to have objects that store callback // and can be piped: action-if-cond1 | action-if-cond-2 | ... ? @@ -366,9 +389,10 @@ public: }); } + Place const place(fb, type); + UnionEqualPlacesIds(place); m_places.ReplaceEqualInRect( - Place(fb, type), - [](Place const & p1, Place const & p2) { return p1.IsEqual(p2); }, + place, [](Place const & p1, Place const & p2) { return p1.IsEqual(p2); }, [](Place const & p1, Place const & p2) { return p1.IsBetterThan(p2); }); return; } @@ -400,6 +424,22 @@ public: Emit(fb); } + void EmitCityBoundary(FeatureBuilder1 const & fb, FeatureParams const & params) override + { + if (!m_boundariesTable) + return; + + auto const type = GetPlaceType(params); + if (type == ftype::GetEmptyValue()) + return; + + auto const id = fb.GetLastOsmId(); + m_boundariesTable->Append(id, indexer::CityBoundary(fb.GetOuterGeometry())); + + Place const place(fb, type); + UnionEqualPlacesIds(place); + } + /// @return false if coasts are not merged and FLAG_fail_on_coasts is set bool Finish() override { diff --git a/generator/osm_source.hpp b/generator/osm_source.hpp index 6c530971ec..5c59180c20 100644 --- a/generator/osm_source.hpp +++ b/generator/osm_source.hpp @@ -34,6 +34,7 @@ public: }; class FeatureBuilder1; +class FeatureParams; // Emitter is used in OsmElemen to FeatureBuilder translation process. class EmitterBase @@ -43,6 +44,9 @@ public: /// This method is used by OsmTranslator to pass |fb| to Emitter for further processing. virtual void operator()(FeatureBuilder1 & fb) = 0; + + virtual void EmitCityBoundary(FeatureBuilder1 const & fb, FeatureParams const & params) = 0; + /// Finish is used in GenerateFeatureImpl to make whatever work is needed after /// all OmsElements are processed. virtual bool Finish() { return true; } diff --git a/generator/osm_translator.hpp b/generator/osm_translator.hpp index b6cc1a342c..5872fb1e84 100644 --- a/generator/osm_translator.hpp +++ b/generator/osm_translator.hpp @@ -324,6 +324,15 @@ class OsmToFeatureTranslator return true; } + bool IsCityBoundary(FeatureParams const & params) + { + feature::TypesHolder types; + for (auto const type : params.m_Types) + types.Add(type); + auto const type = ftypes::IsLocalityChecker::Instance().GetType(types); + return type == ftypes::CITY || type == ftypes::TOWN; + } + void EmitFeatureBase(FeatureBuilder1 & ft, FeatureParams const & params) const { ft.SetParams(params); @@ -363,7 +372,7 @@ class OsmToFeatureTranslator } template - void EmitArea(FeatureBuilder1 & ft, FeatureParams params, MakeFnT makeFn) const + void EmitArea(FeatureBuilder1 & ft, FeatureParams params, MakeFnT makeFn) { using namespace feature; @@ -374,6 +383,13 @@ class OsmToFeatureTranslator // Key point here is that IsDrawableLike and RemoveNoDrawableTypes // work a bit different for GEOM_AREA. + if (IsCityBoundary(params)) + { + auto fb = ft; + makeFn(fb); + m_emitter.EmitCityBoundary(fb, params); + } + if (IsDrawableLike(params.m_Types, GEOM_AREA)) { // Make the area feature if it has unique area styles. diff --git a/generator/routing_helpers.cpp b/generator/routing_helpers.cpp index 0d5376079a..cb30c859de 100644 --- a/generator/routing_helpers.cpp +++ b/generator/routing_helpers.cpp @@ -1,6 +1,6 @@ #include "generator/routing_helpers.hpp" -#include "generator/gen_mwm_info.hpp" +#include "generator/utils.hpp" #include "coding/file_reader.hpp" #include "coding/reader.hpp" @@ -15,25 +15,11 @@ namespace template bool ForEachRoadFromFile(string const & filename, ToDo && toDo) { - gen::OsmID2FeatureID osmIdsToFeatureIds; - try - { - FileReader reader(filename); - ReaderSource src(reader); - osmIdsToFeatureIds.Read(src); - } - catch (FileReader::Exception const & e) - { - LOG(LERROR, ("Exception while reading file:", filename, ". Msg:", e.Msg())); - return false; - } - - osmIdsToFeatureIds.ForEach([&](gen::OsmID2FeatureID::ValueT const & p) { - if (p.first.IsWay()) - toDo(p.second /* feature id */, p.first /* osm id */); - }); - - return true; + return generator::ForEachOsmId2FeatureId(filename, + [&](osm::Id const & osmId, uint32_t const featureId) { + if (osmId.IsWay()) + toDo(featureId, osmId); + }); } } // namespace diff --git a/generator/utils.hpp b/generator/utils.hpp index f00dba6e5c..db026718e7 100644 --- a/generator/utils.hpp +++ b/generator/utils.hpp @@ -1,8 +1,38 @@ #pragma once +#include "generator/gen_mwm_info.hpp" + #include "indexer/index.hpp" +#include "coding/file_reader.hpp" +#include "coding/reader.hpp" + +#include "base/logging.hpp" + namespace generator { void LoadIndex(Index & index); + +template +bool ForEachOsmId2FeatureId(string const & path, ToDo && toDo) +{ + gen::OsmID2FeatureID mapping; + try + { + FileReader reader(path); + NonOwningReaderSource source(reader); + mapping.Read(source); + } + catch (FileReader::Exception const & e) + { + LOG(LERROR, ("Exception while reading file:", path, ", message:", e.Msg())); + return false; + } + + mapping.ForEach([&](gen::OsmID2FeatureID::ValueT const & p) { + toDo(p.first /* osm id */, p.second /* feature id */); + }); + + return true; +} } // namespace generator diff --git a/geometry/diamond_box.hpp b/geometry/diamond_box.hpp index 341249c591..29aa5b8fcf 100644 --- a/geometry/diamond_box.hpp +++ b/geometry/diamond_box.hpp @@ -33,7 +33,7 @@ public: bool operator==(DiamondBox const & rhs) const { return m_box == rhs.m_box; } - DECLARE_VISITOR(visitor(Points(), "points")) + DECLARE_VISITOR(visitor(m_box, "box")) DECLARE_DEBUG_PRINT(DiamondBox) private: diff --git a/indexer/cities_boundaries_serdes.hpp b/indexer/cities_boundaries_serdes.hpp index 913802ddad..c19e46ac1e 100644 --- a/indexer/cities_boundaries_serdes.hpp +++ b/indexer/cities_boundaries_serdes.hpp @@ -1,30 +1,37 @@ #pragma once +#include "indexer/city_boundary.hpp" #include "indexer/coding_params.hpp" #include "indexer/geometry_coding.hpp" #include "coding/bit_streams.hpp" #include "coding/elias_coder.hpp" #include "coding/point_to_integer.hpp" +#include "coding/reader.hpp" #include "coding/varint.hpp" +#include "coding/write_to_sink.hpp" #include "geometry/bounding_box.hpp" #include "geometry/calipers_box.hpp" #include "geometry/diamond_box.hpp" +#include "geometry/mercator.hpp" #include "geometry/point2d.hpp" #include "base/assert.hpp" #include "base/logging.hpp" +#include "base/macros.hpp" +#include "base/visitor.hpp" #include #include #include +#include #include namespace indexer { template -class CityBoundaryEncoder +class CitiesBoundariesEncoder { public: struct Visitor @@ -39,7 +46,7 @@ public: void operator()(m2::PointU const & p) { - WriteVarUint(m_sink, EncodeDelta(p, m_last)); + WriteVarUint(m_sink, ::EncodeDelta(p, m_last)); m_last = p; } @@ -54,43 +61,50 @@ public: void operator()(m2::CalipersBox const & cbox) { - double const kEps = 1e-5; - auto ps = cbox.Points(); - auto us = ToU(ps); CHECK(!ps.empty(), ()); CHECK_LESS_OR_EQUAL(ps.size(), 4, ()); CHECK(ps.size() != 3, ()); - while (ps.size() != 4) + if (ps.size() == 1) { - auto const lp = ps.back(); - ps.push_back(lp); + auto const p0 = ps[0]; + while (ps.size() != 4) + ps.push_back(p0); + } + else if (ps.size() == 2) + { + auto const p0 = ps[0]; + auto const p1 = ps[1]; - auto const lu = us.back(); - us.push_back(lu); + ps.push_back(p1); + ps.push_back(p0); } ASSERT_EQUAL(ps.size(), 4, ()); - ASSERT_EQUAL(us.size(), 4, ()); - size_t pivot = ps.size(); + size_t bestCurr = ps.size(); + double bestLength = -1; for (size_t curr = 0; curr < ps.size(); ++curr) { size_t const next = (curr + 1) % ps.size(); - if (ps[next].x >= ps[curr].x - kEps && ps[next].y >= ps[curr].y - kEps) + + auto const length = ps[curr].Length(ps[next]); + if (length > bestLength) { - pivot = curr; - break; + bestCurr = curr; + bestLength = length; } } - CHECK(pivot != ps.size(), ()); - std::rotate(us.begin(), us.begin() + pivot, us.end()); + CHECK(bestCurr != ps.size(), ()); + std::rotate(ps.begin(), ps.begin() + bestCurr, ps.end()); + + auto const us = ToU(ps); (*this)(us[0]); - EncodePositiveDelta(us[0], us[1]); + EncodeDelta(us[0], us[1]); uint64_t const width = us[3].Length(us[0]); WriteVarUint(m_sink, width); @@ -131,6 +145,14 @@ public: return us; } + void EncodeDelta(m2::PointU const & curr, m2::PointU const & next) + { + auto const dx = static_cast(next.x) - static_cast(curr.x); + auto const dy = static_cast(next.y) - static_cast(curr.y); + WriteVarInt(m_sink, dx); + WriteVarInt(m_sink, dy); + } + void EncodePositiveDelta(m2::PointU const & curr, m2::PointU const & next) { ASSERT_GREATER_OR_EQUAL(next.x, curr.x, ()); @@ -149,7 +171,7 @@ public: m2::PointU m_last; }; - CityBoundaryEncoder(Sink & sink, serial::CodingParams const & params) + CitiesBoundariesEncoder(Sink & sink, serial::CodingParams const & params) : m_sink(sink), m_visitor(sink, params) { } @@ -162,8 +184,11 @@ public: BitWriter writer(m_sink); for (auto const & bs : boundaries) { - CHECK(!bs.empty(), ()); - coding::GammaCoder::Encode(writer, bs.size()); + CHECK_LESS(bs.size(), std::numeric_limits::max(), ()); + auto const success = + coding::GammaCoder::Encode(writer, static_cast(bs.size()) + 1); + ASSERT(success, ()); + UNUSED_VALUE(success); } } @@ -176,12 +201,11 @@ public: private: Sink & m_sink; - serial::CodingParams m_params; Visitor m_visitor; }; template -class CityBoundaryDecoder +class CitiesBoundariesDecoderV0 { public: struct Visitor @@ -201,7 +225,7 @@ public: void operator()(m2::PointU & p) { - p = DecodeDelta(ReadVarUint(m_source), m_last); + p = ::DecodeDelta(ReadVarUint(m_source), m_last); m_last = p; } @@ -223,7 +247,7 @@ public: std::vector points(4); points[0] = pivot; - points[1] = pivot + DecodePositiveDelta(); + points[1] = DecodeDelta(pivot); auto const width = ReadVarUint(m_source); @@ -273,6 +297,13 @@ public: return ps; } + m2::PointU DecodeDelta(m2::PointU const & base) + { + auto const dx = ReadVarInt(m_source); + auto const dy = ReadVarInt(m_source); + return m2::PointU(base.x + dx, base.y + dy); + } + m2::PointU DecodePositiveDelta() { auto const dx = ReadVarUint(m_source); @@ -285,7 +316,7 @@ public: m2::PointU m_last; }; - CityBoundaryDecoder(Source & source, serial::CodingParams const & params) + CitiesBoundariesDecoderV0(Source & source, serial::CodingParams const & params) : m_source(source), m_visitor(source, params) { } @@ -300,7 +331,8 @@ public: for (auto & bs : boundaries) { auto const size = coding::GammaCoder::Decode(reader); - bs.resize(size); + ASSERT_GREATER_OR_EQUAL(size, 1, ()); + bs.resize(size - 1); } } @@ -315,4 +347,100 @@ private: Source & m_source; Visitor m_visitor; }; + +struct CitiesBoundariesSerDes +{ + template + struct WriteToSinkVisitor + { + WriteToSinkVisitor(Sink & sink) : m_sink(sink) {} + + template + typename enable_if::value || is_enum::value, void>::type operator()( + T const & t, char const * /* name */ = nullptr) + { + WriteToSink(m_sink, t); + } + + template + typename enable_if::value && !is_enum::value, void>::type operator()( + T const & t, char const * /* name */ = nullptr) + { + t.Visit(*this); + } + + Sink & m_sink; + }; + + template + struct ReadFromSourceVisitor + { + ReadFromSourceVisitor(Source & source) : m_source(source) {} + + template + typename enable_if::value || is_enum::value, void>::type operator()( + T & t, char const * /* name */ = nullptr) + { + t = ReadPrimitiveFromSource(m_source); + } + + template + typename enable_if::value && !is_enum::value, void>::type operator()( + T & t, char const * /* name */ = nullptr) + { + t.Visit(*this); + } + + Source & m_source; + }; + + static uint8_t constexpr kLatestVersion = 0; + + struct HeaderV0 + { + static uint8_t const kDefaultCoordBits = 19; + + HeaderV0() {} + + DECLARE_VISITOR(visitor(m_coordBits, "coordBits")) + + uint8_t m_coordBits = kDefaultCoordBits; + }; + + template + static void Serialize(Sink & sink, std::vector> const & boundaries) + { + uint8_t const version = kLatestVersion; + + WriteToSinkVisitor visitor(sink); + visitor(version); + + HeaderV0 const header; + visitor(header); + + serial::CodingParams const params(header.m_coordBits, + m2::PointD(MercatorBounds::minX, MercatorBounds::minY)); + CitiesBoundariesEncoder encoder(sink, params); + encoder(boundaries); + } + + template + static void Deserialize(Source & source, std::vector> & boundaries) + { + ReadFromSourceVisitor visitor(source); + + uint8_t version; + visitor(version); + + CHECK_EQUAL(version, 0, ()); + + HeaderV0 header; + visitor(header); + + serial::CodingParams const params(header.m_coordBits, + m2::PointD(MercatorBounds::minX, MercatorBounds::minY)); + CitiesBoundariesDecoderV0 decoder(source, params); + decoder(boundaries); + } +}; } // namespace indexer diff --git a/indexer/indexer_tests/cities_boundaries_serdes_tests.cpp b/indexer/indexer_tests/cities_boundaries_serdes_tests.cpp index 7bf99d2a00..534044be3d 100644 --- a/indexer/indexer_tests/cities_boundaries_serdes_tests.cpp +++ b/indexer/indexer_tests/cities_boundaries_serdes_tests.cpp @@ -23,78 +23,88 @@ namespace using Boundary = vector; using Boundaries = vector; -void TestEqual(BoundingBox const & lhs, BoundingBox const & rhs, double eps) +static_assert(CitiesBoundariesSerDes::kLatestVersion == 0, ""); +static_assert(CitiesBoundariesSerDes::HeaderV0::kDefaultCoordBits == 19, ""); + +// Precision of mercator coords encoded with 19 bits. +double const kEps = 1e-3; + +void TestEqual(vector const & lhs, vector const & rhs) { - TEST(AlmostEqualAbs(lhs.Min(), rhs.Min(), eps), (lhs, rhs)); - TEST(AlmostEqualAbs(lhs.Max(), rhs.Max(), eps), (lhs, rhs)); + TEST_EQUAL(lhs.size(), rhs.size(), (lhs, rhs)); + for (size_t i = 0; i < lhs.size(); ++i) + TEST(AlmostEqualAbs(lhs[i], rhs[i], kEps), (lhs, rhs)); } -void TestEqual(CalipersBox const & lhs, CalipersBox const & rhs, double eps) {} +void TestEqual(BoundingBox const & lhs, BoundingBox const & rhs) +{ + TEST(AlmostEqualAbs(lhs.Min(), rhs.Min(), kEps), (lhs, rhs)); + TEST(AlmostEqualAbs(lhs.Max(), rhs.Max(), kEps), (lhs, rhs)); +} -void TestEqual(DiamondBox const & lhs, DiamondBox const & rhs, double eps) +void TestEqual(CalipersBox const & lhs, CalipersBox const & rhs) +{ + TestEqual(lhs.Points(), rhs.Points()); +} + +void TestEqual(DiamondBox const & lhs, DiamondBox const & rhs) { auto const lps = lhs.Points(); auto const rps = rhs.Points(); TEST_EQUAL(lps.size(), 4, (lhs)); TEST_EQUAL(rps.size(), 4, (rhs)); - for (size_t i = 0; i < 4; ++i) - TEST(AlmostEqualAbs(lps[i], rps[i], eps), (lhs, rhs)); + TestEqual(lps, rps); } -void TestEqual(CityBoundary const & lhs, CityBoundary const & rhs, double eps) +void TestEqual(CityBoundary const & lhs, CityBoundary const & rhs) { - TestEqual(lhs.m_bbox, rhs.m_bbox, eps); - TestEqual(lhs.m_cbox, rhs.m_cbox, eps); - TestEqual(lhs.m_dbox, rhs.m_dbox, eps); + TestEqual(lhs.m_bbox, rhs.m_bbox); + TestEqual(lhs.m_cbox, rhs.m_cbox); + TestEqual(lhs.m_dbox, rhs.m_dbox); } -void TestEqual(Boundary const & lhs, Boundary const & rhs, double eps) +void TestEqual(Boundary const & lhs, Boundary const & rhs) { TEST_EQUAL(lhs.size(), rhs.size(), (lhs, rhs)); for (size_t i = 0; i < lhs.size(); ++i) - TestEqual(lhs[i], rhs[i], eps); + TestEqual(lhs[i], rhs[i]); } -void TestEqual(Boundaries const & lhs, Boundaries const & rhs, double eps) +void TestEqual(Boundaries const & lhs, Boundaries const & rhs) { TEST_EQUAL(lhs.size(), rhs.size(), (lhs, rhs)); for (size_t i = 0; i < lhs.size(); ++i) - TestEqual(lhs[i], rhs[i], eps); + TestEqual(lhs[i], rhs[i]); } -Boundaries EncodeDecode(Boundaries const & boundaries, CodingParams const & params) +Boundaries EncodeDecode(Boundaries const & boundaries) { vector buffer; { MemWriter sink(buffer); - CityBoundaryEncoder encoder(sink, params); - encoder(boundaries); + CitiesBoundariesSerDes::Serialize(sink, boundaries); } { Boundaries boundaries; MemReader reader(buffer.data(), buffer.size()); NonOwningReaderSource source(reader); - CityBoundaryDecoder decoder(source, params); - decoder(boundaries); + CitiesBoundariesSerDes::Deserialize(source, boundaries); return boundaries; } } -void TestEncodeDecode(Boundaries const & expected, CodingParams const & params, double eps) +void TestEncodeDecode(Boundaries const & expected) { - Boundaries const actual = EncodeDecode(expected, params); - TestEqual(expected, actual, eps); + Boundaries const actual = EncodeDecode(expected); + TestEqual(expected, actual); } UNIT_TEST(CitiesBoundariesSerDes_Smoke) { - CodingParams const params(19 /* coordBits */, PointD(MercatorBounds::minX, MercatorBounds::minY)); - double const kEps = 1e-3; - { Boundaries const expected; - TestEncodeDecode(expected, params, kEps); + TestEncodeDecode(expected); } { @@ -107,7 +117,7 @@ UNIT_TEST(CitiesBoundariesSerDes_Smoke) vector{{PointD(1.000, 1.000), PointD(1.002, 1.000), PointD(1.002, 1.003)}}); Boundaries const expected = {{boundary0, boundary1}}; - TestEncodeDecode(expected, params, kEps); + TestEncodeDecode(expected); } } } // namespace