From f3da0c3ca1cb915c8326f23f7f415dc8f689a050 Mon Sep 17 00:00:00 2001 From: Yury Melnichek Date: Sat, 29 Jan 2011 22:32:11 +0100 Subject: [PATCH] Implement EncodePolyline() and DecodePolyline() + unit tests. --- indexer/geometry_coding.cpp | 231 ++++++++++++++++++ indexer/geometry_coding.hpp | 64 ++++- .../indexer_tests/geometry_coding_test.cpp | 114 +++++++++ indexer/indexer_tests/indexer_tests.pro | 1 + 4 files changed, 402 insertions(+), 8 deletions(-) create mode 100644 indexer/indexer_tests/geometry_coding_test.cpp diff --git a/indexer/geometry_coding.cpp b/indexer/geometry_coding.cpp index 6914723956..74a83b0a11 100644 --- a/indexer/geometry_coding.cpp +++ b/indexer/geometry_coding.cpp @@ -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(actual.x) - static_cast(prediction.x)), + bits::ZigZagEncode(static_cast(actual.y) - static_cast(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 const & varints, vector & serialOutput) +{ + PushBackByteSink > sink(serialOutput); + for (vector::const_iterator it = varints.begin(); it != varints.end(); ++it) + WriteVarUint(sink, *it); +} + +template +inline m2::PointU ClampPoint(m2::PointU const & maxPoint, m2::Point point) +{ + return m2::PointU( + point.x < 0 ? 0 : (point.x < maxPoint.x ? static_cast(point.x) : maxPoint.x), + point.y < 0 ? 0 : (point.y < maxPoint.y ? static_cast(point.y) : maxPoint.y)); +} + +bool TestDecoding(vector const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput, + void (* fnDecode)(char const * pBeg, char const * pEnd, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & points)) +{ + vector 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 const c1(p1.x, p1.y); + complex const c2(p2.x, p2.y); + complex const c3(p3.x, p3.y); + complex const c0 = c1 + (c1 - c2) * (c1 - c2) / (c2 - c3); + return ClampPoint(maxPoint, m2::PointD(c0.real(), c0.imag())); +} + +void EncodePolylinePrev1(vector const & points, + m2::PointU const & basePoint, + m2::PointU const & /*maxPoint*/, + vector & serialOutput) +{ + vector 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 & points) +{ + vector 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 const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput) +{ + vector 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 & points) +{ + vector 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 const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput) +{ + ASSERT_LESS_OR_EQUAL(basePoint.x, maxPoint.x, (basePoint, maxPoint)); + ASSERT_LESS_OR_EQUAL(basePoint.y, maxPoint.y, (basePoint, maxPoint)); + + vector 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 & points) +{ + ASSERT_LESS_OR_EQUAL(basePoint.x, maxPoint.x, (basePoint, maxPoint)); + ASSERT_LESS_OR_EQUAL(basePoint.y, maxPoint.y, (basePoint, maxPoint)); + + vector 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)); + } + } + } + } +} diff --git a/indexer/geometry_coding.hpp b/indexer/geometry_coding.hpp index 73acc49dbe..8de007c512 100644 --- a/indexer/geometry_coding.hpp +++ b/indexer/geometry_coding.hpp @@ -5,15 +5,63 @@ #include "../std/vector.hpp" #include "../std/tuple.hpp" -void EncodePolyline(vector const & points, - m2::PointU const & basePoint, - m2::PointU const & maxPoint, - vector & 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 & 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 const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput); + +void DecodePolylinePrev1(char const * pBeg, char const * pEnd, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & points); + +void EncodePolylinePrev2(vector const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput); + +void DecodePolylinePrev2(char const * pBeg, char const * pEnd, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & points); + +void EncodePolylinePrev3(vector const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput); + +void DecodePolylinePrev3(char const * pBeg, char const * pEnd, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & points); + +inline void EncodePolyline(vector const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput) +{ + EncodePolylinePrev3(points, basePoint, maxPoint, serialOutput); +} + +inline void DecodePolyline(char const * pBeg, char const * pEnd, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & points) +{ + DecodePolylinePrev3(pBeg, pEnd, basePoint, maxPoint, points); +} void EncodeTriangles(vector const & points, vector > const & triangles, diff --git a/indexer/indexer_tests/geometry_coding_test.cpp b/indexer/indexer_tests/geometry_coding_test.cpp new file mode 100644 index 0000000000..e3f97e92ec --- /dev/null +++ b/indexer/indexer_tests/geometry_coding_test.cpp @@ -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 const & points, + m2::PointU const & maxPoint, + void (* fnEncode)(vector const & points, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & serialOutput), + void (* fnDecode)(char const * pBeg, char const * pEnd, + m2::PointU const & basePoint, + m2::PointU const & maxPoint, + vector & points)) +{ + m2::PointU const basePoint = (points.empty() ? m2::PointU(0, 0) : points[points.size() / 2]); + vector data; + fnEncode(points, basePoint, maxPoint, data); + vector 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 SimplifyPoints(vector const & points, double eps) +{ + vector simpPoints; + typedef mn::DistanceToLineSquare DistanceF; + SimplifyNearOptimal(20, points.begin(), points.end(), eps, + AccumulateSkipSmallTrg(simpPoints, eps)); + return simpPoints; +} + +void TestEncodePolyline(char const * name, m2::PointU maxPoint, vector 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 points; + points.reserve(polygonSize); + for (size_t i = 0; i < polygonSize; ++i) + points.push_back(m2::PointU(static_cast(kLargePolygon[i].x * 1000), + static_cast((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)); + } +} diff --git a/indexer/indexer_tests/indexer_tests.pro b/indexer/indexer_tests/indexer_tests.pro index 4a9b260410..e29803206e 100644 --- a/indexer/indexer_tests/indexer_tests.pro +++ b/indexer/indexer_tests/indexer_tests.pro @@ -37,4 +37,5 @@ SOURCES += \ data_header_test.cpp \ feature_bucketer_test.cpp \ feature_routine.cpp \ + geometry_coding_test.cpp