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.