diff --git a/doc/stages/filters.range.rst b/doc/stages/filters.range.rst index e53eb98d60..4e91c65640 100644 --- a/doc/stages/filters.range.rst +++ b/doc/stages/filters.range.rst @@ -78,3 +78,10 @@ limits .. code-block:: bash Classification[2:2] + + Any range can be negated by prefacing with the '~' character. The following + will select all classifications that aren't equal to the value 2. + + .. code-block:: bash + + Classification~[2:2] diff --git a/filters/range/RangeFilter.cpp b/filters/range/RangeFilter.cpp index f9034d02ec..d779d9ff10 100644 --- a/filters/range/RangeFilter.cpp +++ b/filters/range/RangeFilter.cpp @@ -64,6 +64,7 @@ RangeFilter::Range parseRange(const std::string& r) std::string::size_type pos, count; bool ilb = true; bool iub = true; + bool negate = false; const char *start; char *end; std::string name; @@ -82,6 +83,12 @@ RangeFilter::Range parseRange(const std::string& r) name = r.substr(pos, count); pos += count; + if (r[pos] == '~') + { + negate = true; + pos++; + } + if (r[pos] == '(') ilb = false; else if (r[pos] != '[') @@ -129,7 +136,7 @@ RangeFilter::Range parseRange(const std::string& r) oss << "filters.range: invalid 'limits' option: '" << r << "': " << s; throw pdal_error(oss.str()); } - return RangeFilter::Range(name, lb, ub, ilb, iub); + return RangeFilter::Range(name, lb, ub, ilb, iub, negate); } } @@ -199,7 +206,8 @@ PointViewSet RangeFilter::run(PointViewPtr inView) if (v >= d.second.m_upper_bound) keep_point = false; } - + if (d.second.m_negate) + keep_point = !keep_point; if (keep_point) break; } diff --git a/filters/range/RangeFilter.hpp b/filters/range/RangeFilter.hpp index 49fcd2d08d..297c771510 100644 --- a/filters/range/RangeFilter.hpp +++ b/filters/range/RangeFilter.hpp @@ -58,11 +58,13 @@ class PDAL_DLL RangeFilter : public pdal::Filter double lower_bound, double upper_bound, bool inclusive_lower_bound, - bool inclusive_upper_bound) : + bool inclusive_upper_bound, + bool negate) : m_name(name), m_lower_bound(lower_bound), m_upper_bound(upper_bound), m_inclusive_lower_bound(inclusive_lower_bound), - m_inclusive_upper_bound(inclusive_upper_bound) + m_inclusive_upper_bound(inclusive_upper_bound), + m_negate(negate) {} Range() @@ -73,6 +75,7 @@ class PDAL_DLL RangeFilter : public pdal::Filter double m_upper_bound; bool m_inclusive_lower_bound; bool m_inclusive_upper_bound; + bool m_negate; }; RangeFilter() : Filter() diff --git a/test/unit/filters/RangeFilterTest.cpp b/test/unit/filters/RangeFilterTest.cpp index eaffd0ebf7..01980fd4a6 100644 --- a/test/unit/filters/RangeFilterTest.cpp +++ b/test/unit/filters/RangeFilterTest.cpp @@ -189,6 +189,41 @@ TEST(RangeFilterTest, onlyMax) EXPECT_FLOAT_EQ(5.0, view->getFieldAs(Dimension::Id::Z, 4)); } +TEST(RangeFilterTest, negation) +{ + BOX3D srcBounds(0.0, 0.0, 1.0, 0.0, 0.0, 10.0); + + Options ops; + ops.add("bounds", srcBounds); + ops.add("mode", "ramp"); + ops.add("num_points", 10); + + StageFactory f; + FauxReader reader; + reader.setOptions(ops); + + Options rangeOps; + rangeOps.add("limits", "Z~[2:5]"); + + RangeFilter filter; + filter.setOptions(rangeOps); + filter.setInput(reader); + + PointTable table; + filter.prepare(table); + PointViewSet viewSet = filter.execute(table); + PointViewPtr view = *viewSet.begin(); + + EXPECT_EQ(1u, viewSet.size()); + EXPECT_EQ(6u, view->size()); + EXPECT_FLOAT_EQ(1.0, view->getFieldAs(Dimension::Id::Z, 0)); + EXPECT_FLOAT_EQ(6.0, view->getFieldAs(Dimension::Id::Z, 1)); + EXPECT_FLOAT_EQ(7.0, view->getFieldAs(Dimension::Id::Z, 2)); + EXPECT_FLOAT_EQ(8.0, view->getFieldAs(Dimension::Id::Z, 3)); + EXPECT_FLOAT_EQ(9.0, view->getFieldAs(Dimension::Id::Z, 4)); + EXPECT_FLOAT_EQ(10.0, view->getFieldAs(Dimension::Id::Z, 5)); +} + TEST(RangeFilterTest, equals) { BOX3D srcBounds(0.0, 0.0, 1.0, 0.0, 0.0, 10.0);