diff --git a/include/boost/geometry/algorithms/detail/is_valid/box.hpp b/include/boost/geometry/algorithms/detail/is_valid/box.hpp index e7a67252b..863ce625f 100644 --- a/include/boost/geometry/algorithms/detail/is_valid/box.hpp +++ b/include/boost/geometry/algorithms/detail/is_valid/box.hpp @@ -20,6 +20,7 @@ #include #include +#include #include @@ -66,6 +67,20 @@ struct has_valid_corners } }; + +template +struct is_valid_box +{ + template + static inline bool apply(Box const& box, VisitPolicy& visitor) + { + return + ! has_invalid_coordinate::apply(box, visitor) + && + has_valid_corners::value>::apply(box, visitor); + } +}; + }} // namespace detail::is_valid #endif // DOXYGEN_NO_DETAIL @@ -85,7 +100,7 @@ namespace dispatch // Reference (for polygon validity): OGC 06-103r4 (6.1.11.1) template struct is_valid - : detail::is_valid::has_valid_corners::value> + : detail::is_valid::is_valid_box {}; diff --git a/include/boost/geometry/algorithms/detail/is_valid/has_invalid_coordinate.hpp b/include/boost/geometry/algorithms/detail/is_valid/has_invalid_coordinate.hpp new file mode 100644 index 000000000..6e6823d62 --- /dev/null +++ b/include/boost/geometry/algorithms/detail/is_valid/has_invalid_coordinate.hpp @@ -0,0 +1,151 @@ +// Boost.Geometry (aka GGL, Generic Geometry Library) + +// Copyright (c) 2014-2015, Oracle and/or its affiliates. + +// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle +// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle + +// Licensed under the Boost Software License version 1.0. +// http://www.boost.org/users/license.html + +#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_HAS_INVALID_COORDINATE_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_HAS_INVALID_COORDINATE_HPP + +#include + +#include + +#include +#include + +#include + +#include +#include +#include + + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace is_valid +{ + +struct always_valid +{ + template + static inline bool apply(Geometry const&, VisitPolicy& visitor) + { + return ! visitor.template apply(); + } +}; + +struct point_has_invalid_coordinate +{ + template + static inline bool apply(Point const& point, VisitPolicy& visitor) + { + boost::ignore_unused(visitor); + + return + geometry::has_non_finite_coordinate(point) + ? + (! visitor.template apply()) + : + (! visitor.template apply()); + } + + template + static inline bool apply(Point const& point) + { + return geometry::has_non_finite_coordinate(point); + } +}; + +struct indexed_has_invalid_coordinate +{ + template + static inline bool apply(Geometry const& geometry, VisitPolicy& visitor) + { + geometry::detail::indexed_point_view p0(geometry); + geometry::detail::indexed_point_view p1(geometry); + + return point_has_invalid_coordinate::apply(p0, visitor) + || point_has_invalid_coordinate::apply(p1, visitor); + } +}; + + +struct range_has_invalid_coordinate +{ + struct point_has_valid_coordinates + { + template + static inline bool apply(Point const& point) + { + return ! point_has_invalid_coordinate::apply(point); + } + }; + + template + static inline bool apply(Geometry const& geometry, VisitPolicy& visitor) + { + boost::ignore_unused(visitor); + + bool const has_valid_coordinates = detail::check_iterator_range + < + point_has_valid_coordinates, + true // do not consider an empty range as problematic + >::apply(geometry::points_begin(geometry), + geometry::points_end(geometry)); + + return has_valid_coordinates + ? + (! visitor.template apply()) + : + (! visitor.template apply()); + } +}; + + +template +< + typename Geometry, + typename Tag = typename tag::type, + bool HasFloatingPointCoordinates = boost::is_floating_point + < + typename coordinate_type::type + >::value +> +struct has_invalid_coordinate + : range_has_invalid_coordinate +{}; + +template +struct has_invalid_coordinate + : always_valid +{}; + +template +struct has_invalid_coordinate + : point_has_invalid_coordinate +{}; + +template +struct has_invalid_coordinate + : indexed_has_invalid_coordinate +{}; + +template +struct has_invalid_coordinate + : indexed_has_invalid_coordinate +{}; + + +}} // namespace detail::is_valid +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_IS_VALID_HAS_INVALID_COORDINATE_HPP diff --git a/include/boost/geometry/algorithms/detail/is_valid/linear.hpp b/include/boost/geometry/algorithms/detail/is_valid/linear.hpp index e30064faf..a49e07723 100644 --- a/include/boost/geometry/algorithms/detail/is_valid/linear.hpp +++ b/include/boost/geometry/algorithms/detail/is_valid/linear.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include @@ -46,6 +47,11 @@ struct is_valid_linestring static inline bool apply(Linestring const& linestring, VisitPolicy& visitor) { + if (has_invalid_coordinate::apply(linestring, visitor)) + { + return false; + } + if (boost::size(linestring) < 2) { return visitor.template apply(); diff --git a/include/boost/geometry/algorithms/detail/is_valid/pointlike.hpp b/include/boost/geometry/algorithms/detail/is_valid/pointlike.hpp index e51ab7464..51035f7a7 100644 --- a/include/boost/geometry/algorithms/detail/is_valid/pointlike.hpp +++ b/include/boost/geometry/algorithms/detail/is_valid/pointlike.hpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -36,10 +37,13 @@ template struct is_valid { template - static inline bool apply(Point const&, VisitPolicy& visitor) + static inline bool apply(Point const& point, VisitPolicy& visitor) { boost::ignore_unused(visitor); - return visitor.template apply(); + return ! detail::is_valid::has_invalid_coordinate + < + Point + >::apply(point, visitor); } }; @@ -63,7 +67,10 @@ struct is_valid { // we allow empty multi-geometries, so an empty multipoint // is considered valid - return visitor.template apply(); + return ! detail::is_valid::has_invalid_coordinate + < + MultiPoint + >::apply(multipoint, visitor); } else { diff --git a/include/boost/geometry/algorithms/detail/is_valid/ring.hpp b/include/boost/geometry/algorithms/detail/is_valid/ring.hpp index c35e84341..925c03a47 100644 --- a/include/boost/geometry/algorithms/detail/is_valid/ring.hpp +++ b/include/boost/geometry/algorithms/detail/is_valid/ring.hpp @@ -30,8 +30,9 @@ #include #include #include -#include #include +#include +#include #include #include @@ -153,17 +154,23 @@ struct is_valid_ring static inline bool apply(Ring const& ring, VisitPolicy& visitor) { // return invalid if any of the following condition holds: - // (a) the ring's size is below the minimal one - // (b) the ring consists of at most two distinct points - // (c) the ring is not topologically closed - // (d) the ring has spikes - // (e) the ring has duplicate points (if AllowDuplicates is false) - // (f) the boundary of the ring has self-intersections - // (g) the order of the points is inconsistent with the defined order + // (a) the ring's point coordinates are not invalid (e.g., NaN) + // (b) the ring's size is below the minimal one + // (c) the ring consists of at most two distinct points + // (d) the ring is not topologically closed + // (e) the ring has spikes + // (f) the ring has duplicate points (if AllowDuplicates is false) + // (g) the boundary of the ring has self-intersections + // (h) the order of the points is inconsistent with the defined order // // Note: no need to check if the area is zero. If this is the // case, then the ring must have at least two spikes, which is - // checked by condition (c). + // checked by condition (d). + + if (has_invalid_coordinate::apply(ring, visitor)) + { + return false; + } closure_selector const closure = geometry::closure::value; typedef typename closeable_view::type view_type; diff --git a/include/boost/geometry/algorithms/detail/is_valid/segment.hpp b/include/boost/geometry/algorithms/detail/is_valid/segment.hpp index a93d2bfe9..f92f73381 100644 --- a/include/boost/geometry/algorithms/detail/is_valid/segment.hpp +++ b/include/boost/geometry/algorithms/detail/is_valid/segment.hpp @@ -19,7 +19,7 @@ #include #include #include - +#include #include @@ -53,7 +53,14 @@ struct is_valid detail::assign_point_from_index<0>(segment, p[0]); detail::assign_point_from_index<1>(segment, p[1]); - if(! geometry::equals(p[0], p[1])) + if (detail::is_valid::has_invalid_coordinate + < + Segment + >::apply(segment, visitor)) + { + return false; + } + else if (! geometry::equals(p[0], p[1])) { return visitor.template apply(); } diff --git a/include/boost/geometry/algorithms/validity_failure_type.hpp b/include/boost/geometry/algorithms/validity_failure_type.hpp index 42de99c58..3fbf8027b 100644 --- a/include/boost/geometry/algorithms/validity_failure_type.hpp +++ b/include/boost/geometry/algorithms/validity_failure_type.hpp @@ -78,7 +78,10 @@ enum validity_failure_type /// The top-right corner of the box is lexicographically smaller /// than its bottom-left corner /// (applies to boxes only) - failure_wrong_corner_order = 50 // for boxes + failure_wrong_corner_order = 50, // for boxes + /// The geometry has at least one point with an invalid coordinate + /// (for example, the coordinate is a NaN) + failure_invalid_coordinate = 60 }; diff --git a/include/boost/geometry/policies/is_valid/failing_reason_policy.hpp b/include/boost/geometry/policies/is_valid/failing_reason_policy.hpp index d1918adbd..bb28091d9 100644 --- a/include/boost/geometry/policies/is_valid/failing_reason_policy.hpp +++ b/include/boost/geometry/policies/is_valid/failing_reason_policy.hpp @@ -54,6 +54,8 @@ inline char const* validity_failure_type_message(validity_failure_type failure) return "Geometry has duplicate (consecutive) points"; case failure_wrong_corner_order: return "Box has corners in wrong order"; + case failure_invalid_coordinate: + return "Geometry has point(s) with invalid coordinate(s)"; default: // to avoid -Wreturn-type warning return ""; } diff --git a/include/boost/geometry/util/has_infinite_coordinate.hpp b/include/boost/geometry/util/has_infinite_coordinate.hpp new file mode 100644 index 000000000..3f1d11a3b --- /dev/null +++ b/include/boost/geometry/util/has_infinite_coordinate.hpp @@ -0,0 +1,55 @@ +// Boost.Geometry + +// Copyright (c) 2015 Oracle and/or its affiliates. + +// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle + +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_GEOMETRY_UTIL_HAS_INFINITE_COORDINATE_HPP +#define BOOST_GEOMETRY_UTIL_HAS_INFINITE_COORDINATE_HPP + +#include + +#include +#include +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail +{ + +struct isinf +{ + template + static inline bool apply(T const& t) + { + return boost::math::isinf(t); + } +}; + +} // namespace detail +#endif // DOXYGEN_NO_DETAIL + +template +bool has_infinite_coordinate(Point const& point) +{ + return detail::has_coordinate_with_property + < + Point, + detail::isinf, + boost::is_floating_point + < + typename coordinate_type::type + >::value + >::apply(point); +} + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_UTIL_HAS_INFINITE_COORDINATE_HPP diff --git a/include/boost/geometry/util/has_nan_coordinate.hpp b/include/boost/geometry/util/has_nan_coordinate.hpp index b442c1c2b..93d7c7f03 100644 --- a/include/boost/geometry/util/has_nan_coordinate.hpp +++ b/include/boost/geometry/util/has_nan_coordinate.hpp @@ -3,6 +3,7 @@ // Copyright (c) 2015 Oracle and/or its affiliates. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle +// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle // Use, modification and distribution is subject to the Boost Software License, // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at @@ -13,31 +14,62 @@ #include +#include + #include #include +#include #include -namespace boost { namespace geometry { +namespace boost { namespace geometry +{ #ifndef DOXYGEN_NO_DETAIL -namespace detail { - -template ::value> -struct has_nan_coordinate +namespace detail { - static bool apply(Point const& point) + +struct isnan +{ + template + static inline bool apply(T const& t) { - return boost::math::isnan(geometry::get(point)) - || has_nan_coordinate::apply(point); + return boost::math::isnan(t); } }; -template -struct has_nan_coordinate +template +< + typename Point, + typename Predicate, + bool Enable, + std::size_t I = 0, + std::size_t N = geometry::dimension::value +> +struct has_coordinate_with_property +{ + static bool apply(Point const& point) + { + return Predicate::apply(geometry::get(point)) + || has_coordinate_with_property + < + Point, Predicate, Enable, I+1, N + >::apply(point); + } +}; + +template +struct has_coordinate_with_property +{ + static inline bool apply(Point const&) + { + return false; + } +}; + +template +struct has_coordinate_with_property { static bool apply(Point const& ) { @@ -51,7 +83,15 @@ struct has_nan_coordinate template bool has_nan_coordinate(Point const& point) { - return detail::has_nan_coordinate::apply(point); + return detail::has_coordinate_with_property + < + Point, + detail::isnan, + boost::is_floating_point + < + typename coordinate_type::type + >::value + >::apply(point); } }} // namespace boost::geometry diff --git a/include/boost/geometry/util/has_non_finite_coordinate.hpp b/include/boost/geometry/util/has_non_finite_coordinate.hpp new file mode 100644 index 000000000..50075641e --- /dev/null +++ b/include/boost/geometry/util/has_non_finite_coordinate.hpp @@ -0,0 +1,55 @@ +// Boost.Geometry + +// Copyright (c) 2015 Oracle and/or its affiliates. + +// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle + +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_GEOMETRY_UTIL_HAS_NON_FINITE_COORDINATE_HPP +#define BOOST_GEOMETRY_UTIL_HAS_NON_FINITE_COORDINATE_HPP + +#include + +#include +#include +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail +{ + +struct is_not_finite +{ + template + static inline bool apply(T const& t) + { + return ! boost::math::isfinite(t); + } +}; + +} // namespace detail +#endif // DOXYGEN_NO_DETAIL + +template +bool has_non_finite_coordinate(Point const& point) +{ + return detail::has_coordinate_with_property + < + Point, + detail::is_not_finite, + boost::is_floating_point + < + typename coordinate_type::type + >::value + >::apply(point); +} + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_UTIL_HAS_NON_FINITE_COORDINATE_HPP diff --git a/test/algorithms/is_valid.cpp b/test/algorithms/is_valid.cpp index 60dbd4883..6d2360ec1 100644 --- a/test/algorithms/is_valid.cpp +++ b/test/algorithms/is_valid.cpp @@ -13,12 +13,15 @@ #define BOOST_TEST_MODULE test_is_valid #endif +#include #include #include #include "test_is_valid.hpp" +#include + #include #include #include @@ -1176,7 +1179,138 @@ BOOST_AUTO_TEST_CASE( test_is_valid_multipolygon ) test_open_multipolygons(); } -BOOST_AUTO_TEST_CASE( test_geometry_with_NaN_coordinates ) + +template +inline void check_one(Geometry const& geometry) +{ + bool const is_fp = boost::is_floating_point::value; + + bg::validity_failure_type failure; + bool validity = bg::is_valid(geometry, failure); + if (BOOST_GEOMETRY_CONDITION(is_fp)) + { + BOOST_CHECK(! validity); + BOOST_CHECK(failure == bg::failure_invalid_coordinate); + } + else + { + BOOST_CHECK(failure == bg::no_failure + || failure != bg::failure_invalid_coordinate); + } +} + +template +inline void test_with_invalid_coordinate(CoordinateType invalid_value) +{ + typedef bg::model::segment

segment_t; + typedef bg::model::box

box_t; + typedef bg::model::linestring

linestring_t; + typedef bg::model::ring

ring_t; // cw, closed + typedef bg::model::polygon

polygon_t; // cw, closed + typedef bg::model::multi_point

multi_point_t; + typedef bg::model::multi_linestring multi_linestring_t; + typedef bg::model::multi_polygon multi_polygon_t; + + typedef typename bg::coordinate_type

::type coord_t; + + std::string wkt_point = "POINT(1 1)"; + std::string wkt_segment = "LINESTRING(1 1,10 20)"; + std::string wkt_box = "BOX(1 1,10 20)"; + std::string wkt_linestring = "LINESTRING(1 1,2 3,4 -5)"; + std::string wkt_ring = "POLYGON((1 1,2 2,2 1,1 1))"; + std::string wkt_polygon = "POLYGON((1 1,1 200,200 200,200 1,1 1),(50 50,50 51,49 50,50 50))"; + std::string wkt_multipoint = "MULTIPOINT(1 1,2 3,4 -5,-5 2)"; + std::string wkt_multilinestring = + "MULTILINESTRING((1 1,2 3,4 -5),(-4 -2,-8 9))"; + std::string wkt_multipolygon = "MULTIPOLYGON(((1 1,1 200,200 200,200 1,1 1),(50 50,50 51,49 50,50 50)),((500 500,550 550,550 500,500 500)))"; + + { + P p; + bg::read_wkt(wkt_point, p); + BOOST_CHECK(bg::is_valid(p)); + bg::set<1>(p, invalid_value); + check_one(p); + } + { + segment_t s; + bg::read_wkt(wkt_segment, s); + BOOST_CHECK(bg::is_valid(s)); + bg::set<1, 1>(s, invalid_value); + check_one(s); + } + { + box_t b; + bg::read_wkt(wkt_box, b); + BOOST_CHECK(bg::is_valid(b)); + bg::set<1, 0>(b, invalid_value); + check_one(b); + } + { + linestring_t ls; + bg::read_wkt(wkt_linestring, ls); + BOOST_CHECK(bg::is_valid(ls)); + bg::set<1>(ls[1], invalid_value); + check_one(ls); + } + { + ring_t r; + bg::read_wkt(wkt_ring, r); + BOOST_CHECK(bg::is_valid(r)); + bg::set<0>(r[1], invalid_value); + check_one(r); + } + { + polygon_t pgn; + bg::read_wkt(wkt_polygon, pgn); + BOOST_CHECK(bg::is_valid(pgn)); + bg::set<0>(bg::interior_rings(pgn)[0][1], invalid_value); + check_one(pgn); + } + { + multi_point_t mp; + bg::read_wkt(wkt_multipoint, mp); + BOOST_CHECK(bg::is_valid(mp)); + bg::set<0>(mp[2], invalid_value); + check_one(mp); + } + { + multi_linestring_t mls; + bg::read_wkt(wkt_multilinestring, mls); + BOOST_CHECK(bg::is_valid(mls)); + bg::set<0>(mls[1][1], invalid_value); + check_one(mls); + } + { + multi_polygon_t mpgn; + bg::read_wkt(wkt_multipolygon, mpgn); + BOOST_CHECK(bg::is_valid(mpgn)); + bg::set<0>(bg::exterior_ring(mpgn[1])[1], invalid_value); + check_one(mpgn); + } +} + +template +inline void test_with_invalid_coordinate() +{ + typedef typename bg::coordinate_type

::type coord_t; + + coord_t const q_nan = std::numeric_limits::quiet_NaN(); + coord_t const inf = std::numeric_limits::infinity(); + + test_with_invalid_coordinate(q_nan); + test_with_invalid_coordinate(inf); +} + +BOOST_AUTO_TEST_CASE( test_geometries_with_invalid_coordinates ) +{ + typedef point_type fp_point_type; + typedef bg::model::point int_point_type; + + test_with_invalid_coordinate(); + test_with_invalid_coordinate(); +} + +BOOST_AUTO_TEST_CASE( test_with_NaN_coordinates ) { #ifdef BOOST_GEOMETRY_TEST_DEBUG std::cout << std::endl << std::endl; @@ -1200,12 +1334,12 @@ BOOST_AUTO_TEST_CASE( test_geometry_with_NaN_coordinates ) test_valid < tester_allow_spikes, multi_linestring_type - >::apply("mls-NaN", mls, true); + >::apply("mls-NaN", mls, false); test_valid < tester_disallow_spikes, multi_linestring_type - >::apply("mls-NaN", mls, true); + >::apply("mls-NaN", mls, false); } BOOST_AUTO_TEST_CASE( test_is_valid_variant )