diff --git a/src/algorithm/isSimple.cpp b/src/algorithm/isSimple.cpp new file mode 100644 index 00000000..7e532f8a --- /dev/null +++ b/src/algorithm/isSimple.cpp @@ -0,0 +1,117 @@ +// Copyright (c) 2012-2013, IGN France. +// Copyright (c) 2012-2024, Oslandia. +// SPDX-License-Identifier: LGPL-2.0-or-later + +#include "SFCGAL/algorithm/isSimple.h" + +#include "SFCGAL/GeometryCollection.h" +#include "SFCGAL/LineString.h" +#include "SFCGAL/MultiLineString.h" +#include "SFCGAL/MultiPoint.h" +#include "SFCGAL/MultiPolygon.h" +#include "SFCGAL/MultiSolid.h" +#include "SFCGAL/Polygon.h" +#include "SFCGAL/PolyhedralSurface.h" +#include "SFCGAL/Triangle.h" +#include "SFCGAL/TriangulatedSurface.h" + +#include "SFCGAL/Kernel.h" +#include "SFCGAL/algorithm/intersects.h" +#include "SFCGAL/algorithm/plane.h" +#include "SFCGAL/detail/algorithm/coversPoints.h" + +using namespace SFCGAL::detail::algorithm; + +namespace SFCGAL { + +namespace algorithm { + +auto +isSimple(const LineString &linestring) -> const Simplicity +{ + if (linestring.is3D() ? selfIntersects3D(linestring) : selfIntersects(linestring)) { + return Simplicity::complex(boost::format("linestring self intersects").str()); + } + return Simplicity::simple(); +} + +auto +isSimple(const Polygon &polygon, const double &toleranceAbs = 1e-9) -> const Simplicity +{ + // Polygone must be planar (all points in the same plane) + if (polygon.is3D() && !isPlane3D(polygon, toleranceAbs)) { + return Simplicity::complex("Points don't lie in the same plane."); + } + return Simplicity::simple(); +} + +auto +isSimple(const MultiPoint &multipoint) -> const Simplicity +{ + const size_t numPoint = multipoint.numGeometries(); + + for (size_t l = 0; l != numPoint - 1; ++l) { + for (size_t other_l = l + 1; other_l != numPoint; ++other_l) { + bool const duplicated_points = multipoint.pointN(l) == multipoint.pointN(other_l); + if (duplicated_points) { + return Simplicity::complex( + (boost::format("Points %d and %d are duplicated in the MultiPoint.") % l % other_l) + .str()); + } + } + } + + return Simplicity::simple(); +} + +auto +isSimple(const Geometry &g, const double &toleranceAbs) -> const Simplicity +{ + switch (g.geometryTypeId()) { + case TYPE_POINT: + return Simplicity::simple(); + + case TYPE_LINESTRING: // no self-intersecting (excepted at ending point) + return isSimple(g.as()); + + case TYPE_POLYGON: // are the rings simple? (3D) -> check the coplanarity + return isSimple(g.as(), toleranceAbs); + + case TYPE_TRIANGLE: + return Simplicity::simple(); + + case TYPE_SOLID: // every phs is simple + return isSimple(g.as()); + + case TYPE_MULTIPOINT: // no equal points + return isSimple(g.as()); + + case TYPE_MULTILINESTRING: // every ls is simple, and only intersections are at boundaries + return isSimple(g.as()); + + case TYPE_MULTIPOLYGON: // every polygon is simple + return isSimple(g.as()); + + case TYPE_MULTISOLID: // every solid is simple + return isSimple(g.as()); + + case TYPE_GEOMETRYCOLLECTION: // every geometry is simple + return isSimple(g.as()); + + case TYPE_TRIANGULATEDSURFACE: // every triangle is simple + return isSimple(g.as()); + + case TYPE_POLYHEDRALSURFACE: // every polygon is simple + return isSimple(g.as()); + } + + BOOST_THROW_EXCEPTION(Exception( + (boost::format("isSimple( %s ) is not defined") % g.geometryType()) + .str())); + return Simplicity::complex( + (boost::format("isSimple( %s ) is not defined") % g.geometryType()) + .str()); // to avoid warning +} + +} // namespace algorithm +} // namespace SFCGAL diff --git a/src/algorithm/isSimple.h b/src/algorithm/isSimple.h new file mode 100644 index 00000000..68cf4c8f --- /dev/null +++ b/src/algorithm/isSimple.h @@ -0,0 +1,31 @@ +// Copyright (c) 2012-2013, IGN France. +// Copyright (c) 2012-2022, Oslandia. +// SPDX-License-Identifier: LGPL-2.0-or-later + +#ifndef SFCGAL_ALGORITHM_ISSIMPLE_H_ +#define SFCGAL_ALGORITHM_ISSIMPLE_H_ + +#include "SFCGAL/Geometry.h" +#include "SFCGAL/Simplicity.h" + +namespace SFCGAL { + +/** + * Functions used to assert for geometry simplicity + */ +void SFCGAL_API +SFCGAL_ASSERT_GEOMETRY_SIMPLICITY(const Geometry &g); + +namespace algorithm { + +/** + * @brief Check simplicity of a geometry + * @ingroup public_api + */ +SFCGAL_API const Simplicity +isSimple(const Geometry &g, const double &toleranceAbs = 1e-9); + +} // namespace algorithm +} // namespace SFCGAL + +#endif diff --git a/src/capi/sfcgal_c.cpp b/src/capi/sfcgal_c.cpp index f996895f..ac1de0c9 100644 --- a/src/capi/sfcgal_c.cpp +++ b/src/capi/sfcgal_c.cpp @@ -40,6 +40,7 @@ #include "SFCGAL/algorithm/extrude.h" #include "SFCGAL/algorithm/intersection.h" #include "SFCGAL/algorithm/intersects.h" +#include "SFCGAL/algorithm/isSimple.h" #include "SFCGAL/algorithm/isValid.h" #include "SFCGAL/algorithm/lineSubstring.h" #include "SFCGAL/algorithm/minkowskiSum.h" @@ -218,6 +219,39 @@ sfcgal_geometry_is_valid_detail(const sfcgal_geometry_t *geom, return static_cast(is_valid); } +extern "C" auto +sfcgal_geometry_is_simple(const sfcgal_geometry_t *geom) -> int +{ + SFCGAL_GEOMETRY_CONVERT_CATCH_TO_ERROR( + return (int)bool(SFCGAL::algorithm::isSimple( + *reinterpret_cast(geom)));) +} + +extern "C" auto +sfcgal_geometry_is_simple_detail(const sfcgal_geometry_t *geom, + char **invalidity_reason) -> int +{ + // set to null for now + if (complexity_reason != nullptr) { + *complexity_reason = nullptr; + } + + const auto *g = reinterpret_cast(geom); + bool is_simple = false; + try { + SFCGAL::Simplicity const simplicity = SFCGAL::algorithm::isSimple(*g); + is_simple = simplicity; + if (!is_simple && (complexity_reason != nullptr)) { + *complexity_reason = strdup(simplicity.reason().c_str()); + } + } catch (SFCGAL::Exception &e) { + if (complexity_reason != nullptr) { + *complexity_reason = strdup(e.what()); + } + } + return static_cast(is_simple); +} + extern "C" auto sfcgal_geometry_is_3d(const sfcgal_geometry_t *geom) -> int { diff --git a/src/capi/sfcgal_c.h b/src/capi/sfcgal_c.h index 34253ba6..510b3e0c 100644 --- a/src/capi/sfcgal_c.h +++ b/src/capi/sfcgal_c.h @@ -102,6 +102,26 @@ sfcgal_geometry_is_valid_detail(const sfcgal_geometry_t *geom, char **invalidity_reason, sfcgal_geometry_t **invalidity_location); +/** + * Tests if the given geometry is simple or not + * @ingroup capi + */ +SFCGAL_API int +sfcgal_geometry_is_simple(const sfcgal_geometry_t *); + +/** + * Tests if the given geometry is simple or not + * And return details in case of complexity + * @param geom the input geometry + * @param complexity_reason input/output parameter. If non null, a + * null-terminated string could be allocated and contain reason of the + * complexity + * @ingroup capi + */ +SFCGAL_API int +sfcgal_geometry_is_complexity_detail(const sfcgal_geometry_t *geom, + char **complexity_reason); + /** * Tests if the given geometry is 3D or not * @ingroup capi diff --git a/test/unit/SFCGAL/algorithm/IsSimpleTest.cpp b/test/unit/SFCGAL/algorithm/IsSimpleTest.cpp new file mode 100644 index 00000000..64cb6cc0 --- /dev/null +++ b/test/unit/SFCGAL/algorithm/IsSimpleTest.cpp @@ -0,0 +1,184 @@ +/** + * SFCGAL + * + * Copyright (C) 2012-2024 Oslandia + * Copyright (C) 2012-2013 IGN (http://www.ign.fr) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, see + . + */ +#include + +#include "SFCGAL/GeometryCollection.h" +#include "SFCGAL/LineString.h" +#include "SFCGAL/MultiLineString.h" +#include "SFCGAL/MultiPoint.h" +#include "SFCGAL/MultiPolygon.h" +#include "SFCGAL/MultiSolid.h" +#include "SFCGAL/Point.h" +#include "SFCGAL/Polygon.h" +#include "SFCGAL/PolyhedralSurface.h" +#include "SFCGAL/Solid.h" +#include "SFCGAL/Triangle.h" +#include "SFCGAL/TriangulatedSurface.h" +#include "SFCGAL/algorithm/isSimple.h" +#include "SFCGAL/io/wkt.h" + +using namespace boost::unit_test; +using namespace SFCGAL; + +BOOST_AUTO_TEST_SUITE(SFCGAL_algorithm_IsSimple) + +BOOST_AUTO_TEST_CASE(pointIsSimple) +{ + std::string const wkt = "POINT (3.0 4.0)"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +} + +BOOST_AUTO_TEST_CASE(point3DIsSimple) +{ + std::string const wkt = "POINT (3.0 4.0 2.0)"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +} + +BOOST_AUTO_TEST_CASE(ShortLinestringIsSimple) +{ + std::string const wkt = "LINESTRING (0.0 0.0, 2.0 0.0)"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(LongLinestringIsSimple) +{ + std::string const wkt = "LINESTRING (0.0 0.0, 2.0 0.0, 1.0 1.0)"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(ComplexLongLinestringIsSimple) +{ + std::string const wkt = "LINESTRING (0.0 0.0, 2.0 0.0, 1.0 1.0, 1.0 -1.0)"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::complex("linestring self intersects"), + (boost::format("%s should be complex: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(ClosedLongLinestringIsSimple) +{ + std::string const wkt = "LINESTRING (0.0 0.0, 2.0 0.0, 1.0 1.0, 0.0 0.0)"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(PolygonIsSimple) +{ + std::string const wkt = "POLYGON Z ((0.0 0.0 0.0, 1.0 0.0 0.0, 1.0 1.0 0.0, 0.0 1.0 0.0, 0.0 0.0 0.0))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(ComplexPolygonIsSimple) +{ + std::string const wkt = "POLYGON Z ((0.0 0.0 1.0, 1.0 0.0 0.0, 1.0 1.0 0.0, 0.0 1.0 0.0, 0.0 0.0 0.0))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::complex("Points don't lie in the same plane."), + (boost::format("%s should be complex: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(TriangleIsSimple) +{ + std::string const wkt = "TRIANGLE Z ((0.0 0.0 0.0, 1.0 0.0 0.0, 1.0 1.0 0.0, 0.0 0.0 0.0))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(MultiPointIsSimple) +{ + std::string const wkt = "MULTIPOINT ((0.0 0.0), (1.0 0.0), (1.0 1.0), (0.0 1.0))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::simple(), + (boost::format("%s should be simple: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_CASE(ComplexMultiPointIsSimple) +{ + std::string const wkt = "MULTIPOINT ((0.0 0.0), (1.0 0.0), (1.0 1.0), (0.0 1.0), (0.0 0.0))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE(s == Simplicity::complex("Points 0 and 4 are duplicated in the MultiPoint."), + (boost::format("%s should be complex: %s") + % g->geometryType() + % g->asText() + )); +; +} + +BOOST_AUTO_TEST_SUITE_END()