/****************************************************************************** * $Id$ * * Project: GDAL algorithms * Purpose: Tests for the marching squares algorithm * Author: Hugo Mercier, * ****************************************************************************** * Copyright (c) 2018, Hugo Mercier, * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "gdal_unit_test.h" #include "gdal_alg.h" #include "marching_squares/square.h" #include "marching_squares/level_generator.h" #include #include #include #include "gtest_include.h" namespace marching_squares { struct Writer { typedef std::pair Segment; void addSegment(int levelIdx, const Point &first, const Point &second) { contours[levelIdx].push_back(Segment(first, second)); } void addBorderSegment(int levelIdx, const Point &first, const Point &second) { borders[levelIdx].push_back(Segment(first, second)); } std::map> contours; std::map> borders; const bool polygonize = true; }; } // namespace marching_squares namespace { using namespace marching_squares; // Common fixture with test data struct test_ms_square : public ::testing::Test { }; // Dummy test TEST_F(test_ms_square, dummy) { { const double levels[] = {0, 4}; FixedLevelRangeIterator levelGenerator(levels, 2); auto r = levelGenerator.range(0, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, 1); EXPECT_EQ((*b).second, 4.0); auto e = r.end(); EXPECT_EQ((*e).first, 2); EXPECT_EQ((*e).second, Inf); } { IntervalLevelRangeIterator levelGenerator(0, 4); auto r = levelGenerator.range(0, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, 1); EXPECT_EQ((*b).second, 4.0); auto e = r.end(); EXPECT_EQ((*e).first, 2); EXPECT_EQ((*e).second, 8.0); } { IntervalLevelRangeIterator levelGenerator(0, 10); auto r = levelGenerator.range(-18, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, -1); EXPECT_EQ((*b).second, -10.0); auto e = r.end(); EXPECT_EQ((*e).first, 1); EXPECT_EQ((*e).second, 10.0); } { ExponentialLevelRangeIterator levelGenerator(2); auto r = levelGenerator.range(0, 5.0); auto b = r.begin(); EXPECT_EQ((*b).first, 1); EXPECT_EQ((*b).second, 1.0); ++b; EXPECT_EQ((*b).first, 2); EXPECT_EQ((*b).second, 2.0); ++b; EXPECT_EQ((*b).first, 3); EXPECT_EQ((*b).second, 4.0); auto e = r.end(); EXPECT_EQ((*e).first, 4); EXPECT_EQ((*e).second, 8.0); } } TEST_F(test_ms_square, only_zero) { // Square with only 0, level = 0.1 Square square(ValuedPoint(0, 1, 0), ValuedPoint(1, 1, 0), ValuedPoint(0, 0, 0), ValuedPoint(1, 0, 0)); Square::Segments segments(square.segments(.1)); // // 0 0 // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // 0 0 EXPECT_EQ(segments.size(), size_t(0)); } TEST_F(test_ms_square, only_one) { // Square with only 1, level = 0.1 Square square(ValuedPoint(0, 1, 1), ValuedPoint(1, 1, 1), ValuedPoint(0, 0, 1), ValuedPoint(1, 0, 1)); // // 1 1 // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // 1 1 Square::Segments segments(square.segments(.1)); EXPECT_EQ(segments.size(), size_t(0)); } TEST_F(test_ms_square, only_zero_level_1) { // Square with only 1, level = 1.0 Square square(ValuedPoint(0, 1, 1), ValuedPoint(1, 1, 1), ValuedPoint(0, 0, 1), ValuedPoint(1, 0, 1)); // // 1 1 // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // 1 1 Square::Segments segments(square.segments(1.0)); EXPECT_EQ(segments.size(), size_t(0)); } TEST_F(test_ms_square, one_segment) { // Square with one segment, level = 0.1 Square square(ValuedPoint(0, 1, 1), ValuedPoint(1, 1, 0), ValuedPoint(0, 0, 0), ValuedPoint(1, 0, 0)); // // 0 0 // +------------------+ // | | // | | // | | // | | // | | // o | // | \ | // +---o--------------+ // 1 0 Square::Segments segments(square.segments(.1)); EXPECT_EQ(segments.size(), size_t(1)); EXPECT_TRUE(segments[0].first == Point(.9, 1)); EXPECT_TRUE(segments[0].second == Point(0, .1)); } TEST_F(test_ms_square, fudge_test_1) { // Fudge test 1 Square square(ValuedPoint(0, 1, 0), ValuedPoint(1, 1, 1), ValuedPoint(0, 0, 1), ValuedPoint(1, 0, 1)); // // 0 1 // +------------------o // | __/| // | __/ | // | __/ | // | _/ | // | __/ | // | __/ | // |/ | // o------------------+ // 1 1 // (0,0) { Square::Segments segments(square.segments(0.0)); EXPECT_EQ(segments.size(), size_t(0)); } { Square::Segments segments(square.segments(1.0)); EXPECT_EQ(segments.size(), size_t(1)); EXPECT_NEAR(segments[0].first.x, 0.0, 0.001); EXPECT_NEAR(segments[0].first.y, 0.0, 0.001); EXPECT_NEAR(segments[0].second.x, 1.0, 0.001); EXPECT_NEAR(segments[0].second.y, 1.0, 0.001); } } TEST_F(test_ms_square, fudge_test_2) { // Fudge test 2 Square square(ValuedPoint(0, 1, 1), ValuedPoint(1, 1, 0), ValuedPoint(0, 0, 0), ValuedPoint(1, 0, 0)); // // 1 0 // +o-----------------+ // o+ | // | | // | | // | | // | | // | | // | | // +------------------+ // 0 0 // (0,0) { Square::Segments segments(square.segments(1.0)); EXPECT_EQ(segments.size(), 1); EXPECT_NEAR(segments[0].first.x, 0.0, 0.001); EXPECT_NEAR(segments[0].first.y, 1.0, 0.001); EXPECT_NEAR(segments[0].second.x, 0.0, 0.001); EXPECT_NEAR(segments[0].second.y, 1.0, 0.001); } { Square::Segments segments(square.segments(0.0)); EXPECT_EQ(segments.size(), 0); } } TEST_F(test_ms_square, nan) { // A square with NaN const Square square(ValuedPoint(2.500000, 1.500000, 224.990005), ValuedPoint(3.500000, 1.500000, NaN), ValuedPoint(2.500000, 2.500000, 225.029999), ValuedPoint(3.500000, 2.500000, 224.770004)); // // 224.990005 NaN // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // 225.029999 224.770004 const Square ul(square.upperLeftSquare()); const Square ll(square.lowerLeftSquare()); // upper left and lower left squares // // 224.990005 224.990005 NaN // +--------+---------+ // | | | // | | | // | | | // +--------+ 224.930002 // 225.010002 | | // | | | // | 224.900001 | // +--------+---------+ // 225.029999 224.770004 EXPECT_NEAR(ul.lowerLeft.value, 225.010002, 0.000001); EXPECT_NEAR(ul.lowerRight.value, 224.930002, 0.000001); EXPECT_NEAR(ul.upperRight.value, 224.990005, 0.000001); EXPECT_NEAR(ll.lowerRight.value, 224.900001, 0.000001); EXPECT_EQ(ul.lowerLeft.x, ll.upperLeft.x); EXPECT_EQ(ul.lowerLeft.y, ll.upperLeft.y); EXPECT_EQ(ul.lowerLeft.value, ll.upperLeft.value); EXPECT_EQ(ul.lowerRight.x, ll.upperRight.x); EXPECT_EQ(ul.lowerRight.y, ll.upperRight.y); EXPECT_EQ(ul.lowerRight.value, ll.upperRight.value); const Square::Segments segments_up(ul.segments(225)); const Square::Segments segments_down(ll.segments(225)); // segments on 225 // // 224.990005 224.990005 NaN // <--------<---------+ // | | | // o_ | | // | \ | | // >--o-----< 224.930002 // 225.01|002 | | // | \ | | // | |224.900001 | // >---o----<---------+ // 225.029999 224.770004 EXPECT_EQ(segments_up.size(), 1); EXPECT_EQ(segments_down.size(), 1); // the two segments have a point in common EXPECT_EQ(segments_up[0].second, segments_down[0].first); } TEST_F(test_ms_square, border_test_1) { // Border test 1 const Square square(ValuedPoint(0.5, 0.5, NaN), ValuedPoint(1.5, 0.5, NaN), ValuedPoint(0.5, 1.5, 272.87), ValuedPoint(1.5, 1.5, 272.93)); // // NaN NaN // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // 272.87 272.93 const Square ll(square.lowerLeftSquare()); const Square lr(square.lowerRightSquare()); // // NaN NaN // +------------------+ // | | // | | // 272.87 272.90000 272.93 // +--------+---------+ // | | | // | | | // | | | // +--------+---------+ // 272.87 272.90000 272.93 Square::Segments segments_l(ll.segments(272.9)); Square::Segments segments_r(lr.segments(272.9)); // the level falls exactly on corners // thanks to the fudge, each corner should be shifted away a bit // // NaN NaN // +------------------+ // | | // | | // 272.87 272.90000 272.93 // <-------o>---------> // | :| | // | :| | // | :| | // <-------o>---------> // 272.87 272.90000 272.93 EXPECT_EQ(segments_l.size(), size_t(1)); EXPECT_EQ(segments_r.size(), size_t(0)); } TEST_F(test_ms_square, multiple_levels) { // Multiple levels const Square square( ValuedPoint(0.5, 1.5, 272.99), ValuedPoint(1.5, 1.5, NaN), ValuedPoint(0.5, 0.5, 273.03), ValuedPoint(1.5, 0.5, 272.9)); // // 272.99 NaN // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // 273.03 272.90 const Square ul(square.upperLeftSquare()); // // 272.99 272.99 NaN // +---------+--------+ // | | | // | | | // | | | // +---------+ | // 273.01 272.97 | // | | // | | // +------------------+ // 273.03 272.90 EXPECT_TRUE((std::fabs(ul.lowerLeft.value - 273.01) < 0.01)); EXPECT_TRUE((std::fabs(ul.lowerRight.value - 272.97) < 0.01)); EXPECT_TRUE((std::fabs(ul.upperRight.value - 272.99) < 0.01)); // We have a NaN value on the right, we should then have a right border EXPECT_TRUE((ul.borders == Square::RIGHT_BORDER)); Writer writer; // levels starting at min and increasing by 0.1 IntervalLevelRangeIterator levelGenerator(0, .1); ul.process(levelGenerator, writer); // we only have a contour when level = 273.0 // (0.5, 1.5) (1.5, 1.5) // 272.99 272.99 NaN // +---------+--------+ // | || | // o || | // |\ || | // +-o-------+ | // 273.01 272.97 | // | | // | | // +------------------+ // 273.03 272.90 // (0.5, 0.5) (1.5, 0.5) EXPECT_TRUE((writer.contours.size() == 2)); EXPECT_TRUE((writer.borders.size() == 1)); EXPECT_TRUE((writer.contours.find(2730) != writer.contours.end())); EXPECT_TRUE((writer.contours.find(2731) != writer.contours.end())); EXPECT_TRUE((writer.borders.find(2730) != writer.borders.end())); // we have one segment border on the right EXPECT_TRUE((writer.borders[2730].size() == 1)); EXPECT_TRUE((writer.contours[2730].size() == 1)); EXPECT_TRUE((writer.contours[2731].size() == 1)); } TEST_F(test_ms_square, border_test_3) { // Border test 3 Square square(ValuedPoint(0, 0, 10), ValuedPoint(1, 0, 5), ValuedPoint(0, 1, NaN), ValuedPoint(1, 1, 4)); // level value = 7 // 10 7.5 5 // +---------+--------+ // | | | // | _o | // | _/ | | // 10 +====o====+ 6.33 | // | | // | | // | | // +------------------+ // NaN 4 const Square ul(square.upperLeftSquare()); // "Lower left value", EXPECT_TRUE((std::fabs(ul.lowerLeft.value - 10.00) < 0.01)); // "Lower right value", EXPECT_TRUE((std::fabs(ul.lowerRight.value - 6.33) < 0.01)); // "Upper right value", EXPECT_TRUE((std::fabs(ul.upperRight.value - 7.50) < 0.01)); // We have a NaN value on the right, we should then have a right border // "We have the lower border", EXPECT_TRUE(ul.borders == Square::LOWER_BORDER); { // ... with a level interval Writer writer; IntervalLevelRangeIterator levelGenerator(7, 5); ul.process(levelGenerator, writer); // we have one contour at 7 and 12 // and two borders: one, at 7 and the second at >7 (12) // "We have 2 borders", EXPECT_EQ(writer.borders.size(), size_t(2)); // "We have 2 contours", EXPECT_EQ(writer.contours.size(), size_t(2)); // "Border at 0", EXPECT_TRUE(writer.borders.find(0) != writer.borders.end()); // "Border at 1", EXPECT_TRUE(writer.borders.find(1) != writer.borders.end()); // "No contour at 0", EXPECT_TRUE(writer.contours.find(0) != writer.contours.end()); // and we have one contour and 2 borders // "1 contour at 0", EXPECT_EQ(writer.contours[0].size(), size_t(1)); // "1 border at 0", EXPECT_EQ(writer.borders[0].size(), size_t(1)); // "1 border at 1", EXPECT_EQ(writer.borders[1].size(), size_t(1)); // the border at 7.0 is around 0.5, 0.5 // "Border at 1 is around 0.5, 0.5", EXPECT_TRUE((writer.borders[0][0].first.x == 0.5 && writer.borders[0][0].first.y == 0.5) || (writer.borders[0][0].second.x == 0.5 && writer.borders[0][0].second.y == 0.5)); // the border at 12.0 is around 0, 0.5 // "Border at 1 is around 0, 0.5", EXPECT_TRUE((writer.borders[1][0].first.x == 0.0 && writer.borders[1][0].first.y == 0.5) || (writer.borders[1][0].second.x == 0.0 && writer.borders[1][0].second.y == 0.5)); } // test with a fixed set of levels { Writer writer; std::vector levels = {7.0}; FixedLevelRangeIterator levelGenerator(&levels[0], 1); ul.process(levelGenerator, writer); // we have one contour at 7 and 12 // and two borders: one, at 7 and the second at >7 (12) // "We have 2 borders", EXPECT_EQ(writer.borders.size(), size_t(2)); // "We have 2 contours", EXPECT_EQ(writer.contours.size(), size_t(2)); // "Border at 0", EXPECT_TRUE(writer.borders.find(0) != writer.borders.end()); // "Border at 1", EXPECT_TRUE(writer.borders.find(1) != writer.borders.end()); // "No contour at 0", EXPECT_TRUE(writer.contours.find(0) != writer.contours.end()); // and we have one contour and 2 borders // "1 contour at 0", EXPECT_EQ(writer.contours[0].size(), size_t(1)); // "1 border at 0", EXPECT_EQ(writer.borders[0].size(), size_t(1)); // "1 border at 1", EXPECT_EQ(writer.borders[1].size(), size_t(1)); // the border at 7.0 is around 0.5, 0.5 // "Border at 1 is around 0.5, 0.5", EXPECT_TRUE((writer.borders[0][0].first.x == 0.5 && writer.borders[0][0].first.y == 0.5) || (writer.borders[0][0].second.x == 0.5 && writer.borders[0][0].second.y == 0.5)); // the border at 12.0 is around 0, 0.5 // "Border at 1 is around 0, 0.5", EXPECT_TRUE((writer.borders[1][0].first.x == 0.0 && writer.borders[1][0].first.y == 0.5) || (writer.borders[1][0].second.x == 0.0 && writer.borders[1][0].second.y == 0.5)); } } TEST_F(test_ms_square, level_value_below_square_values) { // Test level value below square values Square square(ValuedPoint(0, 0, 10), ValuedPoint(1, 0, 5), ValuedPoint(0, 1, 8), ValuedPoint(1, 1, 4)); // level value = 2 // 10 5 // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // 8 4 { Writer writer; std::vector levels = {2.0}; FixedLevelRangeIterator levelGenerator(&levels[0], 1); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 0)); EXPECT_TRUE((writer.contours.size() == 0)); } } TEST_F(test_ms_square, full_border_test_1) { // Full border test 1 Square square(ValuedPoint(-0.5, -0.5, NaN), ValuedPoint(0.5, -0.5, NaN), ValuedPoint(-0.5, 0.5, NaN), ValuedPoint(0.5, 0.5, 5)); // level value = 0, 10 // NaN NaN // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // NaN 5 { Writer writer; IntervalLevelRangeIterator levelGenerator(0, 10.0); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 1)); EXPECT_TRUE((writer.borders[1].size() == 2)); EXPECT_TRUE(((writer.borders[1][0].first.x == 0.0 && writer.borders[1][0].first.y == 0.0) || (writer.borders[1][0].second.x == 0.0 && writer.borders[1][0].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][0].first.x == 0.5 && writer.borders[1][0].first.y == 0.0) || (writer.borders[1][0].second.x == 0.5 && writer.borders[1][0].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][1].first.x == 0.0 && writer.borders[1][1].first.y == 0.0) || (writer.borders[1][1].second.x == 0.0 && writer.borders[1][1].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][1].first.x == 0.0 && writer.borders[1][1].first.y == 0.0) || (writer.borders[1][1].second.x == 0.0 && writer.borders[1][1].second.y == 0.5))); } } TEST_F(test_ms_square, full_border_test_2) { // Full border test 2 Square square(ValuedPoint(-0.5, -0.5, NaN), ValuedPoint(0.5, -0.5, NaN), ValuedPoint(-0.5, 0.5, NaN), ValuedPoint(0.5, 0.5, 5)); // level value = 5.0, 10.0 // NaN NaN // +------------------+ // | | // | | // | | // | | // | | // | | // | | // +------------------+ // NaN 5 { Writer writer; IntervalLevelRangeIterator levelGenerator(5.0, 5.0); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 1)); EXPECT_TRUE((writer.borders[1].size() == 2)); EXPECT_TRUE(((writer.borders[1][0].first.x == 0.0 && writer.borders[1][0].first.y == 0.0) || (writer.borders[1][0].second.x == 0.0 && writer.borders[1][0].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][0].first.x == 0.5 && writer.borders[1][0].first.y == 0.0) || (writer.borders[1][0].second.x == 0.5 && writer.borders[1][0].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][1].first.x == 0.0 && writer.borders[1][1].first.y == 0.0) || (writer.borders[1][1].second.x == 0.0 && writer.borders[1][1].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][1].first.x == 0.0 && writer.borders[1][1].first.y == 0.0) || (writer.borders[1][1].second.x == 0.0 && writer.borders[1][1].second.y == 0.5))); } { Writer writer; std::vector levels = {5.0}; FixedLevelRangeIterator levelGenerator(&levels[0], 1); square.process(levelGenerator, writer); EXPECT_TRUE((writer.borders.size() == 1)); EXPECT_TRUE((writer.borders[1].size() == 2)); EXPECT_TRUE(((writer.borders[1][0].first.x == 0.0 && writer.borders[1][0].first.y == 0.0) || (writer.borders[1][0].second.x == 0.0 && writer.borders[1][0].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][0].first.x == 0.5 && writer.borders[1][0].first.y == 0.0) || (writer.borders[1][0].second.x == 0.5 && writer.borders[1][0].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][1].first.x == 0.0 && writer.borders[1][1].first.y == 0.0) || (writer.borders[1][1].second.x == 0.0 && writer.borders[1][1].second.y == 0.0))); EXPECT_TRUE(((writer.borders[1][1].first.x == 0.0 && writer.borders[1][1].first.y == 0.0) || (writer.borders[1][1].second.x == 0.0 && writer.borders[1][1].second.y == 0.5))); } } } // namespace