<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:copyright="http://blogs.law.harvard.edu/tech/rss" xmlns:image="http://purl.org/rss/1.0/modules/image/">
    <channel>
        <title>Spatial</title>
        <link>http://www.jasonfollas.com/blog/category/11.aspx</link>
        <description>Spatial</description>
        <language>en-US</language>
        <copyright>Jason Follas</copyright>
        <managingEditor>jason@jasonfollas.com</managingEditor>
        <generator>Subtext Version 2.0.0.43</generator>
        <item>
            <title>For Jeff: Spatial Querying</title>
            <link>http://jasonfollas.com/blog/archive/2009/03/04/for-jeff-spatial-querying.aspx</link>
            <description>&lt;p&gt;A quick sample of usage as requested by my friend &lt;a href="http://www.mcwherter.net/blog/"&gt;Jeff McWherter&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;Querying for all data in a table where the "zip code polygon" is within 20 miles of a geocode point (must convert 20 miles to meters in the predicate)::&lt;/p&gt;
&lt;pre class="sql" name="code"&gt;select * 
from dbo.fe_2007_us_zcta500 
where Boundary.STDistance('POINT(-79.8884595930576 43.2609696686268)') &amp;lt; (20 * 1609.344) &lt;/pre&gt;
&lt;img src="http://jasonfollas.com/blog/aggbug/67.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2009/03/04/for-jeff-spatial-querying.aspx</guid>
            <pubDate>Wed, 04 Mar 2009 23:08:55 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2009/03/04/for-jeff-spatial-querying.aspx#feedback</comments>
            <slash:comments>1</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/67.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/67.aspx</trackback:ping>
        </item>
        <item>
            <title>Knowledge++ [4]</title>
            <link>http://jasonfollas.com/blog/archive/2009/02/21/knowledge-4.aspx</link>
            <description>&lt;p&gt;I recently developed a spatially-aware .NET application that did not use SQL Server 2008 as the backend (this enterprise was still on SS2005, but we needed the spatial support in the application today).  While the application worked properly on my laptop, it was a huge failboat when deployed to the server environment.&lt;/p&gt;
&lt;p&gt;I had previously posted that you can get the Microsoft.SqlServer.Types library from MS Downloads, but it turns out that this alone is not sufficient to allow your application to run.  You also need to ensure that the SQL Server 2008 Native Client is also installed (regardless of whether you're accessing a SS2008 instance or not). &lt;em&gt;&lt;strong&gt;&lt;font color="#ff0000"&gt;Update!&lt;/font&gt; &lt;/strong&gt;You actually don't... read below.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Both the Types library and the Native Client can be downloaded from the following:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=228de03f-3b5a-428a-923f-58a033d316e1&amp;amp;DisplayLang=en"&gt;http://www.microsoft.com/downloads/details.aspx?FamilyID=228de03f-3b5a-428a-923f-58a033d316e1&amp;amp;DisplayLang=en&lt;/a&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My discovery source: &lt;font face="Arial"&gt;&lt;a href="https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=355402&amp;amp;wa=wsignin1.0"&gt;https://connect.microsoft.com/SQLServer/feedback/ViewFeedback.aspx?FeedbackID=355402&amp;amp;wa=wsignin1.0&lt;/a&gt;&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;UPDATE: &lt;/strong&gt;Per &lt;a href="http://blogs.msdn.com/isaac/default.aspx"&gt;Isaac Kunen&lt;/a&gt; (in this blog post's comments as well as offline discussion), the missing component from the Types library is simply an updated version of the C Runtime.  The fix of using the Native Client is a hack in this case because its MSI actually installs the updated CRT (which the MSI for the Types library should have done also - it's a goof that MS Downloads hasn't been updated with an updated version of the Types API after the above Connect feedback was answered...&lt;/p&gt;
&lt;p&gt;The Microsoft Visual C++ 2008 redistributable by itself can be downloaded from:&lt;/p&gt;
&lt;p&gt;&lt;font face="Arial"&gt;&lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=A5C84275-3B97-4AB7-A40D-3802B2AF5FC2&amp;amp;displaylang=en"&gt;http://www.microsoft.com/downloads/details.aspx?FamilyID=A5C84275-3B97-4AB7-A40D-3802B2AF5FC2&amp;amp;displaylang=en&lt;/a&gt;&lt;/font&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/63.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2009/02/21/knowledge-4.aspx</guid>
            <pubDate>Sun, 22 Feb 2009 02:45:26 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2009/02/21/knowledge-4.aspx#feedback</comments>
            <slash:comments>3</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/63.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/63.aspx</trackback:ping>
        </item>
        <item>
            <title>Knowledge++ [3]</title>
            <link>http://jasonfollas.com/blog/archive/2009/01/29/knowledge-3.aspx</link>
            <description>&lt;p&gt;When you write a query that SELECTs a SQL Server 2008 Spatial type, the returned result is a binary value.  Without closely examining the bytes, I just assumed that this was WKB (Well-Known Binary).&lt;/p&gt;
&lt;p&gt;Well, as it turns out, the bytes that are returned are simply a serialized version of the .NET object (the UDT has a Write() method that it calls internally to serialize the object in binary).  This is NOT directly compatible with WKB.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My discovery source: &lt;font face="Arial"&gt;&lt;a href="http://social.msdn.microsoft.com/Forums/en-US/sqlspatial/thread/40ee9466-d7bf-4340-b295-53217ae5128e"&gt;http://social.msdn.microsoft.com/Forums/en-US/sqlspatial/thread/40ee9466-d7bf-4340-b295-53217ae5128e&lt;/a&gt;&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;
&lt;img src="http://jasonfollas.com/blog/aggbug/62.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2009/01/29/knowledge-3.aspx</guid>
            <pubDate>Thu, 29 Jan 2009 14:32:28 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2009/01/29/knowledge-3.aspx#feedback</comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/62.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/62.aspx</trackback:ping>
        </item>
        <item>
            <title>Using SQL Server Spatial Objects as ADO.NET Parameter Values</title>
            <link>http://jasonfollas.com/blog/archive/2008/12/11/using-ado.net-with-sql-server-spatial-objects.aspx</link>
            <description>&lt;p&gt;I've &lt;a target="_blank" href="http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx"&gt;previously mentioned&lt;/a&gt; that the SQL Server 2008 Spatial data types are freely available for use in your .NET applications, regardless of whether you have SQL Server 2008 or not.  This allows you to incorporate some powerful spatial capabilities right into your application.  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Look for "&lt;font face="Arial"&gt;Microsoft SQL Server System CLR Types" on this page: &lt;font face="Arial"&gt;&lt;a href="http://www.microsoft.com/downloads/details.aspx?FamilyID=228DE03F-3B5A-428A-923F-58A033D316E1&amp;amp;displaylang=en"&gt;http://www.microsoft.com/downloads/details.aspx?FamilyID=228DE03F-3B5A-428A-923F-58A033D316E1&amp;amp;displaylang=en&lt;/a&gt; )&lt;/font&gt;&lt;/font&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;However, in most usage scenarios, there will come a time when you have an instance of a SQL Server spatial object in your .NET application, and need to commit it to your SQL Server 2008 database.  How would you do this, without losing fidelity or resorting to serialization of the object to WKT first?&lt;/p&gt;
&lt;p&gt;The solutions is to create a Parameter object of type System.Data.SqlDbType.Udt.  Then set the UdtTypeName parameter to the SQL Server-recognized type name (i.e., for SqlGeometry, you would simply use Geometry).&lt;/p&gt;
&lt;p&gt;The following code demonstrates executing an UPDATE statement that sets the value of a Spatial field to a newly constructed object.&lt;/p&gt;
&lt;pre class="c#" name="code"&gt;using (SqlConnection conn = new SqlConnection("Server=.;Integrated Security=true;Initial Catalog=scratch"))
{
   using (SqlCommand cmd = new SqlCommand("UPDATE fe_2007_us_zcta500 SET Boundary=@boundary WHERE id=@id", conn))
   {
      SqlParameter id = cmd.Parameters.Add("@id", System.Data.SqlDbType.Int);

      SqlParameter boundary = cmd.Parameters.Add("@boundary", System.Data.SqlDbType.Udt);
      boundary.UdtTypeName = "geometry";

      SqlGeometry geom = SqlGeometry.Parse("POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))");
      
      boundary.Value = geom;
      id.Value = 123;

      conn.Open();
      cmd.ExecuteNonQuery();
      conn.Close();
   }
}
&lt;/pre&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/58.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/12/11/using-ado.net-with-sql-server-spatial-objects.aspx</guid>
            <pubDate>Thu, 11 Dec 2008 19:04:12 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2008/12/11/using-ado.net-with-sql-server-spatial-objects.aspx#feedback</comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/58.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/58.aspx</trackback:ping>
        </item>
        <item>
            <title>SqlGeography: Ring Orientation of Polygon Interior Rings (Holes)</title>
            <link>http://jasonfollas.com/blog/archive/2008/11/25/sqlgeography-ring-orientation-of-polygon-interior-rings-holes.aspx</link>
            <description>&lt;p&gt;I have mentioned before how the Ring Orientation for the exterior ring of a Polygon is significant when instantiating a SqlGeography object.  In this case, a Counter-Clockwise orientation is required so that as an observer walks along the path, the interior of the Polygon is always to their left.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ring Orientation for SqlGeography" hspace="5" align="right" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/r_RingOrientation.PNG" /&gt;But, what I have never really seen documented (or paid attention to, at least) is the fact that the interior rings, or holes, of a Polygon also have specific Ring Orientation requirements.  &lt;/p&gt;
&lt;p&gt;In keeping with the "Left-handed" rule, interior rings must be defined in a Clockwise manner - the opposite orientation of the shape's exterior ring.  This is because holes within a Polygon are considered to be part of the exterior of the shape, so the observer walking in a Clockwise direction is still keeping the Polygon's interior to their left.&lt;/p&gt;
&lt;p&gt;(I should note here that the Ring Orientation for SqlGeography is the exact opposite of ESRI's ShapeFile format, which is why Ring Orientation has been on my mind for the past few days).&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/55.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/11/25/sqlgeography-ring-orientation-of-polygon-interior-rings-holes.aspx</guid>
            <pubDate>Tue, 25 Nov 2008 14:14:18 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2008/11/25/sqlgeography-ring-orientation-of-polygon-interior-rings-holes.aspx#feedback</comments>
            <slash:comments>3</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/55.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/55.aspx</trackback:ping>
        </item>
        <item>
            <title>Spatial: Determining Ring Orientation</title>
            <link>http://jasonfollas.com/blog/archive/2008/11/24/spatial-determining-ring-orientation.aspx</link>
            <description>&lt;p&gt;A Ring is a list of points such that the starting point and ending point are the same (forming a closed shape).  The order the you define the points that make up a Ring  - known as Ring Orientation - is significant, for various data formats (including SQL Server's Geography type) imply special meaning for rings that are defined in a clockwise manner as opposed to a counter-clockwise manner.  &lt;/p&gt;
&lt;p&gt;Given a list of points with no additional context, it can be difficult to determine the Ring Orientation being used.  &lt;/p&gt;
&lt;p&gt;For example, suppose that you have a generic list of points that represent the boundary of a postal code, and that you wish to use these points in order to construct a Polygon instance using the SqlGeography type.  SqlGeography happens to use "Left-handed" ordering, so that as an observer walks along the set of points in the order defined, the "inside" of the polygon is always to their left.  This also implies that the exterior ring of a Polygon is defined in a counter-clockwise manner.&lt;/p&gt;
&lt;p&gt;If you try to define a polygon with an area greater than a single hemisphere (this is a nice way to say "if you screw up and use the wrong orientation"), then the SqlGeography type will throw an exception.  So, aside from using Try-Catch, what can you do?&lt;/p&gt;
&lt;p&gt;While researching solutions to this problem, I stumbled upon a paper entitled "&lt;a href="http://www.engr.colostate.edu/~dga/dga/papers/point_in_polygon.pdf"&gt;A Winding Number and Point-in-Polygon Algorithm&lt;/a&gt;" from the Colorado State University.  It turns out that a simple algorithm with O(n) complexity can be used to determine if a point is within a Polygon, and a side effect also provides the Ring Orientation.  The key to this algorithm is determining the trend of the ring at each crossing of an axis.&lt;/p&gt;
&lt;p&gt;Since I was only interested in Ring Orientation (and not point enclosure detection), I didn't need to use this particular algorithm.  Instead, I took inspiration from the winding concept, and created a simpler derivative algorithm:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Visual example of how to determine ring orientation at the extreme left point" hspace="5" align="right" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/9/o_orientation_example.png" /&gt; &lt;/p&gt;
&lt;ol&gt;
    &lt;li&gt;Iterate the point collection and determine the extreme "left" and "right" points &lt;/li&gt;
    &lt;li&gt;Normalize the line segments connected to these points so that they each have the same "X" dimension length &lt;/li&gt;
    &lt;li&gt;Compare the "Y" values of the normalized segments to establish the trend through that extreme point (i.e., is the "previous" segment above or below the "next" segment) &lt;/li&gt;
    &lt;li&gt;In the spirit of the Winding algorithm, use opposite orientations for the left and right points so that the results coincide with one another &lt;/li&gt;
    &lt;li&gt;A negative result (negative indicates Clockwise orientation, positive result indicates Counter-Clockwise orientation, and a result of zero would be undefined &lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I've actually written (and posted) several versions of this algorithm, each time discovering some edge case exception that would cause me to take down the post and rewrite the algorithm.  I believe the code below works for all simple polygons on a Cartesian coordinate system (read: I have more testing to see if this will work with an ellipsoidal model, like SqlGeography).  &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: The following code is generic in nature, and as such, I've defined my own Point structure instead of using a SqlGeometry or SqlGeography, etc. &lt;/em&gt;&lt;/p&gt;
&lt;pre class="c#" name="code"&gt;struct Point
{
    public double X { get; set; }
    public double Y { get; set; }
}

enum RingOrientation : int
{
    Unknown = 0,
    Clockwise = -1,
    CounterClockwise = 1
};

RingOrientation Orientation(Point[] points)
{
    // Inspired by http://www.engr.colostate.edu/~dga/dga/papers/point_in_polygon.pdf

    // This algorithm is to simply determine the Ring Orientation, so to do so, find the
    // extreme left and right points, and then check orientation

    if (points.Length &amp;lt; 4)
    {
        throw new ArgumentException("A polygon requires at least 4 points.");
    }

    if (points[0].X != points[points.Length - 1].X || points[0].Y != points[points.Length - 1].Y)
    {
        throw new ArgumentException("The array of points is not a polygon.  The first and last point must be identical.");
    }

    int rightmostIndex = 0;
    int leftmostIndex = 0;

    for (int i = 1; i &amp;lt; points.Length; i++)
    {
        if (points[i].X &amp;lt; points[leftmostIndex].X)
        {
            leftmostIndex = i;
        }
        if (points[i].X &amp;gt; points[rightmostIndex].X)
        {
            rightmostIndex = i;
        }
    }


    Point p0; // Point before the extreme
    Point p1; // The extreme point
    Point p2; // Point after the extreme

    double m; // Holds line slope

    double lenP2x;  // Length of the P1-P2 line segment's delta X
    double newP0y;  // The Y value of the P1-P0 line segment adjusted for X=lenP2x

    RingOrientation left_orientation;
    RingOrientation right_orientation;

    // Determine the orientation at the Left Point
    if (leftmostIndex == 0)
        p0 = points[points.Length - 2];
    else
        p0 = points[leftmostIndex - 1];

    p1 = points[leftmostIndex];

    if (leftmostIndex == points.Length - 1)
        p2 = points[1];
    else
        p2 = points[leftmostIndex + 1];

    m = (p1.Y - p0.Y) / (p1.X - p0.X);

    if (double.IsInfinity(m))
    {
        // This is a vertical line segment, so just calculate the dY to
        // determine orientation

        left_orientation = (RingOrientation)Math.Sign(p0.Y - p1.Y);
    }
    else
    {
        lenP2x = p2.X - p1.X;
        newP0y = p1.Y + (m * lenP2x);

        left_orientation = (RingOrientation)Math.Sign(newP0y - p2.Y);
    }



    // Determine the orientation at the Right Point
    if (rightmostIndex == 0)
        p0 = points[points.Length - 2];
    else
        p0 = points[rightmostIndex - 1];

    p1 = points[rightmostIndex];

    if (rightmostIndex == points.Length - 1)
        p2 = points[1];
    else
        p2 = points[rightmostIndex + 1];

    m = (p1.Y - p0.Y) / (p1.X - p0.X);

    if (double.IsInfinity(m))
    {
        // This is a vertical line segment, so just calculate the dY to
        // determine orientation

        right_orientation = (RingOrientation)Math.Sign(p1.Y - p0.Y);
    }
    else
    {
        lenP2x = p2.X - p1.X;
        newP0y = p1.Y + (m * lenP2x);

        right_orientation = (RingOrientation)Math.Sign(p2.Y - newP0y);
    }


    if (left_orientation == RingOrientation.Unknown)
    {
        return right_orientation;
    }
    else
    {
        return left_orientation;
    }
}


void Test()
{
    // Simple triangle - left extreme point is vertically "in between" line segments
    Point[] points = new Point[]
    {
        new Point(5,-1),
        new Point(0,0),
        new Point(5,1),
        new Point(5,-1)
    };

    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise);
    Array.Reverse(points);
    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise);

    // Case where both line segments are above the left extreme point
    points = new Point[]
    {
        new Point(2,1),
        new Point(0,0),
        new Point(1,1),
        new Point(2,1)
    };

    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise);
    Array.Reverse(points);
    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise);

    // Case where both line segments are below the left extreme point
    points = new Point[]
    {
        new Point(2,-1),
        new Point(0,0),
        new Point(1,-1),
        new Point(2,-1)
    };

    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise);
    Array.Reverse(points);
    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise);

    // Case where line segment is vertical (slope cannot be determined)
    points = new Point[]
    {
        new Point(0,0),
        new Point(0,1),
        new Point(1,1),
        new Point(1,0),
        new Point(0,0)
    };

    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise);
    Array.Reverse(points);
    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise);

    // Case where angle thru left extreme point is a right angle
    points = new Point[]
    {
        new Point(0,0),
        new Point(1,1),
        new Point(1,-1),
        new Point(0,0)
    };

    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise);
    Array.Reverse(points);
    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise);

    // Real-world case from a SHP file
    points = new Point[]
    {
        new Point(-156.92467299999998,20.738695999999997),
        new Point(-156.924636,20.738822),
        new Point(-156.924608,20.73894),
        new Point(-156.92458,20.739082),
        new Point(-156.92460599999998,20.739234),
        new Point(-156.924551,20.739326),
        new Point(-156.924507,20.739241999999997),
        new Point(-156.924482,20.739082),
        new Point(-156.924466,20.738854999999997),
        new Point(-156.924387,20.738602999999998),
        new Point(-156.924308,20.738325),
        new Point(-156.924239,20.738063999999998),
        new Point(-156.92424,20.737887999999998),
        new Point(-156.924285,20.737811999999998),
        new Point(-156.924475,20.73762),
        new Point(-156.92458299999998,20.737603999999997),
        new Point(-156.924754,20.737579),
        new Point(-156.924851,20.737731),
        new Point(-156.924956,20.738101),
        new Point(-156.924909,20.738343999999998),
        new Point(-156.924818,20.738487),
        new Point(-156.92467299999998,20.738695999999997)
    };

    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.Clockwise);
    Array.Reverse(points);
    System.Diagnostics.Debug.Assert(Orientation(points) == RingOrientation.CounterClockwise);

}&lt;/pre&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/54.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/11/24/spatial-determining-ring-orientation.aspx</guid>
            <pubDate>Mon, 24 Nov 2008 18:00:46 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2008/11/24/spatial-determining-ring-orientation.aspx#feedback</comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/54.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/54.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008: Spatial Data, Part 8</title>
            <link>http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx</link>
            <description>&lt;p&gt; &lt;/p&gt;
&lt;p&gt;In this, the eighth part in a series on the new Spatial Data types in SQL Server 2008, I'll step away from the database and do a little spatial coding using .NET.&lt;/p&gt;
&lt;h2&gt;Redistributable .NET Library&lt;/h2&gt;
&lt;p&gt;Up to this point in &lt;a target="_blank" href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;the series&lt;/a&gt;, I have demonstrated a lot of interesting (?) things that you can do with the new Spatial data types (&lt;strong&gt;Geometry&lt;/strong&gt; and &lt;strong&gt;Geography&lt;/strong&gt;) in SQL Server 2008.  You might be thinking, "That's swell, and all, but I wish I could do some of that stuff without needing to be tethered to a database."  Well, you know what?  You can!&lt;/p&gt;
&lt;p&gt;I mentioned in a previous post that the Spatial data types were implemented as SQLCLR User-Defined Types.  I've since been corrected by &lt;a target="_blank" href="http://blogs.msdn.com/isaac"&gt;Isaac Kunen&lt;/a&gt;, who stated that they are more accurately described as System-Defined Types, with the difference being that these are automatically installed and available for use as part of SQL Server 2008, regardless of whether the ENABLE CLR bit has been activated.  Semantics aside, these types are merely classes within a .NET assembly, and Microsoft is making this freely available as part of a Feature Pack for SQL Server (which will be redistributable as part of your stand-alone application, according to Isaac):&lt;/p&gt;
&lt;p&gt;&lt;a target="_blank" href="http://www.microsoft.com/downloads/details.aspx?FamilyId=089A9DAD-E2DF-43E9-9CD8-C06320520B40&amp;amp;displaylang=en"&gt;SQL Server 2008 RC0 Feature Pack Download Page&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Look for "Microsoft SQL Server System CLR Types," which includes the two Spatial types plus the HierarchyID type.  This link is for RC0, and may not be applicable to future versions as the product is finalized and released.)&lt;/p&gt;
&lt;h2&gt;Builder API&lt;/h2&gt;
&lt;p&gt;A new feature that was included with the first Release Candidate (RC0) is the &lt;a target="_blank" href="http://blogs.msdn.com/isaac/archive/2008/05/30/our-upcoming-builder-api.aspx"&gt;Builder API&lt;/a&gt;.  This is a collection of interfaces and classes that helps you to construct spatial types by specifying one point at a time until all points have been added.&lt;/p&gt;
&lt;p&gt;The Builder API is not only useful for creating new instances of spatial data, but also for consuming existing instances one point at a time (maybe to convert an instance into another format).  Documentation is light at the moment, so I'm still trying to grok exactly how to best utilize it.&lt;/p&gt;
&lt;p&gt;For my first experiment with the API, I obtained some Zip Code Boundary data in ASCII format from the U.S. Census Bureau:&lt;/p&gt;
&lt;p&gt;&lt;a title="http://www.census.gov/geo/www/cob/z52000.html#ascii" href="http://www.census.gov/geo/www/cob/z52000.html#ascii"&gt;http://www.census.gov/geo/www/cob/z52000.html#ascii&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My goal was to parse the data, and then create a new &lt;strong&gt;SqlGeography&lt;/strong&gt; instance for each zip code.  (Note: &lt;strong&gt;SqlGeography&lt;/strong&gt; is the .NET class name that T-SQL refers to simply as &lt;strong&gt;Geography&lt;/strong&gt;).  The &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; class proved to be perfect for accomplishing this task.&lt;/p&gt;
&lt;p&gt;At its core, the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; implements the &lt;strong&gt;IGeographySink&lt;/strong&gt; interface.  If you wanted to consume an existing &lt;strong&gt;SqlGeography&lt;/strong&gt; instance, you could implement &lt;strong&gt;IGeographySink&lt;/strong&gt; in your own class, and then invoke the &lt;strong&gt;SqlGeography&lt;/strong&gt;'s Populate() instance method, passing in your object as the parameter.  The Populate() method takes care of calling the appropriate &lt;strong&gt;IGeographySink&lt;/strong&gt; methods within your class.&lt;/p&gt;
&lt;p&gt;In this case, I'm not starting with an existing &lt;strong&gt;SqlGeography&lt;/strong&gt; instance, so my code will need to call the methods of the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; in the correct order:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart8_139B8/IGeographySink_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="386" alt="IGeographySink" width="416" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart8_139B8/IGeographySink_thumb.png" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;After EndGeography() has been invoked, the new instance is available via the ConstructedGeography property of the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; class.  &lt;/p&gt;
&lt;p&gt;Simple enough, right?  Yeah, I'm still a little lost myself...  But, here's some code to help demonstrate what's going on!&lt;/p&gt;
&lt;p&gt;First, let's look at the ASCII data.  A single zip code's boundary might be defined as:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;      1469      -0.824662148292608E+02       0.413848583827499E+02
      -0.824602851767940E+02       0.413864290595145E+02
      -0.824610630000000E+02       0.413860590000000E+02
      -0.824685900000000E+02       0.413841470000000E+02
      -0.824686034536111E+02       0.413843846804627E+02
      -0.824605990000000E+02       0.413863160000000E+02
      -0.824602851767940E+02       0.413864290595145E+02
END&lt;/pre&gt;
&lt;style type="text/css"&gt;&lt;![CDATA[
.csharpcode, .csharpcode pre
{
	font-size: small;
	color: black;
	font-family: consolas, "Courier New", courier, monospace;
	background-color: #ffffff;
	/*white-space: pre;*/
}
.csharpcode pre { margin: 0em; }
.csharpcode .rem { color: #008000; }
.csharpcode .kwrd { color: #0000ff; }
.csharpcode .str { color: #006080; }
.csharpcode .op { color: #0000c0; }
.csharpcode .preproc { color: #cc6633; }
.csharpcode .asp { background-color: #ffff00; }
.csharpcode .html { color: #800000; }
.csharpcode .attr { color: #ff0000; }
.csharpcode .alt 
{
	background-color: #f4f4f4;
	width: 100%;
	margin: 0em;
}
.csharpcode .lnum { color: #606060; }]]&gt;&lt;/style&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;The very first line happens to contain an identifier (maps to a second file that lists the actual USPS zip code).  The coordinate listed in the first line is not actually part of the boundary, but rather appears to be the population center of that area.  The actual boundary begins with the second line, and continues until you encounter the "END".  Also, in case you couldn't tell, coordinates in this data are in Longitude-Latitude order.&lt;/p&gt;
&lt;p&gt;Since a Zip Code is a polygon, and since we are working with &lt;strong&gt;SqlGeography&lt;/strong&gt;, we must be aware of ring ordering.  That is, the exterior ring of a polygon must be defined in a counter-clockwise order so that as you "walk the ring", the interior is always to your left.  If you reverse the order, then &lt;strong&gt;SqlGeography&lt;/strong&gt; assumes that you're trying to define a polygon containing the entire world &lt;em&gt;except&lt;/em&gt; for the small area inside of the polygon.&lt;/p&gt;
&lt;p&gt;Well, in this case, the order of the points of the Zip Code boundary is defined in clockwise order... so, we must be aware of this and call into the &lt;strong&gt;SqlGeographyBuilder&lt;/strong&gt; in the opposite order (so the last point defined in the ASCII data is the first point used while building our new instance).  &lt;/p&gt;
&lt;p&gt;To accomplish this, I simply parse the Lat/Long coordinates as "double" types, and then push them onto a stack.  Then, I pop the stack and call into the Builder API with each point.  At the end, I obtain the new &lt;strong&gt;SqlGeography&lt;/strong&gt; instance from the ConstructedGeography property.  &lt;/p&gt;
&lt;p&gt;(Note: This is demonstrative code - some things should probably be cleaned up/refactored/error handled... You have been warned)&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;public&lt;/span&gt; SqlGeography ParseAsGeography(&lt;span class="kwrd"&gt;string&lt;/span&gt; zipcode_points)
{
    StringReader sr = &lt;span class="kwrd"&gt;new&lt;/span&gt; StringReader(zipcode_points);
    &lt;span class="kwrd"&gt;string&lt;/span&gt; line = sr.ReadLine();

    Stack&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;[]&amp;gt; Points = &lt;span class="kwrd"&gt;new&lt;/span&gt; Stack&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;[]&amp;gt;();

    &lt;span class="kwrd"&gt;while&lt;/span&gt; (line != &lt;span class="kwrd"&gt;null&lt;/span&gt;  &amp;amp;&amp;amp; line != &lt;span class="str"&gt;"END"&lt;/span&gt;)
    {
        &lt;span class="kwrd"&gt;if&lt;/span&gt; (line != String.Empty)
        {
            Points.Push(ParseLatLngValues(line));
        }

        line = sr.ReadLine();
    }

    &lt;span class="kwrd"&gt;return&lt;/span&gt; CreateGeography(Points);
}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[] ParseLatLngValues(&lt;span class="kwrd"&gt;string&lt;/span&gt; line)
{
    &lt;span class="rem"&gt;//      -0.838170700000000E+02       0.409367390000000E+02&lt;/span&gt;

    &lt;span class="kwrd"&gt;double&lt;/span&gt;[] ret = &lt;span class="kwrd"&gt;new&lt;/span&gt; &lt;span class="kwrd"&gt;double&lt;/span&gt;[2];

    &lt;span class="kwrd"&gt;string&lt;/span&gt; lng = System.Text.RegularExpressions.Regex&lt;br /&gt;                            .Matches(line, &lt;span class="str"&gt;"\\S+"&lt;/span&gt;)[0].Value;
    &lt;span class="kwrd"&gt;string&lt;/span&gt; lat = System.Text.RegularExpressions.Regex&lt;br /&gt;                            .Matches(line, &lt;span class="str"&gt;"\\S+"&lt;/span&gt;)[1].Value;

    &lt;span class="kwrd"&gt;double&lt;/span&gt;.TryParse(lat, &lt;span class="kwrd"&gt;out&lt;/span&gt; ret[0]);
    &lt;span class="kwrd"&gt;double&lt;/span&gt;.TryParse(lng, &lt;span class="kwrd"&gt;out&lt;/span&gt; ret[1]);

    &lt;span class="kwrd"&gt;return&lt;/span&gt; ret;
}&lt;/pre&gt;
&lt;p&gt;&lt;a title="http://www.microsoft.com/downloads/details.aspx?FamilyId=089A9DAD-E2DF-43E9-9CD8-C06320520B40&amp;amp;displaylang=en" href="http://www.microsoft.com/downloads/details.aspx?FamilyId=089A9DAD-E2DF-43E9-9CD8-C06320520B40&amp;amp;displaylang=en"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;private&lt;/span&gt; SqlGeography CreateGeography(Stack&amp;lt;&lt;span class="kwrd"&gt;double&lt;/span&gt;[]&amp;gt; points)
{
    SqlGeographyBuilder builder = &lt;span class="kwrd"&gt;new&lt;/span&gt; SqlGeographyBuilder();
    builder.SetSrid(4326);
    builder.BeginGeography(OpenGisGeographyType.Polygon);

    &lt;span class="kwrd"&gt;double&lt;/span&gt;[] point = points.Pop();

    builder.BeginFigure(point[0], point[1]);

    &lt;span class="kwrd"&gt;while&lt;/span&gt; (points.Count &amp;gt; 0)
    {
        point = points.Pop();
        builder.AddLine(point[0], point[1]);
    }

    builder.EndFigure();
    builder.EndGeography();

    &lt;span class="kwrd"&gt;return&lt;/span&gt; builder.ConstructedGeography;
}&lt;/pre&gt;
&lt;p&gt;&lt;a href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;SQL Server 2008: Spatial Data, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/27/sql-server-2008-spatial-data-part-2.aspx"&gt;SQL Server 2008: Spatial Data, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/28/sql-server-2008-spatial-data-part-3.aspx"&gt;SQL Server 2008: Spatial Data, Part 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;SQL Server 2008: Spatial Data, Part 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;SQL Server 2008: Spatial Data, Part 5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx"&gt;SQL Server 2008: Spatial Data, Part 6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx"&gt;SQL Server 2008: Spatial Data, Part 7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f06%2f23%2fsql-server-2008-spatial-data-part-8.aspx"&gt;&lt;img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f06%2f23%2fsql-server-2008-spatial-data-part-8.aspx" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/46.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx</guid>
            <pubDate>Tue, 24 Jun 2008 02:18:47 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx#feedback</comments>
            <slash:comments>20</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/46.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/46.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008: Spatial Data, Part 7</title>
            <link>http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx</link>
            <description>&lt;p&gt;The &lt;a target="_blank" href="http://www.opengeospatial.org"&gt;Open Geospatial Consortium&lt;/a&gt;'s &lt;a target="_blank" href="http://www.opengeospatial.org/standards/sfa"&gt;Simple Features specification&lt;/a&gt;, which SQL Server 2008's &lt;strong&gt;Geometry&lt;/strong&gt; data type is based upon, defines standards for working with spatial data using a flat-earth (projected planar) model.  Ironically, these standards don't exactly cover the intricacies of using an ellipsoidal model, which is needed to "accurately" represent the world that we live in.  In other words, the OGC standards define how to work with paper maps of the world, but not globes.&lt;/p&gt;
&lt;p&gt;Fortunately, the SQL Server team recognized that that the &lt;strong&gt;Geometry&lt;/strong&gt; type is inadequate for a lot of scenarios, and implemented a second data type just for representing geospatial data using a true ellipsoidal model: &lt;strong&gt;Geography&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;In this, the seventh part of a series about the SQL Server Spatial Data Type, I'll examine some of the key differences between the &lt;strong&gt;Geometry&lt;/strong&gt; and the &lt;strong&gt;Geography&lt;/strong&gt; type that developers should be aware of.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Latitude and Longitude&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Locations on a flat model are defined in terms of X and Y.  There exists some point known as the Origin where X and Y are both zero.  From there, it is defined that values of X will increase (or decrease, in the case of negatve numbers) if you move horizontally away from the Origin.  Likewise, the values of Y will increase if you move vertically away from the Origin.  &lt;/p&gt;
&lt;p&gt;By convention, both X and Y will grow to infinity, so flat models do not "wrap around" and start approaching the Origin again if you go too far in one direction.  Usually, a coordinate system will be based on some underlying representation of the real-world, so coordinates that are beyond the defined boundaries of that map are logically undefined.&lt;/p&gt;
&lt;p&gt;By contrast, though, an ellipsoidal model &lt;em&gt;does&lt;/em&gt; wrap around.  If you started at a point in the middle and kept traveling in a straight line to the right, you will eventually return to that starting point.  &lt;/p&gt;
&lt;p&gt;So, it turns out not to be very practical to define points on a ball using X and Y.  Instead, points are defined using angles.  Longitude is the horizontal angle (how far East or West from a &lt;a target="_blank" href="http://en.wikipedia.org/wiki/Prime_meridian"&gt;Prime Meridian&lt;/a&gt;) and ranges from -180 degrees to 180 degrees (with -180 and 180 being the same).  Latitude is the vertical angle (how far North or South from the Equator) and ranges from -90 degrees to 90 degrees (with -90 representing the South Pole and 90 representing the North Pole).&lt;/p&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="240" alt="latlon" width="229" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/latlon_3.jpg" /&gt; &lt;/p&gt;
&lt;p&gt;In terms of the &lt;strong&gt;Geography&lt;/strong&gt; data type, just be aware that there is no X and Y.  Instead, you work with Long and Lat.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: All of the SQL Server 2008 CTPs to date that include Spatial support, including the most recent February 2008 version, use Lat-Long ordering within WKT.  This was a design decision based on the fact that the OGC standard did not already define parameter ordering for angular coordinates.  Starting with the first Release Candidate, however, these parameters will be swapped to use Long-Lat ordering.  Doing so will align SQL Server's spatial support with other platforms that have already implemented Long-Lat ordering.  Note also that that this is aligned with the concepts of X and Y, which by convention lists the X value first.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Straight Lines&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
The shortest distance between two points is a straight line.  But, a straight line on a flat-earth model is far different than a straight line on an ellipsoidal model.  To demonstrate, consider the shortest path from Redmond, WA, USA to Cambridge, England, UK: &lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_1_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="290" alt="s7_1" width="380" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_1_thumb.png" /&gt;&lt;/a&gt;  &lt;br /&gt;
On this planar projection, it certainly looks like the shortest path.  Even when examined on a 3D model, it looks correct: &lt;br /&gt;
&lt;br /&gt;
&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_2_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="274" alt="s7_2" width="364" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_2_thumb.png" /&gt;&lt;/a&gt; &lt;br /&gt;
But, if the camera is moved towards the North Pole, then the error becomes apparent:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_3_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="274" alt="s7_3" width="364" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_3_thumb.png" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;In the ellipsoidal model, the shortest path between the points is not the red line, which roughly parallels the lines of Latitude, but rather the black arrow!  Converted back to a planar projection, this actual shortest path appears curved:&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_4_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="290" alt="s7_4" width="380" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/s7_4_thumb.png" /&gt;&lt;/a&gt; &lt;/p&gt;
&lt;p&gt;&lt;em&gt;(in this view, the black curve was [hastily] plotted by hand)&lt;/em&gt; &lt;br /&gt;
&lt;br /&gt;
&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;
&lt;/p&gt;
&lt;h2&gt;Instance Methods&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
The following slide shows in all of the instance methods that have been implemented for the &lt;strong&gt;Geography&lt;/strong&gt; type as of the February 2008 CTP.  For comparison, instance methods from the &lt;strong&gt;Geometry&lt;/strong&gt; type that do not exist in the &lt;strong&gt;Geography&lt;/strong&gt; type are shown in gray.&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/GeographyMethods_2.png"&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="260" alt="GeographyMethods" width="340" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart7_128A3/GeographyMethods_thumb.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It may be impossible to define some of these equivalent methods for &lt;strong&gt;Geography&lt;/strong&gt;, simply because the rules are different.  For instance, if you define a set of points that make up a Polygon, what is considered to be the interior and exterior of that shape?  Since the world coordinates wrap around in an ellipsoidal model, you might be intending to represent a shape whose interior is the entire world except for the small portion.  There is simply no way to convey your intent using the methods as described by the OGC standard.&lt;/p&gt;
&lt;p&gt;In an attempt to prevent this particular scenario, the SQL Server team has imposed a limit on the size of a &lt;strong&gt;Geography&lt;/strong&gt; in the February 2008 CTP: you cannot define a &lt;strong&gt;Geography&lt;/strong&gt; that is larger than a hemisphere.   &lt;/p&gt;
&lt;p&gt;There may very well be logical solutions for working around some of the issues that prevented the SQL team from implementing all of &lt;strong&gt;Geometry's&lt;/strong&gt; methods in the &lt;strong&gt;Geography&lt;/strong&gt; type.  However, in this case, Microsoft appears to be waiting for the OGC to define certain rules as part of a standard rather than coming up with their own assumptions, which could be invalidated later by the standards group going in a different direction.&lt;/p&gt;
&lt;p&gt;More on the &lt;strong&gt;Geography&lt;/strong&gt; type later!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;SQL Server 2008: Spatial Data, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/27/sql-server-2008-spatial-data-part-2.aspx"&gt;SQL Server 2008: Spatial Data, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/28/sql-server-2008-spatial-data-part-3.aspx"&gt;SQL Server 2008: Spatial Data, Part 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;SQL Server 2008: Spatial Data, Part 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;SQL Server 2008: Spatial Data, Part 5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx"&gt;SQL Server 2008: Spatial Data, Part 6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx"&gt;(next part) SQL Server 2008: Spatial Data, Part 8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f05%2f16%2fsql-server-2008-spatial-data-part-7.aspx"&gt;&lt;img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f05%2f16%2fsql-server-2008-spatial-data-part-7.aspx" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/42.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx</guid>
            <pubDate>Sat, 17 May 2008 02:53:41 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx#feedback</comments>
            <slash:comments>5</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/42.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/42.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008: Spatial Data, Part 6</title>
            <link>http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx</link>
            <description>&lt;p&gt; &lt;/p&gt;
&lt;p&gt;In the &lt;a target="_blank" href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;Part 4&lt;/a&gt; and &lt;a target="_blank" href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;Part 5&lt;/a&gt; of the series, I demonstrated some instance methods of the &lt;strong&gt;Geometry&lt;/strong&gt; type that returned a new &lt;strong&gt;Geometry&lt;/strong&gt; based on existing instances.  In this part, I will concentrate on instance methods and properties of the &lt;strong&gt;Geometry&lt;/strong&gt; type that return scalar values and Points. &lt;br /&gt;
&lt;/p&gt;
&lt;h2&gt;STArea, STLength&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Typically, your spatial data will represent something from the real world.  A LineString may be the collection of points gathered from a GPS device, and together they may represent the path that you took from your home to the office.  A Polygon may be the collection of points around the boundary of governmental territory, like a county or a parish within your state.&lt;/p&gt;
&lt;p&gt;In both of these cases, the time will come when you will want to know the length of the LineString (or length of the perimeter of the Polygon) and the area within the Polygon.  OGC standard method &lt;font face="monospace"&gt;STArea()&lt;/font&gt; returns a float indicating the area of the instance in square units (or 0, if the instance is not a Polygon and does not have area).  &lt;font face="monospace"&gt;STLength()&lt;/font&gt; returns a float indicating the length of the instance in units (or 0, if the instance is a Point and does not have length).  &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g GEOMETRY = &lt;span class="str"&gt;'POLYGON((10 10, 10 40, 40 40, 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STArea(), @g.STLength()

Results:

Area       Length&lt;br /&gt;450        102.426406871193&lt;/pre&gt;
&lt;p&gt; &lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="52" alt="Spatial_6_1" width="57" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_1_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STCentroid&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Thinking back to Mr. Bollenbacher's 10th Grade Geometry class, we had to use a compass and straight edge to construct lines bisecting the angles of polygons (primarily triangles).  The point where the angle bisectors met was the exact center, or &lt;a target="_blank" href="http://en.wikipedia.org/wiki/Centroid"&gt;centroid&lt;/a&gt;, of the shape.  Centroids are important because any line that passes through a centroid will divide the Polygon into two parts of equal area.  It should be noted that a Centroid may not actually be on the surface of a Polygon.&lt;/p&gt;
&lt;p&gt;The OGC standard method &lt;font face="monospace"&gt;STCentroid()&lt;/font&gt; returns a Point indicating the centroid of the shape.  If the instance is not a Polygon (or MultiPolygon), then NULL will be returned.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g GEOMETRY = &lt;span class="str"&gt;'POLYGON((10 10, 10 40, 40 40, 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STCentroid().ToString()

Results:

POINT (20 30)&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="52" alt="Spatial_6_2" width="57" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_2_3.png" /&gt;  &lt;br /&gt;
&lt;em&gt;Note: SpatialViewer displays an individual point as an X. &lt;br /&gt;
&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em /&gt;&lt;/p&gt;
&lt;h2&gt;STWithin, STContains&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Two OGC standard methods returns a 1 or 0 indicating whether all of the points of one instance exist entirely inside of another instance.  &lt;font face="monospace"&gt;STWithin()&lt;/font&gt; tests whether the base instance is inside of the parameter instance, while &lt;font face="monospace"&gt;STContains()&lt;/font&gt; tests whether the parameter instance is inside of the base instance.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry = &lt;span class="str"&gt;'POLYGON ((10 10, 13 30, 30 30, 30 15, &lt;br /&gt;                                 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h geometry = &lt;span class="str"&gt;'LINESTRING (16 16, 16 24, 25 18)'&lt;br /&gt;&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STContains(@h), @g.STWithin(@h)&lt;br /&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @h.STContains(@g), @h.STWithin(@g) &lt;br /&gt;
Results:

1     0&lt;br /&gt;&lt;br /&gt;0     1&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="41" alt="Spatial_6_3" width="51" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_3_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;ST is {something}?&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
There are a number of OGC standard methods to check whether a given instance meets certain specifications: STIsClosed, STIsEmpty, STIsRing, STIsSimple, STIsValid&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CLOSED:&lt;/strong&gt; An instance is considered to be closed if the start point is the same as the end point.  By definition, a Polygon has to be closed, and a Point is not closed.  That really only leaves LineString.  For a collection of objects to be considered closed, all of its members must be closed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EMPTY:&lt;/strong&gt; A &lt;strong&gt;Geometry&lt;/strong&gt; can be initialized in a special way as to not contain any points.  In SQL terms, this is sort of like having a NULL value, except it really is an instantiated object.  For example, LINESTRING EMPTY is a valid LineString, but it has no points.  Another humorous example is POINT EMPTY, which initializes to a Point without a Point....  so it's kind of Pointless, right?  (thank you, I'm here all week, tip your waitress).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;RING:&lt;/strong&gt; An instance is considered to be a ring if it is both Closed and Simple.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SIMPLE:&lt;/strong&gt; An instance is considered to be simple if it does not cross over itself or otherwise touch itself.  For example, a LineString forming the letter 'S' is simple because it never comes in contact with itself.  But, a LineString that forms a Figure-Eight (8) is &lt;u&gt;not&lt;/u&gt; simple because it would have to cross over itself.  Likewise, two circles (MultiPolygon) stacked on top of each other to form a Figure-Eight would &lt;u&gt;not&lt;/u&gt; be simple because they touch each other.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;VALID:&lt;/strong&gt; A Geometry can cross over itself, but it cannot legally trace over itself.  That is, picture a LineString that backtracks over itself at some point, kind of like how I write my letter "P".  This is not considered to be Valid.&lt;/p&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="41" alt="Spatial_6_4" width="51" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_4_3.png" /&gt; &lt;/p&gt;
&lt;p&gt;Tip: SQL Server will allow an invalid &lt;strong&gt;Geometry&lt;/strong&gt; to be instantiated, and Microsoft has provided an extension method called &lt;font face="monospace"&gt;MakeValid()&lt;/font&gt; that will convert the invalid instance into a valid instance.  In the letter "P" example, instead of the vertical line going down and then back up (as I draw it by hand), the valid form will eliminate the duplication of points simply by start at the bottom and going up (so that the LineString never traces over itself).  If it's not possible to simplify a shape in this way so that there is only one continuous path, then it will be broken up into multiple valid shapes (i.e., a MultiLineString, etc).&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STX, STY, Z, M&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Individual coordinates of a Point can be accessed via the OGC Standard properties STX and STY.  Three-dimensional Points also have a Z coordinate, which can be accessed via Microsoft's extended Z property.  Likewise, four-dimensional Points have a M (for Measure) coordinate, which can be accessed via Microsoft's extended M property.  If Z or M is not defined for a given point, then NULL will be returned.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry = &lt;span class="str"&gt;'POINT(1 2)'&lt;/span&gt;
&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h geometry = &lt;span class="str"&gt;'POINT(1 2 3 4)'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STX, @g.STY, @g.Z, @g.M
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @h.STX, @h.STY, @h.Z, @h.M

Results:

1    2    &lt;span class="kwrd"&gt;NULL&lt;/span&gt;    &lt;span class="kwrd"&gt;NULL&lt;/span&gt;
1    2    3       4&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STPointOnSurface&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
When working with spatial data, especially without using a viewer, it can be kind of difficult to pick an arbitrary point that is inside of a Polygon (or on a LineString).  Thankfully, the OGC standard method &lt;font face="monospace"&gt;STPointOnSurface()&lt;/font&gt; does just that.  Given a &lt;strong&gt;Geometry&lt;/strong&gt; instance, it will return a somewhat random point that is guaranteed to be located within the interior of that instance.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry = &lt;span class="str"&gt;'POLYGON((10 10, 14 15, 50 12, 45 30, &lt;br /&gt;                                10 30, 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STPointOnSurface().ToString()

Results:

POINT (23 25)&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="141" alt="Spatial_6_5" width="208" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A265/Spatial_6_5_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STSrid&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
All of my examples to this point have used the default Spatial Reference ID of 0 (for the &lt;strong&gt;Geometry&lt;/strong&gt; type) simply because I have not been specifying one.  The SRID is the mechanism that defines one geometry as being based on a different set of parameters than a geometry with a different SRID.  &lt;/p&gt;
&lt;p&gt;For example, you may have a set of shapes defined where each unit represents one meter, while another set of shapes is based on a reference system where each unit represents 1.5 inches.  It's totally legal to mix these shapes together the same column of a table in your database, provided that you assign a different SRID to each.  SQL Server does not need to know what units represent, because it will never permit the interaction of a shape from one SRID with a shape from another SRID.  &lt;/p&gt;
&lt;p&gt;The OGC standard property STSrid will get (or set) the SRID of the Geometry instance.&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="rem"&gt;-- @g will have the default SRID = 0&lt;br /&gt;&lt;/span&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g GEOMETRY = &lt;span class="str"&gt;'POLYGON((10 10, 10 40, 40 40, 10 10))'&lt;/span&gt;

&lt;span class="rem"&gt;-- @h is defined with SRID = 123&lt;br /&gt;&lt;/span&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h GEOMETRY
    = GEOMETRY::STGeomFromText(&lt;span class="str"&gt;'POLYGON((10 10, 40 10, 
                                         40 40, 10 10))'&lt;/span&gt;, 
                               123)

&lt;span class="kwrd"&gt;select&lt;/span&gt; @g.STUnion(@h).ToString()

&lt;span class="rem"&gt;-- Returns NULL because of different SRIDs.  But, let's change&lt;br /&gt;&lt;br /&gt;-- @g to use SRID = 123&lt;/span&gt;

&lt;span class="kwrd"&gt;SET&lt;/span&gt; @g.STSrid = 123

&lt;span class="kwrd"&gt;select&lt;/span&gt; @g.STUnion(@h).ToString()

&lt;span class="rem"&gt;-- Returns POLYGON ((10 10, 40 10, 40 40, 10 40, 10 10))&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Jason, What's Next?&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
Enough of this flat Earth stuff!  In the next part, I'll explore the Geography data type.  This is where things really start to get interesting.  Stay tuned!&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;SQL Server 2008: Spatial Data, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/27/sql-server-2008-spatial-data-part-2.aspx"&gt;SQL Server 2008: Spatial Data, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/28/sql-server-2008-spatial-data-part-3.aspx"&gt;SQL Server 2008: Spatial Data, Part 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;SQL Server 2008: Spatial Data, Part 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx"&gt;SQL Server 2008: Spatial Data, Part 5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx"&gt;(next part) SQL Server 2008: Spatial Data, Part 7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx"&gt;SQL Server 2008: Spatial Data, Part 8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f04%2f11%2fsql-server-2008-spatial-data-part-6.aspx"&gt;&lt;img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f04%2f11%2fsql-server-2008-spatial-data-part-6.aspx" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/39.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx</guid>
            <pubDate>Sat, 12 Apr 2008 00:44:19 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx#feedback</comments>
            <slash:comments>4</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/39.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/39.aspx</trackback:ping>
        </item>
        <item>
            <title>SQL Server 2008: Spatial Data, Part 5</title>
            <link>http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx</link>
            <description>&lt;p&gt;In &lt;a target="_blank" href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;the previous part&lt;/a&gt; of this series, I demonstrated instance methods that transformed a single &lt;strong&gt;Geometry&lt;/strong&gt; type into another useful &lt;strong&gt;Geometry&lt;/strong&gt;.  In this post, we'll go a step further and show methods that allow two or more instances to interact with one another in order to produce a new &lt;strong&gt;Geometry&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For my baseline, I'll use two Polygons that overlap each other:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry 
        = &lt;span class="str"&gt;'POLYGON((10 10, 40 10, 40 40, 10 40, 10 10))'&lt;/span&gt;
&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h geometry 
        = &lt;span class="str"&gt;'POLYGON((30 30, 50 30, 50 50, 30 50, 30 30))'&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="63" alt="Spatial_5_1" width="68" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_1_3.png" /&gt; &lt;/p&gt;
&lt;h2&gt;STDifference&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
&lt;font face="monospace"&gt;STDifference()&lt;/font&gt; returns a new instance consisting of all points from the base instance that do not contain points from the parameter instance.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;span class="kwrd" /&gt;&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STDifference(@h).ToString();

&lt;span class="kwrd"&gt;&lt;font color="#000000"&gt;Result&lt;/font&gt;&lt;/span&gt;:

POLYGON ((10 10, 40 10, 40 30, 30 30, 30 40, 10 40, 10 10))&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="63" alt="Spatial_5_2" width="68" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_2_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STIntersection&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
&lt;font face="monospace"&gt;STIntersection()&lt;/font&gt; returns a new instance containing only the points that are in common between the base instance and the parameter instance.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STIntersection(@h).ToString();

&lt;span class="kwrd"&gt;Result&lt;/span&gt;:

POLYGON ((30 30, 40 30, 40 40, 30 40, 30 30))&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="63" alt="Spatial_5_3" width="68" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_3_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STSymDifference&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
&lt;font face="monospace"&gt;STSymDifference()&lt;/font&gt; returns a new instance containing only the points that are unique to both the base instance and the parameter instance (i.e., it excludes the points that &lt;font face="monospace"&gt;STIntersection()&lt;/font&gt; would return).&lt;/p&gt;
&lt;p&gt;In this case, the set of points is actually two different Polygons.  Because &lt;font face="monospace"&gt;STSymDifference()&lt;/font&gt; needs to return a single instance of &lt;em&gt;something&lt;/em&gt;, it will wrap those two Polygons into a collection (MultiPolygon).&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STSymDifference(@h).ToString();

&lt;span class="kwrd"&gt;Result&lt;/span&gt;:

MULTIPOLYGON (((40 30, 50 30, 50 50, 30 50, 30 40, 40 40, 40 30)), 
              ((10 10, 40 10, 40 30, 30 30, 30 40, 10 40, 10 10)))&lt;/pre&gt;
&lt;p&gt; &lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="63" alt="Spatial_5_4" width="68" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_4_6.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;STUnion&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
&lt;font face="monospace"&gt;STUnion()&lt;/font&gt; returns a new instance containing all of the points of the base instance and the parameter instance merged together.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STUnion(@h).ToString();

Results:

POLYGON ((10 10, 40 10, 40 30, 50 30, 50 50, 30 50, &lt;br /&gt;          30 40, 10 40, 10 10))&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="63" alt="Spatial_5_5" width="68" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_5_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;h2&gt;Blended Types&lt;/h2&gt;
&lt;p&gt;&lt;br /&gt;
The instance methods described above do not work just for Polygons.  You can actually use them on different types, or even collections of different types.  &lt;/p&gt;
&lt;p&gt;For instance, if we look at the results of using a LineString as the base instance and a Polygon as the parameter instance, &lt;font face="monospace"&gt;STDifference()&lt;/font&gt; will return a MultiLineString constisting of the points from the original LineString that do not lie within the Polygon: &lt;br /&gt;
&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @g geometry = &lt;span class="str"&gt;'LINESTRING(9 9, 40 40)'&lt;/span&gt;
&lt;span class="kwrd"&gt;DECLARE&lt;/span&gt; @h geometry = &lt;span class="str"&gt;'POLYGON((15 15, 15 30, 30 30, 30 15, 15 15))'&lt;/span&gt;
&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STDifference(@h).ToString();

Results:

MULTILINESTRING ((40 40, 30 30), (15 15, 9 9))&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="51" alt="Spatial_5_6" width="60" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_6_6.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;font face="monospace"&gt;STIntersection()&lt;/font&gt; will return the points from the original LineString that do lie within the Polygon: &lt;br /&gt;
&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STIntersection(@h).ToString();

Results:

LINESTRING (30 30, 15 15)&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="51" alt="Spatial_5_7" width="60" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_7_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;font face="monospace"&gt;STUnion()&lt;/font&gt; cannot determine a single common Geometry type, so it will return a mixed collection of types:&lt;/p&gt;
&lt;pre class="csharpcode"&gt;&lt;span class="kwrd"&gt;SELECT&lt;/span&gt; @g.STUnion(@h).ToString();

Results:

GEOMETRYCOLLECTION 
(
     LINESTRING (40 40, 30 30), 
     POLYGON ((15 15, 30 15, 30 30, 15 30, 15 15)), 
     LINESTRING (15 15, 9 9)
)&lt;/pre&gt;
&lt;p&gt;&lt;img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="51" alt="Spatial_5_8" width="60" border="0" src="http://jasonfollas.com/blog/images/jasonfollas_com/blog/WindowsLiveWriter/SQLServer2008SpatialDataPart5_A67F/Spatial_5_8_3.png" /&gt; &lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/14/sql-server-2008-spatial-data-part-1.aspx"&gt;SQL Server 2008: Spatial Data, Part 1&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/27/sql-server-2008-spatial-data-part-2.aspx"&gt;SQL Server 2008: Spatial Data, Part 2&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/03/28/sql-server-2008-spatial-data-part-3.aspx"&gt;SQL Server 2008: Spatial Data, Part 3&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/03/sql-server-2008-spatial-data-part-4.aspx"&gt;SQL Server 2008: Spatial Data, Part 4&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/04/11/sql-server-2008-spatial-data-part-6.aspx"&gt;(next part) SQL Server 2008: Spatial Data, Part 6&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/05/16/sql-server-2008-spatial-data-part-7.aspx"&gt;SQL Server 2008: Spatial Data, Part 7&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://jasonfollas.com/blog/archive/2008/06/23/sql-server-2008-spatial-data-part-8.aspx"&gt;SQL Server 2008: Spatial Data, Part 8&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://www.dotnetkicks.com/kick/?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f04%2f07%2fsql-server-2008-spatial-data-part-5.aspx"&gt;&lt;img alt="kick it on DotNetKicks.com" border="0" src="http://www.dotnetkicks.com/Services/Images/KickItImageGenerator.ashx?url=http%3a%2f%2fjasonfollas.com%2fblog%2farchive%2f2008%2f04%2f07%2fsql-server-2008-spatial-data-part-5.aspx" /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://jasonfollas.com/blog/aggbug/37.aspx" width="1" height="1" /&gt;</description>
            <dc:creator>Jason Follas</dc:creator>
            <guid>http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx</guid>
            <pubDate>Mon, 07 Apr 2008 15:51:00 GMT</pubDate>
            <comments>http://jasonfollas.com/blog/archive/2008/04/07/sql-server-2008-spatial-data-part-5.aspx#feedback</comments>
            <slash:comments>3</slash:comments>
            <wfw:commentRss>http://jasonfollas.com/blog/comments/commentRss/37.aspx</wfw:commentRss>
            <trackback:ping>http://jasonfollas.com/blog/services/trackbacks/37.aspx</trackback:ping>
        </item>
    </channel>
</rss>