From fdc796d2a99ecdc048bbf7e96250d3684d735e13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Delhome?= Date: Thu, 17 Oct 2024 18:14:36 +0200 Subject: [PATCH] feat: isSimple method for geometries --- src/algorithm/isSimple.cpp | 249 ++++++++++++++ src/algorithm/isSimple.h | 31 ++ src/capi/sfcgal_c.cpp | 34 ++ src/capi/sfcgal_c.h | 20 ++ test/unit/SFCGAL/algorithm/IsSimpleTest.cpp | 362 ++++++++++++++++++++ 5 files changed, 696 insertions(+) create mode 100644 src/algorithm/isSimple.cpp create mode 100644 src/algorithm/isSimple.h create mode 100644 test/unit/SFCGAL/algorithm/IsSimpleTest.cpp diff --git a/src/algorithm/isSimple.cpp b/src/algorithm/isSimple.cpp new file mode 100644 index 00000000..76adac88 --- /dev/null +++ b/src/algorithm/isSimple.cpp @@ -0,0 +1,249 @@ +// 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 +{ + // Polygon 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 PolyhedralSurface &phs, const double &toleranceAbs) + -> const Simplicity +{ + if (phs.isEmpty()) { + return Simplicity::simple(); + } + const size_t numGeom = phs.numGeometries(); + + for (size_t g = 0; g != numGeom; ++g) { + Simplicity const s = isSimple(phs.polygonN(g), toleranceAbs); + + if (!s) { + return Simplicity::complex( + (boost::format("Polygon %d is complex: %s") % g % s.reason()).str()); + } + } + + return Simplicity::simple(); +} + +auto +isSimple(const Solid &solid, const double &toleranceAbs) -> const Simplicity +{ + if (solid.isEmpty()) { + return Simplicity::simple(); + } + const size_t numGeom = solid.numGeometries(); + + for (size_t g = 0; g != numGeom; ++g) { + Simplicity const s = isSimple(solid.shellN(g), toleranceAbs); + + if (!s) { + return Simplicity::complex( + (boost::format("Polygon %d is complex: %s") % g % s.reason()).str()); + } + } + + 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 MultiLineString &mls) -> const Simplicity +{ + if (mls.isEmpty()) { + return Simplicity::simple(); + } + const size_t numGeom = mls.numGeometries(); + + for (size_t g = 0; g != numGeom; ++g) { + Simplicity const s = isSimple(mls.lineStringN(g)); + + if (!s) { + return Simplicity::complex( + (boost::format("LineString %d is complex: %s") % g % s.reason()) + .str()); + } + } + + return Simplicity::simple(); +} + +auto +isSimple(const MultiPolygon &mpoly, const double &toleranceAbs) + -> const Simplicity +{ + if (mpoly.isEmpty()) { + return Simplicity::simple(); + } + const size_t numGeom = mpoly.numGeometries(); + for (size_t g = 0; g != numGeom; ++g) { + Simplicity const s = isSimple(mpoly.polygonN(g), toleranceAbs); + if (!s) { + return Simplicity::complex( + (boost::format("Polygon %d is complex: %s") % g % s.reason()).str()); + } + } + + return Simplicity::simple(); +} + +auto +isSimple(const MultiSolid &msolid, const double &toleranceAbs) + -> const Simplicity +{ + if (msolid.isEmpty()) { + return Simplicity::simple(); + } + const size_t numGeom = msolid.numGeometries(); + + for (size_t g = 0; g != numGeom; ++g) { + Simplicity const s = isSimple(msolid.solidN(g), toleranceAbs); + + if (!s) { + return Simplicity::complex( + (boost::format("Solid %d is complex: %s") % g % s.reason()).str()); + } + } + + return Simplicity::simple(); +} + +auto +isSimple(const GeometryCollection &collection, const double &toleranceAbs) + -> const Simplicity +{ + if (collection.isEmpty()) { + return Simplicity::simple(); + } + const size_t numGeom = collection.numGeometries(); + + for (size_t g = 0; g != numGeom; ++g) { + Simplicity const s = isSimple(collection.geometryN(g), toleranceAbs); + + if (!s) { + return Simplicity::complex( + (boost::format("%s at index %d is complex: %s") % + collection.geometryN(g).geometryType() % g % s.reason()) + .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_POLYHEDRALSURFACE: // every polygon is simple + return isSimple(g.as(), toleranceAbs); + + case TYPE_TRIANGLE: + return Simplicity::simple(); + + case TYPE_TRIANGULATEDSURFACE: // every triangle is simple + return Simplicity::simple(); + + case TYPE_SOLID: // every phs is simple + return isSimple(g.as(), toleranceAbs); + + 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(), toleranceAbs); + + case TYPE_MULTISOLID: // every solid is simple + return isSimple(g.as(), toleranceAbs); + + case TYPE_GEOMETRYCOLLECTION: // every geometry is simple + return isSimple(g.as(), toleranceAbs); + } + + 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..39a2cbe6 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 **complexity_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 2b50017b..7dfbe7bc 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..6c93388c --- /dev/null +++ b/test/unit/SFCGAL/algorithm/IsSimpleTest.cpp @@ -0,0 +1,362 @@ +/** + * 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/extrude.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 1.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(PolyhedralSurfaceIsSimple) +{ + 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 poly(io::readWkt(wkt)); + std::unique_ptr g(algorithm::extrude(*poly, 0.0, 0.0, 10.0)); + 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(ComplexPolyhedralSurfaceIsSimple) +{ + std::string const wkt = "POLYHEDRALSURFACE (\ + ((0 0 0, 0 -2 0, -1 -1 0, -1 0 0, 0 0 0)),\ + ((0 0 0, 0 0 -1, 0 -1 -1, 0 -2 0, 0 0 0)),\ + ((0 0 0, -1 0 0, -1 0 -1, 0 0 -1, 0 0 0)),\ + ((-1 -1 -1, 0 -1 -1, 0 0 -1, -1 0 -1, -1 -1 -1)),\ + ((-1 -1 -1, -1 0 -1, -1 0 0, -1 -1 0, -1 -1 -1)),\ + ((-1 -1 -1, -1 -1 0, 0 -2 0, 0 -1 -1, -1 -1 -1)))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE( + s == Simplicity::complex( + "Polygon 0 is complex: Points don't lie in the same plane."), + (boost::format("%s should be complex: %s") % g->geometryType() % + g->asText())); +} + +BOOST_AUTO_TEST_CASE(TriangulatedSurfaceIsSimple) +{ + std::string const wkt = + "TIN (((0.0 0.0 0.0, 1.0 0.0 0.0, 0.0 1.0 0.0, 0.0 0.0 0.0)), \ + ((0.0 1.0 0.0, 0.0 0.0 0.0, 0.0 0.0 1.0, 0.0 1.0 0.0)), \ + ((0.0 0.0 1.0, 0.0 0.0 0.0, 1.0 0.0 0.0, 0.0 0.0 1.0)), \ + ((1.0 0.0 0.0, 0.0 1.0 0.0, 0.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(SolidIsSimple) +{ + std::string const wkt = "SOLID((((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),\ + ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),\ + ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),\ + ((1 1 1, 0 1 1, 0 0 1, 1 0 1, 1 1 1)),\ + ((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)),\ + ((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1))))"; + 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(ComplexSolidIsSimple) +{ + std::string const wkt = "SOLID ((\ + ((0 0 0, 0 -2 0, -1 -1 0, -1 0 0, 0 0 0)),\ + ((0 0 0, 0 0 -1, 0 -1 -1, 0 -2 0, 0 0 0)),\ + ((0 0 0, -1 0 0, -1 0 -1, 0 0 -1, 0 0 0)),\ + ((-1 -1 -1, 0 -1 -1, 0 0 -1, -1 0 -1, -1 -1 -1)),\ + ((-1 -1 -1, -1 0 -1, -1 0 0, -1 -1 0, -1 -1 -1)),\ + ((-1 -1 -1, -1 -1 0, 0 -2 0, 0 -1 -1, -1 -1 -1))))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE( + s == Simplicity::complex("Shell 0 is complex: Polygon 0 is complex: " + "Points don't lie in the same plane."), + (boost::format("%s should be complex: %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_CASE(MultiLineStringIsSimple) +{ + std::string const wkt = + "MULTILINESTRING((0.0 0.0, 2.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(ComplexMultiLineStringIsSimple) +{ + std::string const wkt = "MULTILINESTRING ((-4.0 0.0, -2.0 0.0), \ + (3.0 0.0, 2.0 1.0), \ + (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 2 is complex: linestring self intersects"), + (boost::format("%s should be complex: %s") % g->geometryType() % + g->asText())); +} + +BOOST_AUTO_TEST_CASE(MultiPolygonIsSimple) +{ + std::string const wkt = "MULTIPOLYGON (((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(ComplexMultiPolygonIsSimple) +{ + std::string const wkt = "MULTIPOLYGON (((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)), ((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 1.0)))"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE( + s == Simplicity::complex( + "Polygon 1 is complex: Points don't lie in the same plane."), + (boost::format("%s should be complex: %s") % g->geometryType() % + g->asText())); +} + +BOOST_AUTO_TEST_CASE(MultiSolidIsSimple) +{ + std::string const wkt = "MULTISOLID((\ + (((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),\ + ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),\ + ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),\ + ((1 1 1, 0 1 1, 0 0 1, 1 0 1, 1 1 1)),\ + ((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)),\ + ((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1))),\ + (((0 0 0, 0 -1 0, -1 -1 0, -1 0 0, 0 0 0)),\ + ((0 0 0, 0 0 -1, 0 -1 -1, 0 -1 0, 0 0 0)),\ + ((0 0 0, -1 0 0, -1 0 -1, 0 0 -1, 0 0 0)),\ + ((-1 -1 -1, 0 -1 -1, 0 0 -1, -1 0 -1, -1 -1 -1)),\ + ((-1 -1 -1, -1 0 -1, -1 0 0, -1 -1 0, -1 -1 -1)),\ + ((-1 -1 -1, -1 -1 0, 0 -1 0, 0 -1 -1, -1 -1 -1)))\ + ))"; + 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(ComplexMultiSolidIsSimple) +{ + std::string const wkt = "MULTISOLID(\ + ((((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),\ + ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),\ + ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),\ + ((1 1 1, 0 1 1, 0 0 1, 1 0 1, 1 1 1)),\ + ((1 1 1, 1 0 1, 1 0 0, 1 1 0, 1 1 1)),\ + ((1 1 1, 1 1 0, 0 1 0, 0 1 1, 1 1 1)))),\ + ((((0 0 0, 0 -2 0, -1 -1 0, -1 0 0, 0 0 0)),\ + ((0 0 0, 0 0 -1, 0 -1 -1, 0 -2 0, 0 0 0)),\ + ((0 0 0, -1 0 0, -1 0 -1, 0 0 -1, 0 0 0)),\ + ((-1 -1 -1, 0 -1 -1, 0 0 -1, -1 0 -1, -1 -1 -1)),\ + ((-1 -1 -1, -1 0 -1, -1 0 0, -1 -1 0, -1 -1 -1)),\ + ((-1 -1 -1, -1 -1 0, 0 -2 0, 0 -1 -1, -1 -1 -1))))\ + )"; + std::unique_ptr const g(io::readWkt(wkt)); + Simplicity const s = algorithm::isSimple(*g); + BOOST_CHECK_MESSAGE( + s == Simplicity::complex( + "Solid 1 is complex: Points don't lie in the same plane."), + (boost::format("%s should be complex: %s") % g->geometryType() % + g->asText())); +} + +BOOST_AUTO_TEST_CASE(GeometryCollectionIsSimple) +{ + std::string const wkt = "GEOMETRYCOLLECTION (POINT (2.0 3.0), TRIANGLE ((0.0 " + "0.0,1.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(ComplexGeometryCollectionIsSimple) +{ + std::string const wkt = + "GEOMETRYCOLLECTION (POINT (2.0 3.0), TRIANGLE ((0.0 0.0,1.0 0.0,1.0 1.0,0.0 0.0)), \ + 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 at index 2 is complex: linestring self intersects."), + (boost::format("%s should be complex: %s") % g->geometryType() % + g->asText())); +} + +BOOST_AUTO_TEST_SUITE_END()