forked from organicmaps/organicmaps-tmp
Implement EncodePolyline() and DecodePolyline() + unit tests.
This commit is contained in:
parent
490f5c3dff
commit
f3da0c3ca1
4 changed files with 402 additions and 8 deletions
|
@ -1,2 +1,233 @@
|
|||
#include "geometry_coding.hpp"
|
||||
#include "../coding/byte_stream.hpp"
|
||||
#include "../coding/varint.hpp"
|
||||
#include "../base/assert.hpp"
|
||||
#include "../base/bits.hpp"
|
||||
#include "../base/stl_add.hpp"
|
||||
#include "../std/complex.hpp"
|
||||
#include "../std/vector.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
inline uint64_t EncodeDelta(m2::PointU const & actual, m2::PointU const & prediction)
|
||||
{
|
||||
return bits::BitwiseMerge(
|
||||
bits::ZigZagEncode(static_cast<int32_t>(actual.x) - static_cast<int32_t>(prediction.x)),
|
||||
bits::ZigZagEncode(static_cast<int32_t>(actual.y) - static_cast<int32_t>(prediction.y)));
|
||||
}
|
||||
|
||||
inline m2::PointU DecodeDelta(uint64_t delta, m2::PointU const & prediction)
|
||||
{
|
||||
uint32_t x, y;
|
||||
bits::BitwiseSplit(delta, x, y);
|
||||
return m2::PointU(prediction.x + bits::ZigZagDecode(x), prediction.y + bits::ZigZagDecode(y));
|
||||
}
|
||||
|
||||
inline void EncodeVarUints(vector<uint64_t> const & varints, vector<char> & serialOutput)
|
||||
{
|
||||
PushBackByteSink<vector<char> > sink(serialOutput);
|
||||
for (vector<uint64_t>::const_iterator it = varints.begin(); it != varints.end(); ++it)
|
||||
WriteVarUint(sink, *it);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline m2::PointU ClampPoint(m2::PointU const & maxPoint, m2::Point<T> point)
|
||||
{
|
||||
return m2::PointU(
|
||||
point.x < 0 ? 0 : (point.x < maxPoint.x ? static_cast<uint32_t>(point.x) : maxPoint.x),
|
||||
point.y < 0 ? 0 : (point.y < maxPoint.y ? static_cast<uint32_t>(point.y) : maxPoint.y));
|
||||
}
|
||||
|
||||
bool TestDecoding(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput,
|
||||
void (* fnDecode)(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points))
|
||||
{
|
||||
vector<m2::PointU> decoded;
|
||||
decoded.reserve(points.size());
|
||||
fnDecode(&serialOutput[0], &serialOutput[0] + serialOutput.size(), basePoint, maxPoint, decoded);
|
||||
ASSERT_EQUAL(points, decoded, (basePoint, maxPoint));
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
m2::PointU PredictPointInPolyline(m2::PointU const & maxPoint,
|
||||
m2::PointU const & p1,
|
||||
m2::PointU const & p2)
|
||||
{
|
||||
return ClampPoint(maxPoint, m2::PointI64(p1) + m2::PointI64(p1) - m2::PointI64(p2));
|
||||
}
|
||||
|
||||
m2::PointU PredictPointInPolyline(m2::PointU const & maxPoint,
|
||||
m2::PointU const & p1,
|
||||
m2::PointU const & p2,
|
||||
m2::PointU const & p3)
|
||||
{
|
||||
CHECK_NOT_EQUAL(p2, p3, ());
|
||||
|
||||
// In complex numbers:
|
||||
// Ci = Ci-1 + (Ci-1 - Ci-2) * (Ci-1 - Ci-2) / (Ci-2 - Ci-3)
|
||||
complex<double> const c1(p1.x, p1.y);
|
||||
complex<double> const c2(p2.x, p2.y);
|
||||
complex<double> const c3(p3.x, p3.y);
|
||||
complex<double> const c0 = c1 + (c1 - c2) * (c1 - c2) / (c2 - c3);
|
||||
return ClampPoint(maxPoint, m2::PointD(c0.real(), c0.imag()));
|
||||
}
|
||||
|
||||
void EncodePolylinePrev1(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & /*maxPoint*/,
|
||||
vector<char> & serialOutput)
|
||||
{
|
||||
vector<uint64_t> deltas;
|
||||
deltas.reserve(points.size());
|
||||
if (points.size() > 0)
|
||||
{
|
||||
deltas.push_back(EncodeDelta(points[0], basePoint));
|
||||
for (size_t i = 1; i < points.size(); ++i)
|
||||
deltas.push_back(EncodeDelta(points[i], points[i-1]));
|
||||
}
|
||||
|
||||
EncodeVarUints(deltas, serialOutput);
|
||||
ASSERT(TestDecoding(points, basePoint, m2::PointU(), serialOutput, &DecodePolylinePrev1), ());
|
||||
}
|
||||
|
||||
void DecodePolylinePrev1(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & /*maxPoint*/,
|
||||
vector<m2::PointU> & points)
|
||||
{
|
||||
vector<uint64_t> deltas;
|
||||
ReadVarUint64Array(pBeg, pEnd, MakeBackInsertFunctor(deltas));
|
||||
points.reserve(points.size() + deltas.size());
|
||||
|
||||
if (deltas.size() > 0)
|
||||
{
|
||||
points.push_back(DecodeDelta(deltas[0], basePoint));
|
||||
for (size_t i = 1; i < deltas.size(); ++i)
|
||||
points.push_back(DecodeDelta(deltas[i], points.back()));
|
||||
}
|
||||
}
|
||||
|
||||
void EncodePolylinePrev2(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput)
|
||||
{
|
||||
vector<uint64_t> deltas;
|
||||
deltas.reserve(points.size());
|
||||
if (points.size() > 0)
|
||||
{
|
||||
deltas.push_back(EncodeDelta(points[0], basePoint));
|
||||
if (points.size() > 1)
|
||||
{
|
||||
deltas.push_back(EncodeDelta(points[1], points[0]));
|
||||
for (size_t i = 2; i < points.size(); ++i)
|
||||
deltas.push_back(EncodeDelta(points[i],
|
||||
PredictPointInPolyline(maxPoint, points[i-1], points[i-2])));
|
||||
}
|
||||
}
|
||||
|
||||
EncodeVarUints(deltas, serialOutput);
|
||||
ASSERT(TestDecoding(points, basePoint, maxPoint, serialOutput, &DecodePolylinePrev2), ());
|
||||
}
|
||||
|
||||
void DecodePolylinePrev2(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points)
|
||||
{
|
||||
vector<uint64_t> deltas;
|
||||
ReadVarUint64Array(pBeg, pEnd, MakeBackInsertFunctor(deltas));
|
||||
points.reserve(points.size() + deltas.size());
|
||||
|
||||
if (deltas.size() > 0)
|
||||
{
|
||||
points.push_back(DecodeDelta(deltas[0], basePoint));
|
||||
if (deltas.size() > 1)
|
||||
{
|
||||
points.push_back(DecodeDelta(deltas[1], points.back()));
|
||||
for (size_t i = 2; i < deltas.size(); ++i)
|
||||
{
|
||||
size_t const n = points.size();
|
||||
points.push_back(DecodeDelta(deltas[i],
|
||||
PredictPointInPolyline(maxPoint, points[n-1], points[n-2])));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void EncodePolylinePrev3(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput)
|
||||
{
|
||||
ASSERT_LESS_OR_EQUAL(basePoint.x, maxPoint.x, (basePoint, maxPoint));
|
||||
ASSERT_LESS_OR_EQUAL(basePoint.y, maxPoint.y, (basePoint, maxPoint));
|
||||
|
||||
vector<uint64_t> deltas;
|
||||
deltas.reserve(points.size());
|
||||
if (points.size() > 0)
|
||||
{
|
||||
deltas.push_back(EncodeDelta(points[0], basePoint));
|
||||
if (points.size() > 1)
|
||||
{
|
||||
deltas.push_back(EncodeDelta(points[1], points[0]));
|
||||
if (points.size() > 2)
|
||||
{
|
||||
m2::PointU const prediction = PredictPointInPolyline(maxPoint, points[1], points[0]);
|
||||
deltas.push_back(EncodeDelta(points[2], prediction));
|
||||
for (size_t i = 3; i < points.size(); ++i)
|
||||
{
|
||||
m2::PointU const prediction =
|
||||
PredictPointInPolyline(maxPoint, points[i-1], points[i-2], points[i-3]);
|
||||
deltas.push_back(EncodeDelta(points[i], prediction));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EncodeVarUints(deltas, serialOutput);
|
||||
ASSERT(TestDecoding(points, basePoint, maxPoint, serialOutput, &DecodePolylinePrev3), ());
|
||||
}
|
||||
|
||||
void DecodePolylinePrev3(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points)
|
||||
{
|
||||
ASSERT_LESS_OR_EQUAL(basePoint.x, maxPoint.x, (basePoint, maxPoint));
|
||||
ASSERT_LESS_OR_EQUAL(basePoint.y, maxPoint.y, (basePoint, maxPoint));
|
||||
|
||||
vector<uint64_t> deltas;
|
||||
ReadVarUint64Array(pBeg, pEnd, MakeBackInsertFunctor(deltas));
|
||||
points.reserve(points.size() + deltas.size());
|
||||
|
||||
if (deltas.size() > 0)
|
||||
{
|
||||
points.push_back(DecodeDelta(deltas[0], basePoint));
|
||||
if (deltas.size() > 1)
|
||||
{
|
||||
m2::PointU const pt0 = points.back();
|
||||
points.push_back(DecodeDelta(deltas[1], pt0));
|
||||
if (deltas.size() > 2)
|
||||
{
|
||||
points.push_back(DecodeDelta(deltas[2],
|
||||
PredictPointInPolyline(maxPoint, points.back(), pt0)));
|
||||
for (size_t i = 3; i < deltas.size(); ++i)
|
||||
{
|
||||
size_t const n = points.size();
|
||||
m2::PointU const prediction =
|
||||
PredictPointInPolyline(maxPoint, points[n-1], points[n-2], points[n-3]);
|
||||
points.push_back(DecodeDelta(deltas[i], prediction));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,15 +5,63 @@
|
|||
#include "../std/vector.hpp"
|
||||
#include "../std/tuple.hpp"
|
||||
|
||||
void EncodePolyline(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput);
|
||||
// Predict point p0 given previous (p1, p2).
|
||||
m2::PointU PredictPointInPolyline(m2::PointU const & maxPoint,
|
||||
m2::PointU const & p1,
|
||||
m2::PointU const & p2);
|
||||
|
||||
void DecodePolyline(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points);
|
||||
// Predict point p0 given previous (p1, p2, p3).
|
||||
m2::PointU PredictPointInPolyline(m2::PointU const & maxPoint,
|
||||
m2::PointU const & p1,
|
||||
m2::PointU const & p2,
|
||||
m2::PointU const & p3);
|
||||
|
||||
|
||||
void EncodePolylinePrev1(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput);
|
||||
|
||||
void DecodePolylinePrev1(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points);
|
||||
|
||||
void EncodePolylinePrev2(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput);
|
||||
|
||||
void DecodePolylinePrev2(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points);
|
||||
|
||||
void EncodePolylinePrev3(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput);
|
||||
|
||||
void DecodePolylinePrev3(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points);
|
||||
|
||||
inline void EncodePolyline(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput)
|
||||
{
|
||||
EncodePolylinePrev3(points, basePoint, maxPoint, serialOutput);
|
||||
}
|
||||
|
||||
inline void DecodePolyline(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points)
|
||||
{
|
||||
DecodePolylinePrev3(pBeg, pEnd, basePoint, maxPoint, points);
|
||||
}
|
||||
|
||||
void EncodeTriangles(vector<m2::PointU> const & points,
|
||||
vector<tuple<uint32_t, uint32_t, uint32_t> > const & triangles,
|
||||
|
|
114
indexer/indexer_tests/geometry_coding_test.cpp
Normal file
114
indexer/indexer_tests/geometry_coding_test.cpp
Normal file
|
@ -0,0 +1,114 @@
|
|||
#include "../geometry_coding.hpp"
|
||||
#include "../../testing/testing.hpp"
|
||||
#include "../../geometry/geometry_tests/large_polygon.hpp"
|
||||
#include "../../geometry/distance.hpp"
|
||||
#include "../../geometry/simplification.hpp"
|
||||
#include "../../base/logging.hpp"
|
||||
|
||||
typedef m2::PointU PU;
|
||||
|
||||
UNIT_TEST(PredictPointsInPolyline2)
|
||||
{
|
||||
TEST_EQUAL(PU(7, 6), PredictPointInPolyline(PU(8, 7), PU(4, 4), PU(1, 2)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PredictPointsInPolyline2_ClampMax)
|
||||
{
|
||||
TEST_EQUAL(PU(6, 6), PredictPointInPolyline(PU(6, 7), PU(4, 4), PU(1, 2)), ());
|
||||
TEST_EQUAL(PU(7, 6), PredictPointInPolyline(PU(8, 7), PU(4, 4), PU(1, 2)), ());
|
||||
TEST_EQUAL(PU(5, 5), PredictPointInPolyline(PU(5, 5), PU(4, 4), PU(1, 2)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PredictPointsInPolyline2_Clamp0)
|
||||
{
|
||||
TEST_EQUAL(PU(4, 0), PredictPointInPolyline(PU(5, 5), PU(4, 1), PU(4, 4)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PredictPointsInPolyline3_Square)
|
||||
{
|
||||
TEST_EQUAL(PU(5, 1), PredictPointInPolyline(PU(6, 6), PU(5, 4), PU(2, 4), PU(2, 1)), ());
|
||||
TEST_EQUAL(PU(5, 3), PredictPointInPolyline(PU(6, 6), PU(4, 1), PU(2, 2), PU(3, 4)), ());
|
||||
}
|
||||
|
||||
UNIT_TEST(PredictPointsInPolyline3_SquareClamp0)
|
||||
{
|
||||
TEST_EQUAL(PU(5, 1), PredictPointInPolyline(PU(6, 6), PU(5, 4), PU(2, 4), PU(2, 1)), ());
|
||||
TEST_EQUAL(PU(4, 0), PredictPointInPolyline(PU(6, 6), PU(2, 0), PU(3, 2), PU(5, 1)), ());
|
||||
}
|
||||
|
||||
|
||||
UNIT_TEST(PredictPointsInPolyline3_90deg)
|
||||
{
|
||||
TEST_EQUAL(PU(3, 2), PredictPointInPolyline(PU(8, 8), PU(3, 6), PU(1, 6), PU(1, 5)), ());
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void TestPolylineEncode(char const * testName,
|
||||
vector<m2::PointU> const & points,
|
||||
m2::PointU const & maxPoint,
|
||||
void (* fnEncode)(vector<m2::PointU> const & points,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<char> & serialOutput),
|
||||
void (* fnDecode)(char const * pBeg, char const * pEnd,
|
||||
m2::PointU const & basePoint,
|
||||
m2::PointU const & maxPoint,
|
||||
vector<m2::PointU> & points))
|
||||
{
|
||||
m2::PointU const basePoint = (points.empty() ? m2::PointU(0, 0) : points[points.size() / 2]);
|
||||
vector<char> data;
|
||||
fnEncode(points, basePoint, maxPoint, data);
|
||||
vector<m2::PointU> decodedPoints;
|
||||
// TODO: push_back
|
||||
fnDecode(&data[0], &data[0] + data.size(), basePoint, maxPoint, decodedPoints);
|
||||
TEST_EQUAL(points, decodedPoints, ());
|
||||
|
||||
if (points.size() > 10)
|
||||
LOG(LINFO, (testName, points.size(), data.size()));
|
||||
}
|
||||
|
||||
vector<m2::PointU> SimplifyPoints(vector<m2::PointU> const & points, double eps)
|
||||
{
|
||||
vector<m2::PointU> simpPoints;
|
||||
typedef mn::DistanceToLineSquare<m2::PointD> DistanceF;
|
||||
SimplifyNearOptimal<DistanceF>(20, points.begin(), points.end(), eps,
|
||||
AccumulateSkipSmallTrg<DistanceF, m2::PointU>(simpPoints, eps));
|
||||
return simpPoints;
|
||||
}
|
||||
|
||||
void TestEncodePolyline(char const * name, m2::PointU maxPoint, vector<m2::PointU> const & points)
|
||||
{
|
||||
TestPolylineEncode(name, points, maxPoint, &EncodePolylinePrev1, &DecodePolylinePrev1);
|
||||
TestPolylineEncode(name, points, maxPoint, &EncodePolylinePrev2, &DecodePolylinePrev2);
|
||||
TestPolylineEncode(name, points, maxPoint, &EncodePolylinePrev3, &DecodePolylinePrev3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
UNIT_TEST(EncodePolyline)
|
||||
{
|
||||
size_t const kSizes [] = { 0, 1, 2, 3, 4, ARRAY_SIZE(kLargePolygon) };
|
||||
m2::PointU const maxPoint(1000000000, 1000000000);
|
||||
for (size_t iSize = 0; iSize < ARRAY_SIZE(kSizes); ++iSize)
|
||||
{
|
||||
size_t const polygonSize = kSizes[iSize];
|
||||
vector<m2::PointU> points;
|
||||
points.reserve(polygonSize);
|
||||
for (size_t i = 0; i < polygonSize; ++i)
|
||||
points.push_back(m2::PointU(static_cast<uint32_t>(kLargePolygon[i].x * 1000),
|
||||
static_cast<uint32_t>((kLargePolygon[i].y + 200) * 1000)));
|
||||
|
||||
TestEncodePolyline("Unsimp", maxPoint, points);
|
||||
TestEncodePolyline("1simp", maxPoint, SimplifyPoints(points, 1));
|
||||
TestEncodePolyline("2simp", maxPoint, SimplifyPoints(points, 2));
|
||||
TestEncodePolyline("4simp", maxPoint, SimplifyPoints(points, 4));
|
||||
TestEncodePolyline("10simp", maxPoint, SimplifyPoints(points, 10));
|
||||
TestEncodePolyline("100simp", maxPoint, SimplifyPoints(points, 100));
|
||||
TestEncodePolyline("500simp", maxPoint, SimplifyPoints(points, 500));
|
||||
TestEncodePolyline("1000simp", maxPoint, SimplifyPoints(points, 1000));
|
||||
TestEncodePolyline("2000simp", maxPoint, SimplifyPoints(points, 2000));
|
||||
TestEncodePolyline("4000simp", maxPoint, SimplifyPoints(points, 4000));
|
||||
}
|
||||
}
|
|
@ -37,4 +37,5 @@ SOURCES += \
|
|||
data_header_test.cpp \
|
||||
feature_bucketer_test.cpp \
|
||||
feature_routine.cpp \
|
||||
geometry_coding_test.cpp
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue