Skip to content

Commit

Permalink
Merge pull request #10220 from rouault/fix_10217
Browse files Browse the repository at this point in the history
Add OGRGeometry::hasEmptyParts()/removeEmptyParts(), and use it in exportToGEOS()
  • Loading branch information
rouault committed Jun 18, 2024
2 parents 2084297 + 1d6ee52 commit 8157d73
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 4 deletions.
128 changes: 128 additions & 0 deletions autotest/cpp/test_ogr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4047,4 +4047,132 @@ TEST_F(test_ogr, OGRGeometry_IsRectangle)
}
}

// Test OGRGeometry::removeEmptyParts()
TEST_F(test_ogr, OGRGeometry_removeEmptyParts)
{
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt("POINT EMPTY", nullptr, &poGeom);
ASSERT_NE(poGeom, nullptr);
EXPECT_FALSE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_TRUE(poGeom->IsEmpty());
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt("POLYGON ((0 0,0 1,1 0,0 0))",
nullptr, &poGeom);
ASSERT_NE(poGeom, nullptr);
EXPECT_FALSE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_NE(poGeom->toPolygon()->getExteriorRing(), nullptr);
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt("POLYGON ((0 0,0 1,1 0,0 0))",
nullptr, &poGeom);
ASSERT_NE(poGeom, nullptr);
poGeom->toPolygon()->addRingDirectly(new OGRLinearRing());
EXPECT_EQ(poGeom->toPolygon()->getNumInteriorRings(), 1);
EXPECT_TRUE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_NE(poGeom->toPolygon()->getExteriorRing(), nullptr);
EXPECT_EQ(poGeom->toPolygon()->getNumInteriorRings(), 0);
EXPECT_FALSE(poGeom->hasEmptyParts());
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt("COMPOUNDCURVE ((0 0,1 1))", nullptr,
&poGeom);
ASSERT_NE(poGeom, nullptr);
EXPECT_FALSE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_EQ(poGeom->toCompoundCurve()->getNumCurves(), 1);
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt("COMPOUNDCURVE ((0 0,1 1),(1 1,2 2))",
nullptr, &poGeom);
ASSERT_NE(poGeom, nullptr);
poGeom->toCompoundCurve()->getCurve(1)->empty();
EXPECT_EQ(poGeom->toCompoundCurve()->getNumCurves(), 2);
EXPECT_TRUE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_FALSE(poGeom->hasEmptyParts());
EXPECT_EQ(poGeom->toCompoundCurve()->getNumCurves(), 1);
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt("GEOMETRYCOLLECTION (POINT(0 0))",
nullptr, &poGeom);
ASSERT_NE(poGeom, nullptr);
EXPECT_FALSE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1);
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt(
"GEOMETRYCOLLECTION (POINT EMPTY,POINT(0 0),POINT EMPTY)", nullptr,
&poGeom);
ASSERT_NE(poGeom, nullptr);
EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 3);
EXPECT_TRUE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_FALSE(poGeom->hasEmptyParts());
EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1);
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt("GEOMETRYCOLLECTION EMPTY", nullptr,
&poGeom);
ASSERT_NE(poGeom, nullptr);
OGRGeometry *poPoly = nullptr;
OGRGeometryFactory::createFromWkt("POLYGON ((0 0,0 1,1 0,0 0))",
nullptr, &poPoly);
EXPECT_NE(poPoly, nullptr);
if (poPoly)
{
poPoly->toPolygon()->addRingDirectly(new OGRLinearRing());
poGeom->toGeometryCollection()->addGeometryDirectly(poPoly);
EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1);
EXPECT_TRUE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_FALSE(poGeom->hasEmptyParts());
EXPECT_EQ(poGeom->toGeometryCollection()->getNumGeometries(), 1);
}
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt(
"POLYHEDRALSURFACE (((0 0,0 1,1 1,0 0)))", nullptr, &poGeom);
ASSERT_NE(poGeom, nullptr);
EXPECT_FALSE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_EQ(poGeom->toPolyhedralSurface()->getNumGeometries(), 1);
delete poGeom;
}
{
OGRGeometry *poGeom = nullptr;
OGRGeometryFactory::createFromWkt(
"POLYHEDRALSURFACE (((0 0,0 1,1 1,0 0)))", nullptr, &poGeom);
ASSERT_NE(poGeom, nullptr);
poGeom->toPolyhedralSurface()->addGeometryDirectly(new OGRPolygon());
EXPECT_EQ(poGeom->toPolyhedralSurface()->getNumGeometries(), 2);
EXPECT_TRUE(poGeom->hasEmptyParts());
poGeom->removeEmptyParts();
EXPECT_FALSE(poGeom->hasEmptyParts());
EXPECT_EQ(poGeom->toPolyhedralSurface()->getNumGeometries(), 1);
delete poGeom;
}
}

} // namespace
13 changes: 13 additions & 0 deletions autotest/ogr/ogr_geos.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,19 @@ def test_ogr_geos_centroid_point_empty():
###############################################################################


def test_ogr_geos_centroid_polygon_with_empty_interior_ring():

g = ogr.CreateGeometryFromWkt("POLYGON((0 0,0 1,1 1,1 0,0 0))")
g.AddGeometry(ogr.Geometry(ogr.wkbLinearRing))

centroid = g.Centroid()

assert centroid.ExportToWkt() == "POINT (0.5 0.5)"


###############################################################################


@pytest.mark.require_geos(3, 12)
def test_ogr_geos_pointzm_empty():

Expand Down
23 changes: 21 additions & 2 deletions ogr/ogr_geometry.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,8 +523,9 @@ class CPL_DLL OGRGeometry

static GEOSContextHandle_t createGEOSContext();
static void freeGEOSContext(GEOSContextHandle_t hGEOSCtxt);
virtual GEOSGeom
exportToGEOS(GEOSContextHandle_t hGEOSCtxt) const CPL_WARN_UNUSED_RESULT;
GEOSGeom
exportToGEOS(GEOSContextHandle_t hGEOSCtxt,
bool bRemoveEmptyParts = false) const CPL_WARN_UNUSED_RESULT;
virtual OGRBoolean hasCurveGeometry(int bLookForNonLinear = FALSE) const;
virtual OGRGeometry *getCurveGeometry(
const char *const *papszOptions = nullptr) const CPL_WARN_UNUSED_RESULT;
Expand Down Expand Up @@ -608,6 +609,9 @@ class CPL_DLL OGRGeometry

OGRGeometry *SetPrecision(double dfGridSize, int nFlags) const;

virtual bool hasEmptyParts() const;
virtual void removeEmptyParts();

//! @cond Doxygen_Suppress
// backward compatibility to non-standard method names.
OGRBoolean Intersect(OGRGeometry *) const
Expand Down Expand Up @@ -2146,6 +2150,9 @@ class CPL_DLL OGRCurveCollection

OGRErr removeCurve(int iIndex, bool bDelete = true);

bool hasEmptyParts() const;
void removeEmptyParts();

OGRErr transform(OGRGeometry *poGeom, OGRCoordinateTransformation *poCT);
void flattenTo2D(OGRGeometry *poGeom);
void segmentize(double dfMaxLength);
Expand Down Expand Up @@ -2335,6 +2342,9 @@ class CPL_DLL OGRCompoundCurve : public OGRCurve

virtual void swapXY() override;

bool hasEmptyParts() const override;
void removeEmptyParts() override;

OGR_ALLOW_UPCAST_TO(Curve)
OGR_ALLOW_CAST_TO_THIS(CompoundCurve)
};
Expand Down Expand Up @@ -2584,6 +2594,9 @@ class CPL_DLL OGRCurvePolygon : public OGRSurface

virtual void swapXY() override;

bool hasEmptyParts() const override;
void removeEmptyParts() override;

OGR_ALLOW_UPCAST_TO(Surface)
OGR_ALLOW_CAST_TO_THIS(CurvePolygon)
};
Expand Down Expand Up @@ -3009,6 +3022,9 @@ class CPL_DLL OGRGeometryCollection : public OGRGeometry
OGRErr addGeometry(std::unique_ptr<OGRGeometry> geom);
virtual OGRErr removeGeometry(int iIndex, int bDelete = TRUE);

bool hasEmptyParts() const override;
void removeEmptyParts() override;

virtual void
assignSpatialReference(const OGRSpatialReference *poSR) override;

Expand Down Expand Up @@ -3506,6 +3522,9 @@ class CPL_DLL OGRPolyhedralSurface : public OGRSurface
virtual void swapXY() override;
OGRErr removeGeometry(int iIndex, int bDelete = TRUE);

bool hasEmptyParts() const override;
void removeEmptyParts() override;

virtual void accept(IOGRGeometryVisitor *visitor) override
{
visitor->visit(this);
Expand Down
18 changes: 18 additions & 0 deletions ogr/ogrcompoundcurve.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -970,3 +970,21 @@ double OGRCompoundCurve::get_AreaOfCurveSegments() const
}
return dfArea;
}

/************************************************************************/
/* hasEmptyParts() */
/************************************************************************/

bool OGRCompoundCurve::hasEmptyParts() const
{
return oCC.hasEmptyParts();
}

/************************************************************************/
/* removeEmptyParts() */
/************************************************************************/

void OGRCompoundCurve::removeEmptyParts()
{
oCC.removeEmptyParts();
}
42 changes: 42 additions & 0 deletions ogr/ogrcurvecollection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -749,4 +749,46 @@ OGRErr OGRCurveCollection::removeCurve(int iIndex, bool bDelete)
return OGRERR_NONE;
}

/************************************************************************/
/* hasEmptyParts() */
/************************************************************************/

/**
* \brief Returns whether a geometry has empty parts/rings.
*
* Returns true if removeEmptyParts() will modify the geometry.
*
* This is different from IsEmpty().
*
* @since GDAL 3.10
*/
bool OGRCurveCollection::hasEmptyParts() const
{
for (int i = 0; i < nCurveCount; ++i)
{
if (papoCurves[i]->IsEmpty() || papoCurves[i]->hasEmptyParts())
return true;
}
return false;
}

/************************************************************************/
/* removeEmptyParts() */
/************************************************************************/

/**
* \brief Remove empty parts/rings from this geometry.
*
* @since GDAL 3.10
*/
void OGRCurveCollection::removeEmptyParts()
{
for (int i = nCurveCount - 1; i >= 0; --i)
{
papoCurves[i]->removeEmptyParts();
if (papoCurves[i]->IsEmpty())
removeCurve(i, true);
}
}

//! @endcond
22 changes: 22 additions & 0 deletions ogr/ogrcurvepolygon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -929,3 +929,25 @@ OGRSurfaceCasterToCurvePolygon OGRCurvePolygon::GetCasterToCurvePolygon() const
}

//! @endcond

/************************************************************************/
/* hasEmptyParts() */
/************************************************************************/

bool OGRCurvePolygon::hasEmptyParts() const
{
return oCC.hasEmptyParts();
}

/************************************************************************/
/* removeEmptyParts() */
/************************************************************************/

void OGRCurvePolygon::removeEmptyParts()
{
auto poExteriorRing = getExteriorRingCurve();
if (poExteriorRing && poExteriorRing->IsEmpty())
empty();
else
oCC.removeEmptyParts();
}
Loading

0 comments on commit 8157d73

Please sign in to comment.