Posts tagged ‘SQL Server’

May 4, 2012

Geocoding in SQL Server with the Bing Maps Locations API

Almost every SQL Server database contains “spatial” data. That information might not use the dedicated geography or geometry spatial datatypes but, more likely, could be a table of customer addresses, the name of a city of region for which a sales manager is responsible, the itinerary of locations at which a delivery vehicle is scheduled to stop, etc. etc.

All of these are examples of spatial information – data that describes the location of objects on the surface of the earth. The problem is that this information is typically stored as a free-text string – “10 Downing Street”, “Round the Back of Sainsbury’s car park”, or “Manchester”, for example. Even semi-structured information such as a postcode, “NR1 6NN”, is not particularly useful for spatial queries. Instead, what is needed is a way to turn this text-based, descriptive information, into a structured spatial format (typically a single pair of latitude/longitude coordinates). And that’s where geocoding comes in.

In my SQLBits session last year, “Who Needs Google Maps?” I discussed a little about what is involved in creating your own geocoding function, based on parsing a supplied text address string and looking up the relevant coordinate location from a local gazetteer table. However, in practice, it’s very unlikely that you’ll ever want to create your own geocoding function when you can just use one somebody else has already made (such as the Bing Maps Locations API).

So, here’s a step-by-step guide to creating a geocoding function in SQL Server that calls the Locations API instead:

Step 1. Get a Bing Maps key

Use of the Bing Maps Locations API is free for many applications, but may involve a cost depending on how many geocodes you request, and what you’re using them for (you can check the terms of use at
http://www.microsoft.com/maps/product/terms.html
). Either way, before using the service you first have to sign up for a key, which you can do at
http://www.bingmapsportal.com
– it only takes a few seconds to do and you can get an evaluation key instantly. The key is an alphanumeric string that looks a bit like: AhGSgD1Twhjx9WqxjJZznG3tY3r0wnFr!gg1ngK3yGnp9b3hupQUVbNdv6Wb0qW

Step 2. Create a Geocoding Function

Fire up Visual Studio and create a new C# Class Library project. Once  created, add a reference to the Microsoft.SqlServer.Types.dll library  (Project –> Add Reference, and select the Microsoft.SqlServer.Types.dll library from the .NET tab).

Then, edit the default Class1.cs file to read as follows, inserting your Bing Maps key where indicated:

using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
using System.Net;
using System.IO;
using System.Xml;
using Microsoft.SqlServer.Types;
using System.Collections.Generic; // Used for List

namespace ProSpatial
{
  public partial class UserDefinedFunctions
  {

    /* Generic function to return XML geocoded location from Bing Maps geocoding service */
    public static XmlDocument Geocode(
      string countryRegion,
      string adminDistrict,
      string locality,
      string postalCode,
      string addressLine
    )
    {
      // Variable to hold the geocode response
      XmlDocument xmlResponse = new XmlDocument();

      // Bing Maps key used to access the Locations API service
      string key = "ENTERYOURBINGMAPSKEYHERE";

      // URI template for making a geocode request
      string urltemplate = "http://dev.virtualearth.net/REST/v1/Locations?countryRegion={0}&adminDistrict={1}&locality={2}&postalCode={3}&addressLine={4}&key={5}&output=xml";

      // Insert the supplied parameters into the URL template
      string url = string.Format(urltemplate, countryRegion, adminDistrict, locality, postalCode, addressLine, key);

      try
      {
        // Initialise web request
        HttpWebRequest webrequest = null;
        HttpWebResponse webresponse = null;
        Stream stream = null;
        StreamReader streamReader = null;

        // Make request to the Locations API REST service
        webrequest = (HttpWebRequest)WebRequest.Create(url);
        webrequest.Method = "GET";
        webrequest.ContentLength = 0;

        // Retrieve the response
        webresponse = (HttpWebResponse)webrequest.GetResponse();
        stream = webresponse.GetResponseStream();
        streamReader = new StreamReader(stream);
        xmlResponse.LoadXml(streamReader.ReadToEnd());

        // Clean up
        webresponse.Close();
        stream.Dispose();
        streamReader.Dispose();
      }
      catch (Exception ex)
      {
        // Exception handling code here;
      }

      // Return an XMLDocument with the geocoded results 
      return xmlResponse;
    }

    /* Wrapper method to expose geocoding functionality as SQL Server User-Defined Function (UDF) */
    [Microsoft.SqlServer.Server.SqlFunction(DataAccess = DataAccessKind.Read)]
    public static SqlGeography GeocodeUDF(
      SqlString countryRegion,
      SqlString adminDistrict,
      SqlString locality,
      SqlString postalCode,
      SqlString addressLine
      )
    {

      // Document to hold the XML geocoded location
      XmlDocument geocodeResponse = new XmlDocument();

      // Attempt to geocode the requested address
      try
      {
        geocodeResponse = Geocode(
          (string)countryRegion,
          (string)adminDistrict,
          (string)locality,
          (string)postalCode,
          (string)addressLine
        );
      }
      // Failed to geocode the address
      catch (Exception ex)
      {
        SqlContext.Pipe.Send(ex.Message.ToString());
      }

      // Declare the XML namespace used in the geocoded response
      XmlNamespaceManager nsmgr = new XmlNamespaceManager(geocodeResponse.NameTable);
      nsmgr.AddNamespace("ab", "http://schemas.microsoft.com/search/local/ws/rest/v1");

      // Check that we received a valid response from the geocoding server
      if (geocodeResponse.GetElementsByTagName("StatusCode")[0].InnerText != "200")
      {
        throw new Exception("Didn't get correct response from geocoding server");
      }

      // Retrieve the list of geocoded locations
      XmlNodeList Locations = geocodeResponse.GetElementsByTagName("Location");

      // Create a geography Point instance of the first matching location
      double Latitude = double.Parse(Locations[0]["Point"]["Latitude"].InnerText);
      double Longitude = double.Parse(Locations[0]["Point"]["Longitude"].InnerText);
      SqlGeography Point = SqlGeography.Point(Latitude, Longitude, 4326);

      // Return the Point to SQL Server
      return Point;
    }
  };
}

This code listing contains both a generic geocoding method to call the Bing Maps Locations API (called Geocode), and a wrapper function to expose that functionality as a scalar User-Defined Function (called GeocodeUDF) that selects the top-matching geocoded coordinates for a supplied address string.

There are several other ways you might want to expose geocoding functionality in SQL Server; geocoding is an imprecise operation, and you often might get more than one possible match returned for a given address. To enable a choice between different possible geocoded locations, you might therefore prefer to return the results in a table, using a Table-Valued Function rather than a scalar UDF. Such a table could also return other columns of information, such as the confidence of the match, or the bounding box around a large feature rather than a single point location to represent its location. These ideas and more are discussed in the Geocoding chapter of Pro Spatial with SQL Server 2012, but for now we’ll stick with the simple case of a UDF that just selects the top hit.

Build the project containing the code above (Build –> Build Solution).

Step 3. Import the Assembly and Register the Function in SQL Server

In SQL Server, first make sure that user-defined CLR code is enabled:

EXEC sp_configure 'clr enabled', '1';
GO
RECONFIGURE;
GO

Then, to interact with the Locations API service, you need to set the appropriate database security permissions to allow access to external services. The easiest way to do this is to simply set the database to be trustworthy (in this example, I’m going to create my geocoding function in the ProSpatial database – change this line to match your database name as appropriate):

ALTER DATABASE ProSpatial SET TRUSTWORTHY ON;
GO

Now, import the assembly containing the geocoding function, and remember to give it EXTERNAL_ACCESS permission. You’ll have to edit the path and filename to match that of the project you created in Step 2:

CREATE ASSEMBLY Geocoder
FROM 'C:\Users\Alastair\Visual Studio 2012\Projects\Geocoder\bin\Debug\Geocoder.dll'  
WITH PERMISSION_SET = EXTERNAL_ACCESS;
GO

Finally, register the function that will call into the GeocodeUDF method in the Geocoder assembly:

CREATE FUNCTION dbo.Geocode(
  @addressLine nvarchar(max),
  @locality nvarchar(max),
  @adminDistrict nvarchar(max),
  @postalCode nvarchar(max),
  @countryRegion nvarchar(max) 
  ) RETURNS geography
AS EXTERNAL NAME 
Geocoder.[ProSpatial.UserDefinedFunctions].GeocodeUDF;

Step 4. Geocode!

Now, to geocode any address from SQL Server, call the Geocode function, supplying the street address, locality, administrative district, postcode, and country/region, as in the following examples: (PLEASE SEE UPDATE BELOW!)

-- Create a new geography Point by geocoding a provided address
-- For any address elements unknown, simply supply an empty string
DECLARE @g geography;
SET @g = dbo.Geocode('10 Downing Street', 'London', '', 'SW1A 2AA', 'UK');

-- Retrieve the WKT of the created instance
SELECT @g.ToString();

Here’s the results of running this query in SQL Server:

image

POINT(-0.127707 51.50355). Now let’s then check these coordinates on Bing Maps. Unsurprisingly, (since the coordinates of the point in SQL Server were obtained from the Bing Maps Locations API), they line up pretty well….

image

UPDATE

Following a few comments from folks who were having trouble getting this code to work, I realised that the example code listing above is wrong – the method signature of the Geocode SQLCLR method expects the countryRegion parameter *first*, then district, locality, postcode, and finally street address. So, my example should really have read:

SELECT dbo.Geocode(‘UK’, ‘LONDON’, ‘London’, ‘SW1A 2AA’, ’10 Downing Street’);

Or, if you prefer to supply the parameters in the more common street address, locality, postcode, country order, just change the method signature and recompile the assembly. Sorry for any confusion caused!

April 13, 2012

Gridding Geometries (or, “Creating patchwork animals in SQL Server”)

This is one of those things that I can’t imagine anybody would ever really want to do but, seeing as I haven’t posted anything for a while, I thought I’d write about it just in case it’s of use to someone…

First, suppose you had an arbitrary geometry. Here’s an example:

DECLARE @snake geometry = geometry::Parse('CIRCULARSTRING(0 0, 5 -5, 10 0, 15 5, 20 0, 25 -5, 29 -1)').STBuffer(2).STDifference(geometry::Parse('MULTIPOINT(28.5 -0.5, 29.75 -0.8)').STBuffer(0.6)).STUnion(geometry::Parse('MULTIPOINT(28.5 -0.5, 29.75 -0.8)').STBuffer(0.2)).STUnion(geometry::Parse('POLYGON((29.2 0.5, 29.5 2.7, 29.79 2.2, 30.22 2.75, 30 0.5, 29.2 0.5))'));
SELECT @snake;

In in the immortal words of Rolf Harris, “can you guess what it is yet?”.

Yes, that’s right – it’s a snake and it looks like this:

image

Now suppose that you wanted to divide that geometry up into a series of individual polygonal tiles on a regular-spaced grid. You could do so using the following T-SQL:

-- Determine the extent of the grid required to cover the geometry
DECLARE @grid geometry = @snake.STEnvelope();
DECLARE @gridwidth float = @grid.STPointN(3).STX - @grid.STPointN(1).STX;
DECLARE @gridheight float = @grid.STPointN(3).STY - @grid.STPointN(2).STY;

-- Work out the lower-left corner of the grid
DECLARE @xoffset float = @grid.STPointN(1).STX,
        @yoffset float = @grid.STPointN(1).STY;

-- Specify the number of columns and rows in the grid
DECLARE @numcolumns float = 80, @numrows float = 35;

-- Work out the height and width of each tile
DECLARE @cellwidth float = @gridwidth / @numcolumns;
DECLARE @cellheight float = @gridheight / @numrows;

-- Create a table variable to hold the tiles
DECLARE @gridcells table (id int, geom geometry)

-- Now, loop through and cut up the geometry into tiles
DECLARE @x int = 0, @y int = 0;
WHILE @y < @numrows
BEGIN
WHILE @x < @numcolumns
BEGIN
INSERT INTO @gridcells VALUES(
@y * @numcolumns + @x,
'POLYGON((' + cast(@xoffset + (@x * @cellwidth) AS varchar(32)) + ' ' + cast(@yoffset + (@y * @cellheight) AS varchar(32)) + ','
+ cast(@xoffset + ((@x + 1)  * @cellwidth) AS varchar(32)) + ' ' + cast(@yoffset + (@y * @cellheight) AS varchar(32)) + ','
+ cast(@xoffset + ((@x + 1)  * @cellwidth) AS varchar(32)) + ' ' + cast(@yoffset + ((@y + 1) * @cellheight) AS varchar(32)) + ','
+ cast(@xoffset + (@x * @cellwidth) AS varchar(32)) + ' ' + cast(@yoffset + ((@y + 1) * @cellheight) AS varchar(32)) + ','
+ cast(@xoffset + (@x * @cellwidth) AS varchar(32)) + ' ' + cast(@yoffset + (@y * @cellheight) AS varchar(32)) + '))'
)
SET @x = @x + 1;
END
SET @x = 0;
SET @y = @y + 1;
END

-- Finally, select the tiles from the table variable
SELECT geom.STIntersection(@snake) FROM @gridcells ORDER BY id;

My snake has now been chopped up into 800 little tiles, and here’s what the result looks like:

image

 

I wonder if this is how Elmer the Elephant was created?

image

March 11, 2012

“Point-of-Maximal Distance” queries (AKA Variations on a Theme of Nearest-Neighbours)

Nearest neighbour queries (or, for my international readers, nearest-neighbor queries), are a pretty common query pattern used in location-based applications and spatial analysis:- given a set of known locations of restaurants, customers, stations etc.,  “What/where is the nearest X to a given location?” I’ve already written about performing nearest-neighbour queries with SQL Server spatial datatypes here and here.

Occasionally, you’ll see this pattern reversed – “What is the X furthest away from my current location?” – which could be referred to as a farthest-neighbour query.

However, there’s another variant that I’ve not often seen discussed, which is: “Given a set of known locations, where is the point on Earth that maximises the distance to the closest X.” In other words, where would you have to go to get as far away as possible from any item in the given set of data? The key difference to appreciate about this query structure is that, unlike the first two examples, the answer is not selected from one of a set of known locations – it is location that must be determined programmatically to maximise the distance from X. This makes the query a lot more complicated, but also more interesting. (I don’t know if there is a generic name for this type of query or not…  a “point-of-maximal distance” query?).

I can think of several potential practical applications for this type of query – a fire response service may want to identify the location at increased risk because it is furthest away from any of their response stations, for example. Or, a mobile telecommunications operator might want to identify the “darkest spot” in their network coverage – the point that lies furthest from any of their transmitters.

As ever, I think a practical example might help demonstrate. One of my recent posts described how to import a set of point data representing Tescos stores in the UK. People that know me will know that I’m a Green, tree-hugging, anti-capitalist hippie, so let’s suppose that I wanted to move house to get as far away from Tescos as possible (for my international readers, Tesco is a multi-billion pound supermarket chain in the UK often presented as one of the faces of modern global capitalism – I think you have similar companies like Wal-Mart or Subway).

I’m going to set an additional constraint that, while I want to live far from Tescos, I still want to live in mainland UK. So, my problem is to find the point that lies somewhere inside this Polygon that is furthest away from any of the dots (which are the locations of Tescos stores):

image

So…. how do that?

Method 1: Iterative T-SQL

To start, I’m going to create the shape of the UK as a geography MultiPolygon called @UK. I’ve already got a table containing various parts of the UK that I can aggregate, but I want to exclude the Scottish Islands and other small polygon geometries that will confuse matters (Scottish islands = good for single malt whiskey, bad for spatial data… they’re just too craggy!). So, I’ll create a simple numbers table in a CTE and join to that table in order to loop through each element in the UK table and only aggregate those polygons that are greater than 10,000km2 in area:

DECLARE @UK geography;

-- Create a MultiPolygon of the UK, omitting any islands
-- with area less than 10,000km2
WITH Numbers(n) AS ( 
  SELECT ROW_NUMBER() OVER (ORDER BY number) FROM master..spt_values
)
SELECT @UK = geography::UnionAggregate(Shape.STGeometryN(n))
FROM UK JOIN Numbers ON Numbers.n  10000000000;

I’ll also create a geography MultiPoint of all Tescos stores called @Tescos. The only slight complication here is that the Garmin POI dataset I’m using seems to have some erroneous data, so I’ll put a WHERE condition to filter only those records that lie within the approximate geographic bounds of the UK:

DECLARE @Tescos geography;

-- Create a MultiPoint of Tescos stores
SELECT @Tescos = geography::UnionAggregate(Location)
FROM TescoStores
WHERE Location.Long BETWEEN -10 AND 3
AND Location.Lat BETWEEN 50 AND 60;

Now that we’ve got the basics set up, how do we go about finding the part of @UK that lies furthest from @Tescos? To explain one approach, consider an analogy in which the shape of the UK is cut out of blotting paper. You then arrange a set of pipettes filled with ink, so that each is positioned above the location of a Tescos store. And then, all at the same time, you start dropping ink from the pipettes onto the location of the stores.

The ink blots centred on the location of each store will expand outwards at the same rate, gradually filling up the blotting paper shape of the UK. The last part of the paper to be covered by ink is that which lies furthest from any of the ink sources, and must be the solution.

We can recreate this approach using SQL Server’s spatial functions:

  • To mimic the expanding ink blots we can call STBuffer() on the @Tescos MultiPoint with increasing buffer sizes to create expanding circular zones centred around each store.
  • To determine the area of blotting paper that is still not covered by ink, we can use STDifference() to compare the inked area to the original shape.
  • If the part of the UK yet to be covered by ink is still of a sufficiently large area, we simply add more ink by looping through again with an increased buffer size. If, however, the whole UK has been covered by ink then we’ve put too much ink on and we need to reduce the buffer size slightly.
  • When the area yet to be covered reaches a sufficiently small size (this approach is unlikely to ever converge on a single Point), declare that to be the solution.

Here’s the query:

DECLARE @x float = 100000; -- Declare an initial buffer size. I'm guessing that the furthest point in the UK lies more than 100km from Tescos
WHILE @UK.STDifference(@Tescos.STBuffer(@x)).STArea() > 10000 -- Stop when we find an area less than 10km2 in area
BEGIN

  -- Print a little debug message to see what's happening on this iteration
  PRINT CAST(@UK.STDifference(@Tescos.STBuffer(@x)).STArea() AS varchar(32)) + 
        'm2 of the UK lies more than ' + CAST(@x AS varchar(32)) + 'm from the closest Tescos';

  -- Amount by which to increase the buffer size
  DECLARE @y float;

  -- Fairly arbitrary, but enlarge the buffer by the square root of the remaining area
  -- Intention here is to close in on the appropriate buffer value relatively quickly
  SET @y = SQRT(@UK.STDifference(@Tescos.STBuffer(@x)).STArea());

  -- Check whether increasing the buffer by this amount would be too great
  -- and lead to the whole UK being covered in ink!
  WHILE @UK.STDifference(@Tescos.STBuffer(@x + @y)).STArea() = 0 BEGIN

    -- Reduce the amount by which the buffer is increased
    SET @y = @y/2;
  END

  -- Apply the increase in buffer
  SET @x = @x + @y;

END

Once this loop has finished, @x will contain a value, measured in metres, that represents the radius to which each of the ink blots must expand so that the area left uncovered has an area of less than 10km2 . Here’s what the map looks like when a circular buffer of radius @x is created centred on the location of each Tesco store:

SELECT @Tescos.STBuffer(@x)
UNION ALL SELECT @Tescos
UNION ALL SELECT @UK;

image

As you can tell, the whole of the UK is pretty much covered – the iteration only stops when the area remaining uncovered is less than 10km2 in in size (WHILE @UK.STDifference(@Tescos.STBuffer(@x)).STArea() > 10000), which means that, at this zoom level, it’s going to be pretty hard to perceive.

To find where the remaining un-inked area is, we can SELECT @UK.STDifference(@Tescos.STBuffer(@x)), and, to make it more obvious, I’ll also add a buffer to enlarge the solution area. And the answer? (as you might have been able to tell from the previous image) is the small blue polygon shown below:

image

Perhaps unsurprisingly, it’s an area at the tip of the highlands of Scotland, and it’s 119,591 metres from the closest Tesco store. Ok, so it’s not an exact point, but the way that this iteration works it would be very hard to reach such a precise location.

Method 2: SQLCLR Triangulation?

My gut instinct told me that there should be a way of coming up with the exact solution to the problem, in a much simpler manner, using triangulation. For example, suppose you were to create the Delaunay triangulation of the set of points such that the vertices of each triangle were formed from the location of the Tesco stores:

image

Since the triangles are formed by connecting vertices of Tesco stores, in those areas where stores are further apart, the circumcircle (the point of equal distance from all three vertices) of the triangle created between them will be larger. In fact, you’d think the point that lies at the centre of the triangle of largest circumradius would be the point that lies furthest from any Tesco store. And this would be true, were it not for (quite literally) “edge cases”…

… when, as in this case, the solution lies on the edge of the area in question, it is not covered by the extent of the triangulation (which only creates triangles that are interior to the set of data). Only by artificially extending the triangles out from the set of Tesco stores towards infinity could we ensure that the whole UK had been covered, and it turns out the SQL Server is not too happy about creating infinite-sized triangles (who’d have thought?).

So that’s a bit disappointing… the iterative T-SQL method still works and, with a bit of tweaking to choose how the buffer should be refined on each iteration, could probably be made to work more efficiently. However, I was hoping to be able to get something exact using SQLCLR.

I should just point out that the fact the triangulation method doesn’t extend beyond the bounds of the supplied dataset can still make it useful for lots of other purposes. For example, creating an aggregate union of the triangles creates a concave hull around the location of all Tescos stores in the UK (not a convex hull as could be created by STConvexHull()). The result looks scarily like the shape of the UK itself:

image

And, deriving a Voronoi tessellation from the set of triangles creates the “catchment area” around each store – the area formed from all those points closest to that store than any other. You can think of this as the materialised form of the results of a nearest-neighbour query:

image

(Note that, as expected, the point that lies furthest away from a Tescos store is contained within the largest Voronoi polygon – the pinkish one at the top of the map). At first, the Voronoi tessellation and the Delaunay triangulation may appear similar – indeed, they are “dual structures” – while Tesco stores form the vertices of each triangle in the Delaunay triangulation, they are the sites around which each polygon is created in the Voronoi tessellation.

If anybody can suggest any alternative approaches to identify the “point of maximal distance” from a set of data, I’d love to hear it…

Note: The SQLCLR code for creating the Delaunay triangulation, concave hull, and Voronoi tessellation demonstrated in this post is taken from “Pro Spatial with SQL Server 2012”, which also contains a chapter explaining these topics in more detail.

March 7, 2012

SQL Server 2012 – What’s New for Spatial?

So Microsoft launched SQL Server 2012 today, and you can download the SQL 2012 Express edition right now from
http://www.microsoft.com/sqlserver/en/us/get-sql-server/try-it.aspx

No doubt there will be lots of people writing blog articles about the various new features – AlwaysOn, Column Store , Power View etc. but I thought I’d stick to what I know and write about what’s new for spatial.

In fact, I don’t really even need to do that, because there’s always a pretty good whitepaper about that from the SQLCAT website, here. This whitepaper is written for Denali CTP3 but, to my knowledge (and from my limited playing around with RTM this morning), there don’t appear to be any new features introduced between then and now.

There’s also already a very useful table here that compares the spatial features currently available in SQL Azure to those in SQL Server 2012.

SQL Server 2012 Spatial Was My Idea

So, what’s left to do? Well – I might start by mentioning the fact that I’m quite chuffed about some of the new features that have been included. Do you remember the Windows 7 advertising slogan with people saying things they did with their PC and that “Windows 7 was my idea”? Well, that’s how a little bit like how I feel about SQL 2012:

I’m not suggesting that these features were introduced solely to appease my personal whims, but don’t let it ever be said that Microsoft don’t listen to customer feedback!

Upgrading a Spatial Application from SQL Server 2008/R2?

Finally, some words of warning for anyone upgrading existing spatial databases from SQL Server 2008/R2 to SQL Server 2012:

Remember to account for Curved Geometries!

The introduction of curved geometry datatypes mean that the range of possible geometries that can be returned by existing methods is increased, and you may need to write additional code paths to deal with that. Take the STConvexHull() method, for example, which returns the convex hull around a geometry. In SQL Server 2008/R2, that method would always return a Polygon (other than in the special case in which all the input points lay on a straight line, in which case it returned a LineString). In SQL 2012, however, if any of the inputs are themselves curved geometries, the convex hull returned by STConvexHull() can be a CurvePolygon, e.g.:

DECLARE @g geometry = 'GEOMETRYCOLLECTION(CIRCULARSTRING(0 0, -5 5, 0 10), CIRCULARSTRING(10 10, 15 10, 10 0))';
SELECT
  @g.STConvexHull(),
  @g.STConvexHull().STAsText();

The result of STConvexHull() in this example is:

CURVEPOLYGON (COMPOUNDCURVE (CIRCULARSTRING (18.090169943749487 5.0000000000000213, 16.351988430497684 9.051195518771241, 12.240554713995163 10.584146142748239), (12.240554713995163 10.584146142748239, -0.23001591065449833 9.9947064659342839), CIRCULARSTRING (-0.23001591065449833 9.9947064659342839, -4.99999999999994 5.0000000000000213, -0.23001591065449833 0.00529353406575872), (-0.23001591065449833 0.00529353406575872, 12.240554713995163 -0.5841461427481951), CIRCULARSTRING (12.240554713995163 -0.5841461427481951, 16.351988430497684 0.94880448122880079, 18.090125812525294 4.9777873878621213), (18.090125812525294 4.9777873878621213, 18.090169943749487 5.0000000000000213)))

image

Calculated Results may differ due to Increased Precision

SQL Server 2012 now uses 48 bit precision for spatial calculations rather than 27 bit as under SQL Server 2008/R2. This means that results of certain spatial queries will differ between those obtained under SQL Server 2008/R2. This has been mentioned before, but it really is worth iterating because it’s both unusual and potentially application-breaking behaviour.

The following very simple example demonstrates the issue:

DECLARE @line1 geometry = 'LINESTRING(0 11, 430 310)';
DECLARE @line2 geometry = 'LINESTRING(0 500, 650 0)';

SELECT @line1.STIntersection(@line2).STIntersects(@line1);
  • When executed under SQL Server 2008/R2, you’ll get the result 0.
  • Upgrade to SQL Server 2012, and you’ll suddenly get the result 1.

For a method that returns a bit value, that’s pretty much the greatest difference it’s possible to get…. so be sure to check your code (especially if you ever rely on exact equality testing between instances).

Follow

Get every new post delivered to your Inbox.

Join 53 other followers