3

I maintain an application whose main job it is to take esri-ish things from users and turn them into sql spatial objects in our database (2008 R2). Below is an excerpt that may be useful:

public static SqlGeometry ToSqlGeometry(this IGeometry geometry)
{
    // Get rid of true curves
    geometry = SplineCurves(geometry);

    // Simplify polygon/polyline shapes so they can be WKBed
    (geometry as ITopologicalOperator).Simplify();

    //WKB goes into a byte array - C style
    IWkb wkb = geometry as IWkb;
    byte[] wkb_bytes = new byte[wkb.WkbSize];
    int byte_count = wkb.WkbSize;
    wkb.ExportToWkb(ref byte_count, out wkb_bytes[0]);

    //Convert to Sql Geometry
    return SqlGeometry.STGeomFromWKB(new SqlBytes(wkb_bytes), SRID);
}

Esri's doc for ITopologicalOperator::Simplify says "no self intersection will occur (except in certain circumstances)", but I can't find anything that elaborates on those 'certain circumstances'.

I've recently seen a case in which a self-overlapping polygon got through the above function unsimplified and caused much consternation in the database. This polygon began life as thirteen hastily-drawn polygons in a shapefile that were dumped into a GeometryBag and run through ITopologicalOperator::ConstructUnion before getting to the code shown above.

I've found that (pTopo.Buffer(1) as ITopologicalOperator).Buffer(-1) removes the self-overlaps that caused the problem and I can even live with the small changes this makes to polygon boundaries, but I'd like a more informed & less kludgy solution if at all possible. IPolygon4::SimplifyEx provides no help here either. Does anyone know what's up?

EDIT: I tried this today with no improvement:

// Simplify polygon/polyline shapes so they can be WKBed
var pTopo = geometry as ITopologicalOperator2;
pTopo.IsKnownSimple_2 = false;
pTopo.Simplify();

EDIT 2: I tried this today with no improvement:

//Convert to Sql Geometry
var geom = SqlGeometry.STGeomFromWKB(new SqlBytes(wkb_bytes), SRID);
if (!geom.STIsValid())
    throw new Exception("Punk, clean up your stuff!");
return geom;

EDIT 3: Given new leads to track down from Michael Miles-Stimpson's answer, I tried various acrobatics with the post-Simplify GeometryCollection with no luck. NB: the things that rely on this method expect a single geometry so his suggestion of returning a list won't do.

  • ConstructUnion on the whole GeometryCollection
  • Looping the collection and ITopologicalOperator4::Union(eachMember)
  • Looping the collection and SqlGeometry::STUnion([eachMember converted to a SqlGeometry])

It might be worth noting that ConstructUnion is typically run on geometries before this method is called, and before this saga I thought that was supposed to produce simple geometries by its nature. It appears that perhaps the problem isn't so much with Simplify (although Michael's correct about my misunderstanding of what it does) but rather with ConstructUnion. Here's one example of what I tried (including message boxes & other uglinesses for debugging):

public static SqlGeometry ToSqlGeometry(this IGeometry geometry)
{
    // Get rid of true curves
    try
    {
        // Place vertices every 15 degrees around a circle (language below specifies max deflection in radians)
        IPolycurve2 pCurve2 = geometry as IPolycurve2;
        pCurve2.DensifyByAngle(0, System.Math.PI / 18);

        if (geometry is IPolygon)
        {
            var pTopo = geometry as ITopologicalOperator5;
           //geometry = (pTopo.Buffer(0.5) as ITopologicalOperator2).Buffer(-0.5);

           // Simplify polygon/polyline shapes so they can be WKBed
           pTopo.IsKnownSimple_2 = false;
           pTopo.Simplify();
           var newPoly = new PolygonClass();
           newPoly.SpatialReference = SpatialReferences.DefaultSpatialReference;
           var pTopo2 = newPoly as ITopologicalOperator5;
           pTopo2.ConstructUnion(pTopo as IEnumGeometry);
           geometry = newPoly;
           esriNonSimpleReasonEnum enu;
           pTopo2.IsKnownSimple_2 = false;
           if (!pTopo2.get_IsSimpleEx(out enu))
           {
               MessageBox.Show(string.Format("NonSimple!\n{0}",enu));
           }
       }
   }
   //  The cast to IPolycurve will generate a NullReferenceException for points, which can't be 
   //  splined or simplified anyway (and we don't want them buffered) so just get on with your life
   catch (NullReferenceException) { }


   //WKB goes into a byte array - C style
   IWkb wkb = geometry as IWkb;
   byte[] wkb_bytes = new byte[wkb.WkbSize];
   int byte_count = wkb.WkbSize;
   wkb.ExportToWkb(ref byte_count, out wkb_bytes[0]);

   //Convert to Sql Geometry
   var returnGeom = SqlGeometry.STGeomFromWKB(new SqlBytes(wkb_bytes), SRID);
   if (!returnGeom.STIsValid())
       MessageBox.Show("Not valid geometry");
   if (!returnGeom.STIsValid())
       returnGeom = returnGeom.MakeValid();
   if (!returnGeom.STIsValid())
       throw new InvalidGeometryException("Geometry cannot be made valid");
   return returnGeom;
}

EDIT 4: Found this thread and modified SATYA's code to detect polyline overlaps using the boundary of the polygon. Still no luck. The most infuriating part of this is that nothing detects a problem with this geometry until it's in the database. Anyway nowadays cost>>benefit on this little problem so I'm gonna stop work.

2

I think you might have a misconception of what simplify does, the geometry is not displaced in any way to fix the self-intersections but endpoints should exist at the location of the intersection and it is returned as a multi-part feature (IGeometryCollection where GeometryCount > 1 to indicate that an intersection exists).

You do need to set the IsKnownSimple to be false before proceeding despite what you see in the example. What is returned is a multi-part geometry which will need to be broken down into paths (or exterior rings for polygons) and then wrapped back up into a polyline or polygon but this means your function will return multiple geometries and not just one.

As an aside you should always strive to use the highest interface that supports your method(s), this is particularly true for ITopologialOperator as the lower numbered methods are a little buggy, the numbers go up to 6 with ITopologicalOperator6 which supports polygons and has a new method SimplifyAsFeature but to support your existing methods perhaps #4 Simplify is for you.

I don't have any experience with SqlGeometry but this should be close to what you're after (assuming your conversion from Esri geometry is correct):

public static List<SqlGeometry> ToSqlGeometry(this IGeometry geometry)
{
    // Get rid of true curves
    geometry = SplineCurves(geometry);

    // Simplify polygon/polyline shapes so they can be WKBed
    ITopologicalOperator4 pTop4 = (ITopologicalOperator4)geometry;
    pTop4.IsKnownSimple_2 = false;
    pTop4.Simplify();
    IGeometryCollection pGeomColl = (IGeometryCollection)geometry;

    List<SqlGeometry> ReturnList = new List<SqlGeometry>();
    SqlGeometry ThisGeom;
    for (int cnt = 0; cnt < pGeomColl.GeometryCount; cnt++)
    {
        IGeometry pSingleGeometry = pGeomColl.get_Geometry(cnt);
        //WKB goes into a byte array - C style
        IWkb wkb = geometry as IWkb;
        byte[] wkb_bytes = new byte[wkb.WkbSize];
        int byte_count = wkb.WkbSize;
        wkb.ExportToWkb(ref byte_count, out wkb_bytes[0]);
        ThisGeom = SqlGeometry.STGeomFromWKB(new SqlBytes(wkb_bytes), SRID);
        ReturnList.Add(ThisGeom);
    }
    //Convert to Sql Geometry
    return ReturnList;
}

The returned list may be indexed like an array to retrieve each part... I don't know if you're familiar with C# List objects; I've found them to be superior to arrays by way of having all the same properties and are much more flexible, IMHO if you don't know about list objects you should try to learn about them..

  • See my edit. Also FWIW, the highest I can go with ConstructUnion is ITopologicalOperator5. Above that, VS/ReSharper turns things red. Building anyway, I get 'ESRI.ArcGIS.Geometry.ITopologicalOperator6' does not contain a definition for 'ConstructUnion' ... This may be due to running VS2013 when the SDK only supports 2012 (I think). – id est laborum Mar 11 '16 at 20:45
  • They changed the names, ITopo6 doesn't use the old methods but has new ones that replace it. It is best to use the VS that suits the ArcGis install, we purchased VS2013 but I have not installed that version as, with purchase, you can download VS2012/VS2010 to suit your ArcGis SDK requirements... I am still using VS2010. I would suggest that getting the recommended version might be a good idea, but if it 'aint broke' no need to fix it; I think you can have multiple VS(year) installs but can't say for sure as I've never tried it. – Michael Stimson Mar 13 '16 at 21:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.