diff --git a/Documentation/doc/Documentation/packages.txt b/Documentation/doc/Documentation/packages.txt index aa061c842d8d..7002020a1a07 100644 --- a/Documentation/doc/Documentation/packages.txt +++ b/Documentation/doc/Documentation/packages.txt @@ -31,6 +31,7 @@ \cgalPackageSection{PartPolygons,Polygons} \package_listing{Polygon} +\package_listing{Polygon_repair} \package_listing{Boolean_set_operations_2} \package_listing{Nef_2} \package_listing{Nef_S2} diff --git a/Documentation/doc/biblio/geom.bib b/Documentation/doc/biblio/geom.bib index 28b9dae88623..f57c0ff3f437 100644 --- a/Documentation/doc/biblio/geom.bib +++ b/Documentation/doc/biblio/geom.bib @@ -152054,3 +152054,13 @@ @inproceedings{tang2009interactive year={2009}, organization={ACM} } + +@article{ledoux2014triangulation, + title={A triangulation-based approach to automatically repair GIS polygons}, + author={Ledoux, Hugo and Ohori, Ken Arroyo and Meijers, Martijn}, + journal={Computers \& Geosciences}, + volume={66}, + pages={121--131}, + year={2014}, + publisher={Elsevier} +} diff --git a/Installation/CHANGES.md b/Installation/CHANGES.md index 01179cc55c75..d84f8bad7e55 100644 --- a/Installation/CHANGES.md +++ b/Installation/CHANGES.md @@ -16,6 +16,10 @@ Release date: October 2023 - **Breaking change**: The usage of `boost::variant` has been replaced by `std::variant`. Packages affected are 2D Arrangements, and the Kernel intersection. - **Breaking change**: The file CMake file `UseCGAL.cmake` has been removed from CGAL. Usages of the CMake variables `${CGAL_USE_FILE}` and `${CGAL_LIBRARIES}` must be replaced by a link to the imported target `CGAL::CGAL`, for example: `target_link_library(the_target PRIVATE CGAL::CGAL)`. +### [Polygon Repair](https://doc.cgal.org/6.0/Manual/packages.html#PkgPolygonRepair) (new package) + +- This package provides functions to repair polygons, polygons with holes, and multipolygons with holes + using the odd-even heuristic. #### 2D Arrangements diff --git a/Installation/include/CGAL/license/Polygon_repair.h b/Installation/include/CGAL/license/Polygon_repair.h new file mode 100644 index 000000000000..b1fc77cedb6a --- /dev/null +++ b/Installation/include/CGAL/license/Polygon_repair.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016 GeometryFactory SARL (France). +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org) +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Andreas Fabri +// +// Warning: this file is generated, see include/CGAL/license/README.md + +#ifndef CGAL_LICENSE_POLYGON_REPAIR_H +#define CGAL_LICENSE_POLYGON_REPAIR_H + +#include +#include + +#ifdef CGAL_POLYGON_REPAIR_COMMERCIAL_LICENSE + +# if CGAL_POLYGON_REPAIR_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +# if defined(CGAL_LICENSE_WARNING) + + CGAL_pragma_warning("Your commercial license for CGAL does not cover " + "this release of the 2D Polygon Repair package.") +# endif + +# ifdef CGAL_LICENSE_ERROR +# error "Your commercial license for CGAL does not cover this release \ + of the 2D Polygon Repair package. \ + You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +# endif // CGAL_POLYGON_REPAIR_COMMERCIAL_LICENSE < CGAL_RELEASE_DATE + +#else // no CGAL_POLYGON_REPAIR_COMMERCIAL_LICENSE + +# if defined(CGAL_LICENSE_WARNING) + CGAL_pragma_warning("\nThe macro CGAL_POLYGON_REPAIR_COMMERCIAL_LICENSE is not defined." + "\nYou use the CGAL 2D Polygon Repair package under " + "the terms of the GPLv3+.") +# endif // CGAL_LICENSE_WARNING + +# ifdef CGAL_LICENSE_ERROR +# error "The macro CGAL_POLYGON_REPAIR_COMMERCIAL_LICENSE is not defined.\ + You use the CGAL 2D Polygon Repair package under the terms of \ + the GPLv3+. You get this error, as you defined CGAL_LICENSE_ERROR." +# endif // CGAL_LICENSE_ERROR + +#endif // no CGAL_POLYGON_REPAIR_COMMERCIAL_LICENSE + +#endif // CGAL_LICENSE_POLYGON_REPAIR_H diff --git a/Polygon/doc/Polygon/Concepts/MultipolygonWithHoles_2.h b/Polygon/doc/Polygon/Concepts/MultipolygonWithHoles_2.h new file mode 100644 index 000000000000..0992dda022cd --- /dev/null +++ b/Polygon/doc/Polygon/Concepts/MultipolygonWithHoles_2.h @@ -0,0 +1,101 @@ +/*! \ingroup PkgPolygon2Concepts + * \cgalConcept + * + * \cgalRefines{CopyConstructible,Assignable,DefaultConstructible} + * + * A model of this concept represents a multipolygon with holes. + * + * \cgalHasModelsBegin + * \cgalHasModels{CGAL::Multipolygon_with_holes_2} + * \cgalHasModelsEnd + */ + +class MultipolygonWithHoles_2 { +public: + +/// \name Types +/// @{ + +//! the polygon type used to represent each polygon with holes of the multipolygon. +typedef unspecified_type Polygon_with_holes_2; + +/*! a bidirectional iterator over the polygons with holes. + * Its value type is `Polygon_with_holes_2`. + */ +typedef unspecified_type Polygon_with_holes_iterator; + +/*! a bidirectional const iterator over the polygons with holes. + * Its value type is `Polygon_with_holes_2`. + */ +typedef unspecified_type Polygon_with_holes_const_iterator; + +//! range type for iterating over polygons with holes. +typedef unspecified_type Polygon_with_holes_container; + +//! size type +typedef unsigned int Size; + +/// @} + +/// \name Creation +/// @{ + +/*! constructs a multipolygon using a range of polygons with holes. + */ +template +MultipolygonWithHoles_2(InputIterator begin, InputIterator end); + +/// @} + +/// \name Predicates +/// @{ + +/*! returns the number of polygons with holes. + */ +Size number_of_polygons_wih_holes(); + +/// @} + +/// \name Access Functions +/// @{ + +/*! returns the begin iterator of the polygons with holes. + */ +Polygon_with_holes_iterator polygons_with_holes_begin(); + +/*! returns the past-the-end iterator of the polygons with holes. + */ + Polygon_with_holes_iterator polygons_with_holes_end(); + +/*! returns the begin iterator of the polygons with holes. + */ +Polygon_with_holes_const_iterator polygons_with_holes_begin() const; + +/*! returns the past-the-end iterator of the polygons with holes. + */ +Polygon_with_holes_const_iterator polygons_with_holes_end() const; + +/*! returns the range of polygons with holes. + */ +const Polygon_with_holes_container& polygons_with_holes() const; + +/// @} + +/// \name Modifiers +/// @{ + +/*! adds a given polygon with holes to the multipolygon. + */ +void add_polygon_with_holes(const Polygon_with_holes_2& polygon); + +/*! erases the specified polygon. + */ +void erase_polygon_with_holes(Polygon_with_holes_const_iterator pit); + +/*! removes all the polygons with holes. + */ +void clear(); + +/// @} + +}; /* end MultipolygonWithHoles_2 */ diff --git a/Polygon/doc/Polygon/PackageDescription.txt b/Polygon/doc/Polygon/PackageDescription.txt index 49c33a812281..8083eb8e15b5 100644 --- a/Polygon/doc/Polygon/PackageDescription.txt +++ b/Polygon/doc/Polygon/PackageDescription.txt @@ -16,7 +16,13 @@ \cgalInclude{CGAL/draw_polygon_with_holes_2.h} */ /// \defgroup PkgDrawPolygonWithHoles2 Draw a 2D Polygon with Holes -/// \ingroup PkgPolygon2Ref + +/*! + \code + #include + \endcode +*/ +/// \defgroup PkgDrawMultipolygonWithHoles2 Draw a 2D Multipolygon with Holes /*! \addtogroup PkgPolygon2Ref @@ -42,11 +48,13 @@ \cgalCRPSection{Concepts} - `PolygonTraits_2` - `GeneralPolygonWithHoles_2` +- `MultipolygonWithHoles_2` \cgalCRPSection{Classes} - `CGAL::Polygon_2` - `CGAL::Polygon_with_holes_2` - `CGAL::General_polygon_with_holes_2` +- `CGAL::Multipolygon_with_holes_2` \cgalCRPSection{Global Functions} - `CGAL::area_2()` @@ -67,4 +75,7 @@ \cgalCRPSection{Draw a Polygon_with_holes_2} - \link PkgDrawPolygonWithHoles2 CGAL::draw() \endlink +\cgalCRPSection{Draw a Multipolygon_with_holes_2} +- \link PkgDrawMultipolygonWithHoles2 CGAL::draw() \endlink + */ diff --git a/Polygon/doc/Polygon/Polygon.txt b/Polygon/doc/Polygon/Polygon.txt index f84a10c8a8ce..093cb03d5f64 100644 --- a/Polygon/doc/Polygon/Polygon.txt +++ b/Polygon/doc/Polygon/Polygon.txt @@ -37,6 +37,19 @@ points, but little more. Especially, computed values are not cached. That is, when the `Polygon_2::is_simple()` member function is called twice or more, the result is computed each time anew. +\section secPolygonWithHole Polygons with Holes and Multipolygons with Holes + +This package also provides classes to represent polygons with holes and multipolygons with holes. + +For polygons with holes, these are `Polygon_with_holes_2` and `General_polygon_with_holes_2`. They can store a polygon that represents +the outer boundary and a sequence of polygons that represent the holes. + +For multipolygons with holes, there is `Multipolygon_with_holes_2`. +It stores a sequence of polygons with holes. + +These classes do not add any semantic requirements on the simplicity +or orientation of their boundary polygons. + \section secPolygonExamples Examples \subsection subsecPolygon The Polygon Class @@ -46,6 +59,13 @@ some member functions. \cgalExample{Polygon/Polygon.cpp} +\subsection SubsectionPolygonRepair_Multipolygon The Multipolygon with Holes Class + +The following example shows the creation of a multipolygon with holes and the traversal +of the polygons in it. + +\cgalExample{Polygon/multipolygon.cpp} + \cgalFigureBegin{polygon2_algo,pgn_algos.png} A polygon and some points \cgalFigureEnd @@ -82,7 +102,8 @@ vertices. It additionally provides a member function `Polygon_2::vertices()` tha \subsection subsecPolygonDraw Draw a Polygon -A polygon can be visualized by calling the \link PkgDrawPolygon2 CGAL::draw

() \endlink function as shown in the following example. This function opens a new window showing the given polygon. A call to this function is blocking, that is the program continues as soon as the user closes the window (a version exists for polygon with holes, cf. \link PkgDrawPolygonWithHoles2 CGAL::draw() \endlink). +A polygon can be visualized by calling the \link PkgDrawPolygon2 CGAL::draw

() \endlink function as shown in the following example. This function opens a new window showing the given polygon. A call to this function is blocking, that is the program continues as soon as the user closes the window. Versions for polygons with holes and multipolygons with holes also exist, cf. \link PkgDrawPolygonWithHoles2 CGAL::draw() \endlink and \link PkgDrawMultipolygonWithHoles2 +CGAL::draw() \endlink. \cgalExample{Polygon/draw_polygon.cpp} @@ -94,15 +115,4 @@ Result of the run of the draw_polygon program. A window shows the polygon and al \cgalFigureEnd */ - -\section secPolygonWithHole Polygons with Holes - -This package also provides two classes to represent polygons with holes, -namely `Polygon_with_holes_2` and `General_polygon_with_holes_2`. They -can store a polygon that represents the outer boundary and a sequence -of polygons that represent the holes. - -These classes do not add any semantic requirements on the simplicity -or orientation of their boundary polygons. - } diff --git a/Polygon/doc/Polygon/examples.txt b/Polygon/doc/Polygon/examples.txt index 3a608da09f40..2341aa5721d6 100644 --- a/Polygon/doc/Polygon/examples.txt +++ b/Polygon/doc/Polygon/examples.txt @@ -7,4 +7,6 @@ \example Stream_support/Polygon_WKT.cpp \example Polygon/draw_polygon.cpp \example Polygon/draw_polygon_with_holes.cpp +\example Polygon/multipolygon.cpp +\example Polygon/draw_multipolygon_with_holes.cpp */ diff --git a/Polygon/examples/Polygon/CMakeLists.txt b/Polygon/examples/Polygon/CMakeLists.txt index ef957f4c572e..1d559bbe210d 100644 --- a/Polygon/examples/Polygon/CMakeLists.txt +++ b/Polygon/examples/Polygon/CMakeLists.txt @@ -18,4 +18,5 @@ endforeach() if(CGAL_Qt6_FOUND) target_link_libraries(draw_polygon PUBLIC CGAL::CGAL_Basic_viewer) target_link_libraries(draw_polygon_with_holes PUBLIC CGAL::CGAL_Basic_viewer) + target_link_libraries(draw_multipolygon_with_holes PUBLIC CGAL::CGAL_Basic_viewer) endif() diff --git a/Polygon/examples/Polygon/draw_multipolygon_with_holes.cpp b/Polygon/examples/Polygon/draw_multipolygon_with_holes.cpp new file mode 100644 index 000000000000..bda77eb63236 --- /dev/null +++ b/Polygon/examples/Polygon/draw_multipolygon_with_holes.cpp @@ -0,0 +1,20 @@ +#include +#include + +#include +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; + +int main() { + std::string wkt = "MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.1 0.1,0.1 0.9,0.9 0.9,0.9 0.1,0.1 0.1)),((0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.2 0.2),(0.3 0.3,0.3 0.7,0.7 0.7,0.7 0.3,0.3 0.3)))"; + std::istringstream iss(wkt); + Multipolygon_with_holes_2 mp; + CGAL::IO::read_multi_polygon_WKT(iss, mp); + CGAL::draw(mp); + + return 0; +} diff --git a/Polygon/examples/Polygon/multipolygon.cpp b/Polygon/examples/Polygon/multipolygon.cpp new file mode 100644 index 000000000000..31ce7d37401c --- /dev/null +++ b/Polygon/examples/Polygon/multipolygon.cpp @@ -0,0 +1,31 @@ +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; + +int main() { + + Point_2 p1_outer[] = {Point_2(0,0), Point_2(1,0), Point_2(1,1), Point_2(0,1)}; + Point_2 p1_inner[] = {Point_2(0.2,0.2), Point_2(0.8,0.2), Point_2(0.8,0.8), Point_2(0.2,0.8)}; + + Polygon_with_holes_2 p1(Polygon_2(p1_outer, p1_outer+4)); + Polygon_2 h(p1_inner, p1_inner+4); + p1.add_hole(h); + + Point_2 p2_outer[] = {Point_2(0.4,0.4), Point_2(0.6,0.4), Point_2(0.6,0.6), Point_2(0.4,0.6)}; + Polygon_with_holes_2 p2(Polygon_2(p2_outer, p2_outer+4)); + + Multipolygon_with_holes_2 mp; + mp.add_polygon_with_holes(p1); + mp.add_polygon_with_holes(p2); + + for (auto const& p: mp.polygons_with_holes()) { + std::cout << p << std::endl; + } + + return 0; +} diff --git a/Polygon/include/CGAL/General_polygon_with_holes_2.h b/Polygon/include/CGAL/General_polygon_with_holes_2.h index 42173fc2ad6f..bb19fcf99436 100644 --- a/Polygon/include/CGAL/General_polygon_with_holes_2.h +++ b/Polygon/include/CGAL/General_polygon_with_holes_2.h @@ -28,8 +28,8 @@ namespace CGAL { * * The class `General_polygon_with_holes_2` models the concept * `GeneralPolygonWithHoles_2`. It represents a general polygon with holes. - * It is parameterized with a type `Polygon` used to define the exposed - * type `Polygon_2`. This type represents the outer boundary of the general + * It is parameterized with a type `Polygon_` used to define the exposed + * type `%Polygon_2`. This type represents the outer boundary of the general * polygon and each hole. * * \tparam Polygon_ must have input and output operators. diff --git a/Polygon/include/CGAL/Multipolygon_with_holes_2.h b/Polygon/include/CGAL/Multipolygon_with_holes_2.h new file mode 100644 index 000000000000..50edf6d592d2 --- /dev/null +++ b/Polygon/include/CGAL/Multipolygon_with_holes_2.h @@ -0,0 +1,186 @@ +// Copyright (c) 2005 +// Utrecht University (The Netherlands), +// ETH Zurich (Switzerland), +// INRIA Sophia-Antipolis (France), +// Max-Planck-Institute Saarbruecken (Germany), +// and Tel-Aviv University (Israel). All rights reserved. +// +// This file is part of CGAL (www.cgal.org) +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial +// +// +// Author(s): Ken Arroyo Ohori + +#ifndef CGAL_MULTIPOLYGON_WITH_HOLES_2_H +#define CGAL_MULTIPOLYGON_WITH_HOLES_2_H + +#include + +namespace CGAL { + +/*! \ingroup PkgPolygon2Ref + * + * The class `Multipolygon_with_holes_2` models the concept `MultipolygonWithHoles_2`. + * It is parameterized with two types (`Kernel` and `Container`) that are used to instantiate + * the types `Polygon_2` and `Polygon_with_holes_2`. + * The latter is used to represent each polygon with holes. The former is converted to the latter. + * + * \cgalModels{MultipolygonWithHoles_2} + */ +template > +class Multipolygon_with_holes_2 { +public: + /// \name Definition + + /// @{ + + /// polygon type + using Polygon_2 = CGAL::Polygon_2; + + /// polygon with holes type + using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; + + /// @} + + using Polygon_with_holes_container = std::deque; + + using Polygon_with_holes_iterator = typename Polygon_with_holes_container::iterator; + using Polygon_with_holes_const_iterator = typename Polygon_with_holes_container::const_iterator; + + /// the size type + using Size = unsigned int; + + /*! %Default constructor. */ + Multipolygon_with_holes_2() {} + + /*! Constructor from polygons. */ + template + Multipolygon_with_holes_2(PolygonsInputIterator p_begin, + PolygonsInputIterator p_end) : + m_polygons(p_begin, p_end) + {} + + Polygon_with_holes_container& polygons_with_holes() { return m_polygons; } + + const Polygon_with_holes_container& polygons_with_holes() const { return m_polygons; } + + Polygon_with_holes_iterator polygons_with_holes_begin() { return m_polygons.begin(); } + + Polygon_with_holes_iterator polygons_with_holes_end() { return m_polygons.end(); } + + Polygon_with_holes_const_iterator polygons_with_holes_begin() const { return m_polygons.begin(); } + + Polygon_with_holes_const_iterator polygons_with_holes_end() const { return m_polygons.end(); } + + void add_polygon(const Polygon_2& pgn) { m_polygons.push_back(Polygon_with_holes_2(pgn)); } + + void add_polygon_with_holes(const Polygon_with_holes_2& pgn) { m_polygons.push_back(pgn); } + + void add_polygon_with_holes(Polygon_with_holes_2&& pgn) { m_polygons.emplace_back(std::move(pgn)); } + + void erase_polygon_with_holes(Polygon_with_holes_iterator pit) { m_polygons.erase(pit); } + + void clear() { m_polygons.clear(); } + + Size number_of_polygons_with_holes() const { return static_cast(m_polygons.size()); } + +protected: + Polygon_with_holes_container m_polygons; +}; + + +template +bool operator==(const Multipolygon_with_holes_2& p1, + const Multipolygon_with_holes_2& p2) +{ + typedef typename + Multipolygon_with_holes_2::Polygon_with_holes_const_iterator HCI; + typedef CGAL::Polygon_with_holes_2 Polygon_2; + if(&p1 == &p2) + return (true); + + if(p1.number_of_polygons_with_holes() != p2.number_of_polygons_with_holes()) + return (false); + + std::list tmp_list(p2.polygons_with_holes_begin(), p2.polygons_with_holes_end()); + + HCI i = p1.polygons_with_holes_begin(); + for(; i!= p1.polygons_with_holes_end(); ++i) + { + typename std::list::iterator j = + (std::find(tmp_list.begin(), tmp_list.end(), *i)); + + if(j == tmp_list.end()) + return (false); + + tmp_list.erase(j); + } + + + CGAL_assertion(tmp_list.empty()); + return (true); +} + +template +inline bool operator!=(const Multipolygon_with_holes_2& p1, + const Multipolygon_with_holes_2& p2) +{ + return (!(p1==p2)); +} +/*! +inserts a multipolygon with holes to the output stream `os`. + +An \ascii and a binary format exist. The format can be selected with +the \cgal modifiers for streams, `set_ascii_mode()` and `set_binary_mode()`, +respectively. The modifier `set_pretty_mode()` can be used to allow for (a +few) structuring comments in the output. Otherwise, the output would +be free of comments. The default for writing is \ascii without comments. + +The number of polygons is written followed by the polygons. For each polygon, +the number of points of the outer boundary is written followed by the +points themselves in counterclockwise order. Then, the number of holes +is written, and for each hole, the number of points on its outer +boundary is written followed by the points themselves in clockwise +order. + +\relates Multipolygon_with_holes_2 +*/ +template +std::ostream& operator<<(std::ostream& os, + const Multipolygon_with_holes_2& mp) { + typename Multipolygon_with_holes_2::Polygon_with_holes_const_iterator i; + + switch(IO::get_mode(os)) { + case IO::ASCII : + os << mp.number_of_polygons_with_holes() << ' '; + for (i = mp.polygons_with_holes_begin(); i != mp.polygons_with_holes_end(); ++i) { + os << *i << ' '; + } + return os; + + case IO::BINARY : + os << mp.number_of_polygons_with_holes(); + for (i = mp.polygons_with_holes_begin(); i != mp.polygons_with_holes_end(); ++i) { + os << *i ; + } + return os; + + default: + os << "Multipolygon_with_holes_2(" << std::endl; + for (i = mp.polygons_with_holes_begin(); i != mp.polygons_with_holes_end(); ++i) { + os << " " << *i << std::endl; + } + + os << ")" << std::endl; + return os; + } +} + + +} //namespace CGAL + +#endif // CGAL_MULTIPOLYGON_WITH_HOLES_2_H diff --git a/Polygon/include/CGAL/Polygon_with_holes_2.h b/Polygon/include/CGAL/Polygon_with_holes_2.h index e41104c943a9..23eb826e9b2a 100644 --- a/Polygon/include/CGAL/Polygon_with_holes_2.h +++ b/Polygon/include/CGAL/Polygon_with_holes_2.h @@ -30,8 +30,8 @@ namespace CGAL { The class `Polygon_with_holes_2` models the concept `GeneralPolygonWithHoles_2`. It represents a linear polygon with holes. It is parameterized with two types (`Kernel` and `Container`) that are used to instantiate -the type `Polygon_2`. The latter is used to -represents the outer boundary and the boundary of the holes (if any exist). +the type `Polygon_2`. This poygon type is used to +represent the outer boundary and the boundary of the holes (if any exist). \cgalModels{GeneralPolygonWithHoles_2} diff --git a/Polygon/include/CGAL/draw_multipolygon_with_holes_2.h b/Polygon/include/CGAL/draw_multipolygon_with_holes_2.h new file mode 100644 index 000000000000..909b4b29da82 --- /dev/null +++ b/Polygon/include/CGAL/draw_multipolygon_with_holes_2.h @@ -0,0 +1,211 @@ +// Copyright (c) 1997 +// Utrecht University (The Netherlands), +// ETH Zurich (Switzerland), +// INRIA Sophia-Antipolis (France), +// Max-Planck-Institute Saarbruecken (Germany), +// and Tel-Aviv University (Israel). All rights reserved. +// +// This file is part of CGAL (www.cgal.org) +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: LGPL-3.0-or-later OR LicenseRef-Commercial +// +// +// Author(s) : Ken Arroyo Ohori +// Guillaume Damiand + +#ifndef CGAL_DRAW_MULTIPOLYGON_WITH_HOLES_2_H +#define CGAL_DRAW_MULTIPOLYGON_WITH_HOLES_2_H + +#include + +#ifdef DOXYGEN_RUNNING +namespace CGAL { + +/*! + * \ingroup PkgDrawMultipolygonWithHoles2 + * + * opens a new window and draws `aph`, an instance of the + * `CGAL::Multipolygon_with_holes_2` class. A call to this function is blocking, that + * is the program continues as soon as the user closes the window. This function + * requires `CGAL_Qt6`, and is only available if the macro + * `CGAL_USE_BASIC_VIEWER` is defined. Linking with the cmake target + * `CGAL::CGAL_Basic_viewer` will link with `CGAL_Qt6` and add the definition + * `CGAL_USE_BASIC_VIEWER`. + * \tparam PH an instance of the `CGAL::Multipolygon_with_holes_2` class. + * \param aph the multipolygon with holes to draw. + */ + +template +void draw(const MPH& aph); + +} /* namespace CGAL */ + +#endif + +#ifdef CGAL_USE_BASIC_VIEWER + +#include +#include + +namespace CGAL { + +// Viewer class for Multipolygon_with_holes_2 +template +class Mpwh_2_basic_viewer_qt : public Basic_viewer_qt { + using Base = Basic_viewer_qt; + using Mpwh = Multipolygon; + using Pwh = typename Mpwh::Polygon_with_holes_2; + using Pgn = typename Mpwh::Polygon_2; + using Pnt = typename Pgn::Point_2; + +public: + /// Construct the viewer. + /// @param parent the active window to draw + /// @param mpwh the multipolygon to view + /// @param title the title of the window + Mpwh_2_basic_viewer_qt(QWidget* parent, const Mpwh& mpwh, + const char* title = "Basic Multipolygon_with_holes_2 Viewer") : + Base(parent, title, true, true, true, false, false), + m_mpwh(mpwh) { + if (mpwh.number_of_polygons_with_holes() == 0) return; + + // mimic the computation of Camera::pixelGLRatio() + auto bbox = bounding_box(); + CGAL::qglviewer::Vec minv(bbox.xmin(), bbox.ymin(), 0); + CGAL::qglviewer::Vec maxv(bbox.xmax(), bbox.ymax(), 0); + auto diameter = (maxv - minv).norm(); + m_pixel_ratio = diameter / m_height; + } + + /*! Intercept the resizing of the window. + */ + virtual void resizeGL(int width, int height) { + CGAL::QGLViewer::resizeGL(width, height); + m_width = width; + m_height = height; + CGAL::qglviewer::Vec p; + auto ratio = camera()->pixelGLRatio(p); + m_pixel_ratio = ratio; + add_elements(); + } + + /*! Obtain the pixel ratio. + */ + double pixel_ratio() const { return m_pixel_ratio; } + + /*! Compute the bounding box. + */ + CGAL::Bbox_2 bounding_box() { + Bbox_2 bbox; + if (m_mpwh.number_of_polygons_with_holes() > 0) bbox = m_mpwh.polygons_with_holes().front().outer_boundary().bbox(); + for (auto const& pwh: m_mpwh.polygons_with_holes()) { + bbox += pwh.outer_boundary().bbox(); + } + return bbox; + } + + /*! Compute the elements of a multipolygon with holes. + */ + void add_elements() { + clear(); + + for (auto const& p: m_mpwh.polygons_with_holes()) { + CGAL::IO::Color c(rand()%255,rand()%255,rand()%255); + face_begin(c); + + const Pnt* point_in_face; + const auto& outer_boundary = p.outer_boundary(); + compute_loop(outer_boundary, false); + point_in_face = &(outer_boundary.vertex(outer_boundary.size()-1)); + + for (auto it = p.holes_begin(); it != p.holes_end(); ++it) { + compute_loop(*it, true); + add_point_in_face(*point_in_face); + } + + face_end(); + } + } + +protected: + /*! Compute the face + */ + void compute_loop(const Pgn& p, bool hole) { + if (hole) add_point_in_face(p.vertex(p.size()-1)); + + auto prev = p.vertices_begin(); + auto it = prev; + add_point(*it); + add_point_in_face(*it); + for (++it; it != p.vertices_end(); ++it) { + add_segment(*prev, *it); // add segment with previous point + add_point(*it); + add_point_in_face(*it); // add point in face + prev = it; + } + + // Add the last segment between the last point and the first one + add_segment(*prev, *(p.vertices_begin())); + } + + virtual void keyPressEvent(QKeyEvent* e) { + // Test key pressed: + // const ::Qt::KeyboardModifiers modifiers = e->modifiers(); + // if ((e->key()==Qt::Key_PageUp) && (modifiers==Qt::NoButton)) { ... } + + // Call: * add_elements() if the model changed, followed by + // * redraw() if some viewing parameters changed that implies some + // modifications of the buffers + // (eg. type of normal, color/mono) + // * update() just to update the drawing + + // Call the base method to process others/classicals key + Base::keyPressEvent(e); + } + +private: + //! The window width in pixels. + int m_width = CGAL_BASIC_VIEWER_INIT_SIZE_X; + + //! The window height in pixels. + int m_height = CGAL_BASIC_VIEWER_INIT_SIZE_Y; + + //! The ratio between pixel and opengl units (in world coordinate system). + double m_pixel_ratio = 1; + + //! The polygon with holes to draw. + const Mpwh& m_mpwh; +}; + +// Specialization of draw function. +template +void draw(const CGAL::Multipolygon_with_holes_2& mpwh, + const char* title = "Multipolygon_with_holes_2 Basic Viewer") +{ +#if defined(CGAL_TEST_SUITE) + bool cgal_test_suite = true; +#else + bool cgal_test_suite = qEnvironmentVariableIsSet("CGAL_TEST_SUITE"); +#endif + + if (! cgal_test_suite) { + using Mpwh = CGAL::Multipolygon_with_holes_2; + using Viewer = Mpwh_2_basic_viewer_qt; + CGAL::Qt::init_ogl_context(4,3); + int argc = 1; + const char* argv[2] = {"t2_viewer", nullptr}; + QApplication app(argc, const_cast(argv)); + Viewer mainwindow(app.activeWindow(), mpwh, title); + mainwindow.add_elements(); + mainwindow.show(); + app.exec(); + } +} + +} // End namespace CGAL + +#endif // CGAL_USE_BASIC_VIEWER + +#endif // CGAL_DRAW_MULTIPOLYGON_WITH_HOLES_2_H diff --git a/Polygon_repair/benchmark/Polygon_repair/clipart.cpp b/Polygon_repair/benchmark/Polygon_repair/clipart.cpp new file mode 100644 index 000000000000..013265e835b8 --- /dev/null +++ b/Polygon_repair/benchmark/Polygon_repair/clipart.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; +using Polygon_repair = CGAL::Polygon_repair::Polygon_repair; + +void print_timer(clock_t start_time) { + clock_t stop_time = clock(); + double seconds = (stop_time-start_time)/(double)CLOCKS_PER_SEC; + std::cout << seconds << " seconds"; +} + +int main(int argc, char* argv[]) { + + std::string folder_in = "/Volumes/T7 Shield/out_fix"; + std::string folder_out = "/Volumes/T7 Shield/repaired"; + double desired_width = 500.0; + clock_t start_time; + + for (const auto& file: std::filesystem::directory_iterator(folder_in)) { + + if (file.path().filename().extension() != ".obj") continue; + std::cout << "Reading " << file.path().filename() << "..."; + if (std::filesystem::exists(folder_out + "/" + file.path().stem().string() + ".svg")) { + std::cout << " skipped: already processed" << std::endl; + continue; + } + + Polygon_repair pr; + std::vector vertices; + std::vector> edges; + + std::ifstream ifs(file.path()); + std::string line; + + while (std::getline(ifs, line)) { + std::istringstream iss(line); + char c; + iss >> c; + if (c == 'v') { + double x, y; + iss >> x >> y; + vertices.emplace_back(x, y); + } else if (c == 'l') { + unsigned int a, b; + iss >> a >> b; + edges.push_back(std::make_pair(vertices[a-1], vertices[b-1])); + } + } ifs.close(); + + std::unordered_set, + boost::hash>> edges_to_insert; + + for (auto const& edge: edges) { + if (edge.first == edge.second) continue; + std::pair pair = (edge.first < edge.second)? + std::make_pair(edge.first, edge.second) : std::make_pair(edge.second, edge.first); + auto inserted = edges_to_insert.insert(pair); + if (!inserted.second) edges_to_insert.erase(inserted.first); + } + + Polygon_repair::Triangulation::Face_handle search_start; + for (auto const& edge: edges_to_insert) { + Polygon_repair::Triangulation::Vertex_handle va = pr.triangulation().insert(edge.first, search_start); + Polygon_repair::Triangulation::Vertex_handle vb = pr.triangulation().insert(edge.second, va->face()); // vb is likely close to va + pr.triangulation().even_odd_insert_constraint(va, vb); + search_start = vb->face(); + } + + if (pr.triangulation().number_of_faces() > 0) { + pr.label_triangulation_even_odd(); + pr.reconstruct_multipolygon(); + } Multipolygon_with_holes_2 mp = pr.multipolygon(); + + if (mp.number_of_polygons_with_holes() > 0) { + CGAL::Bbox_2 bbox = mp.polygons_with_holes().front().bbox(); + for (auto const& polygon: mp.polygons_with_holes()) { + bbox += polygon.outer_boundary().bbox(); + } Kernel::Vector_2 translate(-bbox.xmin(), -bbox.ymin()); + double scale = desired_width/(bbox.xmax()-bbox.xmin()); + + + std::ofstream ofs(folder_out + "/" + file.path().stem().string() + ".svg"); + ofs << "" << std::endl; + + for (auto const& polygon: mp.polygons_with_holes()) { + // std::cout << polygon << std::endl; + ofs << "\t" << std::endl; + for (auto const& hole: polygon.holes()) { + // std::cout << hole << std::endl; + ofs << "\t" << std::endl; + } + } + + ofs << ""; + ofs.close(); + + if (CGAL::Polygon_repair::is_valid(mp)) { + std::cout << " ok" << std::endl; + } + } + + } + + return 0; +} diff --git a/Polygon_repair/benchmark/Polygon_repair/validate_wkt.cpp b/Polygon_repair/benchmark/Polygon_repair/validate_wkt.cpp new file mode 100644 index 000000000000..af560d376291 --- /dev/null +++ b/Polygon_repair/benchmark/Polygon_repair/validate_wkt.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; +using Polygon_repair = CGAL::Polygon_repair::Polygon_repair; + +int main() { + +// std::string folder = "/Users/ken/Downloads/big polygons/"; + std::string folder = "data/in"; + + for (const auto& file: std::filesystem::directory_iterator(folder)) { + if (file.path().filename().extension() != ".wkt") continue; + std::cout << "Testing " << file.path().filename() << "... "; + + // Read test file + std::string in; + std::getline(std::ifstream(file.path()), in); + + // Load test file + std::istringstream iss(in); + bool valid = true; + if (in.find("POLYGON") == 0) { + Polygon_with_holes_2 p; + if (in != "POLYGON()") { // maybe should be checked in WKT reader + CGAL::IO::read_polygon_WKT(iss, p); + valid = CGAL::Polygon_repair::is_valid(p); + } + } else if (in.find("MULTIPOLYGON") == 0) { + Multipolygon_with_holes_2 mp; + CGAL::IO::read_multi_polygon_WKT(iss, mp); + valid = CGAL::Polygon_repair::is_valid(mp); + } if (valid) std::cout << "Valid" << std::endl; + } + + return 0; +} diff --git a/Polygon_repair/benchmark/Polygon_repair/write_labeled_triangulation.cpp b/Polygon_repair/benchmark/Polygon_repair/write_labeled_triangulation.cpp new file mode 100644 index 000000000000..5b21e141505a --- /dev/null +++ b/Polygon_repair/benchmark/Polygon_repair/write_labeled_triangulation.cpp @@ -0,0 +1,61 @@ +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; +using Polygon_repair = CGAL::Polygon_repair::Polygon_repair; + +int main() { + + std::ifstream ifs("data/in/nesting-spike.wkt"); + + Multipolygon_with_holes_2 mp; + CGAL::IO::read_multi_polygon_WKT(ifs, mp); + + Polygon_repair pr; + pr.add_to_triangulation_even_odd(mp); + pr.label_triangulation_even_odd(); + + std::cout << "{" << std::endl; + std::cout << "\t\"type\": \"FeatureCollection\"," << std::endl; + std::cout << "\t\"features\": [" << std::endl; + + for (Polygon_repair::Triangulation::Finite_faces_iterator face = pr.triangulation().finite_faces_begin(); + face != pr.triangulation().finite_faces_end(); ++face) { + std::cout << "\t\t{" << std::endl; + std::cout << "\t\t\t\"type\": \"Feature\"," << std::endl; + std::cout << "\t\t\t\"properties\": {" << std::endl; + std::cout << "\t\t\t\t\"label\": " << face->label() << std::endl; + std::cout << "\t\t\t}," << std::endl; + std::cout << "\t\t\t\"geometry\": {" << std::endl; + std::cout << "\t\t\t\t\"type\": \"Polygon\"," << std::endl; + std::cout << "\t\t\t\t\"coordinates\": [" << std::endl; + std::cout << "\t\t\t\t\t[" << std::endl; + std::cout << "\t\t\t\t\t\t[" << face->vertex(0)->point().x() << ", " << face->vertex(0)->point().y() << "]," << std::endl; + std::cout << "\t\t\t\t\t\t[" << face->vertex(1)->point().x() << ", " << face->vertex(1)->point().y() << "]," << std::endl; + std::cout << "\t\t\t\t\t\t[" << face->vertex(2)->point().x() << ", " << face->vertex(2)->point().y() << "]" << std::endl; + std::cout << "\t\t\t\t\t]" << std::endl; + std::cout << "\t\t\t\t]" << std::endl; + std::cout << "\t\t\t}" << std::endl; + std::cout << "\t\t}"; + Polygon_repair::Triangulation::Finite_faces_iterator next_face = face; + ++next_face; + if (next_face != pr.triangulation().finite_faces_end()) std::cout << ","; + std::cout << std::endl; + } + + std::cout << "\t]" << std::endl; + std::cout << "}" << std::endl; + + pr.reconstruct_multipolygon(); + Multipolygon_with_holes_2 repaired = pr.multipolygon(); + + return 0; +} diff --git a/Polygon_repair/benchmark/Polygon_repair/write_repaired_polygons.cpp b/Polygon_repair/benchmark/Polygon_repair/write_repaired_polygons.cpp new file mode 100644 index 000000000000..e31996ae33eb --- /dev/null +++ b/Polygon_repair/benchmark/Polygon_repair/write_repaired_polygons.cpp @@ -0,0 +1,78 @@ +#include +#include +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; +using Polygon_repair = CGAL::Polygon_repair::Polygon_repair; + +int main() { + + // std::ifstream ifs("/Users/ken/Downloads/180927.wkt"); + // std::ofstream ofs("/Users/ken/Downloads/1.geojson"); + + std::ifstream ifs("/Users/ken/Downloads/2018418.wkt"); + std::ofstream ofs("/Users/ken/Downloads/2.geojson"); + + std::string in; + std::getline(ifs, in); + std::istringstream iss(in); + Polygon_with_holes_2 p; + CGAL::IO::read_polygon_WKT(iss, p); + Polygon_repair pr; + pr.add_to_triangulation_even_odd(p); + pr.label_triangulation_even_odd(); + pr.reconstruct_multipolygon(); + Multipolygon_with_holes_2 rmp = pr.multipolygon(); + std::ostringstream oss; + CGAL::IO::write_multi_polygon_WKT(oss, rmp); + std::string out = oss.str(); + + ofs << std::fixed; + ofs << std::setprecision(15); + + ofs << "{" << std::endl; + ofs << "\t\"type\": \"MultiPolygon\"," << std::endl; + ofs << "\t\"coordinates\": [" << std::endl; + for (int i = 0; i < rmp.polygons_with_holes().size(); ++i) { + ofs << "\t\t[" << std::endl; + + ofs << "\t\t\t[" << std::endl; + for (int j = 0; j < rmp.polygons_with_holes()[i].outer_boundary().size(); ++j) { + ofs << "\t\t\t\t[" << rmp.polygons_with_holes()[i].outer_boundary()[j].x() << + ", " << rmp.polygons_with_holes()[i].outer_boundary()[j].y() << "]"; + if (j < rmp.polygons_with_holes()[i].outer_boundary().size()-1) ofs << ","; + ofs << std::endl; + } ofs << "\t\t\t]"; + if (rmp.polygons_with_holes()[i].number_of_holes() > 0) ofs << ","; + ofs << std::endl; + + for (int j = 0; j < rmp.polygons_with_holes()[i].holes().size(); ++j) { + ofs << "\t\t\t[" << std::endl; + for (int k = 0; k < rmp.polygons_with_holes()[i].holes()[j].size(); ++k) { + ofs << "\t\t\t\t[" << rmp.polygons_with_holes()[i].holes()[j][k].x() << + ", " << rmp.polygons_with_holes()[i].holes()[j][k].y() << "]"; + if (k < rmp.polygons_with_holes()[i].holes()[j].size()-1) ofs << ","; + ofs << std::endl; + } + ofs << "\t\t\t]"; + if (j < rmp.polygons_with_holes()[i].holes().size()-1) ofs << ","; + ofs << std::endl; + } + + ofs << "\t\t]"; + if (i < rmp.polygons_with_holes().size()-1) ofs << ","; + ofs << std::endl; + } ofs << "\t]" << std::endl; + ofs << "}" << std::endl; + + return 0; +} diff --git a/Polygon_repair/doc/Polygon_repair/Doxyfile.in b/Polygon_repair/doc/Polygon_repair/Doxyfile.in new file mode 100644 index 000000000000..2ef1e12b3c0d --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/Doxyfile.in @@ -0,0 +1,3 @@ +@INCLUDE = ${CGAL_DOC_PACKAGE_DEFAULTS} +PROJECT_NAME = "CGAL ${CGAL_DOC_VERSION} - 2D Polygon Repair" + diff --git a/Polygon_repair/doc/Polygon_repair/PackageDescription.txt b/Polygon_repair/doc/Polygon_repair/PackageDescription.txt new file mode 100644 index 000000000000..0bd41658ad43 --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/PackageDescription.txt @@ -0,0 +1,38 @@ +// PRETTY PACKAGE NAME should equal the project title in Doxyfile.in + +/// \defgroup PkgPolygonRepairRef 2D Polygon Repair Reference + +/// \defgroup PkgPolygonRepairFunctions Functions +/// \ingroup PkgPolygonRepairRef + +/*! +\addtogroup PkgPolygonRepairRef + +\cgalPkgDescriptionBegin{2D Polygon Repair,PkgPolygonRepair} +\cgalPkgPicture{Polygon_repair-small.png} + +\cgalPkgSummaryBegin +\cgalPkgAuthors{Ken Arroyo Ohori} +\cgalPkgDesc{This package provides algorithms to repair 2D polygons, polygons with holes, +and multipolygons with holes, by selecting faces of the arrangement of the input based on a selection rule. +Currently, only the even-odd rule is provided. } +\cgalPkgManuals{Chapter_2D_Polygon_repair,PkgPolygonRepairRef} +\cgalPkgSummaryEnd + +\cgalPkgShortInfoBegin +\cgalPkgSince{6.0} +\cgalPkgDependsOn{\ref PkgPolygon2, \ref PkgTriangulation2} +\cgalPkgBib{cgal:a-pr} +\cgalPkgLicense{\ref licensesGPL "GPL"} +\cgalPkgShortInfoEnd + +\cgalPkgDescriptionEnd + +\cgalClassifedRefPages + +\cgalCRPSection{Functions} +- `CGAL::Polygon_repair::repair()` + +\cgalCRPSection{Simplification Rules} +- `CGAL::Polygon_repair::Even_odd_rule` +*/ diff --git a/Polygon_repair/doc/Polygon_repair/Polygon_repair.txt b/Polygon_repair/doc/Polygon_repair/Polygon_repair.txt new file mode 100644 index 000000000000..26b6f6d62c70 --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/Polygon_repair.txt @@ -0,0 +1,188 @@ +namespace CGAL { +/*! + +\mainpage User Manual +\anchor Chapter_2D_Polygon_repair + +\cgalAutoToc +\author Ken Arroyo Ohori + +\section SectionPolygonRepair_Introduction Introduction + +This package implements a polygon repair method. Starting from possibly +invalid input in the form of a polygon, polygon with holes or multipolygon +with holes, the method computes an arrangement of the input edges, labels +each face according to what it represents (exterior, polygon interior +or hole), and reconstructs the polygon(s) represented by the arrangement. +The method returns valid output stored in a multipolygon with holes. + +Different arrangement and labelling heuristics are possible, but +currently only the even-odd rule is implemented in this package. +This rule results in areas that are alternately assigned as polygon +interiors and exterior/holes each time that an input edge is passed. +It does not distinguish between edges that are part of outer boundaries +from those of inner boundaries. In a next version we will add the +winding number rule. + +\section SectionPolygonRepair_Definitions Definitions + +- A valid polygon (without holes) is a point set in \f$ \mathbb{R}^2\f$ +that is bounded by a cycle of linear edges, which is known as its +outer boundary. This outer boundary should be simple, +meaning that the interiors of its edges are pairwise disjoint and all of +its vertices have a degree of two. It is thus topologically equivalent to a +disk and is represented internally as the sequence of points at the common +end points of the edges around its outer boundary. + +- A valid polygon with holes is a point set in \f$ \mathbb{R}^2\f$ +that is bounded by one outer boundary and zero or more inner boundaries, +where each inner boundary represents a hole in the polygon. Considered +independently, each boundary should be simple. The different boundaries of a polygon +are allowed to intersect tangentially at their common vertices (with no common +edges), forming vertices with degrees of a multiple of two the tangential points. +The interior of a polygon with holes should form a connected point set. +Note that a valid polygon can also be represented as a valid polygon with +holes (where the number of holes is zero). + +- A valid multipolygon with holes is a point set in \f$ \mathbb{R}^2\f$ +that is represented by a set of zero or more valid polygons with holes. +The interiors of the polygons with holes should be pairwise disjoint, but they +are allowed to intersect tangentially at their common vertices. Note that +a valid polygon with holes can also be represented as a valid multipolygon +with holes (with only one polygon). + +\cgalFigureBegin{valid, valid.svg} +Valid: (a) polygon, (b-c) polygons with holes, and (d-e) multipolygons with holes. +(c) and (e) show cases where boundaries intersect tangentially at a single vertex. +\cgalFigureEnd + +\cgalFigureBegin{invalid, invalid.svg} +Invalid: (a) self-intersecting polygon self-intersection, (b) self-touching polygon, +(c-d) polygons with badly nested holes, (e) polygon with hole touching at edge, +(f) polygon with hole that separates interior into two parts, (g) multipolygon +with overlapping polygons, and (h) multipolygon with polygons that touch at an edge. +\cgalFigureEnd + +\subsection SubsectionPolygonRepair_Output Stricter Conditions for Output + +The conditions listed above are sufficient to define valid polygons, polygons +with holes and multipolygons with holes for most applications. However, in +order to ensure unique deterministic output from the repair algorithm, +the valid multipolygons with holes returned by the package conform to more +strict criteria: + +- Adjacent collinear edges touching at vertices of degree two are merged +- The sequence of vertices representing a boundary starts from its +lexicographically smallest vertex +- Outer boundaries are oriented counter-clockwise and inner boundaries are +oriented clockwise +- The inner boundaries of a polygon with holes are stored in lexicographic +order +- The polygons with holes of a multipolygon with holes are also stored in +lexicographic order + +\section SectionPolygonRepair_Algorithm Algorithm + +Broadly, the algorithm consists of three steps: + +-# Arrangement: the edges in the polygon, polygon with +holes or multipolygon with holes are added as edges in the arrangement. +-# Labeling of the faces: the resulting faces are labeled with ids +according to what they represent (exterior, polygon interior or hole). +-# Reconstruction of the multipolygon: each boundary is reconstructed, +then these are assembled into individual polygons with holes and put into a +single multipolygon with holes. + +\cgalFigureBegin{inout, inout.svg} +Examples of polygons with holes (a-d) and multipolygons with holes +(e-h) before (left) and after (right) being repaired. +\cgalFigureEnd + +\subsection SubsectionPolygonRepair_Arrangement Arrangement + +For the purposes of the repair operation, the input polygon, polygon with holes +or multipolygon is merely used as a container of input line segments. These line +segments are added to the arrangement as edges. Internally, this is done using +a constrained triangulation where they are added as constraints. + +With the even-odd rule, only the edges that are present an odd number of +times in the input will be edges in the final arrangement. +When these edges are only partially overlapping, only the parts that overlap +an odd number of times will be edges in the final arrangement. + +This procedure is done in two steps: 1. preprocessing to eliminate identical +edges that are present an even number of times, and 2. adding edges incrementally +while applying an even-odd counting mechanism, which erases existing (parts of) +edges when new overlapping ones are added. + +\subsection SubsectionPolygonRepair_Labeling Labeling + +First, the polygon exterior is labeled. For this, all of the faces that can be +accessed from the exterior without passing through an edge are labeled as exterior +faces. + +Then, all other faces are labeled. For the even-odd rule, the label applied +alternates between polygon interior and hole every time that an edge is passed. + +\subsection SubsectionPolygonRepair_Reconstruction Reconstruction of the Multipolygon + +The algorithm reconstructs the multipolygon boundary by boundary, obtaining +counter-clockwise cycles for outer boundaries and clockwise cycles for inner +boundaries. Once all boundaries have been reconstructed, the boundaries are assembled +into multipolygons using the face labels to know which polygon with holes inner/outer +boundaries belong to, and using the orientation to distinguish between the outer and +inner boundaries of each polygon with holes. + +\subsection SubsectionPolygonRepair_Notes Notes on the Output + +If the input is already valid, the method will return a valid output representing +the same area. However, the output might be different in order to conform to the +stricter conditions to generate deterministic output (see +\ref SubsectionPolygonRepair_Output). + +Also, it is worth noting that even the repair of a single polygon without holes +but with self-intersections can result in a multipolygon with holes. This is why +the repair function will always return a multipolygon with holes. The user can +then check whether it consists of a single polygon with holes, and if a polygon +with holes has zero holes and extract these if needed. + +\section SectionPolygonRepair_Examples Examples + +\subsection SubsectionPolygonRepair_Repair Repairing a (Multi)polygon + +It is possible to repair a polygon, polygon with holes or multipolygon with holes +using the even-odd rule by calling the `Polygon_repair::repair()` function +as shown in the following example. This function returns a repaired multipolygon with holes. + +\cgalExample{Polygon_repair/repair_polygon_2.cpp} + +\section SectionPolygonRepair_Performance Performance + +The method can repair large invalid polygons of millions of vertices in a few +seconds as long as the number of intersections between line segments is limited. +This is a realistic assumption with many invalid data sets, which only have +relatively minor issues involving a small number of their vertices/edges. +However, it is worth noting that there can be a potentially quadratic number of +intersection between edges in the worst case, leading to much worse performance +since all of these intersections need to be calculated in the overlay. + +| Polygon | Vertices | Holes | Time | +| :----: | :----: | :----: | | +| ![ ](Corine180927.jpg) | 101973 | 298 | 0.652 sec | +| ![ ](Corine2018418.jpg) | 43925 | 125 | 0.190 sec | + +\section SectionPolygonRepair_History History + +The polygon repair method as originally developed is described by Ledoux et al. +\cgalCite{ledoux2014triangulation} and implemented in the +prepair software. +This package is a reimplementation of the method with a new approach to label +and reconstruct the multipolygons. It also incorporates improvements later +added to prepair, such as the application of the even-odd counting heuristics +to edges, which enables correct counting even on partially overlapping edges. + +Ken Arroyo Ohori developed this package during the Google Summer of +Code 2023 mentored by Sébastien Loriot and Andreas Fabri. + +*/ +} /* namespace CGAL */ diff --git a/Polygon_repair/doc/Polygon_repair/dependencies b/Polygon_repair/doc/Polygon_repair/dependencies new file mode 100644 index 000000000000..6df3ace8d07a --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/dependencies @@ -0,0 +1,7 @@ +Manual +Kernel_23 +STL_Extension +Algebraic_foundations +Circulator +Stream_support +Polygon \ No newline at end of file diff --git a/Polygon_repair/doc/Polygon_repair/examples.txt b/Polygon_repair/doc/Polygon_repair/examples.txt new file mode 100644 index 000000000000..278e5049cf22 --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/examples.txt @@ -0,0 +1,3 @@ +/*! +\example Polygon_repair/repair_polygon_2.cpp +*/ diff --git a/Polygon_repair/doc/Polygon_repair/fig/Corine180927.jpg b/Polygon_repair/doc/Polygon_repair/fig/Corine180927.jpg new file mode 100644 index 000000000000..3748a819d2e5 Binary files /dev/null and b/Polygon_repair/doc/Polygon_repair/fig/Corine180927.jpg differ diff --git a/Polygon_repair/doc/Polygon_repair/fig/Corine2018418.jpg b/Polygon_repair/doc/Polygon_repair/fig/Corine2018418.jpg new file mode 100644 index 000000000000..aba0e3879a20 Binary files /dev/null and b/Polygon_repair/doc/Polygon_repair/fig/Corine2018418.jpg differ diff --git a/Polygon_repair/doc/Polygon_repair/fig/Polygon_repair-small.png b/Polygon_repair/doc/Polygon_repair/fig/Polygon_repair-small.png new file mode 100644 index 000000000000..634b116b03e5 Binary files /dev/null and b/Polygon_repair/doc/Polygon_repair/fig/Polygon_repair-small.png differ diff --git a/Polygon_repair/doc/Polygon_repair/fig/Polygon_repair-small.svg b/Polygon_repair/doc/Polygon_repair/fig/Polygon_repair-small.svg new file mode 100644 index 000000000000..4fb58ada7244 --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/fig/Polygon_repair-small.svg @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Polygon_repair/doc/Polygon_repair/fig/inout.svg b/Polygon_repair/doc/Polygon_repair/fig/inout.svg new file mode 100644 index 000000000000..d04cd3d601c6 --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/fig/inout.svg @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (a) + + + + + + + (b) + + + + + + + (c) + + + + + + + (d) + + + + + + + (e) + + + + + + + (f) + + + + + + + (g) + + + + + + + (h) + + + + + + diff --git a/Polygon_repair/doc/Polygon_repair/fig/invalid.svg b/Polygon_repair/doc/Polygon_repair/fig/invalid.svg new file mode 100644 index 000000000000..28a48816f750 --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/fig/invalid.svg @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + (b) + + + + + + + (a) + + + + + + + (c) + + + + + + + (f) + + + + + + + (g) + + + + + + + + + + + + + + + + + + (d) + + + + + + + + + + + + + + + (e) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (h) + + + + + + diff --git a/Polygon_repair/doc/Polygon_repair/fig/valid.svg b/Polygon_repair/doc/Polygon_repair/fig/valid.svg new file mode 100644 index 000000000000..524d308b323c --- /dev/null +++ b/Polygon_repair/doc/Polygon_repair/fig/valid.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (b) + + + + + + + (a) + + + + + + + (c) + + + + + + + (d) + + + + + + + (e) + + + + + + diff --git a/Polygon_repair/examples/Polygon_repair/CMakeLists.txt b/Polygon_repair/examples/Polygon_repair/CMakeLists.txt new file mode 100644 index 000000000000..4350cb892272 --- /dev/null +++ b/Polygon_repair/examples/Polygon_repair/CMakeLists.txt @@ -0,0 +1,16 @@ +# Created by the script cgal_create_cmake_script +# This is the CMake script for compiling a CGAL application. + +cmake_minimum_required(VERSION 3.1...3.23) +project(Polygon_repair_Examples) + +find_package(CGAL REQUIRED) + +# create a target per cppfile +file( + GLOB cppfiles + RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +foreach(cppfile ${cppfiles}) + create_single_source_cgal_program("${cppfile}") +endforeach() \ No newline at end of file diff --git a/Polygon_repair/examples/Polygon_repair/data/bridge-edge.wkt b/Polygon_repair/examples/Polygon_repair/data/bridge-edge.wkt new file mode 100644 index 000000000000..1e86cf795061 --- /dev/null +++ b/Polygon_repair/examples/Polygon_repair/data/bridge-edge.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25,0.25 0.75,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/examples/Polygon_repair/data/nesting-spike.wkt b/Polygon_repair/examples/Polygon_repair/data/nesting-spike.wkt new file mode 100644 index 000000000000..e0df5a0dc7d8 --- /dev/null +++ b/Polygon_repair/examples/Polygon_repair/data/nesting-spike.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.1 0.1,0.9 0.1,0.9 0.9,0.1 0.9,0.1 0.1)),((0.2 0.2,2 1,0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.2 0.2),(0.3 0.3,0.7 0.3,0.7 0.7,0.3 0.7,0.3 0.3))) \ No newline at end of file diff --git a/Polygon_repair/examples/Polygon_repair/repair_polygon_2.cpp b/Polygon_repair/examples/Polygon_repair/repair_polygon_2.cpp new file mode 100644 index 000000000000..be6a011b4a0c --- /dev/null +++ b/Polygon_repair/examples/Polygon_repair/repair_polygon_2.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; + +int main() { + std::ifstream in("data/bridge-edge.wkt"); + Polygon_with_holes_2 pin; + CGAL::IO::read_polygon_WKT(in, pin); + + Multipolygon_with_holes_2 mp = CGAL::Polygon_repair::repair(pin); + if (mp.number_of_polygons_with_holes() > 1) { + CGAL::IO::write_multi_polygon_WKT(std::cout, mp); + } else { + CGAL::IO::write_polygon_WKT(std::cout, mp.polygons_with_holes()[0]); + } + + return 0; +} diff --git a/Polygon_repair/include/CGAL/Polygon_repair/Even_odd_rule.h b/Polygon_repair/include/CGAL/Polygon_repair/Even_odd_rule.h new file mode 100644 index 000000000000..b2dc51bfe369 --- /dev/null +++ b/Polygon_repair/include/CGAL/Polygon_repair/Even_odd_rule.h @@ -0,0 +1,35 @@ +// Copyright (c) 2023 GeometryFactory. +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Ken Arroyo Ohori + +#ifndef CGAL_POLYGON_REPAIR_EVEN_ODD_RULE_H +#define CGAL_POLYGON_REPAIR_EVEN_ODD_RULE_H + +#include + +namespace CGAL { + +namespace Polygon_repair { + +/// \addtogroup PkgPolygonRepairRef +/// @{ + +/*! + Tag class to select the even odd rule when calling `CGAL::Polygon_repair::repair()`. + */ + struct Even_odd_rule {}; + +///@} + +} // namespace Polygon_repair + +} // namespace CGAL + +#endif // CGAL_POLYGON_REPAIR_EVEN_ODD_RULE_H diff --git a/Polygon_repair/include/CGAL/Polygon_repair/internal/Triangulation_face_base_with_repair_info_2.h b/Polygon_repair/include/CGAL/Polygon_repair/internal/Triangulation_face_base_with_repair_info_2.h new file mode 100644 index 000000000000..0f1482795486 --- /dev/null +++ b/Polygon_repair/include/CGAL/Polygon_repair/internal/Triangulation_face_base_with_repair_info_2.h @@ -0,0 +1,56 @@ +// Copyright (c) 2023 GeometryFactory. +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Ken Arroyo Ohori + +#ifndef CGAL_TRIANGULATION_WITH_REPAIR_INFO_2_H +#define CGAL_TRIANGULATION_WITH_REPAIR_INFO_2_H + +#include + +#include + +namespace CGAL { +namespace Polygon_repair { +namespace internal { + +template > +class Triangulation_face_base_with_repair_info_2 : public FaceBase { + int _label; + bool _processed; +public: + using Vertex_handle = typename FaceBase::Vertex_handle; + using Face_handle = typename FaceBase::Face_handle; + + template + struct Rebind_TDS { + using FaceBase2 = typename FaceBase::template Rebind_TDS::Other; + using Other = Triangulation_face_base_with_repair_info_2; + }; + + Triangulation_face_base_with_repair_info_2() : FaceBase() {} + + Triangulation_face_base_with_repair_info_2(Vertex_handle v0, Vertex_handle v1, Vertex_handle v2) + : FaceBase(v0, v1, v2) {} + + Triangulation_face_base_with_repair_info_2(Vertex_handle v0, Vertex_handle v1, Vertex_handle v2, + Face_handle n0, Face_handle n1, Face_handle n2) + : FaceBase(v0, v1, v2, n0, n1, n2) {} + + const bool& processed() const { return _processed; } + bool& processed() { return _processed; } + const int& label() const { return _label; } + int& label() { return _label; } +}; + +} // namespace internal +} // namespace Polygon_repair +} //namespace CGAL + +#endif // CGAL_TRIANGULATION_WITH_REPAIR_INFO_2_H diff --git a/Polygon_repair/include/CGAL/Polygon_repair/internal/Triangulation_with_even_odd_constraints_2.h b/Polygon_repair/include/CGAL/Polygon_repair/internal/Triangulation_with_even_odd_constraints_2.h new file mode 100644 index 000000000000..e75be739e613 --- /dev/null +++ b/Polygon_repair/include/CGAL/Polygon_repair/internal/Triangulation_with_even_odd_constraints_2.h @@ -0,0 +1,127 @@ +// Copyright (c) 2023 GeometryFactory. +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Ken Arroyo Ohori + +#ifndef CGAL_TRIANGULATION_WITH_EVEN_ODD_CONSTRAINTS_2_H +#define CGAL_TRIANGULATION_WITH_EVEN_ODD_CONSTRAINTS_2_H + +#include +#include + +namespace CGAL { +namespace Polygon_repair { +namespace internal { + +template +class Triangulation_with_even_odd_constraints_2 : public Triangulation_ { +public: + using Base_triangulation = Triangulation_; + using Point = typename Triangulation_::Point; + using Edge = typename Triangulation_::Edge; + using Vertex_handle = typename Triangulation_::Vertex_handle; + using Face_handle = typename Triangulation_::Face_handle; + using List_edges = typename Triangulation_::List_edges; + using List_faces = typename Triangulation_::List_faces; + using All_faces_iterator = typename Triangulation_::All_faces_iterator; + + class Interior_tester { + const Triangulation_with_even_odd_constraints_2 *t; + public: + Interior_tester() {} + Interior_tester(const Triangulation_with_even_odd_constraints_2 *tr) : t(tr) {} + + bool operator()(const All_faces_iterator & fit) const { + return fit->label() < 1; + } + }; + + // iterator over interior faces. + class Interior_faces_iterator : public Filter_iterator { + using Base = Filter_iterator; + using Self = Interior_faces_iterator; + public: + Interior_faces_iterator() : Base() {} + Interior_faces_iterator(const Base &b) : Base(b) {} + Self& operator++() { Base::operator++(); return *this; } + Self& operator--() { Base::operator--(); return *this; } + Self operator++(int) { Self tmp(*this); ++(*this); return tmp; } + Self operator--(int) { Self tmp(*this); --(*this); return tmp; } + operator Face_handle() const { return Base::base(); } + }; + + // Inserts point p in the triangulation and returns the corresponding vertex. + Vertex_handle insert(const Point &p, Face_handle f = Face_handle()) { + return Base_triangulation::insert(p, f); + } + + // Add constraint from va to vb using the odd-even rule + void even_odd_insert_constraint(Vertex_handle va, Vertex_handle vb) { + + // Degenerate edge + if (va == vb) return; + + // [va, vb] is either an existing edge OR + // there's an existing shorter edge from va in the direction of vb + Vertex_handle vc; // [va, vc] is the first edge along [va, vb] + Face_handle incident_face; // incident to [va, vc] + int opposite_vertex; // opposite to [va, vc] + if (Base_triangulation::includes_edge(va, vb, vc, incident_face, opposite_vertex)) { + if (Base_triangulation::is_constrained(Edge(incident_face, opposite_vertex))) { + Base_triangulation::remove_constrained_edge(incident_face, opposite_vertex); + } else Base_triangulation::mark_constraint(incident_face, opposite_vertex); + if (vc != vb) even_odd_insert_constraint(vc, vb); // process edges along [vc, vb] + return; + } + + // [va, vb] intersects a constrained edge or an existing vertex + List_faces intersected_faces; + List_edges conflict_boundary_ab, conflict_boundary_ba; + Vertex_handle intersection; + if (Base_triangulation::find_intersected_faces(va, vb, intersected_faces, conflict_boundary_ab, conflict_boundary_ba, intersection)) { + if (intersection != va && intersection != vb) { + even_odd_insert_constraint(va, intersection); + even_odd_insert_constraint(intersection, vb); + } else even_odd_insert_constraint(va, vb); + return; + } + + // Otherwise + Base_triangulation::triangulate_hole(intersected_faces, conflict_boundary_ab, conflict_boundary_ba); + if (intersection != vb) { + even_odd_insert_constraint(intersection, vb); + } + } + + // Add constraint from pa to pb using the odd-even rule + void even_odd_insert_constraint(Point pa, Point pb) { + Vertex_handle va = insert(pa); + Vertex_handle vb = insert(pb, va->face()); // vb is likely close to va + even_odd_insert_constraint(va, vb); + } + + // Starts at an arbitrary interior face + Interior_faces_iterator interior_faces_begin() { + return CGAL::filter_iterator(Base_triangulation::all_faces_end(), + Interior_tester(this), + Base_triangulation::all_faces_begin()); + } + + // Past-the-end iterator + Interior_faces_iterator interior_faces_end() { + return CGAL::filter_iterator(Base_triangulation::all_faces_end(), + Interior_tester(this)); + } +}; + +} // namespace internal +} // namespace Polygon_repair +} // namespace CGAL + +#endif // CGAL_TRIANGULATION_WITH_EVEN_ODD_CONSTRAINTS_2_H diff --git a/Polygon_repair/include/CGAL/Polygon_repair/repair.h b/Polygon_repair/include/CGAL/Polygon_repair/repair.h new file mode 100644 index 000000000000..0188f1630fb1 --- /dev/null +++ b/Polygon_repair/include/CGAL/Polygon_repair/repair.h @@ -0,0 +1,708 @@ +// Copyright (c) 2023 GeometryFactory. +// All rights reserved. +// +// This file is part of CGAL (www.cgal.org). +// +// $URL$ +// $Id$ +// SPDX-License-Identifier: GPL-3.0-or-later OR LicenseRef-Commercial +// +// Author(s) : Ken Arroyo Ohori + +#ifndef CGAL_POLYGON_REPAIR_H +#define CGAL_POLYGON_REPAIR_H + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace CGAL { + +namespace Polygon_repair { + +#ifndef DOXYGEN_RUNNING +template +class Polygon_repair; +#endif + +/// \ingroup PkgPolygonRepairFunctions +/// repairs polygon `p` using the given rule +/// \tparam Kernel parameter of the input and output polygons +/// \tparam Container parameter of the input and output polygons +/// \tparam Rule must be `Even_odd_rule` +template +Multipolygon_with_holes_2 repair(const Polygon_2& p , Rule = Rule()) +{ + static_assert(std::is_same_v); + CGAL::Polygon_repair::Polygon_repair pr; + pr.add_to_triangulation_even_odd(p); + if (pr.triangulation().number_of_faces() > 0) { + pr.label_triangulation_even_odd(); + pr.reconstruct_multipolygon(); + } return pr.multipolygon(); +} + +/// \ingroup PkgPolygonRepairFunctions +/// repairs polygon with holes `p` using the given rule +/// \tparam Kernel parameter of the input and output polygons +/// \tparam Container parameter of the input and output polygons +/// \tparam Rule must be `Even_odd_rule` +template +Multipolygon_with_holes_2 repair(const Polygon_with_holes_2& p, Rule = Rule()) +{ + static_assert(std::is_same_v); + CGAL::Polygon_repair::Polygon_repair pr; + pr.add_to_triangulation_even_odd(p); + if (pr.triangulation().number_of_faces() > 0) { + pr.label_triangulation_even_odd(); + pr.reconstruct_multipolygon(); + } return pr.multipolygon(); +} + +/// \ingroup PkgPolygonRepairFunctions +/// repairs multipolygon with holes `p` using the given rule +/// \tparam Kernel parameter of the input and output polygons +/// \tparam Container parameter of the input and output polygons +/// \tparam Rule must be `Even_odd_rule` +template +Multipolygon_with_holes_2 repair(const Multipolygon_with_holes_2& p, Rule = Rule()) +{ + static_assert(std::is_same_v); + CGAL::Polygon_repair::Polygon_repair pr; + pr.add_to_triangulation_even_odd(p); + if (pr.triangulation().number_of_faces() > 0) { + pr.label_triangulation_even_odd(); + pr.reconstruct_multipolygon(); + } return pr.multipolygon(); +} + +template +bool is_valid(const Polygon_2& polygon) { + if (polygon.vertices().size() < 3) { + std::cout << "Invalid: less than 3 vertices" << std::endl; + return false; + } for (auto const& edge: polygon.edges()) { + if (edge.source() == edge.target()) { + std::cout << "Invalid: duplicate vertices" << std::endl; + return false; + } + } if (!polygon.is_simple()) { + std::cout << "Invalid: not simple" << std::endl; + return false; + } return true; +} + +template +bool is_valid(const Polygon_with_holes_2& polygon) { + + // Validate outer boundary + for (auto const& edge: polygon.outer_boundary().edges()) { + if (edge.source() == edge.target()) { + std::cout << "Invalid: duplicate vertices in outer boundary" << std::endl; + return false; + } + } if (!polygon.outer_boundary().is_simple()) { + std::cout << "Invalid: outer boundary not simple" << std::endl; + return false; + } + + // Validate holes + for (auto const& hole: polygon.holes()) { + for (auto const& edge: hole.edges()) { + if (edge.source() == edge.target()) { + std::cout << "Invalid: duplicate vertices in hole" << std::endl; + return false; + } + } if (!hole.is_simple()) { + std::cout << "Invalid: hole not simple" << std::endl; + return false; + } + } + + // Create triangulation of outer boundary + typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation vt; + for (auto const& edge: polygon.outer_boundary().edges()) { + try { + vt.insert_constraint(edge.source(), edge.target()); + } catch (typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Intersection_of_constraints_exception ice) { + std::cout << "Invalid: intersection in outer boundary" << std::endl; + return false; + } + } if (vt.number_of_faces() == 0) { + std::cout << "Invalid: no outer boundary" << std::endl; + return false; + } for (auto const face: vt.all_face_handles()) { + face->label() = 0; + face->processed() = false; + } std::list::Validation_triangulation::Face_handle> to_check; + std::list to_check_added_by; + CGAL::Polygon_repair::Polygon_repair::label_region(vt, vt.infinite_face(), -1, to_check, to_check_added_by); // exterior + int regions = 0, holes = 0; + while (!to_check.empty()) { + if (to_check.front()->label() == 0) { // label = 0 means not labeled yet + if (to_check_added_by.front() < 0) { + CGAL::Polygon_repair::Polygon_repair::label_region(vt, to_check.front(), regions+1, to_check, to_check_added_by); + ++regions; + } else { + CGAL::Polygon_repair::Polygon_repair::label_region(vt, to_check.front(), -(holes+2), to_check, to_check_added_by); + ++holes; + } + } to_check.pop_front(); + to_check_added_by.pop_front(); + } CGAL_assertion(regions == 1 && holes == 0); + + // Hole nesting + for (auto const& hole: polygon.holes()) { + for (auto const& vertex: hole.vertices()) { + typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Locate_type lt; + int li; + typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Face_handle f = vt.locate(vertex, lt, li); + if (lt == CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Locate_type::FACE && f->label() != 1) { + std::cout << "Invalid: hole (partly) outside outer boundary" << std::endl; + return false; + } + } + for (auto const& edge: hole.edges()) { + try { + vt.insert_constraint(edge.source(), edge.target()); + } catch (typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Intersection_of_constraints_exception ice) { + std::cout << "Invalid: hole (partly) outside outer boundary" << std::endl; + return false; + } + } + } + + // Connected interior + for (auto const face: vt.all_face_handles()) { + face->label() = 0; + face->processed() = false; + } to_check.clear(); + to_check_added_by.clear(); + CGAL::Polygon_repair::Polygon_repair::label_region(vt, vt.infinite_face(), -1, to_check, to_check_added_by); // exterior + regions = 0; + holes = 0; + while (!to_check.empty()) { + if (to_check.front()->label() == 0) { // label = 0 means not labeled yet + if (to_check_added_by.front() < 0) { + CGAL::Polygon_repair::Polygon_repair::label_region(vt, to_check.front(), regions+1, to_check, to_check_added_by); + ++regions; + } else { + CGAL::Polygon_repair::Polygon_repair::label_region(vt, to_check.front(), -(holes+2), to_check, to_check_added_by); + ++holes; + } + } to_check.pop_front(); + to_check_added_by.pop_front(); + } if (regions != 1) { + std::cout << "Invalid: disconnected interior" << std::endl; + return false; + } CGAL_assertion(holes == polygon.number_of_holes()); + + return true; +} + +template +bool is_valid(const Multipolygon_with_holes_2& multipolygon) { + + // Validate polygons + for (auto const& polygon: multipolygon.polygons_with_holes()) { + if (!is_valid(polygon)) return false; + } + + typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation vt; + typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Locate_type lt; + int li; + for (auto const& polygon: multipolygon.polygons_with_holes()) { + + + if (vt.number_of_faces() > 0) { + + // Relabel + for (auto const face: vt.all_face_handles()) { + face->label() = 0; + face->processed() = false; + } std::list::Validation_triangulation::Face_handle> to_check; + std::list to_check_added_by; + CGAL::Polygon_repair::Polygon_repair::label_region(vt, vt.infinite_face(), -1, to_check, to_check_added_by); // exterior + int regions = 0, holes = 0; + while (!to_check.empty()) { + if (to_check.front()->label() == 0) { // label = 0 means not labeled yet + if (to_check_added_by.front() < 0) { + CGAL::Polygon_repair::Polygon_repair::label_region(vt, to_check.front(), regions+1, to_check, to_check_added_by); + ++regions; + } else { + CGAL::Polygon_repair::Polygon_repair::label_region(vt, to_check.front(), -(holes+2), to_check, to_check_added_by); + ++holes; + } + } to_check.pop_front(); + to_check_added_by.pop_front(); + } + + // Test vertices in labeled triangulation + for (auto const& vertex: polygon.outer_boundary().vertices()) { + typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Face_handle f = vt.locate(vertex, lt, li); + if (lt == CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Locate_type::FACE && f->label() > 0) { + std::cout << "Invalid: (partly) overlapping polygons" << std::endl; + return false; + } + } + for (auto const& hole: polygon.holes()) { + for (auto const& vertex: hole.vertices()) { + typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Face_handle f = vt.locate(vertex, lt, li); + if (lt == CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Locate_type::FACE && f->label() > 0) { + std::cout << "Invalid: (partly) overlapping polygons" << std::endl; + return false; + } + } + } + + } + + // Insert constraints while checking for intersections + for (auto const& edge: polygon.outer_boundary().edges()) { + try { + vt.insert_constraint(edge.source(), edge.target()); + } catch (typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Intersection_of_constraints_exception ice) { + std::cout << "Invalid: (partly) overlapping polygons" << std::endl; + return false; + } + } + for (auto const& hole: polygon.holes()) { + for (auto const& edge: hole.edges()) { + try { + vt.insert_constraint(edge.source(), edge.target()); + } catch (typename CGAL::Polygon_repair::Polygon_repair::Validation_triangulation::Intersection_of_constraints_exception ice) { + std::cout << "Invalid: (partly) overlapping polygons" << std::endl; + return false; + } + } + } + } + + return true; +} + +#ifndef DOXYGEN_RUNNING + +template > +class Polygon_repair { +public: + using Vertex_base = CGAL::Triangulation_vertex_base_2; + using Face_base = CGAL::Constrained_triangulation_face_base_2; + using Face_base_with_repair_info = internal::Triangulation_face_base_with_repair_info_2; + using Triangulation_data_structure = CGAL::Triangulation_data_structure_2; + using Tag = typename std::conditional::value, + CGAL::Exact_predicates_tag, + CGAL::Exact_intersections_tag>::type; + using Constrained_Delaunay_triangulation = CGAL::Constrained_Delaunay_triangulation_2; + using Triangulation = internal::Triangulation_with_even_odd_constraints_2; + // TODO: Edge_map and Vertex_map use std::set and set::map with exact kernels since Point_2 can't be hashed otherwise + using Edge_map = typename std::conditional::value, + std::unordered_set, + boost::hash>>, + std::set>>::type; + using Vertex_map = typename std::conditional::value, + std::unordered_map, + std::map>::type; + + using Validation_tag = CGAL::No_constraint_intersection_tag; + using Validation_triangulation = CGAL::Constrained_triangulation_2; + + struct Polygon_less { + using Polygon_2 = CGAL::Polygon_2; + bool operator()(const Polygon_2& pa, const Polygon_2& pb) const { + typename Polygon_2::Vertex_iterator va = pa.vertices_begin(); + typename Polygon_2::Vertex_iterator vb = pb.vertices_begin(); + while (va != pa.vertices_end() && vb != pb.vertices_end()) { + if (*va != *vb) return *va < *vb; + ++va; + ++vb; + } + if (vb == pb.vertices_end()) return false; + return true; + } + }; + + struct Polygon_with_holes_less { + using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; + Polygon_less pl; + bool operator()(const Polygon_with_holes_2& pa, const Polygon_with_holes_2& pb) const { + if (pl(pa.outer_boundary(), pb.outer_boundary())) return true; + if (pl(pb.outer_boundary(), pa.outer_boundary())) return false; + typename Polygon_with_holes_2::Hole_const_iterator ha = pa.holes_begin(); + typename Polygon_with_holes_2::Hole_const_iterator hb = pb.holes_begin(); + while (ha != pa.holes_end() && hb != pb.holes_end()) { + if (pl(*ha, *hb)) return true; + if (pl(*hb, *ha)) return false; + } + if (hb == pb.holes_end()) return false; + return true; + } + }; + + /// \name Creation + Polygon_repair() : number_of_polygons(0), number_of_holes(0) {} + + /// \name Modifiers + /// @{ + + // Add edges of the polygon to the triangulation + void add_to_triangulation_even_odd(const Polygon_2& polygon) { + + // Get unique edges + for (auto const& edge: polygon.edges()) { + if (edge.source() == edge.target()) continue; + std::pair pair = (edge.source() < edge.target())? + std::make_pair(edge.source(), edge.target()) : std::make_pair(edge.target(), edge.source()); + auto inserted = unique_edges.insert(pair); + if (!inserted.second) unique_edges.erase(inserted.first); + } + + // Insert vertices + Vertex_map vertices; + std::vector> edges_to_insert; + edges_to_insert.reserve(unique_edges.size()); + for (auto const& edge: unique_edges) { + typename Triangulation::Vertex_handle first_vertex, second_vertex; + typename Vertex_map::const_iterator found = vertices.find(edge.first); + if (found == vertices.end()) { + first_vertex = t.insert(edge.first, search_start); + vertices[edge.first] = first_vertex; + } else { + first_vertex = found->second; + } search_start = first_vertex->face(); + found = vertices.find(edge.second); + if (found == vertices.end()) { + second_vertex = t.insert(edge.second, search_start); + vertices[edge.second] = second_vertex; + } else { + second_vertex = found->second; + } search_start = second_vertex->face(); + edges_to_insert.emplace_back(first_vertex, second_vertex); + } + + // Insert edges + for (auto const& edge: edges_to_insert) { + t.even_odd_insert_constraint(edge.first, edge.second); + } + } + + // Add edges of the polygon to the triangulation + void add_to_triangulation_even_odd(const Polygon_with_holes_2& polygon) { + + // Get unique edges + for (auto const& edge: polygon.outer_boundary().edges()) { + if (edge.source() == edge.target()) continue; + std::pair pair = (edge.source() < edge.target())? + std::make_pair(edge.source(), edge.target()) : std::make_pair(edge.target(), edge.source()); + auto inserted = unique_edges.insert(pair); + if (!inserted.second) unique_edges.erase(inserted.first); + } + for (auto const& hole: polygon.holes()) { + for (auto const& edge: hole.edges()) { + if (edge.source() == edge.target()) continue; + std::pair pair = (edge.source() < edge.target())? + std::make_pair(edge.source(), edge.target()) : std::make_pair(edge.target(), edge.source()); + auto inserted = unique_edges.insert(pair); + if (!inserted.second) unique_edges.erase(inserted.first); + } + } + + // Insert vertices + Vertex_map vertices; + std::vector> edges_to_insert; + edges_to_insert.reserve(unique_edges.size()); + for (auto const& edge: unique_edges) { + typename Triangulation::Vertex_handle first_vertex, second_vertex; + typename Vertex_map::const_iterator found = vertices.find(edge.first); + if (found == vertices.end()) { + first_vertex = t.insert(edge.first, search_start); + vertices[edge.first] = first_vertex; + } else { + first_vertex = found->second; + } search_start = first_vertex->face(); + found = vertices.find(edge.second); + if (found == vertices.end()) { + second_vertex = t.insert(edge.second, search_start); + vertices[edge.second] = second_vertex; + } else { + second_vertex = found->second; + } search_start = second_vertex->face(); + edges_to_insert.emplace_back(first_vertex, second_vertex); + } + + // Insert edges + for (auto const& edge: edges_to_insert) { + t.even_odd_insert_constraint(edge.first, edge.second); + } + } + + // Add edges of the polygon to the triangulation + void add_to_triangulation_even_odd(const Multipolygon_with_holes_2& multipolygon) { + + // Get unique edges + for (auto const& polygon: multipolygon.polygons_with_holes()) { + for (auto const& edge: polygon.outer_boundary().edges()) { + if (edge.source() == edge.target()) continue; + std::pair pair = (edge.source() < edge.target())? + std::make_pair(edge.source(), edge.target()) : std::make_pair(edge.target(), edge.source()); + auto inserted = unique_edges.insert(pair); + if (!inserted.second) unique_edges.erase(inserted.first); + } + for (auto const& hole: polygon.holes()) { + for (auto const& edge: hole.edges()) { + if (edge.source() == edge.target()) continue; + std::pair pair = (edge.source() < edge.target())? + std::make_pair(edge.source(), edge.target()) : std::make_pair(edge.target(), edge.source()); + auto inserted = unique_edges.insert(pair); + if (!inserted.second) unique_edges.erase(inserted.first); + } + } + } + + // Insert vertices + Vertex_map vertices; + std::vector> edges_to_insert; + edges_to_insert.reserve(unique_edges.size()); + for (auto const& edge: unique_edges) { + typename Triangulation::Vertex_handle first_vertex, second_vertex; + typename Vertex_map::const_iterator found = vertices.find(edge.first); + if (found == vertices.end()) { + first_vertex = t.insert(edge.first, search_start); + vertices[edge.first] = first_vertex; + } else { + first_vertex = found->second; + } search_start = first_vertex->face(); + found = vertices.find(edge.second); + if (found == vertices.end()) { + second_vertex = t.insert(edge.second, search_start); + vertices[edge.second] = second_vertex; + } else { + second_vertex = found->second; + } search_start = second_vertex->face(); + edges_to_insert.emplace_back(first_vertex, second_vertex); + } + + // Insert edges + for (auto const& edge: edges_to_insert) { + t.even_odd_insert_constraint(edge.first, edge.second); + } + } + + // Label a region of adjacent triangles without passing through constraints + // adjacent triangles that involve passing through constraints are added to to_check + template + static void label_region(T& tt, typename T::Face_handle face, int label, + std::list& to_check, + std::list& to_check_added_by) { + // std::cout << "Labelling region with " << label << std::endl; + std::list to_check_in_region; + face->label() = label; + to_check_in_region.push_back(face); + face->processed() = true; // processed means added to a list (to ensure elements are only added once) + + while (!to_check_in_region.empty()) { + for (int neighbour = 0; neighbour < 3; ++neighbour) { + if (!tt.is_constrained(typename Triangulation::Edge(to_check_in_region.front(), neighbour))) { + if (to_check_in_region.front()->neighbor(neighbour)->label() == 0) { // unlabeled + to_check_in_region.front()->neighbor(neighbour)->label() = label; + to_check_in_region.push_back(to_check_in_region.front()->neighbor(neighbour)); + to_check_in_region.front()->neighbor(neighbour)->processed() = true; + } + } else { // constrained + if (!to_check_in_region.front()->neighbor(neighbour)->processed()) { // not added to to_check + to_check.push_back(to_check_in_region.front()->neighbor(neighbour)); + to_check_added_by.push_back(label); + to_check_in_region.front()->neighbor(neighbour)->processed() = true; + } + } + } to_check_in_region.pop_front(); + } + } + + // Label triangles in triangulation + void label_triangulation_even_odd() { + + // Simplify collinear edges (gets rid of order dependency) + for (auto vertex: t.all_vertex_handles()) { + typename Triangulation::Edge_circulator first_edge = t.incident_edges(vertex); + typename Triangulation::Edge_circulator current_edge = first_edge; + std::vector incident_constrained_edges; + do { + if (t.is_constrained(*current_edge)) { + incident_constrained_edges.push_back(*current_edge); + } ++current_edge; + } while (current_edge != first_edge); + if (incident_constrained_edges.size() == 2) { + typename Kernel::Point_2 v1 = incident_constrained_edges.front().first->vertex(incident_constrained_edges.front().first->ccw(incident_constrained_edges.front().second))->point(); + typename Kernel::Point_2 v2 = incident_constrained_edges.back().first->vertex(incident_constrained_edges.back().first->ccw(incident_constrained_edges.back().second))->point(); + if (CGAL::collinear(v1, vertex->point(), v2)) { + // std::cout << "Collinear points" << std::endl; + // std::cout << "v1: " << v1 << std::endl; + // std::cout << "in: " << vertex->point() << std::endl; + // std::cout << "v2: " << v2 << std::endl; + t.remove_incident_constraints(vertex); + t.remove(vertex); + t.insert_constraint(v1, v2); + } + } + } + + // Init labels + for (auto const face: t.all_face_handles()) { + face->label() = 0; + face->processed() = false; + } + + // Label exterior with label -1, marking it as processed and + // putting interior triangles adjacent to it in to_check + std::list to_check; + std::list to_check_added_by; + label_region(t, t.infinite_face(), -1, to_check, to_check_added_by); + + // Label region of front element to_check list + while (!to_check.empty()) { + + if (to_check.front()->label() == 0) { // label = 0 means not labeled yet + if (to_check_added_by.front() < 0) { + label_region(t, to_check.front(), number_of_polygons+1, to_check, to_check_added_by); + ++number_of_polygons; + } else { + label_region(t, to_check.front(), -(number_of_holes+2), to_check, to_check_added_by); + ++number_of_holes; + } + } to_check.pop_front(); + to_check_added_by.pop_front(); + + } // std::cout << number_of_polygons << " polygons with " << number_of_holes << " holes in triangulation" << std::endl; + } + + // Reconstruct ring boundary starting from an edge (face + opposite vertex) that is part of it + void reconstruct_ring(std::list& ring, + typename Triangulation::Face_handle face_adjacent_to_boundary, + int opposite_vertex) { + // std::cout << "Reconstructing ring for face " << face_adjacent_to_boundary->label() << "..." << std::endl; + + // Create ring + typename Triangulation::Face_handle current_face = face_adjacent_to_boundary; + int current_opposite_vertex = opposite_vertex; + do { + CGAL_assertion(current_face->label() == face_adjacent_to_boundary->label()); + current_face->processed() = true; + typename Triangulation::Vertex_handle pivot_vertex = current_face->vertex(current_face->cw(current_opposite_vertex)); + // std::cout << "\tAdding point " << pivot_vertex->point() << std::endl; + ring.push_back(pivot_vertex->point()); + typename Triangulation::Face_circulator fc = t.incident_faces(pivot_vertex, current_face); + do { + ++fc; + } while (fc->label() != current_face->label()); + current_face = fc; + current_opposite_vertex = fc->cw(fc->index(pivot_vertex)); + } while (current_face != face_adjacent_to_boundary || + current_opposite_vertex != opposite_vertex); + + // Start at lexicographically smallest vertex + typename std::list::iterator smallest_vertex = ring.begin(); + for (typename std::list::iterator current_vertex = ring.begin(); + current_vertex != ring.end(); ++current_vertex) { + if (*current_vertex < *smallest_vertex) smallest_vertex = current_vertex; + } + if (ring.front() != *smallest_vertex) { + ring.splice(ring.begin(), ring, smallest_vertex, ring.end()); + } + } + + // Reconstruct multipolygon based on the triangles labeled as inside the polygon + void reconstruct_multipolygon() { + mp.clear(); + std::vector> polygons; // outer boundaries + std::vector, Polygon_less>> holes; // holes are ordered (per polygon) + polygons.resize(number_of_polygons); + holes.resize(number_of_polygons); + + for (auto const face: t.all_face_handles()) { + face->processed() = false; + } + for (auto const &face: t.finite_face_handles()) { + if (face->label() < 1) continue; // exterior triangle + if (face->processed()) continue; // already reconstructed + for (int opposite_vertex = 0; opposite_vertex < 3; ++opposite_vertex) { + if (face->label() == face->neighbor(opposite_vertex)->label()) continue; // not adjacent to boundary + + // Reconstruct ring + std::list ring; + reconstruct_ring(ring, face, opposite_vertex); + + // Put ring in polygons + Polygon_2 polygon(ring.begin(), ring.end()); + // std::cout << "Reconstructed ring for polygon " << face->label() << " with ccw? " << (polygon.orientation() == CGAL::COUNTERCLOCKWISE) << std::endl; + if (polygon.orientation() == CGAL::COUNTERCLOCKWISE) { + polygons[face->label()-1] = polygon; + } else { + holes[face->label()-1].insert(polygon); + } break; + } + } + + // Create polygons with holes and put in multipolygon + std::set, Polygon_with_holes_less> ordered_polygons; + for (std::size_t i = 0; i < polygons.size(); ++i) { + ordered_polygons.insert(Polygon_with_holes_2(polygons[i], holes[i].begin(), holes[i].end())); + } + for (auto const& polygon: ordered_polygons) { + // std::cout << "Adding polygon " << polygon << std::endl; + mp.add_polygon_with_holes(polygon); + } + } + + // Erases the triangulation. + void clear() { + t.clear(); + unique_edges.clear(); + mp.clear(); + number_of_polygons = 0; + number_of_holes = 0; + search_start = Triangulation::Face_handle(); + } + + /// @} + + /// \name Access Functions + /// @{ + + Triangulation& triangulation() { + return t; + } + + Multipolygon_with_holes_2 multipolygon() { + return mp; + } + + /// @} + + +protected: + Triangulation t; + Edge_map unique_edges; + Multipolygon_with_holes_2 mp; + int number_of_polygons, number_of_holes; + typename Triangulation::Face_handle search_start; +}; + +#endif // DOXYGEN_RUNNING + +} // namespace Polygon_repair +} // namespace CGAL + +#endif // CGAL_POLYGON_REPAIR_H diff --git a/Polygon_repair/package_info/Polygon_repair/copyright b/Polygon_repair/package_info/Polygon_repair/copyright new file mode 100644 index 000000000000..b9a65603a2ee --- /dev/null +++ b/Polygon_repair/package_info/Polygon_repair/copyright @@ -0,0 +1 @@ +GeometryFactory (France) diff --git a/Polygon_repair/package_info/Polygon_repair/dependencies b/Polygon_repair/package_info/Polygon_repair/dependencies new file mode 100644 index 000000000000..107d9f6d0d96 --- /dev/null +++ b/Polygon_repair/package_info/Polygon_repair/dependencies @@ -0,0 +1,24 @@ +Algebraic_foundations +Arithmetic_kernel +Cartesian_kernel +Circulator +Distance_2 +Distance_3 +Filtered_kernel +Hash_map +Installation +Intersections_2 +Intersections_3 +Interval_support +Kernel_23 +Modular_arithmetic +Number_types +Polygon +Polygon_repair +Profiling_tools +Property_map +STL_Extension +Spatial_sorting +Stream_support +TDS_2 +Triangulation_2 diff --git a/Polygon_repair/package_info/Polygon_repair/description.txt b/Polygon_repair/package_info/Polygon_repair/description.txt new file mode 100644 index 000000000000..faa7c2ab3f9c --- /dev/null +++ b/Polygon_repair/package_info/Polygon_repair/description.txt @@ -0,0 +1 @@ +Package Polygon_repair provides functions to repair polygons. diff --git a/Polygon_repair/package_info/Polygon_repair/license.txt b/Polygon_repair/package_info/Polygon_repair/license.txt new file mode 100644 index 000000000000..8bb8efcb72b0 --- /dev/null +++ b/Polygon_repair/package_info/Polygon_repair/license.txt @@ -0,0 +1 @@ +GPL (v3 or later) diff --git a/Polygon_repair/package_info/Polygon_repair/long_description.txt b/Polygon_repair/package_info/Polygon_repair/long_description.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/Polygon_repair/package_info/Polygon_repair/maintainer b/Polygon_repair/package_info/Polygon_repair/maintainer new file mode 100644 index 000000000000..2427333ef578 --- /dev/null +++ b/Polygon_repair/package_info/Polygon_repair/maintainer @@ -0,0 +1 @@ +GeometryFactory diff --git a/Polygon_repair/test/Polygon_repair/CMakeLists.txt b/Polygon_repair/test/Polygon_repair/CMakeLists.txt new file mode 100644 index 000000000000..faced1f88058 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/CMakeLists.txt @@ -0,0 +1,25 @@ +# Created by the script cgal_create_cmake_script +# This is the CMake script for compiling a CGAL application. + +cmake_minimum_required(VERSION 3.1...3.23) +project(Polygon_repair_Tests) + +find_package(CGAL REQUIRED OPTIONAL_COMPONENTS Qt6) + +# create a target per cppfile +#file( +# GLOB cppfiles +# RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} +# ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp) +#foreach(cppfile ${cppfiles}) +# create_single_source_cgal_program("${cppfile}") +#endforeach() + +create_single_source_cgal_program( "draw_test_polygons.cpp" ) +create_single_source_cgal_program( "exact_test.cpp") +create_single_source_cgal_program( "repair_polygon_2_test.cpp" ) + +if(CGAL_Qt6_FOUND) + target_link_libraries(draw_test_polygons PUBLIC CGAL::CGAL_Basic_viewer) + target_link_libraries(exact_test PUBLIC CGAL::CGAL_Basic_viewer) +endif() diff --git a/Polygon_repair/test/Polygon_repair/data/in/back-and-forth.wkt b/Polygon_repair/test/Polygon_repair/data/in/back-and-forth.wkt new file mode 100644 index 000000000000..3911b8f5203a --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/back-and-forth.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 1,0 0,1 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/bowtie.wkt b/Polygon_repair/test/Polygon_repair/data/in/bowtie.wkt new file mode 100644 index 000000000000..3005eb3e3f60 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/bowtie.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 1,1 0,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/bridge-edge.wkt b/Polygon_repair/test/Polygon_repair/data/in/bridge-edge.wkt new file mode 100644 index 000000000000..1e86cf795061 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/bridge-edge.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25,0.25 0.75,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/crossing-polygons-nesting.wkt b/Polygon_repair/test/Polygon_repair/data/in/crossing-polygons-nesting.wkt new file mode 100644 index 000000000000..6b945e81ff7d --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/crossing-polygons-nesting.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 1,3 1,3 2,0 2,0 1)),((1 0,2 0,2 3,1 3,1 0)),((1.25 1.25,1.75 1.25,1.75 1.75,1.25 1.75,1.25 1.25))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/crossing-polygons.wkt b/Polygon_repair/test/Polygon_repair/data/in/crossing-polygons.wkt new file mode 100644 index 000000000000..662e1017f9c6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/crossing-polygons.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 1,3 1,3 2,0 2,0 1)),((1 0,2 0,2 3,1 3,1 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/edge.wkt b/Polygon_repair/test/Polygon_repair/data/in/edge.wkt new file mode 100644 index 000000000000..66839d345315 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/edge.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/empty-multipolygon.wkt b/Polygon_repair/test/Polygon_repair/data/in/empty-multipolygon.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/empty-multipolygon.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/empty-polygon.wkt b/Polygon_repair/test/Polygon_repair/data/in/empty-polygon.wkt new file mode 100644 index 000000000000..aec93ace4b9b --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/empty-polygon.wkt @@ -0,0 +1 @@ +POLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-all.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-all.wkt new file mode 100644 index 000000000000..34482d54ef09 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-all.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0 0,1 0,1 1,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-as-loop.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-as-loop.wkt new file mode 100644 index 000000000000..8c5a4a1c74c9 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-as-loop.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0.25 0.25,0.75 0.25,0.75 0.75,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-carved.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-carved.wkt new file mode 100644 index 000000000000..7b20d27fcfb9 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-carved.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.5 0.5,1 0.5,1 1,0.5 1,0.5 0.5)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-filled.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-filled.wkt new file mode 100644 index 000000000000..ca7167347792 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-filled.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.75 0.25,0.75 0.75,0.25 0.75,0.25 0.25)),((0.25 0.25,0.75 0.25,0.75 0.75,0.25 0.75,0.25 0.25))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-outside.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-outside.wkt new file mode 100644 index 000000000000..9d16d9c87cae --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-outside.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(1.25 1.25,1.75 1.25,1.75 1.75,1.25 1.75,1.25 1.25)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-partly-outside.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-partly-outside.wkt new file mode 100644 index 000000000000..da99d6a31e50 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-partly-outside.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.75 0.75,1.25 0.75,1.25 1.25,0.75 1.25,0.75 0.75)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-touching-edge.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-touching-edge.wkt new file mode 100644 index 000000000000..4953794c84a0 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-touching-edge.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.75 0.25,0.75 0.75,0.5 1,0.25 0.75,0.25 0.25)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-touching-once.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-touching-once.wkt new file mode 100644 index 000000000000..e9e070b92244 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-touching-once.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0 1,0.75 0.75,0.75 0.25,0.25 0.25,0 1)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole-touching-twice.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole-touching-twice.wkt new file mode 100644 index 000000000000..54f1083af911 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole-touching-twice.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,1 0,0.75 0.75,0 1,0.25 0.25)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/hole.wkt b/Polygon_repair/test/Polygon_repair/data/in/hole.wkt new file mode 100644 index 000000000000..6c0e1bfe42b2 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/hole.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.75 0.25,0.75 0.75,0.25 0.75,0.25 0.25)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/nesting-spike.wkt b/Polygon_repair/test/Polygon_repair/data/in/nesting-spike.wkt new file mode 100644 index 000000000000..e0df5a0dc7d8 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/nesting-spike.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.1 0.1,0.9 0.1,0.9 0.9,0.1 0.9,0.1 0.1)),((0.2 0.2,2 1,0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.2 0.2),(0.3 0.3,0.7 0.3,0.7 0.7,0.3 0.7,0.3 0.3))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/nesting.wkt b/Polygon_repair/test/Polygon_repair/data/in/nesting.wkt new file mode 100644 index 000000000000..142ea6dc746f --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/nesting.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.1 0.1,0.9 0.1,0.9 0.9,0.1 0.9,0.1 0.1)),((0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.2 0.2),(0.3 0.3,0.7 0.3,0.7 0.7,0.3 0.7,0.3 0.3))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/not-closed.wkt b/Polygon_repair/test/Polygon_repair/data/in/not-closed.wkt new file mode 100644 index 000000000000..ce5c6b3dbf05 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/not-closed.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge-inside.wkt b/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge-inside.wkt new file mode 100644 index 000000000000..646ca0d5b5ad --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge-inside.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)),((1 0.25,2 0.25,2 0.75,1 0.75,1 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge-partial.wkt b/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge-partial.wkt new file mode 100644 index 000000000000..2dc63c238221 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge-partial.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)),((1 0,2 0,2 0.5,1 0.5,1 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge.wkt b/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge.wkt new file mode 100644 index 000000000000..6e59f2e83e58 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/overlapping-edge.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)),((1 0,2 0,2 1,1 1,1 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/point-double.wkt b/Polygon_repair/test/Polygon_repair/data/in/point-double.wkt new file mode 100644 index 000000000000..ddecaff4c21b --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/point-double.wkt @@ -0,0 +1 @@ +POLYGON((0 0,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/point.wkt b/Polygon_repair/test/Polygon_repair/data/in/point.wkt new file mode 100644 index 000000000000..1c9d31654c2e --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/point.wkt @@ -0,0 +1 @@ +POLYGON((0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/spike-boundary.wkt b/Polygon_repair/test/Polygon_repair/data/in/spike-boundary.wkt new file mode 100644 index 000000000000..477315fcf87d --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/spike-boundary.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 1,0 0,1 0,1 1,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/spike-in.wkt b/Polygon_repair/test/Polygon_repair/data/in/spike-in.wkt new file mode 100644 index 000000000000..49f02f9d1e48 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/spike-in.wkt @@ -0,0 +1 @@ +POLYGON((0 0,0.5 0.5,0 0,1 0,1 1,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/spike-long.wkt b/Polygon_repair/test/Polygon_repair/data/in/spike-long.wkt new file mode 100644 index 000000000000..1668214ed240 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/spike-long.wkt @@ -0,0 +1 @@ +POLYGON((0 0,2 1,0 0,1 0,1 1,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/spike-out.wkt b/Polygon_repair/test/Polygon_repair/data/in/spike-out.wkt new file mode 100644 index 000000000000..7a95b726a2f8 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/spike-out.wkt @@ -0,0 +1 @@ +POLYGON((0 0,-0.5 -0.5,0 0,1 0,1 1,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/spikes-fp.wkt b/Polygon_repair/test/Polygon_repair/data/in/spikes-fp.wkt new file mode 100644 index 000000000000..2bb7ded142f5 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/spikes-fp.wkt @@ -0,0 +1 @@ +POLYGON((0.03 0.02,0.97 0.01,0.99 0.96,0.04 0.98,0.03 0.02),(0.5 0.5,1.5 0.5,0.5 0.5,1.5 0.7,0.5 0.5,1.5 0.9,0.5 0.5,1.5 1.1,0.5 0.5,1.5 1.3,0.5 0.5,1.5 1.5,0.5 0.5,1.3 1.5,0.5 0.5,1.1 1.5,0.5 0.5,0.9 1.5,0.5 0.5,0.7 1.5,0.5 0.5,0.5 1.5,0.5 0.5,0.3 1.5,0.5 0.5,0.1 1.5,0.5 0.5,-0.1 1.5,0.5 0.5,-0.3 1.5,0.5 0.5,-0.5 1.5,0.5 0.5,-0.5 1.3,0.5 0.5,-0.5 1.1,0.5 0.5,-0.5 0.9,0.5 0.5,-0.5 0.9,0.5 0.5,-0.5 0.7,0.5 0.5,-0.5 0.5,0.5 0.5,-0.5 0.3,0.5 0.5,-0.5 0.1,0.5 0.5,-0.5 -0.1,0.5 0.5,-0.5 -0.3,0.5 0.5,-0.5 -0.5,0.5 0.5,-0.3 -0.5,0.5 0.5,-0.1 -0.5,0.5 0.5,0.1 -0.5,0.5 0.5,0.3 -0.5,0.5 0.5,0.5 -0.5,0.5 0.5,0.7 -0.5,0.5 0.5,0.9 -0.5,0.5 0.5,1.1 -0.5,0.5 0.5,1.3 -0.5,0.5 0.5,1.5 -0.5,0.5 0.5,1.5 -0.3,0.5 0.5,1.5 -0.1,0.5 0.5,1.5 0.1,0.5 0.5,1.5 0.3,0.5 0.5)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/spikes.wkt b/Polygon_repair/test/Polygon_repair/data/in/spikes.wkt new file mode 100644 index 000000000000..c831dd8e2506 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/spikes.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.5 0.5,1.5 0.5,0.5 0.5,1.5 0.7,0.5 0.5,1.5 0.9,0.5 0.5,1.5 1.1,0.5 0.5,1.5 1.3,0.5 0.5,1.5 1.5,0.5 0.5,1.3 1.5,0.5 0.5,1.1 1.5,0.5 0.5,0.9 1.5,0.5 0.5,0.7 1.5,0.5 0.5,0.5 1.5,0.5 0.5,0.3 1.5,0.5 0.5,0.1 1.5,0.5 0.5,-0.1 1.5,0.5 0.5,-0.3 1.5,0.5 0.5,-0.5 1.5,0.5 0.5,-0.5 1.3,0.5 0.5,-0.5 1.1,0.5 0.5,-0.5 0.9,0.5 0.5,-0.5 0.9,0.5 0.5,-0.5 0.7,0.5 0.5,-0.5 0.5,0.5 0.5,-0.5 0.3,0.5 0.5,-0.5 0.1,0.5 0.5,-0.5 -0.1,0.5 0.5,-0.5 -0.3,0.5 0.5,-0.5 -0.5,0.5 0.5,-0.3 -0.5,0.5 0.5,-0.1 -0.5,0.5 0.5,0.1 -0.5,0.5 0.5,0.3 -0.5,0.5 0.5,0.5 -0.5,0.5 0.5,0.7 -0.5,0.5 0.5,0.9 -0.5,0.5 0.5,1.1 -0.5,0.5 0.5,1.3 -0.5,0.5 0.5,1.5 -0.5,0.5 0.5,1.5 -0.3,0.5 0.5,1.5 -0.1,0.5 0.5,1.5 0.1,0.5 0.5,1.5 0.3,0.5 0.5)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/spiral.wkt b/Polygon_repair/test/Polygon_repair/data/in/spiral.wkt new file mode 100644 index 000000000000..d79a0892188c --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/spiral.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0.1 0.1,0.9 0.1,0.9 0.9,0.1 0.9,0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.3 0.3,0.7 0.3,0.7 0.7,0.3 0.7,0.4 0.4,0.6 0.4,0.6 0.6,0.4 0.6,0.5 0.5,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/square-hole-rhombus.wkt b/Polygon_repair/test/Polygon_repair/data/in/square-hole-rhombus.wkt new file mode 100644 index 000000000000..1cfa76db480b --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/square-hole-rhombus.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0),(0.5 0,1 0.5,0.5 1,0 0.5,0.5 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/square.wkt b/Polygon_repair/test/Polygon_repair/data/in/square.wkt new file mode 100644 index 000000000000..d97ff8d423bf --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/square.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1 0,1 1,0 1,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/in/star.wkt b/Polygon_repair/test/Polygon_repair/data/in/star.wkt new file mode 100644 index 000000000000..eb43ea7debbf --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/in/star.wkt @@ -0,0 +1 @@ +POLYGON((0 0,1.5 3,3 0,0 1.5,3 3,1.5 0,0 3,3 1.5,0 0)) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/back-and-forth.wkt b/Polygon_repair/test/Polygon_repair/data/ref/back-and-forth.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/back-and-forth.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/bowtie.wkt b/Polygon_repair/test/Polygon_repair/data/ref/bowtie.wkt new file mode 100644 index 000000000000..0ca7c0da217b --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/bowtie.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,0.5 0.5,0 1,0 0)),((0.5 0.5,1 0,1 1,0.5 0.5))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/bridge-edge.wkt b/Polygon_repair/test/Polygon_repair/data/ref/bridge-edge.wkt new file mode 100644 index 000000000000..99b3f4c1cbbf --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/bridge-edge.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/crossing-polygons-nesting.wkt b/Polygon_repair/test/Polygon_repair/data/ref/crossing-polygons-nesting.wkt new file mode 100644 index 000000000000..64190bb04e92 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/crossing-polygons-nesting.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 1,1 1,1 2,0 2,0 1)),((1 0,2 0,2 1,1 1,1 0)),((1 2,2 2,2 3,1 3,1 2)),((1.25 1.25,1.75 1.25,1.75 1.75,1.25 1.75,1.25 1.25)),((2 1,3 1,3 2,2 2,2 1))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/crossing-polygons.wkt b/Polygon_repair/test/Polygon_repair/data/ref/crossing-polygons.wkt new file mode 100644 index 000000000000..815bc8c56258 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/crossing-polygons.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 1,1 1,1 2,0 2,0 1)),((1 0,2 0,2 1,1 1,1 0)),((1 2,2 2,2 3,1 3,1 2)),((2 1,3 1,3 2,2 2,2 1))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/edge.wkt b/Polygon_repair/test/Polygon_repair/data/ref/edge.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/edge.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/empty-multipolygon.wkt b/Polygon_repair/test/Polygon_repair/data/ref/empty-multipolygon.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/empty-multipolygon.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/empty-polygon.wkt b/Polygon_repair/test/Polygon_repair/data/ref/empty-polygon.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/empty-polygon.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-all.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-all.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-all.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-as-loop.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-as-loop.wkt new file mode 100644 index 000000000000..b5ee1d022877 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-as-loop.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0 1,0.75 0.75,0.75 0.25,0.25 0.25,0 1))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-carved.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-carved.wkt new file mode 100644 index 000000000000..d6e9a18d49e8 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-carved.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 0.5,0.5 0.5,0.5 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-filled.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-filled.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-filled.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-outside.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-outside.wkt new file mode 100644 index 000000000000..1d003ce0e92d --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-outside.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0)),((1.25 1.25,1.75 1.25,1.75 1.75,1.25 1.75,1.25 1.25))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-partly-outside.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-partly-outside.wkt new file mode 100644 index 000000000000..503189d5d256 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-partly-outside.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 0.75,0.75 0.75,0.75 1,0 1,0 0)),((0.75 1,1 1,1 0.75,1.25 0.75,1.25 1.25,0.75 1.25,0.75 1))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-edge.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-edge.wkt new file mode 100644 index 000000000000..4d3bb2343970 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-edge.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0.5 1,0 1,0 0),(0.25 0.25,0.25 0.75,0.5 1,0.75 0.75,0.75 0.25,0.25 0.25))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-once.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-once.wkt new file mode 100644 index 000000000000..b5ee1d022877 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-once.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0 1,0.75 0.75,0.75 0.25,0.25 0.25,0 1))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-twice.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-twice.wkt new file mode 100644 index 000000000000..bc530227b8fb --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole-touching-twice.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,0.25 0.25,0 1,0 0)),((0 1,0.75 0.75,1 0,1 1,0 1))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/hole.wkt b/Polygon_repair/test/Polygon_repair/data/ref/hole.wkt new file mode 100644 index 000000000000..99b3f4c1cbbf --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/hole.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.25 0.25,0.25 0.75,0.75 0.75,0.75 0.25,0.25 0.25))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/nesting-spike.wkt b/Polygon_repair/test/Polygon_repair/data/ref/nesting-spike.wkt new file mode 100644 index 000000000000..15587830fca1 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/nesting-spike.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.1 0.1,0.1 0.9,0.9 0.9,0.9 0.1,0.1 0.1)),((0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.2 0.2),(0.3 0.3,0.3 0.7,0.7 0.7,0.7 0.3,0.3 0.3))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/nesting.wkt b/Polygon_repair/test/Polygon_repair/data/ref/nesting.wkt new file mode 100644 index 000000000000..15587830fca1 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/nesting.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0),(0.1 0.1,0.1 0.9,0.9 0.9,0.9 0.1,0.1 0.1)),((0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.2 0.2),(0.3 0.3,0.3 0.7,0.7 0.7,0.7 0.3,0.3 0.3))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/not-closed.wkt b/Polygon_repair/test/Polygon_repair/data/ref/not-closed.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/not-closed.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge-inside.wkt b/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge-inside.wkt new file mode 100644 index 000000000000..f801b7ff6ecc --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge-inside.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 0.25,2 0.25,2 0.75,1 0.75,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge-partial.wkt b/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge-partial.wkt new file mode 100644 index 000000000000..5588dadf3e63 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge-partial.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,2 0,2 0.5,1 0.5,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge.wkt b/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge.wkt new file mode 100644 index 000000000000..ed4275a4e2a8 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/overlapping-edge.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,2 0,2 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/point-double.wkt b/Polygon_repair/test/Polygon_repair/data/ref/point-double.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/point-double.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/point.wkt b/Polygon_repair/test/Polygon_repair/data/ref/point.wkt new file mode 100644 index 000000000000..21e6672095b6 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/point.wkt @@ -0,0 +1 @@ +MULTIPOLYGON() \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/spike-boundary.wkt b/Polygon_repair/test/Polygon_repair/data/ref/spike-boundary.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/spike-boundary.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/spike-in.wkt b/Polygon_repair/test/Polygon_repair/data/ref/spike-in.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/spike-in.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/spike-long.wkt b/Polygon_repair/test/Polygon_repair/data/ref/spike-long.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/spike-long.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/spike-out.wkt b/Polygon_repair/test/Polygon_repair/data/ref/spike-out.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/spike-out.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/spikes-fp.wkt b/Polygon_repair/test/Polygon_repair/data/ref/spikes-fp.wkt new file mode 100644 index 000000000000..861654da6079 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/spikes-fp.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0.03 0.02,0.97 0.01,0.99 0.96,0.04 0.98,0.03 0.02))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/spikes.wkt b/Polygon_repair/test/Polygon_repair/data/ref/spikes.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/spikes.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/spiral.wkt b/Polygon_repair/test/Polygon_repair/data/ref/spiral.wkt new file mode 100644 index 000000000000..9ba30e469e2d --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/spiral.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0.1 0.1,0 0),(0.1 0.1,0.2 0.2,0.1 0.9,0.9 0.9,0.9 0.1,0.1 0.1)),((0.2 0.2,0.8 0.2,0.8 0.8,0.2 0.8,0.3 0.3,0.2 0.2),(0.3 0.3,0.4 0.4,0.3 0.7,0.7 0.7,0.7 0.3,0.3 0.3)),((0.4 0.4,0.6 0.4,0.6 0.6,0.4 0.6,0.5 0.5,0.4 0.4))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/square-hole-rhombus.wkt b/Polygon_repair/test/Polygon_repair/data/ref/square-hole-rhombus.wkt new file mode 100644 index 000000000000..d55952100cef --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/square-hole-rhombus.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,0.5 0,0 0.5,0 0)),((0 0.5,0.5 1,0 1,0 0.5)),((0.5 0,1 0,1 0.5,0.5 0)),((0.5 1,1 0.5,1 1,0.5 1))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/square.wkt b/Polygon_repair/test/Polygon_repair/data/ref/square.wkt new file mode 100644 index 000000000000..511245f448ea --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/square.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1 0,1 1,0 1,0 0))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/data/ref/star.wkt b/Polygon_repair/test/Polygon_repair/data/ref/star.wkt new file mode 100644 index 000000000000..b782e4bbd40e --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/data/ref/star.wkt @@ -0,0 +1 @@ +MULTIPOLYGON(((0 0,1.2 0.6,1 1,0.6 1.2,0 0)),((0 1.5,0.6 1.2,0.75 1.5,0.6 1.8,0 1.5)),((0 3,0.6 1.8,1 2,1.2 2.4,0 3)),((0.75 1.5,1 1,1.5 0.75,2 1,2.25 1.5,2 2,1.5 2.25,1 2,0.75 1.5)),((1.2 0.6,1.5 0,1.8 0.6,1.5 0.75,1.2 0.6)),((1.2 2.4,1.5 2.25,1.8 2.4,1.5 3,1.2 2.4)),((1.8 0.6,3 0,2.4 1.2,2 1,1.8 0.6)),((1.8 2.4,2 2,2.4 1.8,3 3,1.8 2.4)),((2.25 1.5,2.4 1.2,3 1.5,2.4 1.8,2.25 1.5))) \ No newline at end of file diff --git a/Polygon_repair/test/Polygon_repair/draw_test_polygons.cpp b/Polygon_repair/test/Polygon_repair/draw_test_polygons.cpp new file mode 100644 index 000000000000..a61e8f80531b --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/draw_test_polygons.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; +using Polygon_repair = CGAL::Polygon_repair::Polygon_repair; + +int main() { + + for (const auto& file: std::filesystem::directory_iterator("data/in")) { + if (file.path().filename().extension() != ".wkt") continue; + std::cout << "Reading " << file.path().filename() << "..." << std::endl; + + if (file.path().filename() != "nesting-spike.wkt") continue; + + std::string in; + std::getline(std::ifstream(file.path()), in); + std::istringstream iss(in); + Multipolygon_with_holes_2 rmp; + + if (in.find("POLYGON") == 0) { + Polygon_with_holes_2 p; + if (in != "POLYGON()") { // maybe should be checked in WKT reader + CGAL::IO::read_polygon_WKT(iss, p); + } CGAL::draw(p); + rmp = CGAL::Polygon_repair::repair(p, CGAL::Polygon_repair::Even_odd_rule()); + } else if (in.find("MULTIPOLYGON") == 0) { + Multipolygon_with_holes_2 mp; + CGAL::IO::read_multi_polygon_WKT(iss, mp); + CGAL::draw(mp); + rmp = CGAL::Polygon_repair::repair(mp, CGAL::Polygon_repair::Even_odd_rule()); + } std::ostringstream oss; + CGAL::IO::write_multi_polygon_WKT(oss, rmp); + std::string out = oss.str(); + std::cout << "\tin: " << in << std::endl; + std::cout << "\tout: " << out; + CGAL::draw(rmp); + + } + + return 0; +} diff --git a/Polygon_repair/test/Polygon_repair/exact_test.cpp b/Polygon_repair/test/Polygon_repair/exact_test.cpp new file mode 100644 index 000000000000..4182f8faa061 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/exact_test.cpp @@ -0,0 +1,52 @@ +#define CGAL_NO_CDT_2_WARNING + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_exact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; +using Polygon_repair = CGAL::Polygon_repair::Polygon_repair; + +int main() { + + std::string in = "POLYGON((0.03 0.02,0.97 0.01,0.99 0.96,0.04 0.98,0.03 0.02),(0.5 0.5,1.5 0.5,0.5 0.5,1.5 0.7,0.5 0.5,1.5 0.9,0.5 0.5,1.5 1.1,0.5 0.5,1.5 1.3,0.5 0.5,1.5 1.5,0.5 0.5,1.3 1.5,0.5 0.5,1.1 1.5,0.5 0.5,0.9 1.5,0.5 0.5,0.7 1.5,0.5 0.5,0.5 1.5,0.5 0.5,0.3 1.5,0.5 0.5,0.1 1.5,0.5 0.5,-0.1 1.5,0.5 0.5,-0.3 1.5,0.5 0.5,-0.5 1.5,0.5 0.5,-0.5 1.3,0.5 0.5,-0.5 1.1,0.5 0.5,-0.5 0.9,0.5 0.5,-0.5 0.9,0.5 0.5,-0.5 0.7,0.5 0.5,-0.5 0.5,0.5 0.5,-0.5 0.3,0.5 0.5,-0.5 0.1,0.5 0.5,-0.5 -0.1,0.5 0.5,-0.5 -0.3,0.5 0.5,-0.5 -0.5,0.5 0.5,-0.3 -0.5,0.5 0.5,-0.1 -0.5,0.5 0.5,0.1 -0.5,0.5 0.5,0.3 -0.5,0.5 0.5,0.5 -0.5,0.5 0.5,0.7 -0.5,0.5 0.5,0.9 -0.5,0.5 0.5,1.1 -0.5,0.5 0.5,1.3 -0.5,0.5 0.5,1.5 -0.5,0.5 0.5,1.5 -0.3,0.5 0.5,1.5 -0.1,0.5 0.5,1.5 0.1,0.5 0.5,1.5 0.3,0.5 0.5))"; + std::istringstream iss(in); + Multipolygon_with_holes_2 rmp; + + Polygon_with_holes_2 p; + CGAL::IO::read_polygon_WKT(iss, p); + CGAL::draw(p); + Polygon_repair pr; + for (auto const edge: p.outer_boundary().edges()) { + pr.triangulation().even_odd_insert_constraint(edge.source(), edge.target()); + } int spikes = 20; + for (auto const& hole: p.holes()) { + for (auto const edge: hole.edges()) { + if (spikes-- <= 0) break; + pr.triangulation().even_odd_insert_constraint(edge.source(), edge.target()); + } + } + pr.label_triangulation_even_odd(); + pr.reconstruct_multipolygon(); + rmp = CGAL::Polygon_repair::repair(p, CGAL::Polygon_repair::Even_odd_rule()); + std::ostringstream oss; + CGAL::IO::write_multi_polygon_WKT(oss, rmp); + std::string out = oss.str(); + std::cout << "\tin: " << in << std::endl; + std::cout << "\tout: " << out; + CGAL::draw(rmp); + + return 0; +} diff --git a/Polygon_repair/test/Polygon_repair/repair_polygon_2_test.cpp b/Polygon_repair/test/Polygon_repair/repair_polygon_2_test.cpp new file mode 100644 index 000000000000..3ca4f4225df7 --- /dev/null +++ b/Polygon_repair/test/Polygon_repair/repair_polygon_2_test.cpp @@ -0,0 +1,105 @@ +#include +#include +#include + +#include + +// work around for old compilers (Apple clang < 11 for example) +#define HAS_FILESYSTEM 1 +#if (__has_include) +#if !__has_include() +#undef HAS_FILESYSTEM +#define HAS_FILESYSTEM 0 +#endif +#endif + + +#if HAS_FILESYSTEM + +#include +#include +#include +#include + +using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel; +using Point_2 = Kernel::Point_2; +using Polygon_2 = CGAL::Polygon_2; +using Polygon_with_holes_2 = CGAL::Polygon_with_holes_2; +using Multipolygon_with_holes_2 = CGAL::Multipolygon_with_holes_2; +using Polygon_repair = CGAL::Polygon_repair::Polygon_repair; + +int main() { + + for (const auto& file: std::filesystem::directory_iterator("data/in")) { + if (file.path().filename().extension() != ".wkt") continue; + std::cout << "Testing " << file.path().filename() << "... "; + + // Read test file + std::string in; + std::getline(std::ifstream(file.path()), in); + + // Load test file and repair to create output + std::istringstream iss(in); + Multipolygon_with_holes_2 rmp, refmp; + if (in.find("POLYGON") == 0) { + Polygon_with_holes_2 p; + if (in != "POLYGON()") { // maybe should be checked in WKT reader + CGAL::IO::read_polygon_WKT(iss, p); + } rmp = CGAL::Polygon_repair::repair(p, CGAL::Polygon_repair::Even_odd_rule()); + } else if (in.find("MULTIPOLYGON") == 0) { + Multipolygon_with_holes_2 mp; + CGAL::IO::read_multi_polygon_WKT(iss, mp); + rmp = CGAL::Polygon_repair::repair(mp, CGAL::Polygon_repair::Even_odd_rule()); + } std::stringstream oss; + CGAL::IO::write_multi_polygon_WKT(oss, rmp); + std::string out = oss.str(); + rmp.clear(); + CGAL::IO::read_multi_polygon_WKT(oss, rmp); + + // Read reference file + std::string ref_path = "data/ref/"; + ref_path += file.path().filename().string(); + std::ifstream ref_ifs(ref_path); + if (ref_ifs.fail()) { + std::cout << std::endl << "\tin: " << in << std::endl; + std::cout << "\tout: " << out; + std::cout << "\tno reference output -> skipped" << std::endl; + continue; + } std::string ref; + std::getline(ref_ifs, ref); + ref += "\n"; + std::stringstream refss(ref); + CGAL::IO::read_multi_polygon_WKT(refss, refmp); + + // Compare output with reference file + if (rmp == refmp) { + std::cout << "ok" << std::endl; + } else { + std::cout << "fail" << std::endl; + std::cout << "\tin: " << in << std::endl; + std::cout << "\tout: " << out << std::flush; + std::cout << "\tref: " << ref << std::flush; + } + assert(rmp == refmp); + + // Test orientations + for (auto const& polygon: rmp.polygons_with_holes()) { + assert(polygon.outer_boundary().orientation() == CGAL::COUNTERCLOCKWISE); + for (auto const &hole: polygon.holes()) { + assert(hole.orientation() == CGAL::CLOCKWISE); + } + } + } + + return 0; +} + +#else + +int main() +{ + std::cout << "Warning: filesystem feature is not present on the system, nothing will be tested\n"; + return 0; +} + +#endif diff --git a/Stream_support/examples/Stream_support/Polygon_WKT.cpp b/Stream_support/examples/Stream_support/Polygon_WKT.cpp index f417e4a48ee2..9bf7a37cf4bd 100644 --- a/Stream_support/examples/Stream_support/Polygon_WKT.cpp +++ b/Stream_support/examples/Stream_support/Polygon_WKT.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -11,7 +12,7 @@ int main(int argc, char* argv[]) { typedef CGAL::Polygon_with_holes_2 Polygon; typedef std::deque MultiPolygon; - + typedef CGAL::Multipolygon_with_holes_2 Multipolygon_with_holes_2; { std::ifstream is((argc>1)?argv[1]:"data/polygons.wkt"); std::list polys; @@ -33,5 +34,14 @@ int main(int argc, char* argv[]) for(Polygon p : mp) std::cout<2)?argv[2]:"data/multipolygon.wkt"); + Multipolygon_with_holes_2 mp; + CGAL::IO::read_multi_polygon_WKT(is, mp); + std::cout << mp << std::endl; + CGAL::IO::write_multi_polygon_WKT(std::cout, mp); + std::cout << std::endl; + } return 0; } diff --git a/Stream_support/include/CGAL/IO/WKT.h b/Stream_support/include/CGAL/IO/WKT.h index 266939338fde..3a3eea129d64 100644 --- a/Stream_support/include/CGAL/IO/WKT.h +++ b/Stream_support/include/CGAL/IO/WKT.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include @@ -67,7 +68,7 @@ void pop_back_if_equal_to_front(CGAL::Polygon_with_holes_2& pwh) //! //! \tparam Point can be a `CGAL::Point_2` or `CGAL::Point_3`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::Point_2` //! \see `CGAL::Point_3` @@ -112,10 +113,10 @@ bool read_point_WKT(std::istream& in, //! and have: //! - a function `push_back()` that takes the same point type, //! - a function `clear()`, -//! - a function `resize()` that takes an `size_type` +//! - a function `resize()` that takes a `size_type` //! - an `operator[]()` that takes a `size_type`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::Point_2` //! \see `CGAL::Point_3` @@ -159,10 +160,10 @@ bool read_multi_point_WKT(std::istream& in, //! and have: //! - a function `push_back()` that takes a `CGAL::Point_2`. //! - a function `clear()`, -//! - a function `resize()` that takes an `size_type` +//! - a function `resize()` that takes a `size_type` //! - an `operator[]()` that takes a `size_type`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::Point_2` template @@ -203,10 +204,10 @@ bool read_linestring_WKT(std::istream& in, //! and have: //! - a function `push_back()` that takes a `Linestring`, //! - a function `clear()`, -//! - a function `resize()` that takes an `size_type` +//! - a function `resize()` that takes a `size_type` //! - an `operator[]()` that takes a `size_type`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::Point_2` template @@ -258,7 +259,7 @@ bool read_multi_linestring_WKT(std::istream& in, //! //! \tparam Polygon is a `CGAL::General_polygon_with_holes_2`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::General_polygon_with_holes_2` template @@ -302,10 +303,10 @@ bool read_polygon_WKT(std::istream& in, //! and have: //! - a function `push_back()` that takes a `CGAL::General_polygon_with_holes_2`, //! - a function `clear()`, -//! - a function `resize()` that takes an `size_type` +//! - a function `resize()` that takes a `size_type` //! - an `operator[]()` that takes a `size_type`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::General_polygon_with_holes_2` template @@ -345,13 +346,22 @@ bool read_multi_polygon_WKT(std::istream& in, return !in.fail(); } + +template +bool read_multi_polygon_WKT(std::istream& in, + Multipolygon_with_holes_2& mp) +{ + return read_multi_polygon_WKT(in, mp.polygons_with_holes()); +} + + //! \ingroup PkgStreamSupportIoFuncsWKT //! //! \brief writes `point` into a WKT stream. //! //! \tparam Point is a `CGAL::Point_2` //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::Point_2` template @@ -371,7 +381,7 @@ std::ostream& write_point_WKT(std::ostream& out, //! //! \tparam Polygon must be a `CGAL::General_polygon_with_holes_2` //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::General_polygon_with_holes_2` template @@ -391,7 +401,7 @@ std::ostream& write_polygon_WKT(std::ostream& out, //! //! \tparam LineString must be a `RandomAccessRange` of `CGAL::Point_2`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //!\see `CGAL::Point_2` template @@ -412,7 +422,7 @@ std::ostream& write_linestring_WKT(std::ostream& out, //! //! \tparam MultiPoint must be a `RandomAccessRange` of `CGAL::Point_2`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //!\see `CGAL::Point_2` template @@ -433,7 +443,7 @@ std::ostream& write_multi_point_WKT(std::ostream& out, //! //! \tparam MultiPolygon must be a `RandomAccessRange` of `CGAL::General_polygon_with_holes_2`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //!\see `CGAL::General_polygon_with_holes_2` template @@ -448,13 +458,20 @@ std::ostream& write_multi_polygon_WKT(std::ostream& out, return out; } +template +std::ostream& write_multi_polygon_WKT(std::ostream& out, + Multipolygon_with_holes_2& mp) +{ + return write_multi_polygon_WKT(out, mp.polygons_with_holes()); +} + //! \ingroup PkgStreamSupportIoFuncsWKT //! //! \brief writes the content of `mls` into a WKT stream. //! //! \tparam MultiLineString must be a `RandomAccessRange` of `LineString`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::IO::write_linestring_WKT()` template @@ -489,7 +506,7 @@ std::ostream& write_multi_linestring_WKT(std::ostream& out, //! \tparam MultiLineString must be a `RandomAccessRange` of `Linestring`. //! \tparam MultiPolygon must be a model of `RandomAccessRange` of `CGAL::General_polygon_with_holes_2`. //! -//! \attention Only Cartesian Kernels with double or float as `FT` are supported. +//! \attention Only %Cartesian Kernels with double or float as `FT` are supported. //! //! \see `CGAL::IO::read_linestring_WKT()` template