Reimers.dk

.NET, AJAX and Google APIs brought together
Welcome to Reimers.dk Sign in | Join | Help
in Search

Jacob Reimers Weblog

Ensuring Valid SqlGeography Objects

Yesterday I wrote a post about the release of a project that can convert shapefiles and KML files to SqlGeography objects for use in a SQL Server 2008 spatial database. The big issue was that the SqlGeography class won't create valid objects from any collection of points. First of all the drawn polygon cannot be larger than a hemisphere. You would think that Microsoft's coders would be smart enough to render the input as the smaller of the two options. Google Earth does not require your KML files to be ordered in a special way. The second problem is that polygon border cannot cross themselves. Again you would think that this could have been handled by some input validation, but no.

I received a comment from k.van.daalen who explained how he had managed to clean his database by SQL script. Since the SQL scripts call CLR functions, it is possible to do the same using code. Below you can see the process of generating valid SqlGeography objects. The input is a GooglePolygon which can be created either using the Shapefile reader or by feeding a KML definition to a GoogleOverlayCollection.

    private static void WritePolygon(GooglePolygon shape, SqlGeographyBuilder b)

    {

        if (shape.Points[0] != shape.Points[shape.Points.Count - 1])

        {

            shape.Points.Add(shape.Points[0].Clone());

        }

        b.BeginGeography(OpenGisGeographyType.Polygon);

        b.BeginFigure(shape.Points[0].Latitude, shape.Points[0].Longitude);

        for (int i = 1; i < shape.Points.Count; i++)

        {

            b.AddLine(shape.Points[i].Latitude, shape.Points[i].Longitude);

        }

        b.EndFigure();

        b.EndGeography();

    }

When the polygon has been written to the SqlGeographyBuilder the SqlGeography object can be retrieved from the ConstructedGeography. If the written polygon is not valid an exception is thrown. The builder doesn't have any way to know if the built object is valid, so you are left with catching the exception.

The catch code goes like this:

As k.van.daalen told, the SqlGeometry class has a MakeValid method which is not present in the SqlGeography class (why??). So the polygon is recreated as a SqlGeometry object which is then 'made valid'.

    catch

    {

        SqlGeometryBuilder gb = GetGeometryBuilder();

        WriteGeometryPolygon(shape, gb);

        SqlGeometry geom = gb.ConstructedGeometry.MakeValid();

        return ConvertToSqlGeography(geom);

    }

The last line call a method the where the SqlGeometry object is converted to a SqlGeography object by writing it out as a string and letting the SqlGeography parse that string, like so:

    return SqlGeography.STPolyFromText(geom.STAsText(), 4326);

As you can see generating valid SqlGeography object can be quite a heavy operation.Depending on how large your dataset is you may have to let the computer crunch it for about an hour (which is what my 1 CPU laptop took to crunch 500MB of shapefiles), which is still managable. The above code is available in the SVN trunk at svn://svn.reimers.dk/sqlspatial

Published 10. februar 2009 22:33 by jjrdk

Comments

 

k.van.daalen said:

Jacob,

In your code you have missed a crucial piece of the conversion steps.

You should also try to reorder the ringorientation,so that your polygon can't be larger than a hemisphere.

This can be accomplished by the following SQL Statement:

UPDATE GeometryShapeTable

SET geomCollumn = geomCollumn.MakeValid().STUnion(geomCollumn.STStartPoint());

In your code this would be someting like this:

catch

   {

       SqlGeometryBuilder gb = GetGeometryBuilder();

       WriteGeometryPolygon(shape, gb);

       SqlGeometry geom = gb.ConstructedGeometry.MakeValid();

//Reorder Ringorientation if needed

       geom = geom.MakeValid().STUnion(geom.STStartPoint());

       return ConvertToSqlGeography(geom);

   }

This step reorders the ringorientation if needed. The key to this step is the the STUnion() method.  Since this is an OGC-based method, working on the entire geometry for a given feature, it forces polygons into the orientation required for the method - which just happens to be the one used for the Geography type.  The method illustrated seems to be quite efficient and only changes ringorientation for wrongly orentated polygones.

februar 11, 2009 10:21
 

jjrdk said:

Thanks for the feedback. I actually reverse the polygon points directly in my GooglePolygon and it works in my test. I'll try and compare with geom = geom.MakeValid().STUnion(geom.STStartPoint()) and see which is faster.

I think one improvement that could be made is simply starting out creating the polygon as a geometry figure and then converting it. This means you don't have to catch any exceptions.

februar 11, 2009 11:08
Anonymous comments are disabled