Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mapsme/omim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYuri Gorshenin <y@maps.me>2017-09-27 17:44:05 +0300
committermpimenov <mpimenov@users.noreply.github.com>2017-09-28 18:54:14 +0300
commite04d25505fb37abff651434ed4e29e7692af808f (patch)
tree72ff1216127379085f9141eec89165e09172896b
parent6737cd92217fdd9e02ee0e19eddc45156e7b104a (diff)
[indexer] Simplified boundaries encoding/decoding.
-rw-r--r--base/CMakeLists.txt1
-rw-r--r--base/base.pro1
-rw-r--r--base/visitor.hpp13
-rw-r--r--geometry/CMakeLists.txt1
-rw-r--r--geometry/bounding_box.cpp21
-rw-r--r--geometry/bounding_box.hpp32
-rw-r--r--geometry/calipers_box.cpp4
-rw-r--r--geometry/calipers_box.hpp12
-rw-r--r--geometry/diamond_box.cpp17
-rw-r--r--geometry/diamond_box.hpp27
-rw-r--r--geometry/geometry.pro1
-rw-r--r--geometry/point2d.hpp3
-rw-r--r--indexer/CMakeLists.txt2
-rw-r--r--indexer/boundary_boxes.hpp50
-rw-r--r--indexer/boundary_boxes_serdes.hpp318
-rw-r--r--indexer/indexer.pro2
-rw-r--r--indexer/indexer_tests/CMakeLists.txt1
-rw-r--r--indexer/indexer_tests/boundary_boxes_serdes_tests.cpp114
-rw-r--r--indexer/indexer_tests/indexer_tests.pro1
-rw-r--r--ugc/types.hpp14
20 files changed, 610 insertions, 25 deletions
diff --git a/base/CMakeLists.txt b/base/CMakeLists.txt
index f343fcb7dc..70f658e657 100644
--- a/base/CMakeLists.txt
+++ b/base/CMakeLists.txt
@@ -87,6 +87,7 @@ set(
timer.hpp
uni_string_dfa.cpp
uni_string_dfa.hpp
+ visitor.hpp
worker_thread.cpp
worker_thread.hpp
)
diff --git a/base/base.pro b/base/base.pro
index 5eece9bb1f..a963709b98 100644
--- a/base/base.pro
+++ b/base/base.pro
@@ -99,5 +99,6 @@ HEADERS += \
timegm.hpp \
timer.hpp \
uni_string_dfa.hpp \
+ visitor.hpp \
waiter.hpp \
worker_thread.hpp \
diff --git a/base/visitor.hpp b/base/visitor.hpp
new file mode 100644
index 0000000000..8f3f0f73a8
--- /dev/null
+++ b/base/visitor.hpp
@@ -0,0 +1,13 @@
+#pragma once
+
+#define DECLARE_VISITOR(...) \
+ template <typename Visitor> \
+ void Visit(Visitor & visitor) \
+ { \
+ __VA_ARGS__; \
+ } \
+ template <typename Visitor> \
+ void Visit(Visitor & visitor) const \
+ { \
+ __VA_ARGS__; \
+ }
diff --git a/geometry/CMakeLists.txt b/geometry/CMakeLists.txt
index 5985cef06e..7c42d28dde 100644
--- a/geometry/CMakeLists.txt
+++ b/geometry/CMakeLists.txt
@@ -19,6 +19,7 @@ set(
convex_hull.hpp
covering.hpp
covering_utils.hpp
+ diamond_box.cpp
diamond_box.hpp
distance.hpp
distance_on_sphere.cpp
diff --git a/geometry/bounding_box.cpp b/geometry/bounding_box.cpp
index 957f51ff6b..d968239e2f 100644
--- a/geometry/bounding_box.cpp
+++ b/geometry/bounding_box.cpp
@@ -1,6 +1,7 @@
#include "geometry/bounding_box.hpp"
#include <algorithm>
+#include <sstream>
using namespace std;
@@ -14,14 +15,24 @@ BoundingBox::BoundingBox(vector<PointD> const & points)
void BoundingBox::Add(double x, double y)
{
- m_minX = min(m_minX, x);
- m_minY = min(m_minY, y);
- m_maxX = max(m_maxX, x);
- m_maxY = max(m_maxY, y);
+ m_min.x = min(m_min.x, x);
+ m_min.y = min(m_min.y, y);
+ m_max.x = max(m_max.x, x);
+ m_max.y = max(m_max.y, y);
}
bool BoundingBox::HasPoint(double x, double y) const
{
- return x >= m_minX && x <= m_maxX && y >= m_minY && y <= m_maxY;
+ return x >= m_min.x && x <= m_max.x && y >= m_min.y && y <= m_max.y;
+}
+
+string DebugPrint(BoundingBox const & bbox)
+{
+ ostringstream os;
+ os << "BoundingBox [ ";
+ os << "min: " << DebugPrint(bbox.Min()) << ", ";
+ os << "max: " << DebugPrint(bbox.Max());
+ os << " ]";
+ return os.str();
}
} // namespace m2
diff --git a/geometry/bounding_box.hpp b/geometry/bounding_box.hpp
index a343e93093..2e9209cfab 100644
--- a/geometry/bounding_box.hpp
+++ b/geometry/bounding_box.hpp
@@ -2,7 +2,10 @@
#include "geometry/point2d.hpp"
+#include "base/visitor.hpp"
+
#include <limits>
+#include <string>
#include <vector>
namespace m2
@@ -19,17 +22,34 @@ public:
bool HasPoint(PointD const & p) const { return HasPoint(p.x, p.y); }
bool HasPoint(double x, double y) const;
- PointD Min() const { return PointD(m_minX, m_minY); }
- PointD Max() const { return PointD(m_maxX, m_maxY); }
+ PointD Min() const { return m_min; }
+ PointD Max() const { return m_max; }
+
+ std::vector<m2::PointD> Points() const
+ {
+ std::vector<m2::PointD> points(4);
+ points[0] = Min();
+ points[2] = Max();
+ points[1] = PointD(points[2].x, points[0].y);
+ points[3] = PointD(points[0].x, points[2].y);
+ return points;
+ }
+
+ bool operator==(BoundingBox const & rhs) const
+ {
+ return m_min == rhs.m_min && m_max == rhs.m_max;
+ }
+
+ DECLARE_VISITOR(visitor(m_min), visitor(m_max))
private:
static_assert(std::numeric_limits<double>::has_infinity, "");
static double constexpr kPositiveInfinity = std::numeric_limits<double>::infinity();
static double constexpr kNegativeInfinity = -kPositiveInfinity;
- double m_minX = kPositiveInfinity;
- double m_minY = kPositiveInfinity;
- double m_maxX = kNegativeInfinity;
- double m_maxY = kNegativeInfinity;
+ m2::PointD m_min = m2::PointD(kPositiveInfinity, kPositiveInfinity);
+ m2::PointD m_max = m2::PointD(kNegativeInfinity, kNegativeInfinity);
};
+
+std::string DebugPrint(BoundingBox const & bbox);
} // namespace m2
diff --git a/geometry/calipers_box.cpp b/geometry/calipers_box.cpp
index 046c81a89f..a5ad7fe27b 100644
--- a/geometry/calipers_box.cpp
+++ b/geometry/calipers_box.cpp
@@ -86,7 +86,7 @@ CalipersBox::CalipersBox(vector<PointD> const & points) : m_points({})
{
ConvexHull hull(points, kEps);
- if (hull.Size() < 4)
+ if (hull.Size() < 3)
{
m_points = hull.Points();
return;
@@ -147,4 +147,6 @@ bool CalipersBox::HasPoint(PointD const & p) const
}
return true;
}
+
+string DebugPrint(CalipersBox const & cbox) { return "CalipersBox " + ::DebugPrint(cbox.Points()); }
} // namespace m2
diff --git a/geometry/calipers_box.hpp b/geometry/calipers_box.hpp
index 19119ad147..6cf7299dc5 100644
--- a/geometry/calipers_box.hpp
+++ b/geometry/calipers_box.hpp
@@ -2,12 +2,15 @@
#include "geometry/point2d.hpp"
+#include "base/visitor.hpp"
+
+#include <string>
#include <vector>
namespace m2
{
// When the size of the convex hull over a set of |points| is less
-// than 4, stores convex hull explicitly, otherwise stores smallest
+// than 3, stores convex hull explicitly, otherwise stores smallest
// rectangle containing the hull. Note that at least one side of the
// rectangle should contain a facet of the hull, and in general sides
// of the rectangle may not be parallel to the axes. In any case,
@@ -15,6 +18,7 @@ namespace m2
class CalipersBox
{
public:
+ CalipersBox() = default;
CalipersBox(std::vector<PointD> const & points);
std::vector<PointD> const & Points() const { return m_points; }
@@ -22,7 +26,13 @@ public:
bool HasPoint(PointD const & p) const;
bool HasPoint(double x, double y) const { return HasPoint(PointD(x, y)); }
+ bool operator==(CalipersBox const & rhs) const { return m_points == rhs.m_points; }
+
+ DECLARE_VISITOR(visitor(m_points))
+
private:
std::vector<PointD> m_points;
};
+
+std::string DebugPrint(CalipersBox const & cbox);
} // namespace m2
diff --git a/geometry/diamond_box.cpp b/geometry/diamond_box.cpp
new file mode 100644
index 0000000000..07fc21e1a2
--- /dev/null
+++ b/geometry/diamond_box.cpp
@@ -0,0 +1,17 @@
+#include "geometry/diamond_box.hpp"
+
+using namespace std;
+
+namespace m2
+{
+DiamondBox::DiamondBox(vector<PointD> const & points)
+{
+ for (auto const & p : points)
+ Add(p);
+}
+
+string DebugPrint(DiamondBox const & dbox)
+{
+ return "DiamondBox [ " + ::DebugPrint(dbox.Points()) + " ]";
+}
+} // namespace m2
diff --git a/geometry/diamond_box.hpp b/geometry/diamond_box.hpp
index 5bbd4d2f6f..436eca4009 100644
--- a/geometry/diamond_box.hpp
+++ b/geometry/diamond_box.hpp
@@ -3,6 +3,11 @@
#include "geometry/bounding_box.hpp"
#include "geometry/point2d.hpp"
+#include "base/visitor.hpp"
+
+#include <string>
+#include <vector>
+
namespace m2
{
// Bounding box for a set of points on the plane, rotated by 45
@@ -10,13 +15,35 @@ namespace m2
class DiamondBox
{
public:
+ DiamondBox() = default;
+ DiamondBox(std::vector<PointD> const & points);
+
void Add(PointD const & p) { return Add(p.x, p.y); }
void Add(double x, double y) { return m_box.Add(x + y, x - y); }
bool HasPoint(PointD const & p) const { return HasPoint(p.x, p.y); }
bool HasPoint(double x, double y) const { return m_box.HasPoint(x + y, x - y); }
+ std::vector<m2::PointD> Points() const
+ {
+ auto points = m_box.Points();
+ for (auto & p : points)
+ p = ToOrig(p);
+ return points;
+ }
+
+ bool operator==(DiamondBox const & rhs) const { return m_box == rhs.m_box; }
+
+ DECLARE_VISITOR(visitor(m_box))
+
private:
+ static m2::PointD ToOrig(m2::PointD const & p)
+ {
+ return m2::PointD(0.5 * (p.x + p.y), 0.5 * (p.x - p.y));
+ }
+
BoundingBox m_box;
};
+
+std::string DebugPrint(DiamondBox const & dbox);
} // namespace m2
diff --git a/geometry/geometry.pro b/geometry/geometry.pro
index bf5bd72b54..fcef54da1b 100644
--- a/geometry/geometry.pro
+++ b/geometry/geometry.pro
@@ -15,6 +15,7 @@ SOURCES += \
calipers_box.cpp \
clipping.cpp \
convex_hull.cpp \
+ diamond_box.cpp \
distance_on_sphere.cpp \
latlon.cpp \
line2d.cpp \
diff --git a/geometry/point2d.hpp b/geometry/point2d.hpp
index 4eaffd1fc0..409e7936f9 100644
--- a/geometry/point2d.hpp
+++ b/geometry/point2d.hpp
@@ -175,6 +175,9 @@ namespace m2
y = sinAngle * oldX + cosAngle * y;
}
+ // Returns vector rotated 90 degrees counterclockwise.
+ Point Ort() const { return Point(-y, x); }
+
void Transform(m2::Point<T> const & org,
m2::Point<T> const & dx, m2::Point<T> const & dy)
{
diff --git a/indexer/CMakeLists.txt b/indexer/CMakeLists.txt
index 943505c0e1..4cba9f5bf4 100644
--- a/indexer/CMakeLists.txt
+++ b/indexer/CMakeLists.txt
@@ -6,6 +6,8 @@ set(
SRC
altitude_loader.cpp
altitude_loader.hpp
+ boundary_boxes.hpp
+ boundary_boxes_serdes.hpp
categories_holder_loader.cpp
categories_holder.cpp
categories_holder.hpp
diff --git a/indexer/boundary_boxes.hpp b/indexer/boundary_boxes.hpp
new file mode 100644
index 0000000000..381bddc821
--- /dev/null
+++ b/indexer/boundary_boxes.hpp
@@ -0,0 +1,50 @@
+#pragma once
+
+#include "geometry/bounding_box.hpp"
+#include "geometry/calipers_box.hpp"
+#include "geometry/diamond_box.hpp"
+#include "geometry/point2d.hpp"
+
+#include "base/visitor.hpp"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+namespace indexer
+{
+struct BoundaryBoxes
+{
+ BoundaryBoxes() = default;
+
+ BoundaryBoxes(std::vector<m2::PointD> const & ps) : m_bbox(ps), m_cbox(ps), m_dbox(ps) {}
+
+ bool HasPoint(m2::PointD const & p) const
+ {
+ return m_bbox.HasPoint(p) && m_dbox.HasPoint(p) && m_cbox.HasPoint(p);
+ }
+
+ bool HasPoint(double x, double y) const { return HasPoint(m2::PointD(x, y)); }
+
+ bool operator==(BoundaryBoxes const & rhs) const
+ {
+ return m_bbox == rhs.m_bbox && m_cbox == rhs.m_cbox && m_dbox == rhs.m_dbox;
+ }
+
+ DECLARE_VISITOR(visitor(m_bbox), visitor(m_cbox), visitor(m_dbox))
+
+ m2::BoundingBox m_bbox;
+ m2::CalipersBox m_cbox;
+ m2::DiamondBox m_dbox;
+};
+
+inline std::string DebugPrint(BoundaryBoxes const & boxes)
+{
+ std::ostringstream os;
+ os << "BoundaryBoxes [ ";
+ os << DebugPrint(boxes.m_bbox) << ", ";
+ os << DebugPrint(boxes.m_cbox) << ", ";
+ os << DebugPrint(boxes.m_dbox) << " ]";
+ return os.str();
+}
+} // namespace indexer
diff --git a/indexer/boundary_boxes_serdes.hpp b/indexer/boundary_boxes_serdes.hpp
new file mode 100644
index 0000000000..3158ba6a02
--- /dev/null
+++ b/indexer/boundary_boxes_serdes.hpp
@@ -0,0 +1,318 @@
+#pragma once
+
+#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/varint.hpp"
+
+#include "geometry/bounding_box.hpp"
+#include "geometry/calipers_box.hpp"
+#include "geometry/diamond_box.hpp"
+#include "geometry/point2d.hpp"
+
+#include "base/assert.hpp"
+#include "base/logging.hpp"
+
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <vector>
+
+namespace indexer
+{
+template <typename Sink>
+class BoundaryBoxesEncoder
+{
+public:
+ struct Visitor
+ {
+ public:
+ Visitor(Sink & sink, serial::CodingParams const & params)
+ : m_sink(sink), m_params(params), m_last(params.GetBasePoint())
+ {
+ }
+
+ void operator()(m2::PointD const & p) { return (*this)(ToU(p)); }
+
+ void operator()(m2::PointU const & p)
+ {
+ WriteVarUint(m_sink, EncodeDelta(p, m_last));
+ m_last = p;
+ }
+
+ void operator()(m2::BoundingBox const & bbox)
+ {
+ auto const min = ToU(bbox.Min());
+ auto const max = ToU(bbox.Max());
+
+ (*this)(min);
+ EncodePositiveDelta(min, max);
+ }
+
+ 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)
+ {
+ auto const lp = ps.back();
+ ps.push_back(lp);
+
+ auto const lu = us.back();
+ us.push_back(lu);
+ }
+
+ ASSERT_EQUAL(ps.size(), 4, ());
+ ASSERT_EQUAL(us.size(), 4, ());
+
+ size_t pivot = ps.size();
+ 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)
+ {
+ pivot = curr;
+ break;
+ }
+ }
+
+ CHECK(pivot != ps.size(), ());
+ std::rotate(us.begin(), us.begin() + pivot, us.end());
+
+ (*this)(us[0]);
+ EncodePositiveDelta(us[0], us[1]);
+
+ uint64_t const width = us[3].Length(us[0]);
+ WriteVarUint(m_sink, width);
+ }
+
+ void operator()(m2::DiamondBox const & dbox)
+ {
+ auto const ps = ToU(dbox.Points());
+ auto const base = ps[0];
+ auto const next = ps[1];
+ auto const prev = ps[3];
+
+ (*this)(base);
+
+ ASSERT_GREATER_OR_EQUAL(next.x, base.x, ());
+ ASSERT_GREATER_OR_EQUAL(next.y, base.y, ());
+ WriteVarUint(m_sink, next.x >= base.x ? next.x - base.x : 0);
+
+ ASSERT_GREATER_OR_EQUAL(prev.x, base.x, ());
+ ASSERT_LESS_OR_EQUAL(prev.y, base.y, ());
+ WriteVarUint(m_sink, prev.x >= base.x ? prev.x - base.x : 0);
+ }
+
+ template <typename R>
+ void operator()(R const & r)
+ {
+ r.Visit(*this);
+ }
+
+ private:
+ m2::PointU ToU(m2::PointD const & p) const { return PointD2PointU(p, m_params.GetCoordBits()); }
+
+ std::vector<m2::PointU> ToU(std::vector<m2::PointD> const & ps) const
+ {
+ std::vector<m2::PointU> us(ps.size());
+ for (size_t i = 0; i < ps.size(); ++i)
+ us[i] = ToU(ps[i]);
+ return us;
+ }
+
+ void EncodePositiveDelta(m2::PointU const & curr, m2::PointU const & next)
+ {
+ ASSERT_GREATER_OR_EQUAL(next.x, curr.x, ());
+ ASSERT_GREATER_OR_EQUAL(next.y, curr.y, ());
+
+ // Paranoid checks due to possible floating point artifacts
+ // here. In general, next.x >= curr.x and next.y >= curr.y.
+ auto const dx = next.x >= curr.x ? next.x - curr.x : 0;
+ auto const dy = next.y >= curr.y ? next.y - curr.y : 0;
+ WriteVarUint(m_sink, dx);
+ WriteVarUint(m_sink, dy);
+ }
+
+ Sink & m_sink;
+ serial::CodingParams m_params;
+ m2::PointU m_last;
+ };
+
+ BoundaryBoxesEncoder(Sink & sink, serial::CodingParams const & params)
+ : m_sink(sink), m_visitor(sink, params)
+ {
+ }
+
+ void operator()(std::vector<std::vector<BoundaryBoxes>> const & boxes)
+ {
+ WriteVarUint(m_sink, boxes.size());
+
+ {
+ BitWriter<Sink> writer(m_sink);
+ for (auto const & bs : boxes)
+ {
+ CHECK(!bs.empty(), ());
+ coding::GammaCoder::Encode(writer, bs.size());
+ }
+ }
+
+ for (auto const & bs : boxes)
+ {
+ for (auto const & b : bs)
+ m_visitor(b);
+ }
+ }
+
+private:
+ Sink & m_sink;
+ serial::CodingParams m_params;
+ Visitor m_visitor;
+};
+
+template <typename Source>
+class BoundaryBoxesDecoder
+{
+public:
+ struct Visitor
+ {
+ public:
+ Visitor(Source & source, serial::CodingParams const & params)
+ : m_source(source), m_params(params), m_last(params.GetBasePoint())
+ {
+ }
+
+ void operator()(m2::PointD & p)
+ {
+ m2::PointU u;
+ (*this)(u);
+ p = FromU(u);
+ }
+
+ void operator()(m2::PointU & p)
+ {
+ p = DecodeDelta(ReadVarUint<uint64_t>(m_source), m_last);
+ m_last = p;
+ }
+
+ void operator()(m2::BoundingBox & bbox)
+ {
+ m2::PointU min;
+ (*this)(min);
+ auto const max = min + DecodePositiveDelta();
+
+ bbox = m2::BoundingBox();
+ bbox.Add(FromU(min));
+ bbox.Add(FromU(max));
+ }
+
+ void operator()(m2::CalipersBox & cbox)
+ {
+ m2::PointU pivot;
+ (*this)(pivot);
+
+ std::vector<m2::PointU> points(4);
+ points[0] = pivot;
+ points[1] = pivot + DecodePositiveDelta();
+
+ auto const width = ReadVarUint<uint64_t>(m_source);
+
+ auto r01 = m2::PointD(points[1] - points[0]);
+ if (!r01.IsAlmostZero())
+ r01 = r01.Normalize();
+ auto const r21 = r01.Ort() * width;
+
+ points[2] = points[1] + r21;
+ points[3] = points[0] + r21;
+
+ cbox = m2::CalipersBox(FromU(points));
+ }
+
+ void operator()(m2::DiamondBox & dbox)
+ {
+ m2::PointU base;
+ (*this)(base);
+
+ auto const nx = ReadVarUint<uint32_t>(m_source);
+ auto const px = ReadVarUint<uint32_t>(m_source);
+
+ dbox = m2::DiamondBox();
+ dbox.Add(FromU(base));
+ dbox.Add(FromU(base + m2::PointU(nx, nx)));
+ dbox.Add(FromU(base + m2::PointU(px, -px)));
+ dbox.Add(FromU(base + m2::PointU(nx + px, nx - px)));
+ }
+
+ template <typename R>
+ void operator()(R & r)
+ {
+ r.Visit(*this);
+ }
+
+ private:
+ m2::PointD FromU(m2::PointU const & u) const
+ {
+ return PointU2PointD(u, m_params.GetCoordBits());
+ }
+
+ std::vector<m2::PointD> FromU(std::vector<m2::PointU> const & us) const
+ {
+ std::vector<m2::PointD> ps(us.size());
+ for (size_t i = 0; i < us.size(); ++i)
+ ps[i] = FromU(us[i]);
+ return ps;
+ }
+
+ m2::PointU DecodePositiveDelta()
+ {
+ auto const dx = ReadVarUint<uint32_t>(m_source);
+ auto const dy = ReadVarUint<uint32_t>(m_source);
+ return m2::PointU(static_cast<uint32_t>(dx), static_cast<uint32_t>(dy));
+ }
+
+ Source & m_source;
+ serial::CodingParams const m_params;
+ m2::PointU m_last;
+ };
+
+ BoundaryBoxesDecoder(Source & source, serial::CodingParams const & params)
+ : m_source(source), m_visitor(source, params)
+ {
+ }
+
+ void operator()(std::vector<std::vector<BoundaryBoxes>> & boxes)
+ {
+ auto const size = ReadVarUint<uint64_t>(m_source);
+ boxes.resize(size);
+
+ {
+ BitReader<Source> reader(m_source);
+ for (auto & bs : boxes)
+ {
+ auto const size = coding::GammaCoder::Decode(reader);
+ bs.resize(size);
+ }
+ }
+
+ for (auto & bs : boxes)
+ {
+ for (auto & b : bs)
+ m_visitor(b);
+ }
+ }
+
+private:
+ Source & m_source;
+ Visitor m_visitor;
+};
+} // namespace indexer
diff --git a/indexer/indexer.pro b/indexer/indexer.pro
index eaf272d32f..03a3d6fea8 100644
--- a/indexer/indexer.pro
+++ b/indexer/indexer.pro
@@ -64,6 +64,8 @@ SOURCES += \
HEADERS += \
altitude_loader.hpp \
+ boundary_boxes.hpp \
+ boundary_boxes_serdes.hpp \
categories_holder.hpp \
categories_index.hpp \
cell_coverer.hpp \
diff --git a/indexer/indexer_tests/CMakeLists.txt b/indexer/indexer_tests/CMakeLists.txt
index ffadc3d332..43e299568e 100644
--- a/indexer/indexer_tests/CMakeLists.txt
+++ b/indexer/indexer_tests/CMakeLists.txt
@@ -2,6 +2,7 @@ project(indexer_tests)
set(
SRC
+ boundary_boxes_serdes_tests.cpp
categories_test.cpp
cell_coverer_test.cpp
cell_id_test.cpp
diff --git a/indexer/indexer_tests/boundary_boxes_serdes_tests.cpp b/indexer/indexer_tests/boundary_boxes_serdes_tests.cpp
new file mode 100644
index 0000000000..1a5511d5bf
--- /dev/null
+++ b/indexer/indexer_tests/boundary_boxes_serdes_tests.cpp
@@ -0,0 +1,114 @@
+#include "testing/testing.hpp"
+
+#include "indexer/boundary_boxes.hpp"
+#include "indexer/boundary_boxes_serdes.hpp"
+#include "indexer/coding_params.hpp"
+
+#include "coding/reader.hpp"
+#include "coding/writer.hpp"
+
+#include "geometry/mercator.hpp"
+#include "geometry/point2d.hpp"
+
+#include <cstdint>
+#include <vector>
+
+using namespace indexer;
+using namespace m2;
+using namespace serial;
+using namespace std;
+
+namespace
+{
+using Boundary = vector<BoundaryBoxes>;
+using Boundaries = vector<Boundary>;
+
+void TestEqual(BoundingBox const & lhs, BoundingBox const & rhs, double eps)
+{
+ TEST(AlmostEqualAbs(lhs.Min(), rhs.Min(), eps), (lhs, rhs));
+ TEST(AlmostEqualAbs(lhs.Max(), rhs.Max(), eps), (lhs, rhs));
+}
+
+void TestEqual(CalipersBox const & lhs, CalipersBox const & rhs, double eps) {}
+void TestEqual(DiamondBox const & lhs, DiamondBox const & rhs, double eps)
+{
+ 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));
+ }
+}
+
+void TestEqual(BoundaryBoxes const & lhs, BoundaryBoxes const & rhs, double eps)
+{
+ TestEqual(lhs.m_bbox, rhs.m_bbox, eps);
+ TestEqual(lhs.m_cbox, rhs.m_cbox, eps);
+ TestEqual(lhs.m_dbox, rhs.m_dbox, eps);
+}
+
+void TestEqual(Boundary const & lhs, Boundary const & rhs, double eps)
+{
+ TEST_EQUAL(lhs.size(), rhs.size(), (lhs, rhs));
+ for (size_t i = 0; i < lhs.size(); ++i)
+ TestEqual(lhs[i], rhs[i], eps);
+}
+
+void TestEqual(Boundaries const & lhs, Boundaries const & rhs, double eps)
+{
+ TEST_EQUAL(lhs.size(), rhs.size(), (lhs, rhs));
+ for (size_t i = 0; i < lhs.size(); ++i)
+ TestEqual(lhs[i], rhs[i], eps);
+}
+
+Boundaries EncodeDecode(Boundaries const & boundaries, CodingParams const & params)
+{
+ vector<uint8_t> buffer;
+ {
+ MemWriter<decltype(buffer)> sink(buffer);
+ BoundaryBoxesEncoder<decltype(sink)> encoder(sink, params);
+ encoder(boundaries);
+ }
+
+ {
+ Boundaries boundaries;
+ MemReader reader(buffer.data(), buffer.size());
+ NonOwningReaderSource source(reader);
+ BoundaryBoxesDecoder<decltype(source)> decoder(source, params);
+ decoder(boundaries);
+ return boundaries;
+ }
+}
+
+void TestEncodeDecode(Boundaries const & expected, CodingParams const & params, double eps)
+{
+ Boundaries const actual = EncodeDecode(expected, params);
+ TestEqual(expected, actual, eps);
+}
+
+UNIT_TEST(BoundaryBoxesSerDes_Smoke)
+{
+ CodingParams const params(19 /* coordBits */, PointD(MercatorBounds::minX, MercatorBounds::minY));
+ double const kEps = 1e-3;
+
+ {
+ Boundaries const expected;
+ TestEncodeDecode(expected, params, kEps);
+ }
+
+ {
+ Boundary boundary0;
+ boundary0.emplace_back(vector<PointD>{{PointD(0.1234, 5.6789)}});
+ boundary0.emplace_back(vector<PointD>{{PointD(3.1415, 2.1828), PointD(2.1828, 3.1415)}});
+
+ Boundary boundary1;
+ boundary1.emplace_back(
+ vector<PointD>{{PointD(1.000, 1.000), PointD(1.002, 1.000), PointD(1.002, 1.003)}});
+
+ Boundaries const expected = {{boundary0, boundary1}};
+ TestEncodeDecode(expected, params, kEps);
+ }
+}
+} // namespace
diff --git a/indexer/indexer_tests/indexer_tests.pro b/indexer/indexer_tests/indexer_tests.pro
index 78d6fe1d5f..1220c484c2 100644
--- a/indexer/indexer_tests/indexer_tests.pro
+++ b/indexer/indexer_tests/indexer_tests.pro
@@ -28,6 +28,7 @@ HEADERS += \
SOURCES += \
../../testing/testingmain.cpp \
+ boundary_boxes_serdes_tests.cpp \
categories_test.cpp \
cell_coverer_test.cpp \
cell_id_test.cpp \
diff --git a/ugc/types.hpp b/ugc/types.hpp
index 5e26c6cd73..e5beb77031 100644
--- a/ugc/types.hpp
+++ b/ugc/types.hpp
@@ -4,6 +4,8 @@
#include "coding/hex.hpp"
+#include "base/visitor.hpp"
+
#include <chrono>
#include <cstdint>
#include <memory>
@@ -12,18 +14,6 @@
#include <string>
#include <vector>
-#define DECLARE_VISITOR(...) \
- template <typename Visitor> \
- void Visit(Visitor & visitor) \
- { \
- __VA_ARGS__; \
- } \
- template <typename Visitor> \
- void Visit(Visitor & visitor) const \
- { \
- __VA_ARGS__; \
- }
-
namespace ugc
{
using Clock = std::chrono::system_clock;