OGR – Understanding Multipolygon Models with Holes in Shapefiles

holesogrshapefile

I'm trying to unpack shapefiles in C# using GDAL-OGR library, but I'm confused about the structure of multipolygon with holes in OGR. As far as I know, the models of shapefiles and OGR geometry (the latter following simple feature specifications) are different. Shapefiles have a "polygon" type that might be in OGR term either a polygon or a multipolygon. So I must handle both cases. When reading a shapefile, OGR does some topological analysis to figure out the spatial relationship between rings and determine if they must be outer or inner rings.

An OGR polygon is a collection of rings, the first one being the outer ring and the following ones the inner rings. So, this code will work fine.

Feature feat;
while ((feat = layer.GetNextFeature()) != null) 
{
     var geom = feat.GetGeometryRef();
     for (var i = 0; i < geom.GetGeometryCount(); i++)
     {
          //First one is the outer ring and, all the next ones are the inner rings
          var ring = geom.GetGeometryRef(i);
          ...
     }
}

But, if the geometry is multipolygon, e.g 2 polygons with holes. How do you determine which one is the outer ring of the second polygon? Because if I understand it correctly, geom.GetGeometryRef() will only return the first polygon. Shouldn't Feature have something like feat.GetGeometryCount() (to determine the number of polygons – the outer rings only) and feat.GetGeometryRef(i) (to get the i outer ring of i polygon) or something? I checked, and there is none. How do you handle such case? Can anyone help me with it, please?

Also, how many nested levels that OGR support for polygons (i.e, holes inside holes)? How do handle such cases?

Sorry for my English! I'm not a native speaker.

Best Answer

A multipolygon is a different type of geometry than a polygon. So feature.getGeomtryRef() will give you the Multipolygon. Multipolygon itself inherits from MultiSurface, which inherits from GeometryCollection, which inherits from Geometry.

Polygon inherits from CurvePolygon, then from Surface, then from Geometry.

So all you have to do is "go one level deeper". Use getNumGeometries() or GetGeometryCount() - depending on what ogr implementation you use - to get the number of geometries in a GeometryCollection, which is the number of polygons inside the multipolygon.

In the case of a Multipolygon, calling getGeometryRef(i) will return a Polygon, which you then treat exactly as you already do in your example to retrieve the single outer ring and zero or more inner rings.

This also answers the second part of your question: As each Polygon is still distinctly separate, each of them still has an outer and 0+ inner rings, which you can treat separately.

This also means it doesn't matter how these polygons are situated inside the multipolygon: Each of them has to be a valid polygon itself and adhere to the rules of multipolygons to be valid, which makes any problems like nested polygons (islands inside a hole of an outer polygon) perfectly possible, as long as no invalid line intersections occur.

Multiple inner rings for a single polygon are meant to be used when there are multiple holes inside it.

Example: A multipolygon "park" consists of:

  • One polygon defines the landmass. There are multiple lakes, which are represented by holes, each of those being an inner ring.
    • If there are islands in those lakes, those will be their own polygons. But still part of the same multipolygon!
      • These islands could then have their own lakes/holes, and so on.

This should work, then (no guarantees, I rarely work with C# these days):

Feature feat;
while ((feat = layer.GetNextFeature()) != null) 
{
     // Get GeometryCollection/Multipolygon
     var gc = feat.GetGeometryRef(); 
     // Let's see how many child geometries this has
     for (var i = 0; i < gc.GetGeometryCount(); i++)
     {
         // So this must be our polygon
         var geometry = gc.getGeometryRef(i);
         for (var j = 0; j < geometry.GetGeometryCount(); j++)
         {
             //And here we have the rings. First one is the outer, all others are inner, defining holes
             var ring = geometry.GetGeometryRef(j);
     }
}

This only works if you already know it's a multipolygon. Unless this is an entirely static script where you know the inputs, you'll need to add type checks (GetGeometryType()) to ensure the GeometryCollection is what you expect it to be.

If you check the main API page for OGRGeometry, you can nicely see how the different types of geometry are "related" and inherit from each other.