Skip to content

Commit

Permalink
feat: isSimple method for geometries
Browse files Browse the repository at this point in the history
  • Loading branch information
delhomer committed Oct 24, 2024
1 parent ce23582 commit 6e17474
Show file tree
Hide file tree
Showing 6 changed files with 747 additions and 0 deletions.
51 changes: 51 additions & 0 deletions src/Simplicity.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2012-2013, IGN France.
// Copyright (c) 2012-2022, Oslandia.
// SPDX-License-Identifier: LGPL-2.0-or-later

#ifndef SFCGAL_SIMPLICITY_H_
#define SFCGAL_SIMPLICITY_H_

namespace SFCGAL {

/**
* @brief the class, convertible to bool, that stores the reason why a geom is
* not simple
*/
struct Simplicity {
/**
* @note the class has private ctor to force the use of functions valid() and
* invalid(reason) that are clearer in the code than to remember that "Valid
* constructed with a reason is invalid"
*/
static const Simplicity
simple()
{
return Simplicity();
}
static const Simplicity
complex(const std::string &reason)
{
return Simplicity(reason);
}
operator bool() const { return _simple; }
const std::string &
reason() const
{
return _reason;
}

private:
bool _simple; // not const to allow default copy
std::string _reason;
/**
* @brief default ctor for simple
*/
Simplicity() : _simple(true) {}
/**
* @brief if we construct with a reason, the class is complex
*/
Simplicity(const std::string &reason) : _simple(false), _reason(reason) {}
};

} // namespace SFCGAL
#endif
249 changes: 249 additions & 0 deletions src/algorithm/isSimple.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
// Copyright (c) 2012-2013, IGN France.
// Copyright (c) 2012-2024, Oslandia.
// SPDX-License-Identifier: LGPL-2.0-or-later

#include "SFCGAL/algorithm/isSimple.h"

#include "SFCGAL/GeometryCollection.h"
#include "SFCGAL/LineString.h"
#include "SFCGAL/MultiLineString.h"
#include "SFCGAL/MultiPoint.h"
#include "SFCGAL/MultiPolygon.h"
#include "SFCGAL/MultiSolid.h"
#include "SFCGAL/Polygon.h"
#include "SFCGAL/PolyhedralSurface.h"
#include "SFCGAL/Triangle.h"
#include "SFCGAL/TriangulatedSurface.h"

#include "SFCGAL/Kernel.h"
#include "SFCGAL/algorithm/intersects.h"
#include "SFCGAL/algorithm/plane.h"
#include "SFCGAL/detail/algorithm/coversPoints.h"

using namespace SFCGAL::detail::algorithm;

namespace SFCGAL {

namespace algorithm {

auto
isSimple(const LineString &linestring) -> const Simplicity
{
if (linestring.is3D() ? selfIntersects3D(linestring)
: selfIntersects(linestring)) {
return Simplicity::complex(
boost::format("linestring self intersects").str());
}
return Simplicity::simple();
}

auto
isSimple(const Polygon &polygon, const double &toleranceAbs = 1e-9)
-> const Simplicity
{
// Polygon must be planar (all points in the same plane)
if (polygon.is3D() && !isPlane3D<Kernel>(polygon, toleranceAbs)) {
return Simplicity::complex("Points don't lie in the same plane.");
}
return Simplicity::simple();
}

auto
isSimple(const PolyhedralSurface &phs, const double &toleranceAbs)
-> const Simplicity
{
if (phs.isEmpty()) {
return Simplicity::simple();
}
const size_t numGeom = phs.numGeometries();

for (size_t g = 0; g != numGeom; ++g) {
Simplicity const s = isSimple(phs.polygonN(g), toleranceAbs);

if (!s) {
return Simplicity::complex(
(boost::format("Polygon %d is complex: %s") % g % s.reason()).str());
}
}

return Simplicity::simple();
}

auto
isSimple(const Solid &solid, const double &toleranceAbs) -> const Simplicity
{
if (solid.isEmpty()) {
return Simplicity::simple();
}
const size_t numGeom = solid.numGeometries();

for (size_t g = 0; g != numGeom; ++g) {
Simplicity const s = isSimple(solid.shellN(g), toleranceAbs);

if (!s) {
return Simplicity::complex(
(boost::format("Polygon %d is complex: %s") % g % s.reason()).str());
}
}

return Simplicity::simple();
}

auto
isSimple(const MultiPoint &multipoint) -> const Simplicity
{
const size_t numPoint = multipoint.numGeometries();

for (size_t l = 0; l != numPoint - 1; ++l) {
for (size_t other_l = l + 1; other_l != numPoint; ++other_l) {
bool const duplicated_points =
multipoint.pointN(l) == multipoint.pointN(other_l);
if (duplicated_points) {
return Simplicity::complex(
(boost::format(
"Points %d and %d are duplicated in the MultiPoint.") %
l % other_l)
.str());
}
}
}

return Simplicity::simple();
}

auto
isSimple(const MultiLineString &mls) -> const Simplicity
{
if (mls.isEmpty()) {
return Simplicity::simple();
}
const size_t numGeom = mls.numGeometries();

for (size_t g = 0; g != numGeom; ++g) {
Simplicity const s = isSimple(mls.lineStringN(g));

if (!s) {
return Simplicity::complex(
(boost::format("LineString %d is complex: %s") % g % s.reason())
.str());
}
}

return Simplicity::simple();
}

auto
isSimple(const MultiPolygon &mpoly, const double &toleranceAbs)
-> const Simplicity
{
if (mpoly.isEmpty()) {
return Simplicity::simple();
}
const size_t numGeom = mpoly.numGeometries();
for (size_t g = 0; g != numGeom; ++g) {
Simplicity const s = isSimple(mpoly.polygonN(g), toleranceAbs);
if (!s) {
return Simplicity::complex(
(boost::format("Polygon %d is complex: %s") % g % s.reason()).str());
}
}

return Simplicity::simple();
}

auto
isSimple(const MultiSolid &msolid, const double &toleranceAbs)
-> const Simplicity
{
if (msolid.isEmpty()) {
return Simplicity::simple();
}
const size_t numGeom = msolid.numGeometries();

for (size_t g = 0; g != numGeom; ++g) {
Simplicity const s = isSimple(msolid.solidN(g), toleranceAbs);

if (!s) {
return Simplicity::complex(
(boost::format("Solid %d is complex: %s") % g % s.reason()).str());
}
}

return Simplicity::simple();
}

auto
isSimple(const GeometryCollection &collection, const double &toleranceAbs)
-> const Simplicity
{
if (collection.isEmpty()) {
return Simplicity::simple();
}
const size_t numGeom = collection.numGeometries();

for (size_t g = 0; g != numGeom; ++g) {
Simplicity const s = isSimple(collection.geometryN(g), toleranceAbs);

if (!s) {
return Simplicity::complex(
(boost::format("%s at index %d is complex: %s") %
collection.geometryN(g).geometryType() % g % s.reason())
.str());
}
}

return Simplicity::simple();
}

auto
isSimple(const Geometry &g, const double &toleranceAbs) -> const Simplicity
{
switch (g.geometryTypeId()) {
case TYPE_POINT:
return Simplicity::simple();

case TYPE_LINESTRING: // no self-intersecting (excepted at ending point)
return isSimple(g.as<LineString>());

case TYPE_POLYGON: // are the rings simple? (3D) -> check the coplanarity
return isSimple(g.as<Polygon>(), toleranceAbs);

case TYPE_POLYHEDRALSURFACE: // every polygon is simple
return isSimple(g.as<PolyhedralSurface>(), toleranceAbs);

case TYPE_TRIANGLE:
return Simplicity::simple();

case TYPE_TRIANGULATEDSURFACE: // every triangle is simple
return Simplicity::simple();

case TYPE_SOLID: // every phs is simple
return isSimple(g.as<Solid>(), toleranceAbs);

case TYPE_MULTIPOINT: // no equal points
return isSimple(g.as<MultiPoint>());

case TYPE_MULTILINESTRING: // every ls is simple, and only intersections are
// at boundaries
return isSimple(g.as<MultiLineString>());

case TYPE_MULTIPOLYGON: // every polygon is simple
return isSimple(g.as<MultiPolygon>(), toleranceAbs);

case TYPE_MULTISOLID: // every solid is simple
return isSimple(g.as<MultiSolid>(), toleranceAbs);

case TYPE_GEOMETRYCOLLECTION: // every geometry is simple
return isSimple(g.as<GeometryCollection>(), toleranceAbs);
}

BOOST_THROW_EXCEPTION(Exception(
(boost::format("isSimple( %s ) is not defined") % g.geometryType())
.str()));
return Simplicity::complex(
(boost::format("isSimple( %s ) is not defined") % g.geometryType())
.str()); // to avoid warning
}

} // namespace algorithm
} // namespace SFCGAL
31 changes: 31 additions & 0 deletions src/algorithm/isSimple.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright (c) 2012-2013, IGN France.
// Copyright (c) 2012-2022, Oslandia.
// SPDX-License-Identifier: LGPL-2.0-or-later

#ifndef SFCGAL_ALGORITHM_ISSIMPLE_H_
#define SFCGAL_ALGORITHM_ISSIMPLE_H_

#include "SFCGAL/Geometry.h"
#include "SFCGAL/Simplicity.h"

namespace SFCGAL {

/**
* Functions used to assert for geometry simplicity
*/
void SFCGAL_API
SFCGAL_ASSERT_GEOMETRY_SIMPLICITY(const Geometry &g);

namespace algorithm {

/**
* @brief Check simplicity of a geometry
* @ingroup public_api
*/
SFCGAL_API const Simplicity
isSimple(const Geometry &g, const double &toleranceAbs = 1e-9);

} // namespace algorithm
} // namespace SFCGAL

#endif
34 changes: 34 additions & 0 deletions src/capi/sfcgal_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#include "SFCGAL/algorithm/extrude.h"
#include "SFCGAL/algorithm/intersection.h"
#include "SFCGAL/algorithm/intersects.h"
#include "SFCGAL/algorithm/isSimple.h"
#include "SFCGAL/algorithm/isValid.h"
#include "SFCGAL/algorithm/lineSubstring.h"
#include "SFCGAL/algorithm/minkowskiSum.h"
Expand Down Expand Up @@ -218,6 +219,39 @@ sfcgal_geometry_is_valid_detail(const sfcgal_geometry_t *geom,
return static_cast<int>(is_valid);
}

extern "C" auto
sfcgal_geometry_is_simple(const sfcgal_geometry_t *geom) -> int
{
SFCGAL_GEOMETRY_CONVERT_CATCH_TO_ERROR(
return (int)bool(SFCGAL::algorithm::isSimple(
*reinterpret_cast<const SFCGAL::Geometry *>(geom)));)
}

extern "C" auto
sfcgal_geometry_is_simple_detail(const sfcgal_geometry_t *geom,
char **complexity_reason) -> int
{
// set to null for now
if (complexity_reason != nullptr) {
*complexity_reason = nullptr;
}

const auto *g = reinterpret_cast<const SFCGAL::Geometry *>(geom);
bool is_simple = false;
try {
SFCGAL::Simplicity const simplicity = SFCGAL::algorithm::isSimple(*g);
is_simple = simplicity;
if (!is_simple && (complexity_reason != nullptr)) {
*complexity_reason = strdup(simplicity.reason().c_str());
}
} catch (SFCGAL::Exception &e) {
if (complexity_reason != nullptr) {
*complexity_reason = strdup(e.what());
}
}
return static_cast<int>(is_simple);
}

extern "C" auto
sfcgal_geometry_is_3d(const sfcgal_geometry_t *geom) -> int
{
Expand Down
Loading

0 comments on commit 6e17474

Please sign in to comment.