Skip to content

Commit

Permalink
Add OGR_G_MakeValid() (requires GEOS 3.8) and bind it to SWIG
Browse files Browse the repository at this point in the history
  • Loading branch information
rouault committed Feb 28, 2019
1 parent a529c1a commit 29693f9
Show file tree
Hide file tree
Showing 7 changed files with 207 additions and 3 deletions.
36 changes: 33 additions & 3 deletions autotest/ogr/ogr_geom.py
Expand Up @@ -3231,8 +3231,38 @@ def test_ogr_geom_create_from_wkt_polyhedrasurface():
assert g.ExportToWkt() == 'POLYHEDRALSURFACE Z (((0 0 0,0 0 1,0 1 1,0 1 0,0 0 0)))'

###############################################################################
# cleanup


def test_ogr_geom_cleanup():
pass
def test_ogr_geom_makevalid():

if not ogrtest.have_geos():
pytest.skip()

g = ogr.CreateGeometryFromWkt('POINT (0 0)')
g = g.MakeValid()
assert g is None or g.ExportToWkt() == 'POINT (0 0)'

g = ogr.CreateGeometryFromWkt('POINT EMPTY')
g = g.MakeValid()
assert g is None or g.ExportToWkt() == 'POINT EMPTY'

g = ogr.CreateGeometryFromWkt('LINESTRING (0 0)')
with gdaltest.error_handler():
g = g.MakeValid()
assert not g

g = ogr.CreateGeometryFromWkt('CURVEPOLYGON ((0 0,0 1,1 0,0 0))')
g = g.MakeValid()
assert g is None or g.ExportToWkt() == 'CURVEPOLYGON ((0 0,0 1,1 0,0 0))'

# Invalid
g = ogr.CreateGeometryFromWkt('POLYGON ((0 0,10 10,0 10,10 0,0 0))')
g = g.MakeValid()
assert g is None or g.ExportToWkt() == 'MULTIPOLYGON (((0 0,5 5,10 0,0 0)),((5 5,0 10,10 10,5 5)))'

# Invalid
g = ogr.CreateGeometryFromWkt('CURVEPOLYGON ((0 0,10 10,0 10,10 0,0 0))')
g = g.MakeValid()
assert g is None or g.ExportToWkt() == 'MULTIPOLYGON (((0 0,5 5,10 0,0 0)),((5 5,0 10,10 10,5 5)))'

return 'success'
1 change: 1 addition & 0 deletions gdal/ogr/ogr_api.h
Expand Up @@ -190,6 +190,7 @@ void CPL_DLL OGR_G_Empty( OGRGeometryH );
int CPL_DLL OGR_G_IsEmpty( OGRGeometryH );
int CPL_DLL OGR_G_IsValid( OGRGeometryH );
/*char CPL_DLL *OGR_G_IsValidReason( OGRGeometryH );*/
OGRGeometryH CPL_DLL OGR_G_MakeValid( OGRGeometryH ) CPL_WARN_UNUSED_RESULT;
int CPL_DLL OGR_G_IsSimple( OGRGeometryH );
int CPL_DLL OGR_G_IsRing( OGRGeometryH );

Expand Down
1 change: 1 addition & 0 deletions gdal/ogr/ogr_geometry.h
Expand Up @@ -357,6 +357,7 @@ class CPL_DLL OGRGeometry
int CoordinateDimension() const;
virtual OGRBoolean IsEmpty() const = 0;
virtual OGRBoolean IsValid() const;
virtual OGRGeometry* MakeValid() const;
virtual OGRBoolean IsSimple() const;
/*! Returns whether the geometry has a Z component. */
OGRBoolean Is3D() const { return flags & OGR_G_3D; }
Expand Down
118 changes: 118 additions & 0 deletions gdal/ogr/ogrgeometry.cpp
Expand Up @@ -3509,6 +3509,124 @@ static OGRBoolean OGRGEOSBooleanPredicate(
#endif // HAVE_GEOS


/************************************************************************/
/* MakeValid() */
/************************************************************************/

/**
* \brief Attempts to make an invalid geometry valid without losing vertices.
*
* Already-valid geometries are cloned without further intervention.
*
* This method is the same as the C function OGR_G_MakeValid().
*
* This function is built on the GEOS >= 3.8 library, check it for the definition
* of the geometry operation.
* If OGR is built without the GEOS >= 3.8 library, this function will return
* a clone of the input geometry if it is valid, or NULL if it is invalid
*
* @return a newly allocated geometry now owned by the caller, or NULL
* on failure.
*
* @since GDAL 2.5
*/
OGRGeometry *OGRGeometry::MakeValid() const
{
#ifndef HAVE_GEOS
if( IsValid() )
return clone();

CPLError( CE_Failure, CPLE_NotSupported,
"GEOS support not enabled." );
return nullptr;
#elif GEOS_VERSION_MAJOR < 3 || \
(GEOS_VERSION_MAJOR == 3 && GEOS_VERSION_MINOR < 8)
if( IsValid() )
return clone();

CPLError( CE_Failure, CPLE_NotSupported,
"GEOS 3.8 or later needed for MakeValid." );
return nullptr;
#else
if( IsSFCGALCompatible() )
{
if( IsValid() )
return clone();
}
else if( wkbFlatten(getGeometryType()) == wkbCurvePolygon )
{
GEOSContextHandle_t hGEOSCtxt = initGEOS_r( nullptr, nullptr );
OGRBoolean bIsValid = FALSE;
GEOSGeom hGeosGeom = exportToGEOS(hGEOSCtxt);
if( hGeosGeom )
{
bIsValid = GEOSisValid_r(hGEOSCtxt, hGeosGeom);
GEOSGeom_destroy_r( hGEOSCtxt, hGeosGeom );
}
freeGEOSContext( hGEOSCtxt );
if( bIsValid )
return clone();
}

OGRGeometry *poOGRProduct = nullptr;

GEOSContextHandle_t hGEOSCtxt = createGEOSContext();
GEOSGeom hGeosGeom = exportToGEOS(hGEOSCtxt);
if( hGeosGeom != nullptr )
{
GEOSGeom hGEOSRet = GEOSMakeValid_r( hGEOSCtxt, hGeosGeom );
GEOSGeom_destroy_r( hGEOSCtxt, hGeosGeom );

if( hGEOSRet != nullptr )
{
poOGRProduct =
OGRGeometryFactory::createFromGEOS(hGEOSCtxt, hGEOSRet);
if( poOGRProduct != nullptr && getSpatialReference() != nullptr )
poOGRProduct->assignSpatialReference(getSpatialReference());
poOGRProduct =
OGRGeometryRebuildCurves(this, nullptr, poOGRProduct);
GEOSGeom_destroy_r( hGEOSCtxt, hGEOSRet);
}
}
freeGEOSContext( hGEOSCtxt );

return poOGRProduct;
#endif
}

/************************************************************************/
/* OGR_G_MakeValid() */
/************************************************************************/

/**
* \brief Attempts to make an invalid geometry valid without losing vertices.
*
* Already-valid geometries are cloned without further intervention.
*
* This function is the same as the C++ method OGRGeometry::MakeValid().
*
* This function is built on the GEOS >= 3.8 library, check it for the definition
* of the geometry operation.
* If OGR is built without the GEOS >= 3.8 library, this function will return
* a clone of the input geometry if it is valid, or NULL if it is invalid
*
* @param hGeom The Geometry to make valid.
*
* @return a newly allocated geometry now owned by the caller, or NULL
* on failure.
*
* @since GDAL 2.5
*/

OGRGeometryH OGR_G_MakeValid( OGRGeometryH hGeom )

{
VALIDATE_POINTER1( hGeom, "OGR_G_MakeValid", nullptr );

return reinterpret_cast<OGRGeometryH>(
reinterpret_cast<OGRGeometry *>(hGeom)->MakeValid());
}

/************************************************************************/
/* ConvexHull() */
/************************************************************************/
Expand Down
5 changes: 5 additions & 0 deletions gdal/swig/include/ogr.i
Expand Up @@ -2967,6 +2967,11 @@ public:
return (OGRGeometryShadow*) OGR_G_ConvexHull(self);
}

%newobject MakeValid;
OGRGeometryShadow* MakeValid() {
return (OGRGeometryShadow*) OGR_G_MakeValid(self);
}

%newobject Buffer;
#ifndef SWIGJAVA
%feature("kwargs") Buffer;
Expand Down
44 changes: 44 additions & 0 deletions gdal/swig/python/extensions/ogr_wrap.cpp
Expand Up @@ -5205,6 +5205,9 @@ SWIGINTERN OGRGeometryShadow *OGRGeometryShadow_GetBoundary(OGRGeometryShadow *s
SWIGINTERN OGRGeometryShadow *OGRGeometryShadow_ConvexHull(OGRGeometryShadow *self){
return (OGRGeometryShadow*) OGR_G_ConvexHull(self);
}
SWIGINTERN OGRGeometryShadow *OGRGeometryShadow_MakeValid(OGRGeometryShadow *self){
return (OGRGeometryShadow*) OGR_G_MakeValid(self);
}
SWIGINTERN OGRGeometryShadow *OGRGeometryShadow_Buffer(OGRGeometryShadow *self,double distance,int quadsecs=30){
return (OGRGeometryShadow*) OGR_G_Buffer( self, distance, quadsecs );
}
Expand Down Expand Up @@ -26835,6 +26838,46 @@ SWIGINTERN PyObject *_wrap_Geometry_ConvexHull(PyObject *SWIGUNUSEDPARM(self), P
}


SWIGINTERN PyObject *_wrap_Geometry_MakeValid(PyObject *SWIGUNUSEDPARM(self), PyObject *args) {
PyObject *resultobj = 0; int bLocalUseExceptionsCode = bUseExceptions;
OGRGeometryShadow *arg1 = (OGRGeometryShadow *) 0 ;
void *argp1 = 0 ;
int res1 = 0 ;
PyObject * obj0 = 0 ;
OGRGeometryShadow *result = 0 ;

if (!PyArg_ParseTuple(args,(char *)"O:Geometry_MakeValid",&obj0)) SWIG_fail;
res1 = SWIG_ConvertPtr(obj0, &argp1,SWIGTYPE_p_OGRGeometryShadow, 0 | 0 );
if (!SWIG_IsOK(res1)) {
SWIG_exception_fail(SWIG_ArgError(res1), "in method '" "Geometry_MakeValid" "', argument " "1"" of type '" "OGRGeometryShadow *""'");
}
arg1 = reinterpret_cast< OGRGeometryShadow * >(argp1);
{
if ( bUseExceptions ) {
ClearErrorState();
}
{
SWIG_PYTHON_THREAD_BEGIN_ALLOW;
result = (OGRGeometryShadow *)OGRGeometryShadow_MakeValid(arg1);
SWIG_PYTHON_THREAD_END_ALLOW;
}
#ifndef SED_HACKS
if ( bUseExceptions ) {
CPLErr eclass = CPLGetLastErrorType();
if ( eclass == CE_Failure || eclass == CE_Fatal ) {
SWIG_exception( SWIG_RuntimeError, CPLGetLastErrorMsg() );
}
}
#endif
}
resultobj = SWIG_NewPointerObj(SWIG_as_voidptr(result), SWIGTYPE_p_OGRGeometryShadow, SWIG_POINTER_OWN | 0 );
if ( ReturnSame(bLocalUseExceptionsCode) ) { CPLErr eclass = CPLGetLastErrorType(); if ( eclass == CE_Failure || eclass == CE_Fatal ) { Py_XDECREF(resultobj); SWIG_Error( SWIG_RuntimeError, CPLGetLastErrorMsg() ); return NULL; } }
return resultobj;
fail:
return NULL;
}


SWIGINTERN PyObject *_wrap_Geometry_Buffer(PyObject *SWIGUNUSEDPARM(self), PyObject *args, PyObject *kwargs) {
PyObject *resultobj = 0; int bLocalUseExceptionsCode = bUseExceptions;
OGRGeometryShadow *arg1 = (OGRGeometryShadow *) 0 ;
Expand Down Expand Up @@ -35334,6 +35377,7 @@ static PyMethodDef SwigMethods[] = {
"a handle to a newly allocated geometry now owned by the caller, or\n"
"NULL on failure. \n"
""},
{ (char *)"Geometry_MakeValid", _wrap_Geometry_MakeValid, METH_VARARGS, (char *)"Geometry_MakeValid(Geometry self) -> Geometry"},
{ (char *)"Geometry_Buffer", (PyCFunction) _wrap_Geometry_Buffer, METH_VARARGS | METH_KEYWORDS, (char *)"\n"
"Geometry_Buffer(Geometry self, double distance, int quadsecs=30) -> Geometry\n"
"\n"
Expand Down
5 changes: 5 additions & 0 deletions gdal/swig/python/osgeo/ogr.py
Expand Up @@ -6641,6 +6641,11 @@ def ConvexHull(self, *args):
return _ogr.Geometry_ConvexHull(self, *args)


def MakeValid(self, *args):
"""MakeValid(Geometry self) -> Geometry"""
return _ogr.Geometry_MakeValid(self, *args)


def Buffer(self, *args, **kwargs):
"""
Buffer(Geometry self, double distance, int quadsecs=30) -> Geometry
Expand Down

0 comments on commit 29693f9

Please sign in to comment.