Skip to content

Commit

Permalink
CAPI: GEOSCoverageIsValid, GEOSCoverageSimplifyVW GH-867
Browse files Browse the repository at this point in the history
Expose the CoverateSimplifier and CoverageValidator via the CAPI
  • Loading branch information
pramsey committed Apr 13, 2023
1 parent 93540f4 commit 3cab6cb
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 1 deletion.
4 changes: 3 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ xxxx-xx-xx

- New things:
- C++14 is now required.
- Polygonal coverage operations: CoverageValidator, CoveragePolygonValidator,
- Polygonal coverages: CoverageValidator, CoveragePolygonValidator,
CoverageGapFinder, CoverageUnion (JTS-900, Martin Davis & Paul Ramsey)
- Support reading and writing M values through WKB and WKT readers/writers
(GH-721, Dan Baston)
Expand All @@ -19,6 +19,8 @@ xxxx-xx-xx
- CAPI: GEOSConcaveHullByLength (GH-849, Martin Davis)
- CAPI: GEOSGeomGetM (GH-864, Mike Taves)
- Voronoi: Add option to create diagram in order consistent with inputs (GH-781, Dan Baston)
- Polygonal coverages: CoverageSimplifier (JTS-911, Martin Davis)
- CAPI: GEOSCoverageIsValid, GEOSCoverageSimplifyVW (GH-867, Paul Ramsey)

- Fixes/Improvements:
- WKTReader: Fix parsing of Z and M flags in WKTReader (#676 and GH-669, Dan Baston)
Expand Down
16 changes: 16 additions & 0 deletions capi/geos_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1804,4 +1804,20 @@ extern "C" {
cx, cy);
}

int
GEOSCoverageIsValid(
const Geometry* input,
double gapWidth,
Geometry** invalidEdges)
{
return GEOSCoverageIsValid_r(handle, input, gapWidth, invalidEdges);
}

Geometry*
GEOSCoverageSimplifyVW(const Geometry* input, double tolerance, int preserveBoundary)
{
return GEOSCoverageSimplifyVW_r(handle, input, tolerance, preserveBoundary);
}


} /* extern "C" */
86 changes: 86 additions & 0 deletions capi/geos_c.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,24 @@ extern void GEOS_DLL GEOSGeom_destroy_r(
GEOSContextHandle_t handle,
GEOSGeometry* g);

/* ========= Coverages ========= */

/** \see GEOSCoverageIsValid */
int
GEOSCoverageIsValid_r(
GEOSContextHandle_t extHandle,
const GEOSGeometry* input,
double gapWidth,
GEOSGeometry** output);

/** \see GEOSCoverageSimplify */
extern GEOSGeometry GEOS_DLL *
GEOSCoverageSimplifyVW_r(
GEOSContextHandle_t extHandle,
const GEOSGeometry* input,
double tolerance,
int preserveBoundary);

/* ========= Topology Operations ========= */

/** \see GEOSEnvelope */
Expand Down Expand Up @@ -3719,6 +3737,74 @@ extern GEOSGeometry GEOS_DLL *GEOSOffsetCurve(const GEOSGeometry* g,

///@}


/* ====================================================================== */
/** @name Coverages
* Functions to work with coverages represented by lists of polygons
* that exactly share edge geometry.
*/
///@{

/**
* Analyze a coverage (represented as a collection of polygonal geometry
* with exactly matching edge geometry) to find places where the
* assumption of exactly matching edges is not met.
*
* \param input The polygonal coverage to access,
* stored in a geometry collection. All members must be POLYGON
* or MULTIPOLYGON.
* \param gapWidth The maximum width of gaps to detect.
* \param invalidEdges When there are invalidities in the coverage,
* this pointer
* will be set with a geometry collection of the same length as
* the input, with a MULTILINESTRING of the error edges for each
* invalid polygon, or an EMPTY where the polygon is a valid
* participant in the coverage. Pass NULL if you do not want
* the invalid edges returned.
* \return A value of 1 for a valid coverage, 0 for invalid and 2 for
* an exception or error. Invalidity includes polygons that overlap,
* that have gaps smaller than the gapWidth, or non-polygonal
* entries in the input collection.
*
* \since 3.12
*/
extern int GEOSCoverageIsValid(
const GEOSGeometry* input,
double gapWidth,
GEOSGeometry** invalidEdges);

/**
* Operates on a coverage (represented as a list of polygonal geometry
* with exactly matching edge geometry) to apply a Visvalingam–Whyatt
* simplification to the edges, reducing complexity in proportion with
* the provided tolerance, while retaining a valid coverage (no edges
* will cross or touch after the simplification).
* Geometries never disappear, but they may be simplified down to just
* a triangle. Also, some invalid geoms (such as Polygons which have too
* few non-repeated points) will be returned unchanged.
* If the input dataset is not a valid coverage due to overlaps,
* it will still be simplified, but invalid topology such as crossing
* edges will still be invalid.
*
* \param input The polygonal coverage to access,
* stored in a geometry collection. All members must be POLYGON
* or MULTIPOLYGON.
* \param tolerance A tolerance parameter in linear units.
* \param preserveBoundary Use 1 to preserve the outside edges
* of the coverage without simplification,
* 0 to allow them to be simplified.
* \return A collection containing the simplified geometries, or null
* on error.
*
* \since 3.12
*/
extern GEOSGeometry GEOS_DLL * GEOSCoverageSimplifyVW(
const GEOSGeometry* input,
double tolerance,
int preserveBoundary);

///@}

/* ========== Construction Operations ========== */
/** @name Geometric Constructions
* Functions for computing geometric constructions.
Expand Down
81 changes: 81 additions & 0 deletions capi/geos_ts_c.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
#include <geos/algorithm/distance/DiscreteFrechetDistance.h>
#include <geos/algorithm/hull/ConcaveHull.h>
#include <geos/algorithm/hull/ConcaveHullOfPolygons.h>
#include <geos/coverage/CoverageValidator.h>
#include <geos/coverage/CoverageSimplifier.h>
#include <geos/geom/Coordinate.h>
#include <geos/geom/CoordinateSequence.h>
#include <geos/geom/Envelope.h>
Expand Down Expand Up @@ -4063,4 +4065,83 @@ extern "C" {
});
}

int
GEOSCoverageIsValid_r(GEOSContextHandle_t extHandle,
const Geometry* input,
double gapWidth,
Geometry** invalidEdges)
{
using geos::coverage::CoverageValidator;

return execute(extHandle, 2, [&]() {
const GeometryCollection* col = dynamic_cast<const GeometryCollection*>(input);
if (!col)
throw geos::util::IllegalArgumentException("input is not a collection");

// Initialize to nullptr
if (invalidEdges) *invalidEdges = nullptr;

std::vector<const Geometry*> coverage;
for (const auto& g : *col) {
coverage.push_back(g.get());
}

CoverageValidator cov(coverage);
cov.setGapWidth(gapWidth);
std::vector<std::unique_ptr<Geometry>> invalid = cov.validate();
bool hasInvalid = CoverageValidator::hasInvalidResult(invalid);

if (invalidEdges && hasInvalid) {
const GeometryFactory* gf = input->getFactory();
for (auto& g : invalid) {
// Replace nullptr with 'MULTILINESTRING EMPTY'
if (g == nullptr) {
auto empty = gf->createEmpty(GEOS_MULTILINESTRING);
g.reset(empty.release());
}
}
auto r = gf->createGeometryCollection(std::move(invalid));
*invalidEdges = r.release();
}

return hasInvalid ? 0 : 1;
});
}

Geometry*
GEOSCoverageSimplifyVW_r(GEOSContextHandle_t extHandle,
const Geometry* input,
double tolerance,
int preserveBoundary)
{
using geos::coverage::CoverageSimplifier;

return execute(extHandle, [&]() -> Geometry* {
const GeometryCollection* col = dynamic_cast<const GeometryCollection*>(input);
if (!col)
return nullptr;

std::vector<const Geometry*> coverage;
for (const auto& g : *col) {
coverage.push_back(g.get());
}
CoverageSimplifier cov(coverage);
std::vector<std::unique_ptr<Geometry>> simple;
if (preserveBoundary == 1) {
simple = cov.simplifyInner(tolerance);
}
else if (preserveBoundary == 0) {
simple = cov.simplify(tolerance);
}
else return nullptr;

const GeometryFactory* gf = input->getFactory();
std::unique_ptr<Geometry> r = gf->createGeometryCollection(std::move(simple));
return r.release();
});
}




} /* extern "C" */
67 changes: 67 additions & 0 deletions tests/unit/capi/GEOSCoverageSimplifyTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//
// Test Suite for C-API GEOSCoverageUnion

#include <tut/tut.hpp>
// geos
#include <geos_c.h>
// std
#include <cstdarg>
#include <cstdio>
#include <cstdlib>
#include <cstring>

#include "capi_test_utils.h"

namespace tut {
//
// Test Group
//

// Common data used in test cases.
struct test_capicoveragesimplify_data : public capitest::utility {

test_capicoveragesimplify_data() {
}

~test_capicoveragesimplify_data() {
}

};


typedef test_group<test_capicoveragesimplify_data> group;
typedef group::object object;

group test_capicoveragesimplify_group("capi::GEOSCoverageSimplify");

//
// Test Cases
//


template<>
template<> void object::test<1>
()
{
const char* inputWKT = "GEOMETRYCOLLECTION(POLYGON ((100 100, 200 200, 300 100, 200 101, 100 100)), POLYGON ((150 0, 100 100, 200 101, 300 100, 250 0, 150 0)))";

input_ = fromWKT(inputWKT);
result_ = GEOSCoverageSimplifyVW(input_, 10.0, 0);

ensure( result_ != nullptr );
ensure( GEOSGeomTypeId(result_) == GEOS_GEOMETRYCOLLECTION );

const char* expectedWKT = "GEOMETRYCOLLECTION(POLYGON ((100 100, 200 200, 300 100, 100 100)), POLYGON ((150 0, 100 100, 300 100, 250 0, 150 0)))";

expected_ = fromWKT(expectedWKT);

// std::cout << toWKT(result_) << std::endl;
// std::cout << toWKT(expected_) << std::endl;

ensure_geometry_equals(result_, expected_, 0.1);
}



} // namespace tut

0 comments on commit 3cab6cb

Please sign in to comment.