<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	xmlns:georss="http://www.georss.org/georss" xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#" xmlns:media="http://search.yahoo.com/mrss/"
	>

<channel>
	<title>Alastair Aitchison</title>
	<atom:link href="http://alastaira.wordpress.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://alastaira.wordpress.com</link>
	<description>Spatial stuff with SQL Server, Bing Maps and more...</description>
	<lastBuildDate>Thu, 23 Feb 2012 16:23:33 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.com/</generator>
<cloud domain='alastaira.wordpress.com' port='80' path='/?rsscloud=notify' registerProcedure='' protocol='http-post' />
<image>
		<url>http://s2.wp.com/i/buttonw-com.png</url>
		<title>Alastair Aitchison</title>
		<link>http://alastaira.wordpress.com</link>
	</image>
	<atom:link rel="search" type="application/opensearchdescription+xml" href="http://alastaira.wordpress.com/osd.xml" title="Alastair Aitchison" />
	<atom:link rel='hub' href='http://alastaira.wordpress.com/?pushpress=hub'/>
		<item>
		<title>Updating a SQL Server Table with Nearest Neighbours (Optimised for SQL Server 2012 / SQL Azure)</title>
		<link>http://alastaira.wordpress.com/2012/02/22/updating-a-sql-server-table-with-nearest-neighbours-optimised-for-sql-server-2012-sql-azure/</link>
		<comments>http://alastaira.wordpress.com/2012/02/22/updating-a-sql-server-table-with-nearest-neighbours-optimised-for-sql-server-2012-sql-azure/#comments</comments>
		<pubDate>Wed, 22 Feb 2012 19:49:08 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Nearest Neighbour]]></category>
		<category><![CDATA[SQL Azure]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1167</guid>
		<description><![CDATA[So I’ve been trying to think of some examples that make use of the Garmin POI data from my last post. Seeing as POI data naturally lends itself to nearest-neighbour queries, I thought I’d write a little about those. The &#8230; <a href="http://alastaira.wordpress.com/2012/02/22/updating-a-sql-server-table-with-nearest-neighbours-optimised-for-sql-server-2012-sql-azure/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1167&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>So I’ve been trying to think of some examples that make use of the Garmin POI data from my <a href="https://alastaira.wordpress.com/2012/02/20/load-garmin-poi-data-to-sql-server/">last post</a>. Seeing as POI data naturally lends itself to nearest-neighbour queries, I thought I’d write a little about those.</p>
<p>The general form of the nearest neighbor query is: “<em>Show me the closest x restaurants/pubs/train stations to this location</em>”; this is the query pattern used by location-aware applications to find POIs close to the user’s current location, for example. However, another pattern of which I’ve seen much fewer examples is how to update an entire table of records in order to determine and populate the nearest neighbour for each row in the table (indeed, <a href="http://social.msdn.microsoft.com/Forums/en-US/sqlspatial/thread/f6315347-dc5d-498a-a297-1812b7a9c50d">this very question</a> came up on the MSDN spatial forum just last week).</p>
<p>With this in mind, I thought I’d demonstrate a practical scenario: let’s say you’re planning going for a swim at the local pool and, after you’ve swum 50 lengths, you’ll probably feel a bit peckish – perhaps in the need for some fish ‘n’ chips. So, in this example I’m firstly going to import the Garmin POI data for swimming pools and fish ‘n’ chip shops in the UK, each being represented by a geography Point instance. I’ll then add an extra column to the swimming pool table and populate it with the closest fish and chip shop to every swimming pool.</p>
<p>To start with, create tables containing the data for fish and chip shops and swimming pools (download the data from <a href="http://garminpoi.co.uk/dodownload.php?cat=orig">here</a>). Notice that I’ve included an IDENTITY primary key field in each table, which I’ll need in order to add a spatial index later:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:f9a19996-c374-4bba-9ad9-7da022d0f43f" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">SELECT
  Id = IDENTITY(int,1,1),
  CAST(Name AS varchar(255)) AS Name,
  CAST(Details AS varchar(255)) AS Details,
  geography::Point(Latitude,Longitude,4326) AS Location
INTO SwimmingPools
FROM OPENROWSET(
 BULK 'C:\Users\Alastair\Downloads\GarminPOI\Swimming Pools.csv',
 FORMATFILE='C:\temp\garminpoi.xml',
 FIRSTROW=2
) AS SwimmingPools;
GO

ALTER TABLE SwimmingPools ADD PRIMARY KEY(Id);
GO

SELECT
  Id = IDENTITY(int,1,1),
  CAST(Name AS varchar(255)) AS Name,
  CAST(Details AS varchar(255)) AS Details,
  geography::Point(Latitude,Longitude,4326) AS Location
INTO FishAndChips
FROM OPENROWSET(
 BULK 'C:\Users\Alastair\Downloads\GarminPOI\Fish and Chips.csv',
 FORMATFILE='C:\temp\garminpoi.xml',
 FIRSTROW=2
) AS FishAndChips;
GO

ALTER TABLE FishAndChips ADD PRIMARY KEY(Id);
GO</pre></pre>
</div>
<p>Now we’ll add some extra columns to the Swimming Pools table, which we’ll populate with information about the closest fish ‘n’ chip shop:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:c1469bea-223b-487e-a667-96c7af8569ea" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">ALTER TABLE SwimmingPools
ADD ClosestFishbar varchar(255),
    Distance decimal(18,2);</pre></pre>
</div>
<p>Now, you don’t <em>need</em> to add a spatial index to perform the update query, but it’ll be a lot faster with one than without. SQL Server can only make use of one spatial index in a join between tables, so there’s no need to add an index to both tables. We’ll just add one to the Fish And Chips table, as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:3afa6668-8823-402c-9e6a-0c3d563dfab9" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">CREATE SPATIAL INDEX sidx_FishAndChips ON FishAndChips(Location)
USING  GEOGRAPHY_GRID 
WITH (
  GRIDS =(LEVEL_1 = HIGH,LEVEL_2 = HIGH,LEVEL_3 = HIGH,LEVEL_4 = HIGH), 
  CELLS_PER_OBJECT = 16
);
</pre></pre>
</div>
<p>Then, run an UPDATE query to populate the <em>ClosestFishBar</em> and <em>Distance</em> columns, as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:82fa75c4-2612-4a36-9b19-3e7e47b20a30" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">UPDATE s
SET ClosestFishbar = fName,
    Distance = Location.STDistance(fLocation)  
FROM (
  SELECT
    SwimmingPools.*,
    fnc.Name AS fName,
    fnc.Location AS fLocation
  FROM SwimmingPools 
  CROSS APPLY (
    SELECT TOP 1
     Name,
     Location
    FROM FishAndChips WITH(index(sidx_FishAndChips))
    WHERE FishAndChips.Location.STDistance(SwimmingPools.Location) IS NOT NULL
    ORDER BY FishAndChips.Location.STDistance(SwimmingPools.Location) ASC
  ) fnc
) s;</pre></pre>
</div>
<p>This query probably needs some explanation:</p>
<ul>
<li>Generally, you might expect to use a correlated subquery to obtain the closest fish and chip shop to each swimming pool. However, correlated subqueries can only return a single value, and I want to retrieve both the name and the distance to the closest fish bar, so I&#8217;ve used a CROSS APPLY instead.
<li>In the function being applied, I’m sorting the table of fish and chip shops by ascending order of their distance from the current swimming pool (<em>ORDER BY FishAndChips.Location.STDistance(SwimmingPools.Location) ASC</em>), and then selecting the <em>TOP 1</em> Name and Location (i.e. the closest).
<li>To make the query efficient, I’ve also added an index hint to use the spatial index on the Fish and Chips table – <em>WITH(index(sidx_FishAndChips))</em>, and I’ve included an extra predicate, <em>WHERE FishAndChips.Location.STDistance(SwimmingPools.Location) IS NOT NULL</em>. This extra predicate will help the query optimiser choose the dedicated nearest neighbour query plan introduced in SQL Server 2012 and SQL Azure.
<li>Note that the spatial index-optimised nearest neighbour query plan is only available in SQL Server 2012/SQL Azure. If you try to execute the UPDATE query as written above in SQL Server 2008/R2 then you’ll get an error:
<p>“<em>The query processor could not produce a query plan for a query with a spatial index hint.&nbsp; Reason: Spatial indexes do not support the comparator supplied in the predicate.&nbsp; Try removing the index hints or removing SET FORCEPLAN.</em>” </p>
<p>As stated, to resolve this you’ll have to remove the <em>WITH(index(sidx_FishAndChips)) </em>index hint (and also expect your query to run a lot slower!). Instead, you might need to try one of the <a href="http://blogs.msdn.com/b/isaac/archive/2008/10/23/nearest-neighbors.aspx">alternative approaches</a> to efficiently finding nearest neighbours in SQL Server 2008/R2.</p>
<li>Finally, in order to perform the UPDATE, I’ve wrapped the whole lot in a table alias, <em>s</em>, and then updated the two columns as required.</li>
</ul>
<p>This query should take a few seconds to run (or, if you don’t create/use the spatial index, a few minutes), after which you can check the results as follows: </p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:385853e0-acad-42c8-8ec9-082fe69de16e" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">SELECT * FROM SwimmingPools;
</pre></pre>
</div>
<p>And there you have the complete table of swimming pools, each listed with the name and distance to its closest fish and chip shop:</p>
<p><a href="http://alastaira.files.wordpress.com/2012/02/image11.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/02/image_thumb11.png?w=644&#038;h=130" width="644" height="130"></a>&nbsp;</p>
<p>Seeing as it’s less than 100 metres from getting out of the water to a bag of chips, I’ll think I’ll be taking my next dip at the Farnworth swimming pool….</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1167/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1167/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1167/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1167/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1167/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1167/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1167/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1167/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1167/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1167/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1167/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1167/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1167/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1167/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1167&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/02/22/updating-a-sql-server-table-with-nearest-neighbours-optimised-for-sql-server-2012-sql-azure/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/02/image_thumb11.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>
	</item>
		<item>
		<title>Load Garmin POI data to SQL Server</title>
		<link>http://alastaira.wordpress.com/2012/02/20/load-garmin-poi-data-to-sql-server/</link>
		<comments>http://alastaira.wordpress.com/2012/02/20/load-garmin-poi-data-to-sql-server/#comments</comments>
		<pubDate>Mon, 20 Feb 2012 09:36:51 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1160</guid>
		<description><![CDATA[I’m always looking out for interesting new sources of spatial data to provide examples for use in my books and presentations. Microsoft’s own MSDN documentation demonstrates SQL Server spatial methods only using abstract geometries, such as SET @h = geometry::STGeomFromText('POINT(10 &#8230; <a href="http://alastaira.wordpress.com/2012/02/20/load-garmin-poi-data-to-sql-server/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1160&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I’m always looking out for interesting new sources of spatial data to provide examples for use in my books and presentations. Microsoft’s own <a href="http://msdn.microsoft.com/en-us/library/bb933952.aspx">MSDN documentation</a> demonstrates SQL Server spatial methods only using abstract geometries, such as</p>
<pre>SET @h = geometry::STGeomFromText('POINT(10 10)', 0);</pre>
<p>However, people often tell me that they really appreciate real-world examples instead – not only are they more interesting, but it’s easier to understand a practical use for the method, and they’re also more memorable afterwards. Some examples I’ve used in the past include:</p>
<ul>
<li>Use STBuffer() to calculate the free delivery area around a Pizza restaurant</li>
<li>Use STDisjoint() to ensure that a planned road development does not intersect a protected area of natural beauty</li>
<li>Use STIntersection() to identify the section of the <em>Via Appia</em> Roman road that passed through the malaria-infested Pontine marshes.</li>
</ul>
<p>Anyway, I thought I’d share a new source of data I came across recently – it’s a free dataset of (UK) points of interest designed for importing into Garmin sat-nav systems. As such, it contains practical POIs, categorised into things like museums, mountains, petrol stations, and restaurants. However, since it’s distributed in a simple CSV format, it’s super easy to import the data into SQL Server or other systems as well. Here’s how:</p>
<h2></h2>
<h2>1. Download</h2>
<p>Download the dataset of POIs from the garminpoi.co.uk website at <a title="http://garminpoi.co.uk/?page=download" href="http://garminpoi.co.uk/?page=download">http://garminpoi.co.uk/?page=download</a></p>
<p>When unzipped, you’ll find it contains a number of categorised CSV files (each with an accompanying BMP icon, which you could use if you want to display this data using pushpins on an SSRS report or Bing Maps, for example):</p>
<p><a href="http://alastaira.files.wordpress.com/2012/02/image9.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border:0;" title="image" src="http://alastaira.files.wordpress.com/2012/02/image_thumb9.png?w=529&#038;h=289" alt="image" width="529" height="289" border="0" /></a></p>
<h2>2. Create a Format File</h2>
<p>All of the CSV files follow exactly the same structure – with columns for Longitude, Latitude, a Name, and Description. This can be described by the following bulk format file, which you should save as garminpoi.xml:</p>
<div id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:e663dc76-a36b-4204-bd66-c596cd509aab" class="wlWriterEditableSmartContent" style="display:inline;float:none;margin:0;padding:0;">
<p><pre class="brush: xml; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;BCPFORMAT xmlns=&quot;http://schemas.microsoft.com/sqlserver/2004/bulkload/format&quot; xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&gt;
&lt;RECORD&gt;
 &lt;FIELD ID=&quot;0&quot; xsi:type=&quot;CharTerm&quot; TERMINATOR=&quot;,&quot;/&gt;
 &lt;FIELD ID=&quot;1&quot; xsi:type=&quot;CharTerm&quot; TERMINATOR=&quot;,&quot;/&gt;
 &lt;FIELD ID=&quot;2&quot; xsi:type=&quot;CharTerm&quot; TERMINATOR=&quot;,&quot;/&gt;
 &lt;FIELD ID=&quot;3&quot; xsi:type=&quot;CharTerm&quot; TERMINATOR=&quot;\r\n&quot;/&gt;
&lt;/RECORD&gt;
&lt;ROW&gt;
 &lt;COLUMN SOURCE=&quot;0&quot; NAME=&quot;Longitude&quot; xsi:type=&quot;SQLVARYCHAR&quot;/&gt;
 &lt;COLUMN SOURCE=&quot;1&quot; NAME=&quot;Latitude&quot; xsi:type=&quot;SQLVARYCHAR&quot;/&gt;
 &lt;COLUMN SOURCE=&quot;2&quot; NAME=&quot;Name&quot; xsi:type=&quot;SQLVARYCHAR&quot;/&gt;
 &lt;COLUMN SOURCE=&quot;3&quot; NAME=&quot;Details&quot; xsi:type=&quot;SQLVARYCHAR&quot;/&gt;
&lt;/ROW&gt;
&lt;/BCPFORMAT&gt;
</pre></p>
</div>
<h1></h1>
<h2>3. Open the CSV file in SQL Server</h2>
<p>Now, you can access the data from the CSV file directly in SQL Server using OPENROWSET, supplying the name of the CSV file you want to load following the BULK keyword, and the formatfile created above following the FORMATFILE keyword. The CSV files contain column headers, which you can skip by specifying FIRSTROW=2. What’s more, you can pass the Latitude and Longitude coordinate values from the CSV straight to the Point() method of the geography datatype to create point instance at each location on-the-fly.</p>
<p>For example, try executing the following code listing:</p>
<div id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:aeeddc3b-5040-4954-99fb-f221e2d7c8d4" class="wlWriterEditableSmartContent" style="display:inline;float:none;margin:0;padding:0;"><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">SELECT *,
geography::Point(Latitude,Longitude,4326) AS TescoStore
FROM OPENROWSET(
 BULK 'C:\Users\Alastair\Downloads\GarminPOI\Tesco.csv',
 FORMATFILE='C:\temp\garminpoi.xml',
 FIRSTROW=2
) AS GarminPOI;
</pre></p>
</div>
<p>Result? An instant map of all Tesco branches in the UK!</p>
<p><a href="http://alastaira.files.wordpress.com/2012/02/image10.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border:0;" title="image" src="http://alastaira.files.wordpress.com/2012/02/image_thumb10.png?w=604&#038;h=484" alt="image" width="604" height="484" border="0" /></a></p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1160/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1160/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1160/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1160/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1160/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1160/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1160/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1160/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1160/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1160/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1160/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1160/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1160/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1160/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1160&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/02/20/load-garmin-poi-data-to-sql-server/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/02/image_thumb9.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/02/image_thumb10.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>
	</item>
		<item>
		<title>Rendering SQL Server spatial data with Mapnik (Part 2 of n, where n is large)</title>
		<link>http://alastaira.wordpress.com/2012/02/06/rendering-sql-server-spatial-data-with-mapnik-part-2-of-n-where-n-is-large/</link>
		<comments>http://alastaira.wordpress.com/2012/02/06/rendering-sql-server-spatial-data-with-mapnik-part-2-of-n-where-n-is-large/#comments</comments>
		<pubDate>Mon, 06 Feb 2012 11:53:51 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[Mapnik]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1153</guid>
		<description><![CDATA[Last year, I wrote what was intended to be the first of a series of posts about Mapnik – a software toolkit for generating beautiful maps from various sets of spatial data. My intention had been to describe how to &#8230; <a href="http://alastaira.wordpress.com/2012/02/06/rendering-sql-server-spatial-data-with-mapnik-part-2-of-n-where-n-is-large/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1153&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Last year, I wrote what was intended to be the <a href="https://alastaira.wordpress.com/2011/06/16/tile-rendering-with-mapnik-part-1-of-n-where-n-is-large/">first</a> of a series of posts about <a href="http://mapnik.org/">Mapnik</a> – a software toolkit for generating beautiful maps from various sets of spatial data. My intention had been to describe how to render spatial data from SQL Server as styled raster tile layers on Bing Maps, but the series got cut short rather prematurely when I discovered that it didn’t appear to be possible to connect Mapnik to SQL Server (even though it was an OGR-supported format).</p>
<p>Unfortunately, as is often the case with OS software, it seems that almost all Mapnik developers use PostGIS as a backend spatial database, and I failed to find anybody with any experience of SQL Server on either the <a href="http://lists.berlios.de/pipermail/mapnik-users/2011-June/004334.html">Mapnik developers mailing list</a> or on <a href="http://gis.stackexchange.com/questions/13199/can-mapnik-render-spatial-data-from-sql-server">StackExchange</a> (rather amusingly, the top-rated answer given to me on StackExchange gives, as its reference source, <em>my own blog post</em> where I explicitly state that the suggested approach doesn’t work!). </p>
<p><a href="http://alastaira.files.wordpress.com/2012/02/image6.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/02/image_thumb6.png?w=549&#038;h=484" width="549" height="484"></a></p>
<p>Since, at the time, I needed a tool to render SQL geometry data there and then, I left Mapnik alone and developed a solution using <a href="http://mapserver.org/">MapServer</a> instead.</p>
<p>Now, MapServer is a great product: It’s a venerable and highly-respected product in the world of open source spatial software, developed by the University of Minnesota on behalf of NASA, and first released over 16 years ago (to put that in context: MapServer was released at around the same time as HTML 3.2 &#8211; long before CSS, AJAX, or Google Maps were heard of). And it’s still going strong today. However, I find that the archaic MapServer architecture and idiosyncracies of its syntax give away its age slightly.</p>
<p>Mapnik, by contrast, is freshly-designed, has a modern XML-style syntax, supports groovy things like node.js and Ruby, and generally feels like it’s not carrying so much baggage. If only it supported SQL Server….</p>
<p>6 months later, and the <a href="https://github.com/downloads/mapnik/mapnik/mapnik-2.0.1rc0.zip">Release Candidate for Mapnik 2.0</a> is&nbsp; now available to download, which claims to support SQL Server, so I thought I’d give it another try. Having just set it up and rendered some quick OS OpenData, I can confirm that it works. Hurray!</p>
<p><a href="http://alastaira.files.wordpress.com/2012/02/image7.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/02/image_thumb7.png?w=644&#038;h=341" width="644" height="341"></a></p>
<p>SSMS Spatial Results Tab Rendering of SQL Server data</p>
<p><a href="http://alastaira.files.wordpress.com/2012/02/image8.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/02/image_thumb8.png?w=644&#038;h=341" width="644" height="341"></a></p>
<p>Mapnik Rendering of the same SQL Server data</p>
<p>Since it seems that I’m one of only few people trying to use Mapnik and SQL Server together (and one of even less writing about it), I’ve started to document some tips in a page <a href="https://alastaira.wordpress.com/mapnik-for-sql-server/">here</a> &#8211; I’ll add to this page as I go along.</p>
<p>(On a separate note, I now have a whole different problem, because a recent <a href="http://social.msdn.microsoft.com/Forums/en-US/vemapcontroldev/thread/79d6c30c-9d04-45e1-9888-327ed784cfda">change in the Bing Maps Terms of Use</a> prevents you from adding your own custom road tile layers onto the Bing Map control. So, any tiles of OpenStreetMap or Ordnance Survey road data you create with Mapnik <u>can’t be used with Bing Maps</u>. Fortunately, there’s <a href="http://leaflet.cloudmade.com/">a workaround</a> for that, but that’s for another post….)</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1153/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1153/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1153/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1153/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1153/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1153/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1153/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1153/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1153/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1153/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1153/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1153/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1153/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1153/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1153&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/02/06/rendering-sql-server-spatial-data-with-mapnik-part-2-of-n-where-n-is-large/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/02/image_thumb6.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/02/image_thumb7.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/02/image_thumb8.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>
	</item>
		<item>
		<title>Ring Orientation, Bigger-than-a-Hemisphere Polygons, and the ReorientObject method in SQL Server 2012</title>
		<link>http://alastaira.wordpress.com/2012/01/27/ring-orientation-bigger-than-a-hemisphere-polygons-and-the-reorientobject-method-in-sql-server-2012/</link>
		<comments>http://alastaira.wordpress.com/2012/01/27/ring-orientation-bigger-than-a-hemisphere-polygons-and-the-reorientobject-method-in-sql-server-2012/#comments</comments>
		<pubDate>Fri, 27 Jan 2012 13:54:55 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[geography]]></category>
		<category><![CDATA[Invalid]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1129</guid>
		<description><![CDATA[If you’ve ever tried to import spatial data (from an ESRI shapefile, say) into SQL Server, you’ve almost certainly encountered the dreaded “bigger than a hemisphere” error, as follows: Msg 6522, Level 16, State 1, Line 1 A .NET Framework &#8230; <a href="http://alastaira.wordpress.com/2012/01/27/ring-orientation-bigger-than-a-hemisphere-polygons-and-the-reorientobject-method-in-sql-server-2012/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1129&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>If you’ve ever tried to import spatial data (from an ESRI shapefile, say) into SQL Server, you’ve almost certainly encountered the dreaded “bigger than a hemisphere” error, as follows:</p>
<pre>Msg 6522, Level 16, State 1, Line 1
A .NET Framework error occurred during execution of
user-defined routine or aggregate "geography":

Microsoft.SqlServer.Types.GLArgumentException: 24205:
The specified input does not represent a valid geography instance
because it exceeds a single hemisphere. Each geography instance
must fit inside a single hemisphere. A common reason for this
error is that a polygon has the wrong ring orientation.</pre>
<p>This error arises out of a <a href="http://blogs.msdn.com/b/isaac/archive/2009/02/06/the-geography-hemisphere-limitation.aspx">widely</a> <a href="http://technet.microsoft.com/en-us/library/bb964711.aspx">documented</a> limitation in SQL Server 2008/R2 that geography instances cannot span more than a single hemisphere (i.e. they cannot extend over more than half the surface of the earth).</p>
<p>While, strictly speaking, this is a technical limitation, the “smaller-than-a-hemisphere” requirement actually has a beneficial side-effect of providing a useful sanity check of your data: in practice, it is very unusual to ever need a geography instance that spans more than a single hemisphere. Unless you are modelling the Pacific Ocean, or the entire continent of Asia as a single instance, the chances are that you didn’t really want a geography bigger than a hemisphere anyway. Instead, as the error message above states, what probably happened is that the coordinate data of your imported shape had the incorrect ring orientation – the coordinate points were listed in reverse order, which has the effect of inverting the polygon. Therefore, you ended up including everything that was meant to be excluded, and excluding the area that you meant to include. Instead of defining the county of Norfolk, for example, your polygon actually represents the entire surface of the earth <em>except</em> for Norfolk…</p>
<p>Assuming that what you really wanted was to define a relatively small polygon area – a country, state, or sales region, say, then the “within a hemisphere rule” ensures the quality of your data by rejecting any Polygon instances with the incorrect ring orientation.</p>
<h2>Along Comes SQL Server 2012</h2>
<p>Well, SQL Server 2012 now allows geography Polygons to be bigger than a hemisphere, and that brings a whole new interesting problem… because now, you no longer get an error when you try to import data with incorrect ring orientation – it loads just fine. And you might not even realise that there’s anything wrong with your data until you try to perform some calculations on it. </p>
<p>For example, I just downloaded some <a href="https://www.ga.gov.au/products/servlet/controller?event=FILE_SELECTION&amp;catno=42343">Australian river basins data</a> from the Geoscience Australia website, and imported it into SQL Server using <a href="http://gdal.org/ogr/">OGR2OGR</a>. Everything seemed to go successfully, and here’s a glance at what the table looks like with the geography data inserted into the geog4202 column:</p>
<p><a href="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-05-32.png"><img style="background-image:none;border-bottom:0;border-left:0;padding-left:0;padding-right:0;display:inline;border-top:0;border-right:0;padding-top:0;" title="27-01-2012 12-05-32" border="0" alt="27-01-2012 12-05-32" src="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-05-32_thumb.png?w=644&#038;h=247" width="644" height="247"></a></p>
<p>Seems ok, right? But if you calculate the area of any of the river basins in this table using STArea(), you’ll get a result like this:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:58f7cdf5-e3fa-48b3-8ebb-47f6c9b191f4" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">SELECT TOP 1 geog4202.STArea();

-- 510,069,272,680,889 (m2)
</pre></pre>
</div>
<p>I mean, I know Australia is a big place, but… a river basin with an area of 510,069,272 km<sup>2</sup>… <em>Really</em>? That seems awfully close to the entire <a href="http://en.wikipedia.org/wiki/Earth">surface area of the earth</a>. And, if you look on the spatial results tab, you’ll see this:</p>
<p><a href="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-07-18.png"><img style="background-image:none;border-bottom:0;border-left:0;padding-left:0;padding-right:0;display:inline;border-top:0;border-right:0;padding-top:0;" title="27-01-2012 12-07-18" border="0" alt="27-01-2012 12-07-18" src="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-07-18_thumb.png?w=644&#038;h=247" width="644" height="247"></a></p>
<p>Sure enough – it looks pretty much like those polygons <em>do</em> cover the entire surface area of the earth.</p>
<p>The problem here is that the source data does not follow the same “left-hand rule” convention as SQL Server, and some (the majority, in fact) of the imported river basin polygons have been defined inside-out. But, because SQL Server now allows large geographies, no error or warning has been created.</p>
<h2>The Solution</h2>
<p>Fortunately, SQL Server 2012, whilst inadvertently causing this problem, also provides the solution, in the form of the ReorientObject() method, which reverses the ring orientation of any polygon to flip its interior and exterior.</p>
<p>We only want to use ReorientObject() to flip those polygons that have been defined inside-out. Unfortunately, there’s no fool-proof way to check for this, but it’s reasonable to assume that, in a dataset of Australia’s river basins, any polygon that covers more than half the earth’s surface must be incorrect. So, we can identify those polygons that need flipping because their EnvelopeAngle will be equal to or more than 90°, and put this in a CASE statement as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:4796838c-8781-4049-b33f-c15791f50540" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">SELECT
  CASE
    WHEN geog4202.EnvelopeAngle() &gt;= 90 THEN geog4202.ReorientObject()
    ELSE geog4202
  END AS geog4202
FROM rbasin_polygon;
</pre></pre>
</div>
<p>And now, the results look much more like those expected:</p>
<p><a href="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-13-22.png"><img style="background-image:none;border-bottom:0;border-left:0;padding-left:0;padding-right:0;display:inline;border-top:0;border-right:0;padding-top:0;" title="27-01-2012 12-13-22" border="0" alt="27-01-2012 12-13-22" src="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-13-22_thumb.png?w=588&#038;h=333" width="588" height="333"></a></p>
<p>(If you really want, you could even put a CHECK constraint on a geography column that prevented any polygons greater than a hemisphere being inserted into the table in the first place. Effectively, you’d be manually reintroducing a technical limitation from previous versions of SQL Server – ironic, huh?)</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1129/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1129/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1129/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1129/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1129/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1129/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1129/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1129/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1129/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1129/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1129/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1129/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1129/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1129/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1129&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/01/27/ring-orientation-bigger-than-a-hemisphere-polygons-and-the-reorientobject-method-in-sql-server-2012/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-05-32_thumb.png" medium="image">
			<media:title type="html">27-01-2012 12-05-32</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-07-18_thumb.png" medium="image">
			<media:title type="html">27-01-2012 12-07-18</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/27-01-2012-12-13-22_thumb.png" medium="image">
			<media:title type="html">27-01-2012 12-13-22</media:title>
		</media:content>
	</item>
		<item>
		<title>SQL Spatial Puzzle #3: The Seven Bridges of K&#246;nigsberg</title>
		<link>http://alastaira.wordpress.com/2012/01/23/sql-spatial-puzzle-3-the-seven-bridges-of-knigsberg/</link>
		<comments>http://alastaira.wordpress.com/2012/01/23/sql-spatial-puzzle-3-the-seven-bridges-of-knigsberg/#comments</comments>
		<pubDate>Mon, 23 Jan 2012 10:49:15 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[fun]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1120</guid>
		<description><![CDATA[The old city of Königsberg, capital of East Prussia (now Kaliningrad), was built on either side of the river Pregel, with seven bridges across the river between four separate landmasses. A problem made famous by Swiss mathematician Leonard Euler is &#8230; <a href="http://alastaira.wordpress.com/2012/01/23/sql-spatial-puzzle-3-the-seven-bridges-of-knigsberg/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1120&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>The old city of Königsberg, capital of East Prussia (now Kaliningrad), was built on either side of the river Pregel, with seven bridges across the river between four separate landmasses. A problem made famous by Swiss mathematician <a href="http://en.wikipedia.org/wiki/Leonhard_Euler">Leonard Euler</a> is to try to find a route around the city that crosses every bridge once and only once.</p>
<p><a href="http://alastaira.files.wordpress.com/2012/01/image3.png"><img style="background-image:none;border-bottom:0;border-left:0;padding-left:0;padding-right:0;display:inline;border-top:0;border-right:0;padding-top:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/01/image_thumb3.png?w=628&#038;h=437" width="628" height="437"></a></p>
<p>To investigate this puzzle using SQL Server, we’ll model a portion of the city&nbsp; using two tables – one containing Polygons representing the landmasses separated by rivers, and another containing LineStrings representing the bridges across those rivers:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:d19edd10-123e-4471-9505-c4ad69ee7a43" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">CREATE TABLE #islands 
(
islandid char(1),
islandgeom geometry
);

INSERT INTO #islands VALUES
('A', 'POLYGON((20.505 54.706, 20.506 54.708, 20.5077 54.7086, 20.513 54.7074, 20.514 54.7075, 20.52 54.7077, 20.52 54.71, 20.503 54.71, 20.503 54.706, 20.505 54.706))'),
('B', 'POLYGON((20.506 54.706, 20.5073 54.7083, 20.5135 54.7068, 20.5134 54.7053, 20.506 54.706))'),
('C', 'POLYGON((20.52 54.698, 20.5149 54.6999, 20.5137 54.7009, 20.51435 54.704, 20.5135 54.7046, 20.5051 54.7054, 20.503 54.7056, 20.503 54.698, 20.52 54.698))'),
('D', 'POLYGON((20.52 54.707, 20.5162 54.707, 20.514 54.707, 20.5142 54.7051, 20.515 54.704, 20.515 54.703, 20.5149 54.7008, 20.5159 54.7, 20.52 54.699, 20.52 54.707))');

CREATE TABLE #bridges(
bridgeid char(1),
bridgegeom geometry
);
INSERT INTO #bridges VALUES
(1, 'LINESTRING(20.5082 54.7092, 20.5075 54.7076)'),
(2, 'LINESTRING(20.5109 54.7087, 20.5101 54.707)'),
(3, 'LINESTRING(20.5148 54.708, 20.5147 54.70634)'),
(4, 'LINESTRING(20.5128 54.706, 20.5147 54.7057)'),
(5, 'LINESTRING(20.5069 54.70645, 20.5061 54.7048)'),
(6, 'LINESTRING(20.5103 54.706, 20.5099 54.70416)'),
(7, 'LINESTRING(20.5174 54.6985, 20.5187 54.7003)');</pre></pre>
</div>
<p>Each of the islands is lettered from A – D, and each bridge is numbered from 1 – 7, as shown in the results of the following query (I’ve applied a small buffer to the bridges only to make them easier to see in the spatial results tab):</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:a67d295a-14a9-46de-aad6-230a3f1a1fb0" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">SELECT islandid, islandgeom FROM #islands
UNION ALL
SELECT bridgeid, bridgegeom.STBuffer(0.0001) FROM #bridges
</pre></pre>
</div>
<p><a href="http://alastaira.files.wordpress.com/2012/01/image4.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/01/image_thumb4.png?w=737&#038;h=409" width="737" height="409"></a></p>
<p>How, then, can we try to devise a route that crosses each bridge only once? When investigating this problem, the first conclusion reached by Euler was that the shape of the islands, and the path taken <em>within</em> any island, was irrelevant. All that matters is the possible connections that can be made <em>between</em> islands. (This conclusion, and Euler’s later work on the puzzle, led to the foundation of graph theory, in which the entities in a network can be modelled as nodes, and the relationships between those nodes defined by edges).</p>
<p>So, let’s create a table that maps every possible crossing between islands, the edges of the network, by using STIntersects() to determine those islands that are connected to each bridge:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:2841611d-2369-4e0d-8b0f-7fb71889c496" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; toolbar: false; wrap-lines: false;">CREATE TABLE #crossings(
  fromid char(1),
  viabridge char(1),
  toid char(1)
);

INSERT INTO #crossings
SELECT 
  f.islandid AS fromid,
  b.bridgeid AS viabridge,
  t.islandid AS toid
FROM #islands f
  JOIN #bridges b ON f.islandgeom.STIntersects(b.bridgegeom) = 1
  JOIN #islands t ON b.bridgegeom.STIntersects(t.islandgeom) = 1
WHERE f.islandid != t.islandid
ORDER BY f.islandid;</pre></pre>
</div>
<p>Having created a table with every individual bridge crossing, we can now attempt to create a continuous route across the city by navigating through that edges table using a recursive CTE.&nbsp; The anchor member of this query starts by looking at each individual bridge crossing from the #crossings table. Having crossed a bridge to a new island,&nbsp; the recursive member then joins back to the #crossings table to search for any bridges that <em>begin</em> on the island where the last crossing <em>ended</em>, in an attempt to continue the route (note the join condition – <em>#crossings JOIN cte ON cte.toid = #crossings.fromid</em>).</p>
<p>Along the way, the query concatenates the ids of all the bridges crossed into the <em>bridgescrossed</em> field. This field is used in a condition of the recursive member to ensure we don’t cross any bridge that’s already been included at some point in the route (<em>WHERE bridgescrossed NOT LIKE &#8216;%&#8217; + #crossings.viabridge + &#8216;%&#8217;</em>).</p>
<p>Finally, we also concatenate both the bridge identifier and the islands visited in turn to create a nice formatted route of the journey for later examination. e.g. a journey that started at island A and went to island D via bridge 3 would be shown as A – 3 – D.</p>
<p>Here’s the full code listing:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:f4ae62ba-cd6a-4a30-a77b-74c36aca9983" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">WITH cte as (
  SELECT
    #crossings.fromid,
    CAST(#crossings.viabridge AS varchar(32)) AS bridgescrossed,
    CAST(#crossings.fromid + '-' + #crossings.viabridge + '-' + #crossings.toid AS varchar(32)) as formattedroute,
    #crossings.toid
    FROM #crossings
  UNION ALL SELECT
    #crossings.fromid,
    CAST(cte.bridgescrossed + #crossings.viabridge AS varchar(32)) AS bridgescrossed,
    CAST(cte.formattedroute + '-' + #crossings.viabridge + '-' + #crossings.toid AS varchar(32)) as formattedroute,
    #crossings.toid
    FROM #crossings JOIN cte
    ON cte.toid = #crossings.fromid
    WHERE bridgescrossed NOT LIKE '%' + #crossings.viabridge + '%'
)
SELECT
  formattedroute,
  len(bridgescrossed) AS numbridgescrossed
FROM cte
ORDER BY len(bridgescrossed) DESC;</pre></pre>
</div>
<p>The results show every possible route across the city that never cross the same bridge more than once, ordered to show longest routes (i.e. those that cross the most bridges) first. If any route existed that crossed every bridge, we’d expect it to be listed at the top of the results, and for the <em>numbridgescrossed</em> value to be 7.</p>
<p><a href="http://alastaira.files.wordpress.com/2012/01/image5.png"><img style="display:inline;" title="image" alt="image" src="http://alastaira.files.wordpress.com/2012/01/image_thumb5.png?w=291&#038;h=200" width="291" height="200"></a></p>
<p>However, we find that no such route exists. The longest route(s) in the resultset crosses only 6 of the 7 bridges. One such route, starting at island D and ending at island A, is as follows:</p>
<p>D – 7 – C – 6 – B – 4 – D – 3 – A – 2 – B – 1 &#8211; A</p>
<p>And that is empirical proof, using SQL Server, that no solution to this problem exists.</p>
<p>As for understanding <em>why</em> no solution exists? Consider the number of bridges connected to each island. Bear in mind that every time you arrive on an island via a bridge, you must leave that island via a different bridge. It therefore follows that, in order to follow a continuous route that crosses every bridge,&nbsp; every island (with the exception of the start and end island), must have an <u>even</u> number of bridges connected to it – a matched set of entry/exit bridge pairs. In the Konigsberg puzzle, however, Island B has 5 bridges connected to it, and Islands A, C, and D each have 3 bridges connected to them. Since, at most, only two of these islands can be the start/end points of the route, no solution is possible.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1120/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1120/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1120/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1120/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1120/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1120/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1120/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1120/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1120/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1120/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1120/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1120/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1120/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1120/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1120&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/01/23/sql-spatial-puzzle-3-the-seven-bridges-of-knigsberg/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/image_thumb3.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/image_thumb4.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/image_thumb5.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>
	</item>
		<item>
		<title>SQL Spatial Puzzle #2: The Hunter</title>
		<link>http://alastaira.wordpress.com/2012/01/18/sql-spatial-puzzle-2-the-hunter/</link>
		<comments>http://alastaira.wordpress.com/2012/01/18/sql-spatial-puzzle-2-the-hunter/#comments</comments>
		<pubDate>Wed, 18 Jan 2012 09:12:08 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[fun]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1112</guid>
		<description><![CDATA[(Click here for Puzzle #1 &#8211; the disappearing square) There seem to be several variations on this puzzle, but the version I know is as follows: &#8220;A hunter starts at a location. He walks one mile south, one mile east, &#8230; <a href="http://alastaira.wordpress.com/2012/01/18/sql-spatial-puzzle-2-the-hunter/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1112&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>(Click here for <a href="https://alastaira.wordpress.com/2012/01/16/sql-spatial-puzzle-1-the-disappearing-square/">Puzzle #1 &#8211; the disappearing square</a>)
<p>There seem to be several variations on this puzzle, but the version I know is as follows:<br />
<blockquote>
<p>&#8220;A hunter starts at a location. He walks one mile south, one mile east, and then one mile north. He ends up back where he started, and sees a bear. What colour is the bear?&#8221; </p>
</blockquote>
<h2>The Solution</h2>
<p>Before worrying about the colour of the bear, we need to resolve the apparent paradox in the positioning of the hunter. He has moved one mile south, one mile east, and one mile north, and ended up back where he started. Where could he have been in order for this to be true?
<p>The most commonly-known answer is that the hunter started at the <em>North Pole</em>, the bear is therefore a polar bear and is white (don&#8217;t let it worry you that there are no polar bears at the north pole!). Let&#8217;s test whether this answer fits the criteria using SQL Server’s geography datatype.
<p>Firsty, we define the hunter’s start point, @p0, at the North Pole. The latitude of the North Pole is 90 degrees. All lines of longitude converge at the North Pole, so you can use any longitude value. I&#8217;ll use an arbitrary longitude value of 0:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:496e8554-6c40-451d-be16-3e7ad5944a89" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @p0 geography = geography::Point(90, 0, 4326);</pre></pre>
</div>
<p>We now need to declare the first point the hunter travels to, @p1, which is one mile due South of the start. </p>
<ul>
<li>Travelling south reduces latitude, so @p1.Lat &lt; @p0.Lat.
<li>Travelling <em>due south</em> involves maintaining a constant longitude, so @p1.Long = @p0.Long.
<li>The coordinates in this example use SRID 4326, so the results of any linear calculations are stated in metres. 1 mile = 1,609 metres (approx.), so we need @p0.STDistance(@p1) = 1609.
<li>Solving these criteria leads to the following point:</li>
</ul>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:8da5ebf8-dd08-4ab8-9faf-27c156ad1270" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @p1 geography = geography::Point(89.98559, 0, 4326);
</pre></pre>
</div>
<p>The next point that the hunter travels to lies one mile due east of @p1. Therefore, it must lie on the same latitude as @p1, but have a greater longitude (assuming that the antimeridian is not crossed). Using the same logic for the previous point, we can deduce that this point lies at: </p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:0f794e0a-aa7a-417a-8c96-37274d7db090" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @p2 geography = geography::Point(89.98559, 60, 4326);</pre></pre>
</div>
<p>Finally, we need to establish the final point, @pf, which must lie one mile due north of the last point, as follows: </p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:8e7f7f43-1643-43ac-835b-aaa0b3f5d057" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @pf geography = geography::Point(90, 60, 4326);</pre></pre>
</div>
<p>To meet the final criteria of the puzzle, the final point must be the same as the point at which the hunter stated, so @pf = @p0.</p>
<p>We can prove that the points described above meet the criteria for the puzzle as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:0706b75e-6339-4b07-99f1-cc99660b1d5f" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @p0 geography = geography::Point(90, 0, 4326);
DECLARE @p1 geography = geography::Point(89.98559, 0, 4326);
DECLARE @p2 geography = geography::Point(89.98559, 60, 4326);
DECLARE @pf geography = geography::Point(90, 60, 4326);

IF @p1.Lat  @p1.Long
AND CAST(@p2.STDistance(@p1) AS INT) = 1609
SELECT '@p2 lies one mile due east of @p1';

IF @pf.Lat &gt; @p2.Lat
AND @pf.Long = @p2.Long
AND CAST(@pf.STDistance(@p2) AS INT) = 1609
SELECT '@pf lies one mile due north of @p2';

IF @pf.STEquals(@p0) = 1
SELECT '@pf is the same as @p0';</pre></pre>
</div>
<p>This proves that the route p0, p1, p2, pf meets all the criteria described in the riddle and, using the methods of the geography datatype, we have proven that the North Pole is a valid solution to this puzzle. However, it is not the <em>only</em> solution &#8211; there are, in fact, an infinite number of locations on the earth&#8217;s surface that would meet the criteria of the puzzle&#8230; </p>
<p>&#8230;consider the second step of the hunter&#8217;s journey &#8211; walking due east along a parallel of the earth &#8211; a line of constant latitude. As you approach the poles, the circumference of the parallels of the earth become increasingly smaller. Starting at a point just north of the <em>south</em> pole, it is possible to walk east right around the earth along a circle that is one mile in circumference. This occurs at a latitude of about -89.9977, as can be shown by the following:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:3f99fe9f-2df9-476e-8ca0-8f85ce99951e" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @linestring geography = geography::STLineFromText('
LINESTRING(0 -89.9977, 15 -89.9977, 30 -89.9977, 45 -89.9977, 60 -89.9977, 75 -89.9977, 90 -89.9977, 105 -89.9977, 115 -89.9977, 120 -89.9977, 135 -89.9977, 150 -89.9977, 165 -89.9977, 180 -89.9977, 195 -89.9977, 200 -89.9977, 210 -89.9977, 225 -89.9977, 240 -89.9977, 255 -89.9977, 270 -89.9977, 285 -89.9977, 300 -89.9977, 315 -89.9977, 330 -89.9977, 345 -89.9977, 360 -89.9977)
', 4326);

SELECT 
@linestring,
@linestring.STLength();
</pre></pre>
</div>
<p>This linestring is 1,609 metres long &#8211; one mile, but follows a line of constant latitude right around the earth. So, if the hunter starts at <em>any</em> point that is one mile north of this line, they would first walk one mile south onto the line, then one mile east along the line (taking them once around the earth), and finally one mile north again to return to their original starting point. </p>
<p>The following code illustrates an example of just one such point that lies one mile north of the line:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:b57dfd98-3f7e-49b6-a3a8-87caa597373d" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @q0 geography = geography::Point(-89.98329, 0, 4326);
SELECT @q0.STDistance(@linestring);</pre></pre>
</div>
<p>Therefore, an alternative solution to the puzzle could be as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:b7058c4d-412d-4a17-827a-74e58ff9bb4f" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @q0 geography = geography::Point(-89.98329, 0, 4326);
DECLARE @q1 geography = geography::Point(-89.9977, 0, 4326);
DECLARE @q2 geography = geography::Point(-89.9977, 360, 4326);
DECLARE @qf geography = geography::Point(-89.98329, 360, 4326);</pre></pre>
</div>
<p>It’s harder to prove this solution with SQL Server because, since walking east along the line between @q1 and @q2 takes you on an entire revolution of the earth, the result reported by STDistance() for the distance between&nbsp; the points will always be zero (rather than the 1,609 metres it actually took you to walk there).</p>
<p>In fact, not only are there an infinite number of points that lie one mile north of the line at constant latitude –89.9977 that satisfy the puzzle, but there are also an infinite number of lines along which it can be done: Consider the line that goes around the South pole that is 1/2 mile in length, for example, or 1/3 mile. Walking due east for one mile along any of these lines will take you on some number of rotations of the earth but always return you to the starting point. In all these cases, the middle step of &#8220;walking due east for one mile&#8221; effectively becomes nullified, leaving the puzzle as walking due south for one mile and then due north for one mile, which clearly returns you to the start location.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1112/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1112/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1112/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1112/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1112/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1112/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1112/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1112/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1112/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1112/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1112/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1112/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1112/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1112/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1112&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/01/18/sql-spatial-puzzle-2-the-hunter/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>
	</item>
		<item>
		<title>SQL Spatial Puzzle #1: The Disappearing Square</title>
		<link>http://alastaira.wordpress.com/2012/01/16/sql-spatial-puzzle-1-the-disappearing-square/</link>
		<comments>http://alastaira.wordpress.com/2012/01/16/sql-spatial-puzzle-1-the-disappearing-square/#comments</comments>
		<pubDate>Mon, 16 Jan 2012 16:02:58 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[fun]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1110</guid>
		<description><![CDATA[This is the first in what will (hopefully) be a series of posts demonstrating a few light-hearted uses of SQL Server 2008 spatial functions to solve some common mathematical/logical puzzles. To demonstrate the disappearing square puzzle, first of all create &#8230; <a href="http://alastaira.wordpress.com/2012/01/16/sql-spatial-puzzle-1-the-disappearing-square/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1110&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>This is the first in what will (hopefully) be a series of posts demonstrating a few light-hearted uses of SQL Server 2008 spatial functions to solve some common mathematical/logical puzzles. To demonstrate the disappearing square puzzle, first of all create a table and insert four simple geometry shapes into it, as follows: </p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:843f3b85-55dc-459c-9ed0-f19ca2643988" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @table1 TABLE( id CHAR(1), shape geometry );
INSERT INTO @table1 VALUES
('A','POLYGON((0 0, 8 0, 8 3, 0 0))'),
('B','POLYGON((8 0, 13 0, 13 2, 10 2, 10 1, 8 1, 8 0))'),
('C','POLYGON((8 3, 8 1, 10 1, 10 2, 13 2, 13 3, 8 3))'),
('D','POLYGON((8 3, 13 3, 13 5, 8 3))');
</pre></pre>
</div>
<p>If you now select all of the shapes from the table and switch to the spatial results tab in SQL Server Management Studio, you can see that the individual shapes fit together to form a triangle: </p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:ba99ccea-9a7a-4926-85c1-a8ee78996a99" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">SELECT id, shape
FROM @table1;
</pre></pre>
</div>
<p><a href="http://alastaira.files.wordpress.com/2012/01/image.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/01/image_thumb.png?w=754&#038;h=350" width="754" height="350"></a></p>
<p>Now suppose that we insert these same shapes into another table, but move them around slightly: </p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:716c1a2e-9a38-426a-91e4-fbd8ba5ced5a" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @table2 TABLE ( id CHAR(1), shape geometry );

INSERT INTO @table2 VALUES
('A','POLYGON((5 2, 13 2, 13 5, 5 2))'),
('B','POLYGON((8 0, 13 0, 13 2, 10 2, 10 1, 8 1, 8 0))'),
('C','POLYGON((5 2, 5 0, 7 0, 7 1, 10 1, 10 2, 5 2))'),
('D','POLYGON((0 0, 5 0, 5 2, 0 0))');

SELECT id, shape
FROM @table2;</pre></pre>
</div>
<p>Note that all of these shapes are identical to those used in the first example &#8211; they&#8217;ve just been rearranged. However, the triangle formed when the pieces are fitted together now contains a &#8220;hole&#8221; &#8211; 1 unit wide by 1 unit high. </p>
<p>&nbsp;<a href="http://alastaira.files.wordpress.com/2012/01/image1.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/01/image_thumb1.png?w=754&#038;h=351" width="754" height="351"></a> </p>
<p>So, where has the hole appeared from, and what is missing? </p>
<h2>The Solution</h2>
<p>The area of a right-angled triangle can be calculated as half of the base multiplied by the height. Both &#8216;triangles&#8217; appear to be 13 units wide x 5 units high, so we would expect them to have an area of 32.5 units. Let&#8217;s test this:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:cbecbdb2-3801-4dbd-b694-bc37b35c66f2" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">SELECT SUM(shape.STArea()) FROM @table1;
SELECT SUM(shape.STArea()) FROM @table2;
</pre></pre>
</div>
<p>The result of the above query shows that the total area of the individual shapes contained in both triangles is the same &#8211; 32 units, <em>not</em> 32.5 as expected. The area of the bottom triangle, when including the &#8216;missing&#8217; one unit square, is therefore 33 units. </p>
<p>In other words, although the two triangles appear similar, they are not the same, and neither one is the 13&#215;5 right-angled triangle they appear to be. The first triangle is slightly smaller, and the second triangle is slightly larger. </p>
<p>To investigate further, we can find the area of difference between these triangles and the &#8220;true&#8221; 13 x 5 right-angled triangle defined as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:9f3b30e6-52ae-478a-8bb2-9e3ba9182afd" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">DECLARE @truetriangle geometry = geometry::STPolyFromText('POLYGON((0 0, 13 0, 13 5, 0 0))', 0);
</pre></pre>
</div>
<p>First, we need to create a union aggregate of all the component shapes that form each triangle, and then compare this to the true triangle using the STSymDifference() method (note I also use the STConvexHull() method on the second triangle in order to fill in the missing &#8216;hole&#8217;). If you’re using SQL 2012, you can use the UnionAggregate() method here but, if not, you can create a simple union aggregate using STUnion() as shown below:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:a985d534-616f-470b-b037-c9fbb13b1b89" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">-- Create union of all individual shapes in first triangle
DECLARE @triangle1 geometry = 'GEOMETRYCOLLECTION EMPTY';
SELECT @triangle1 = @triangle1.STUnion(shape) FROM @table1; 
 
-- Create union of all individual shapes in second triangle (filling in the gap)
DECLARE @triangle2 geometry = 'GEOMETRYCOLLECTION EMPTY';
SELECT @triangle2 = @triangle2.STUnion(shape).STConvexHull() FROM @table2; 
 
-- Work out the difference between each complete triangle and the true 13 x 5 triangle

SELECT
  @triangle1.STDifference(@truetriangle),
  @triangle1.STDifference(@truetriangle).STArea()
UNION ALL SELECT
  @triangle2.STDifference(@truetriangle),
  @triangle2.STDifference(@truetriangle).STArea();


</pre></pre>
</div>
<p>The results illustrate two thin slivers of area where the two shapes differ from the true triangle, lying along the hypotenuse – each 0.5 units in area. </p>
<p><a href="http://alastaira.files.wordpress.com/2012/01/image2.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2012/01/image_thumb2.png?w=757&#038;h=307" width="757" height="307"></a> </p>
<p>While both shapes formed from the component elements A, B, C, and D <em>appear</em> to be right-angled triangles, they are in fact polygons, since the hypotenuse of each is not a straight line. (You can check this by calling STNumPoints() on both @triangle1 and @triangle2 – you’ll see that each contains four distinct points). The hypotenuse of the @triangle1 is slightly concave, hence why the area is only 32 units and not 32.5 units as expected. The hypotenuse of the @triangle2 is slightly convex, leading to a total area of 33 units. When you add in the missing sliver to @triangle1, or take it away from @triangle2, you’ll end up with the true 13&#215;5 triangle as expected.</p>
<p>The illusion is caused by the fact that the gradients of the component triangles A and D are not the same, therefore swapping them over affects the overall area of the shape. However, they are similar enough that your brain doesn&#8217;t perceive the difference between them, and assumes that you&#8217;re looking at a right-angled triangle in both cases.</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1110/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1110/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1110/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1110/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1110/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1110/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1110/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1110/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1110/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1110/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1110/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1110/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1110/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1110/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1110&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/01/16/sql-spatial-puzzle-1-the-disappearing-square/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/image_thumb.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/image_thumb1.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2012/01/image_thumb2.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>
	</item>
		<item>
		<title>Hello, Stranger&#8230;</title>
		<link>http://alastaira.wordpress.com/2012/01/14/hello-stranger/</link>
		<comments>http://alastaira.wordpress.com/2012/01/14/hello-stranger/#comments</comments>
		<pubDate>Sat, 14 Jan 2012 16:38:26 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/?p=1100</guid>
		<description><![CDATA[Long time no post! The last few months have presented a number of changes for me, both personally and professionally speaking. In order to deal with them I had to enter a sort of “hibernation mode”, shutting down all non-essential &#8230; <a href="http://alastaira.wordpress.com/2012/01/14/hello-stranger/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1100&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>Long time no post!</p>
<p>The last few months have presented a number of changes for me, both personally and professionally speaking. In order to deal with them I had to enter a sort of “hibernation mode”, shutting down all non-essential activities, which includes blogging. (FWIW, I also have not been replying to many emails, and shut down my Twitter account).</p>
<p>Hopefully, things have started to calm down now, and I can get back to a (revised) normality again, which means this blog should start up again.</p>
<p>There’s lots of things to write about in the coming weeks – I’ve fallen in love with <a href="http://mapnik.org">Mapnik</a> again (but have fallen out somewhat with <a href="http://bing.com/maps">Bing Maps</a>). <a href="http://www.microsoft.com/sqlserver/en/us/future-editions.aspx">SQL Server 2012</a> is nearly here, and I’ll be sharing some of the content from my <a href="http://www.apress.com/databases/database-administration/9781430234913">new book</a>. I’ve also got some new stuff about using spatial with SQL Azure, more OGR/GDAL goodness, and some real-life example applications I’ve been working on recently, including a nice spatially-enabled dashboard in SQL Server Reporting Services. As usual, if there’s anything you’d like me to write about, just let me know, and stay tuned for some new content soon!</p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1100/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1100/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1100/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1100/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1100/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1100/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1100/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1100/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1100/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1100/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1100/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1100/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1100/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1100/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1100&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2012/01/14/hello-stranger/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>
	</item>
		<item>
		<title>Exporting Spatial Data From SQL Server to ESRI Shapefile</title>
		<link>http://alastaira.wordpress.com/2011/09/29/exporting-spatial-data-from-sql-server-to-esri-shapefile/</link>
		<comments>http://alastaira.wordpress.com/2011/09/29/exporting-spatial-data-from-sql-server-to-esri-shapefile/#comments</comments>
		<pubDate>Thu, 29 Sep 2011 13:57:00 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Spatial]]></category>
		<category><![CDATA[SQL Server]]></category>
		<category><![CDATA[OGR2OGR]]></category>
		<category><![CDATA[Shapefile]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/2011/09/29/exporting-spatial-data-from-sql-server-to-esri-shapefile/</guid>
		<description><![CDATA[I love the site stats you get from WordPress. This morning I noticed that, in the 9 months that I’ve been writing this blog, nearly 200 people have come to my site after searching for the term “OGR2OGR” in a &#8230; <a href="http://alastaira.wordpress.com/2011/09/29/exporting-spatial-data-from-sql-server-to-esri-shapefile/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1095&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>I love the site stats you get from WordPress. This morning I noticed that, in the 9 months that I’ve been writing this blog, nearly 200 people have come to my site after searching for the term “OGR2OGR” in a search engine. That’s more than any other single word search term. Around another 200 people have come here after searching for variations and combinations on this theme, including “OGR2OGR Sql Server”, “OGR2OGR MSSqlSpatial”, “OGR SQL”, “OGR Import shapefile” etc.</p>
<p>What I’m not sure, though, is how to interpret this statistic or best act upon it. Does it mean, for example, that my articles about <a href="http://www.gdal.org/ogr/">OGR2OGR</a> – the open source toolkit for spatial data conversion and manipulation &#8211; are helpful, or high quality? Sure, several of them get listed on the first page of results for a Google search, but does that simply mean that nobody else on the internet is writing about OGR2OGR? And do the people that come here searching for information about OGR2OGR actually find what they’re looking for, or do they end up leaving empty-handed?</p>
<p>I don’t know the answers to these questions, but since there are obviously some people out there looking for this kind of information (and since I know, from experience, that OGR2OGR can be an absolute bugger to get working correctly) here’s another post about OGR2OGR anyway… and this time it’s about exporting spatial data from SQL Server. There’s plenty of information on the ‘net describing how to import spatial data from ESRI shapefiles into SQL Server (including my own posts, such as <a href="https://alastaira.wordpress.com/2011/06/18/importing-spatial-data-to-sql-server-with-ogr2ogrnow-even-easier/">here</a>, <a href="https://alastaira.wordpress.com/2011/07/01/loading-os-vectormap-data-to-sql-server-with-powershell-and-ogr2ogr/">here</a> and <a href="http://alastaira.wordpress.com/2011/01/14/loading-sql-server-spatial-data-from-the-command-line/">here</a>, for example). However, what I haven’t seen any examples of yet is how to do the reverse: taking geometry or geography data <em>from</em> SQL Server and dumping it <em>into</em> an ESRI shapefile.</p>
<p>There’s plenty of reasons why you might want to do this – despite its age and relative limitations (such as the maximum filesize per file, limited fieldname length, the fact that each file can contain only a single homogenous type of geometry, etc…), the ESRI shapefile format is still largely the industry standard and pretty much universally read by all spatial applications. Recently, I needed to export some data from SQL Server to shapefile format so that it could be rendered as a map in <a href="http://mapnik.org/">mapnik</a>, for example (which, sadly, still can’t directly connect to a MS SQL Server database).</p>
<p>So, here’s a step-by-step guide, including the pitfalls to avoid along the way.</p>
<h2>Setup</h2>
<p>To start with, make sure you’ve got a copy of OGR2OGR version 1.8 or greater (earlier versions do not have the MSSQL driver installed). You can either build it from source supplied on the <a href="http://www.gdal.org/ogr/">GDAL</a> page or, for convenience, download and install pre-compiled windows binaries supplied as part of the <a href="http://trac.osgeo.org/osgeo4w/">OSgeo4W</a> package.</p>
<p>Now, let’s set up some test data in a SQL Server table that we want to export. To test the full range of OGR2OGR features (or should that say, “the full range of error messages you can create”?), I’m going to create a table that contains two different geometry columns – an original geometry and a buffered geometry, populated using a range of different geometry types:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:62042a02-5e4f-4434-a521-d3b677af016d" class="wlWriterEditableSmartContent">
<pre><pre class="brush: sql; gutter: false; pad-line-numbers: false; wrap-lines: false;">CREATE TABLE OGRExportTestTable (
  shapeid int identity(1,1),
  shapename varchar(32),
  shapegeom geometry,
  bufferedshape AS shapegeom.STBuffer(1),
  bufferedshapearea AS shapegeom.STBuffer(1).STArea()
);

INSERT INTO OGRExportTestTable (shapename, shapegeom) VALUES
('Point #1', geometry::STGeomFromText('POINT(13 10)', 2199)),
('Point #2', geometry::STGeomFromText('POINT(7 12)', 2199)),
('Line #1', geometry::STGeomFromText('LINESTRING(0 0, 8 4)', 2199)),
('Polygon #1', geometry::STGeomFromText('POLYGON((2 2, 4 2, 4 4, 2 4, 2 2))', 2199)),
('Line #2', geometry::STGeomFromText('LINESTRING(0 10, 10 10)', 2199));</pre></pre>
</div>
<p>Here’s what the contents of this table looks like in the SSMS Spatial Results tab:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image17.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb16.png?w=644&#038;h=409" width="644" height="409"></a></p>
<h2>Exporting from SQL Server to Shapefile with OGR2OGR (via a string of errors along the way)</h2>
<p>The basic pattern for OGR2OGR usage is given at <a title="http://www.gdal.org/ogr2ogr.html" href="http://www.gdal.org/ogr2ogr.html">http://www.gdal.org/ogr2ogr.html</a>, with additional usage options for the SQL Server driver at <a title="http://www.gdal.org/ogr/drv_mssqlspatial.html" href="http://www.gdal.org/ogr/drv_mssqlspatial.html">http://www.gdal.org/ogr/drv_mssqlspatial.html</a>. So, let’s start by just trying out a basic example to export the entire OGRExportTestTable from my local SQLExpress instance to a shapefile at c:\temp\sqlexport.shp, as follows: (change the connection string to match your server/credentials as appropriate)</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:682e37c2-69b7-48a0-bd61-b285205698d7" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;tables=OGRExportTestTable;trusted_connection=yes;&quot;
</pre></pre>
</div>
<p>This will fail with a couple of errors, but the first one to address is as follows:</p>
<p><strong>ERROR 1: Attempt to write non-point (LINESTRING) geometry to point shapefile.</strong></p>
<p>SQL Server will allow you to mix different types of geometry (Points, LineStrings, Polygons) within a single column of geometry or geography data. An ESRI shapefile, in contrast, can only contain a single homogenous type of geometry. To correct this, rather than trying to dump the entire table by specifying the <em>tables=OGRExportTable</em> in the connection string, we’ll have to manually select rows of only a certain type of geometry at a time by specifying an explicit SQL statement. Let’s start off by concentrating on the points only. This can be done with the <em>–sql</em> option, as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:48f8ffca-e6dd-47ca-b712-047f242472a0" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT * FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot;
</pre></pre>
</div>
<p>This time, a new error occurs:</p>
<p><strong>ERROR 1: C:\temp\sqlexport.shp is not a directory.</strong></p>
<p><strong>ESRI Shapefile driver failed to create C:\temp\sqlexport.shp</strong></p>
<p>Although our first attempt to export the entire table failed, it still created an output file at C:\temp\sqlexport.shp. Seeing as we didn’t specify the behaviour for what to do when the output file already exists, when we run OGR2OGR for the second time it has now errored. To correct this, we’ll add the <em>–overwrite</em> flag.</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:54800157-da6c-4a21-b56b-e26f8a7bd333" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT * FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot;
-overwrite
</pre></pre>
</div>
<p>Running again and the error is now:</p>
<p><strong>ERROR 6: Can&#8217;t create fields of type Binary on shapefile layers.</strong></p>
<p>Ok, so remember that our original shapefile had <u>two</u> geometry columns. When you create a shapefile, one column is used to populate the shape information itself, while every other column becomes an attribute of that shape, stored in the associated .dbf file. Since the remaining geometry column is a binary value, this can’t be stored in the .dbf file. To resolve this, rather than using a SELECT *, we’ll explicitly specify each column to be included in the shapefile. You could, if you want, then omit the second geometry column completely from the list of selected fields. Instead, I’ll use the ToString() method to convert it to WKT, which <em>can</em> then be stored as an attribute value:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:13cf7cd8-3540-48db-94ef-03a42d2a8d07" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot;
-overwrite
</pre></pre>
</div>
<p>This should now run without errors, although you’ll still get a warning:</p>
<p><strong>Warning 6: Normalized/laundered field name: ‘bufferedshapearea’ to bufferedsh</strong></p>
<p>The names of any attribute fields associated with a shapefile can only be up to a maximum of 10 characters in length. In this case, OGR2OGR has manually truncated the name of the buferedshapearea column for us, but you might not like to use the garbled “bufferedsh” as an attribute name in your shapefile. A better approach would be to specify an alias for long column names in the <em>–sql</em> statement itself. In this case, perhaps just “area” will suffice:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:94ff4ec8-f562-4618-a9de-6fb66a55a4e4" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot;
-overwrite</pre></pre>
</div>
<p>And now, for the first time, you should be both error and warning free:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image18.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb17.png?w=672&#038;h=335" width="672" height="335"></a></p>
<p>So, now, just repeat the above but substituting the POINT, LINESTRING, and POLYGON geometry types, and creating three separate corresponding shapefiles:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:0c06ae4c-348c-46b5-a50b-a905436714f4" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_point.shp&quot; &quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot; -sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot; -overwrite

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_linestring.shp&quot; &quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot; -sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'LINESTRING'&quot; -overwrite

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_polygon.shp&quot; &quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot; -sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POLYGON'&quot; -overwrite
</pre></pre>
</div>
<p>All done, right? Let’s just have a quick check on the created files to make sure they look ok. You can do this using ogrinfo with the –al option, which will give you a summary of the elements contained in any spatial data set. I mean, I’m sure they’re fine and everything, but…. hang on a minute:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:73a1d469-a345-4171-947f-8c5d7614894d" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogrinfo -al c:\temp\sqlexport_point.shp
</pre></pre>
</div>
<p>Here’s the POINT shapefile:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image19.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb18.png?w=672&#038;h=407" width="672" height="407"></a></p>
<p>Initially looks ok, the shapefile contains 2 Point features – they’ve got the correct attribute fields, but both points seem to have been incorrectly placed at coordinates of POINT(0.0 2.0). Huh?</p>
<p>What about the LINESTRING shapefile:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image20.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb19.png?w=672&#038;h=407" width="672" height="407"></a></p>
<p>This is even worse – even though we specified that only LineString geometries should be returned from the SQL query, the created shapefile thinks it contains 2 Point features (<em>Geometry: Point</em>, near the top). And those points both lie at POINT (0.0 0.0)…</p>
<p>And the POLYGON shapefile:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image21.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb20.png?w=672&#038;h=407" width="672" height="407"></a></p>
<p>Same problem as the LineString – it’s effectively an empty Point shapefile.</p>
<p>The problem seems to be that, even though we’re only returning geometries of a certain type from the SQL query, we haven’t explicitly stated that to the shapefile creator, so OGR2OGR is creating three empty shapefiles first (each set up to receive the default geometry type of POINT), and then trying to populate them with unmatching shape types, leading to corrupt data. To explicitly state the geometry type of the shapefiles created, we need to supply the SHPT layer creation option for each shapefile as specified at <a title="http://www.gdal.org/ogr/drv_shapefile.html" href="http://www.gdal.org/ogr/drv_shapefile.html">http://www.gdal.org/ogr/drv_shapefile.html</a>, by adding –lco “SHPT=POLYGON”, –lco “SHPT=ARC” (for LineStrings) etc. as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:c907e5c6-b9bd-4871-bb4d-e398d0affcad" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_point.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot;
-overwrite
-lco &quot;SHPT=POINT&quot;

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_linestring.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'LINESTRING'&quot;
-overwrite
-lco &quot;SHPT=ARC&quot;

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_polygon.shp&quot; &quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom, bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POLYGON'&quot;
-overwrite
-lco &quot;SHPT=POLYGON&quot;</pre></pre>
</div>
<p>Unfortunately, that brings us round to almost exactly the same error as we first started with when creating the LineString and Polygon shapefiles (Gah! I thought we’d got rid of them!):</p>
<p><strong>ERROR 1: Attempt to write non-linestring (POINT) geometry to ARC type shapefile</strong></p>
<p><strong>ERROR 1: Attempt to write non-polygon (POINT) geometry to POLYGON type shapefile</strong></p>
<p>We <em>are</em> only selecting geometries of the matching type for each shapefile by filtering the query based on STGeometryType(), so why does OGR2OGR think that we are selecting other types of geometries? What’s more, we haven’t yet explained why the point shapefile (which was, after all, being populated only with point geometries), incorrectly placed both points at coordinates POINT(0.0 2.0). It seems that something is corrupting the results of the SQL statement.</p>
<p>And here’s the “Ta-dah!” moment. According to <a title="http://www.gdal.org/ogr/drv_mssqlspatial.html" href="http://www.gdal.org/ogr/drv_mssqlspatial.html">http://www.gdal.org/ogr/drv_mssqlspatial.html</a>, when retrieving spatial data from SQL Server, <em>“The default [GeometryFormat] value is &#8216;native&#8217;, in this case the native SqlGeometry and SqlGeography serialization format is used”. </em>However, this doesn’t actually appear to hold true. SQL Server stores geometry and geography data in a format very similar to, but slightly different from Well-Known Binary (WKB). The SQL Server binary values for the two points in the OGRExportTestTable are:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:467c4838-db49-4cd3-9104-6a3729f9456d" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">0x00000000010C0000000000002A400000000000002440
0x00000000010C0000000000001C400000000000002840</pre></pre>
</div>
<p>The Well-Known Binary of these two points is , instead, as follows:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:12f61f65-a8c1-4daf-94fd-ed74efd3b009" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">0x01010000000000000000002A400000000000002440
0x01010000000000000000001C400000000000002840</pre></pre>
</div>
<p>As you can see, they’re very similar – the coordinate values are serialised as 8-byte floating point binary values in both cases, but the MSSQL Server native serialisation has a different and slightly longer (i.e. one byte more) header. Thus, if OGR2OGR is expecting to receive one type of data, but actually gets the other, all the bytes will be displaced slightly. This could explain both why OGR believed it was receiving the incorrect geometry types and also why the point coordinates were wrong.</p>
<p>To correct this, rather than retrieving the shapegeom value directly, use the STAsBinary() method in the SQL statement to retrieve the WKB of the shapegeom column instead.</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:a669ca79-44f1-4931-a8be-4de9eeeda3d1" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_point.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom.STAsBinary(), bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot;
-overwrite
-lco &quot;SHPT=POINT&quot;

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_linestring.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom.STAsBinary(), bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'LINESTRING'&quot;
-overwrite
-lco &quot;SHPT=ARC&quot;

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_polygon.shp&quot; &quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom.STAsBinary(), bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POLYGON'&quot;
-overwrite
-lco &quot;SHPT=POLYGON&quot;</pre></pre>
</div>
<p>Right, we are definitely making progress now. Trying ogrinfo again reveals that all of the layers have the correct number of features, or the appropriate type, and all have the right coordinate values. Yay!</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image22.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb21.png?w=672&#038;h=407" width="672" height="407"></a></p>
<p>There’s just one nagging thing and that’s the <strong>Layer SRS WKT: (unknown)</strong>. When we converted from the SQL Server native serialisation to Well-Known Binary, we lost the metadata of the spatial reference identifier (SRID) associated with each geometry. This information contains the details of the datum, coordinate reference system, prime meridian etc. that make the coordinates of each geometry relate to an actual place on the earth’s surface. In the shapefile format, this information is contained in a .PRJ file that usually accompanies each .SHP file but, since we haven’t supplied this information to OGR2OGR, none of the created shapefiles currently have associated .PRJ files, so ogrinfo reports the spatial reference system as “unknown”.</p>
<p>To create a PRJ file, we need to append the <em>–a_srs</em> option, supplying the same EPSG id as was supplied when creating the original geometry instances in the table. (Of course, if you have instances of more than one SRID in the same SQL Server table, you’ll have to split these out into separate shapefiles, just as you have to split different geometry types into separate shapefiles)</p>
<h2>The Final Product</h2>
<p>So here’s the final script that will separate out points, linestrings, and polygons from a SQL Server table into separate shapefiles, via the WKB format but maintaining the SRID of the original instance, and retaining all other columns as attribute values in the .dbf file. Tested and confirmed to work with columns of either the geometry or geography datatype in SQL Server 2008/R2, or SQL Server Denali CTP3.</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:dd497efe-4050-4aec-a1e6-87f34615b548" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_point.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom.STAsBinary(), bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POINT'&quot;
-overwrite
-lco &quot;SHPT=POINT&quot;
-a_srs &quot;EPSG:2199&quot;

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_linestring.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom.STAsBinary(), bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'LINESTRING'&quot;
-overwrite
-lco &quot;SHPT=ARC&quot;
-a_srs &quot;EPSG:2199&quot;

ogr2ogr -f &quot;ESRI Shapefile&quot; &quot;C:\temp\sqlexport_polygon.shp&quot;
&quot;MSSQL:server=localhost\sqlexpress;database=tempdb;trusted_connection=yes;&quot;
-sql &quot;SELECT shapeid, shapename, shapegeom.STAsBinary(), bufferedshape.ToString(), bufferedshapearea AS area  FROM OGRExportTestTable WHERE shapegeom.STGeometryType() = 'POLYGON'&quot;
-overwrite
-lco &quot;SHPT=POLYGON&quot;
-a_srs &quot;EPSG:2199&quot;
</pre></pre>
</div>
<p>And here’s the resulting shapefile layers loaded into qGIS:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image23.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb22.png?w=644&#038;h=474" width="644" height="474"></a></p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1095/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1095/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1095/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1095/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1095/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1095/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1095/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1095/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1095/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1095/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1095/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1095/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1095/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1095/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1095&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2011/09/29/exporting-spatial-data-from-sql-server-to-esri-shapefile/feed/</wfw:commentRss>
		<slash:comments>12</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb16.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb17.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb18.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb19.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb20.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb21.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb22.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>
	</item>
		<item>
		<title>Creating a Windows 8 Metro Slippy Map Application</title>
		<link>http://alastaira.wordpress.com/2011/09/26/creating-a-windows-8-metro-slippy-map-application/</link>
		<comments>http://alastaira.wordpress.com/2011/09/26/creating-a-windows-8-metro-slippy-map-application/#comments</comments>
		<pubDate>Mon, 26 Sep 2011 12:33:39 +0000</pubDate>
		<dc:creator>alastaira</dc:creator>
				<category><![CDATA[Bing Maps]]></category>
		<category><![CDATA[Spatial]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Leaflet]]></category>
		<category><![CDATA[Metro]]></category>
		<category><![CDATA[Windows 8]]></category>

		<guid isPermaLink="false">https://alastaira.wordpress.com/2011/09/26/creating-a-windows-8-metro-slippy-map-application/</guid>
		<description><![CDATA[So (along with half the world, it seems), I grabbed myself a copy of the Windows 8 Developer Preview earlier in the week and have been trying to get to grips with it, both as a user and also with &#8230; <a href="http://alastaira.wordpress.com/2011/09/26/creating-a-windows-8-metro-slippy-map-application/">Continue reading <span class="meta-nav">&#8594;</span></a><img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1079&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></description>
			<content:encoded><![CDATA[<p>So (along with half the world, it seems), I grabbed myself a copy of the <a href="http://msdn.microsoft.com/en-us/windows/apps/br229516">Windows 8 Developer Preview</a> earlier in the week and have been trying to get to grips with it, both as a user and also with the interest of developing some applications.</p>
<p>I’m not going to go into detail about the (fairly radical) changes made in Windows 8, partly because many other people have already written about these, and partly because I’m very unqualified to do so – I’m finding things out myself as I go along. Instead, seeing as Windows 8 is all about the user interface and aimed at supporting touch-enabled devices, the main thing I was interested in was seeing what’s involved in developing a simple “Metro” application with a slippy map interface. This post is a summary of my initial investigations.</p>
<h2>What’s Metro, anyway?</h2>
<p>Metro is the (current) name of the new, touch-centric style that is exposed as the default interface in Windows 8. It’s certainly a radical departure from traditional Windows UI, and more resembles the interface you’d get on a mobile phone handset or other portable device. (This is not coincidence – the big growth market in the PC world at the moment lies not in desktops or laptops, but with tablet PCs, and it’s clear that Microsoft is designing Windows 8 to be run on devices that will “compete” against Apple’s iPad and Android tablets). Apart from the touch interface, there are other similarities as well &#8211; Metro applications are typically small in size, relatively simple, have a full-screen UI, are responsive (due to asynchronous method calls), and they can be packaged up and distributed via the “Windows Application Store” – a model much like Chrome’s web store, Android’s marketplace or Apple’s App Store.</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image12.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb11.png?w=643&#038;h=484" width="643" height="484"></a></p>
<p>So, I thought it would be nice to create a simple little Metro app that displayed a slippy map interface, possibly showed points of interest near to you (using the built-in geolocation), allowed you to plot routes etc. – fairly regular stuff. Since this is Microsoft Windows, after all, you’d think that this would be relatively easy to do with the Bing Maps API, right? Well….</p>
<h2>Developing for Metro – Choose your Weapon</h2>
<p>First things first – the core system component that powers Metro Apps is something called WinRT. WinRT contains APIs for handling devices, graphics, communications etc. that are exposed via COM interfaces. You can write Metro applications that reference these WinRT assemblies using a variety of languages – managed code (C#/VB) or unmanaged code (C/C++) with a XAML front-end, or using Javascript with an HTML/CSS front-end. The Windows 8 technology stack is summarised in this slide shown at the recent BUILD conference:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image13.png"><img style="background-image:none;padding-left:0;padding-right:0;display:inline;padding-top:0;border-width:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb12.png?w=644&#038;h=339" width="644" height="339"></a></p>
<p>Bing Maps comes in lots of different flavours too, including (amongst others) a <a href="http://msdn.microsoft.com/en-us/library/ee681884.aspx">Silverlight control</a>, a <a href="http://www.microsoft.com/download/en/details.aspx?id=27165">WPF control</a>, and a <a href="http://msdn.microsoft.com/en-us/library/gg427610.aspx">Javascript control</a>. Since WPF and Silverlight both make use of XAML for UI and managed code-behind, you might think that it would be relatively easy to port an application that uses the Bing Maps Silverlight/WPF control to Metro XAML and c# code-behind, but actually I’ve not heard of anybody successfully do this yet. Also, considering that the WPF Bing Maps control is still itself only in beta, and the Silverlight control has apparently been in stasis for several years, I certainly wasn’t about to commit any effort to making these work on Windows 8 when I’m not convinced about their future longevity…. </p>
<p>Instead, I thought I’d opt for the HTML/Javascript approach – which are technologies I’m more familiar with anyway, and have a more mature associated Bing Maps AJAX control. However, even then, I’ve been surprised at the number of changes required to port a regular HTML/Javascript Bing Maps web application and get it to work in a Metro app.</p>
<h2>HTML/Javascript in Metro – it’s all about Context</h2>
<p>Most people think of HTML/Javascript as being exclusively used on the web &#8211; the two technologies being responsible for structure/client-side code of webpages respectively, and executed only within the context of a web browser. This, in itself, has historically made the Javascript execution environment somewhat sandboxed. Even though projects such as <a href="http://nodejs.org/">nodejs</a> have shown that Javascript can also be used in other contexts, it has not been widely used for creating desktop applications before. (In a Windows context, Javascript is used for creating sidebar gadgets in Windows Vista/7,&nbsp; but these have generally been little more than mini-webpages pinned to the desktop, and have little access to system resources)</p>
<p>In Metro, however, a Javascript application can be used to access core parts of the operating system (including the file system, networking, and other devices) by calling into the WinRT APIs. This sounds kind of dangerous, doesn’t it? Javascript, with its &lt;script&gt; injection, dynamic DOM manipulation et al. having access to your system resources? To minimise the risk of malicious code in this scenario, Windows 8 introduces different <em>security contexts</em> for Metro apps.</p>
<p>The main HTML landing page for a Metro app runs in the <strong>local context</strong>. It has access to the WinRT APIs, and can also do some other things not possible using Javascript running in a regular webbrowser, such as making cross-domain XmlHttpRequests. However, there are two main restrictions placed on pages running within the local context:</p>
<ul>
<li>You can only load locally-packaged scripts. i.e. you can’t include a &lt;script&gt; tag whose <em>src</em> attribute points to a file on a remote server.
<li>Any methods that attempt to change the DOM (i.e. by setting an element’s innerHTML or calling the document.write method) are sanitised.</li>
</ul>
<p>Unfortunately, that means <u>using the Bing Maps AJAX API is not possible from within a Metro app running within the local context</u>, because the Bing Maps control needs to be referenced from a remote URL (in other words, you need to include the library using <em>&lt;script type=&#8221;text/javascript&#8221; src=&#8221;http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&gt;&lt;/script&gt;</em>), which is not allowed within the local context. Even if you created an offline copy of the Bing Maps AJAX library (which is not supported), it would still not work because of the way in which it manipulates the DOM of the HTML page.</p>
<p>So, what options do you have?</p>
<h2>Option 1: Embed the Map Control in a iFrame using the Web Context</h2>
<p>Although the main HTML page of a Metro app must run in the local context, you can have additional HTML pages that are loaded within the <strong>web context</strong>. When running in the web context, your HTML/JS code behaves almost exactly as it would do if loaded within a regular Internet Explorer browser – in other words, you lose the ability to tap into the WinRT APIs and other new Metro features, but you can include remote script references again as normal. For a full comparison of what’s possible in the local context and in the web context, refer to <a title="http://msdn.microsoft.com/en-us/library/windows/apps/hh465373%28v=VS.85%29.aspx" href="http://msdn.microsoft.com/en-us/library/windows/apps/hh465373%28v=VS.85%29.aspx">http://msdn.microsoft.com/en-us/library/windows/apps/hh465373%28v=VS.85%29.aspx</a></p>
<p>However, I wanted to embed a slippy map as the <em>main</em> frontend UI on my Metro App – I don’t want the user to have to navigate to a separate HTML page in the web context just to load the map. So, is there a way to get the best of both worlds – having elements loaded from within the web context displayed within the main page in the local context? One approach is to create a regular Bing Maps application in a separate HTML page in the web context, and then embed that in our main application in a separate iframe.</p>
<p>To do so, create an iframe in the main page in which the <em>src</em> element points to your Bing Map page, prefixed by&nbsp; <em>ms-wwa-web:///</em> to show that the content of the iframe is to be loaded in the web context. So, here’s a simple default.html page for a Metro App:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:1f3212b2-62b2-4399-b311-065c08ae159c" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;meta charset=&quot;utf-8&quot; /&gt;
    &lt;meta name=&quot;viewport&quot; content=&quot;width=1024, height=768&quot; /&gt;
    &lt;title&gt;WinWebApp1&lt;/title&gt;
    &lt;!-- WinJS references --&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;/winjs/css/ui-dark.css&quot; /&gt;
    &lt;script src=&quot;/winjs/js/base.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;/winjs/js/wwaapp.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;/winjs/js/ui.js&quot;&gt;&lt;/script&gt;
    &lt;script src=&quot;/winjs/js/controls.js&quot;&gt;&lt;/script&gt;
    &lt;!-- WinWebApp1 references --&gt;
    &lt;link rel=&quot;stylesheet&quot; href=&quot;/css/default.css&quot; /&gt;
    &lt;script src=&quot;/js/default.js&quot;&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;header role=&quot;banner&quot; aria-label=&quot;Header content&quot;&gt;
        &lt;div class=&quot;titleArea&quot;&gt;
            &lt;h1 class=&quot;pageTitle win-title&quot; role=&quot;button&quot; aria-label=&quot;Groups&quot; tabindex=&quot;0&quot;&gt;
                Metro Map App&lt;/h1&gt;
        &lt;/div&gt;
    &lt;/header&gt;
    &lt;div&gt;
      &lt;iframe id=&quot;mapIframe&quot; src=&quot;ms-wwa-web:///map.html&quot; width=&quot;1024px&quot; height=&quot;400px&quot;&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre></pre>
</div>
<p>and here’s the map.html page that is loaded within the web context of the iframe: </p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:60b9da7c-54e0-4815-8d37-6630532334c9" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
    &lt;title&gt;Map&lt;/title&gt;
    &lt;!-- We can include remote script because this page will be loaded in the web context (ms-wwa-web:///map.html)  --&gt;
    &lt;script src=&quot;http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=7.0&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
    &lt;script type=&quot;text/javascript&quot;&gt;
        var map = null;

        function initialize() {
            var mapOptions = {
                credentials: &quot;AkeAFl99ZABCDEFG7Kb2D12345678lRQm8vnZMpfMV7HsfNqwertyuiopd&quot;,
                center: new Microsoft.Maps.Location(52, 0),
                mapTypeId: Microsoft.Maps.MapTypeId.road,
                zoom: 7,
                showLogo: false,
                showDashboard: false
            };
            map = new Microsoft.Maps.Map(document.getElementById(&quot;map&quot;), mapOptions);
        }
        document.addEventListener(&quot;DOMContentLoaded&quot;, initialize, false);
    &lt;/script&gt;
    &lt;style type=&quot;text/css&quot;&gt;
        body, html
        {
            margin:0;
        }
        
        #map
        {
          position:absolute;
          width:100%;
          height:100%;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div id=&quot;map&quot;&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre></pre>
</div>
<p>Hit F5 to debug the app and you’ll get something like this – a regular slippy map interface using Bing Maps, contained in an iframe that seamlessly integrates into the container of your Metro App.</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image14.png"><img style="background-image:none;border-bottom:0;border-left:0;padding-left:0;padding-right:0;display:inline;border-top:0;border-right:0;padding-top:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb13.png?w=644&#038;h=484" width="644" height="484"></a></p>
<p>So far so good. But the problem with this approach is that, although <em>visually</em> it may appear integrated, the content of the iframe is essentially isolated from the main application (that, after all, is the whole reason why the iframe is allowed to be loaded in the web context – if it could directly access the WinRT APIs available to the parent application you’d be exposing the security risks that the whole local context scenario is meant to avoid). So how do we go about creating interface elements in the parent container page that update (or are updated by) elements in the map iframe?</p>
<p>You <em>can</em> communicate between the host application running in the local context and the map iframe running in the web context by using the HTML5 postMessage method. If you <a href="http://www.google.co.uk/search?q=postmessage+example+html5">search the internet</a>, you’ll find plenty of examples of postMessage, but they’re very simplistic – most of the examples involve sending a single text string from one window to another and, when it’s received, simply alerting that message to the user. But when you actually want to try to expose entire interfaces between iframe windows, you’ll quickly create a mess of code – the postMessage method is, after all, designed for…. posting messages&#8230; so any data passing between iframes must be passed as text. You then need to setup a messageHandler on the receiving window that will act appropriately based on the text data received. </p>
<p>For example, I set up a simple row of button controls in my container HTML page to pan the map and zoom in/out.</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:35b5b26e-c736-4847-8a2a-330b9596ab06" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">&lt;button onclick=&quot;Pan(-1,0);&quot;&gt;Pan Left&lt;/button&gt;
&lt;button onclick=&quot;Pan(1,0);&quot;&gt;Pan Right&lt;/button&gt;
&lt;button onclick=&quot;Pan(0,1);&quot;&gt;Pan Up&lt;/button&gt;
&lt;button onclick=&quot;Pan(0,-1);&quot;&gt;Pan Down&lt;/button&gt;
&lt;button onclick=&quot;ZoomOut();&quot;&gt;Zoom Out&lt;/button&gt;
&lt;button onclick=&quot;ZoomIn();&quot;&gt;Zoom In&lt;/button&gt;</pre></pre>
</div>
<p>The methods attached to these buttons do not pan or zoom the map directly. Rather, they create an instruction that will be sent via postMessage to the map iframe. Fortunately, the postMessage API implemented in Internet Explorer 10 (which is the engine in which Metro Apps running in the web context are executed) accepts postMessages either as single strings or as JSON objects. So,&nbsp; the “instruction” passed to the map is contained in a JSON object in which <em>method</em> is the action to perform on the map, and <em>args</em> contains any arguments to pass to that method. Notice that I specify that the target domain of the postMessage call lies in the ms-ww-web web context:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:cc133a05-e527-4625-ab0c-88ce11280b97" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">function Pan(dx, dy) {
    var xMsg = { method: 'pan', args: {x: dx, y: dy} };
    mapIframe.postMessage(xMsg, &quot;ms-wwa-web://&quot; + document.location.host);
}

function ZoomIn() {
    var xMsg = { method: 'zoomin' };
    mapIframe.postMessage(xMsg, &quot;ms-wwa-web://&quot; + document.location.host);
}

function ZoomOut(dx, dy) {
    var xMsg = { method: 'zoomout' };
    mapIframe.postMessage(xMsg, &quot;ms-wwa-web://&quot; + document.location.host);
}</pre></pre>
</div>
<p>Then, in the map iframe, I need to set up a listener to any postMessages received:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:a793024c-ec09-4fd5-8baf-a0187bcb3e33" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">window.addEventListener(&quot;message&quot;, receiveMessage, false);
</pre></pre>
</div>
<p>and, in the receiveMessage() callback, unravel and handle the requested JSON instruction (which is contained in the event.data parameter):</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:aefbcac1-8b6b-4fa4-b1cc-7e3f4bbdcdec" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">function receiveMessage(event) {
  switch (event.data.method) {
    case 'pan':
      var dx = event.data.args.x;
      var dy = event.data.args.y;
      var pos = map.tryPixelToLocation(new Microsoft.Maps.Point(map.getWidth()/2 + dx, map.getHeight()/2 + dy, Microsoft.Maps.PixelReference.viewport));
      map.setView({ center: pos });
      break;

    case 'zoomin':
      var currentZoom = map.getZoom();
      map.setView({ zoom: currentZoom + 1 });
      break;

    case 'zoomout':
      var currentZoom = map.getZoom();
      map.setView({ zoom: currentZoom - 1 });
      break;
  }
}
</pre></pre>
</div>
<p> Here’s a diagram of what’s going on:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image15.png"><img style="background-image:none;border-bottom:0;border-left:0;padding-left:0;padding-right:0;display:inline;border-top:0;border-right:0;padding-top:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb14.png?w=644&#038;h=484" width="644" height="484"></a></p>
<p>Bear in mind that all I’m doing is adding the simplest of interactivity here – just panning and zooming the map. The more functionality you want to add, the more complicated the data structures you have to pass in the postMessage call (consider things like the set of points returned in a route requested from a routing service).</p>
<p>Also, notice that at the moment I’m only doing one-way communication <em>from</em> the parent container <em>to</em> the map iframe. But there are plenty of situations in which you’ll also want to pass messages the other way – to notify the container app when a certain action has happened on the map for example. Thus you’ll also need to setup an event listener on the container page to listen to incoming messages from the map iframe. Using the nested iframe approach and postMessage calls to create two-way interfaces like this quickly becomes unmanageable… </p>
<h2>Option 2: Use an AJAX Map Control that works within the Local Context</h2>
<p>In order to be executed directly from an HTML page in the local context, a Javascript library must be loaded locally. So how about we ditch the Bing Maps control altogether and use an alternative control that <em>can</em> be run from a local script. How about <a href="http://leaflet.cloudmade.com/index.html">Leaflet</a>, say? </p>
<p>If you don’t know, Leaflet is a new open source AJAX map control from CloudMade – a company with very close associations to the <a href="http://www.openstreetmap.org/">Open Street Map</a> project. In the past, developers looking for an open source slippy map control have tended to opt for <a href="http://openlayers.org/">OpenLayers</a> – but Leaflet looks set to provide a more modern, lightweight alternative. In fact, even though it’s only a few months old, Leaflet already offers many features not found in commercial map controls from the likes of Bing and Google (including support for WMS services, &lt;canvas&gt; tiles, and extensible map objects). But, for the purposes of developing a Metro application, Leaflet’s best feature is that it’s a very small download, consisting of just a single .js file, one .css file, and a handful of images that you can include in your own project.</p>
<p>Fortunately, the Leaflet API also follows very similar syntax to Bing Maps / Google Maps and will seem familiar to anyone who’s used to the idea of pushpins, infoboxes, polylines and polygons. Within a few minutes, I was therefore able to rewrite my Bing Maps app to use Leaflet instead. And, because I was loading the Leaflet library from a local &lt;script&gt; resource I didn’t have to worry about embedding an iframe or any of that postMessage nonsense – I could incorporate the map directly in my main HTML page in the local context. Creating a basic Leaflet map involves the following:</p>
<div style="display:inline;float:none;margin:0;padding:0;" id="scid:b0ad6aa8-17a2-487e-be61-3d3b25603fab:2664249a-4c84-43a2-865c-da1ccaf8050b" class="wlWriterEditableSmartContent">
<pre><pre class="brush: plain; gutter: false; pad-line-numbers: false; wrap-lines: false;">var map = new L.Map('divMap');
map.setView(new L.LatLng(52,0), 7);</pre></pre>
</div>
<p>Then, with a few calls to the Bing Maps REST service I created a new L.TileLayer using Bing Maps road style tiles to match the look and feel of my previous attempt (by default, Leaflet uses Open Street Map tiles). The result looked like this:</p>
<p><a href="http://alastaira.files.wordpress.com/2011/09/image16.png"><img style="background-image:none;border-bottom:0;border-left:0;padding-left:0;padding-right:0;display:inline;border-top:0;border-right:0;padding-top:0;" title="image" border="0" alt="image" src="http://alastaira.files.wordpress.com/2011/09/image_thumb15.png?w=644&#038;h=484" width="644" height="484"></a></p>
<p><p>Visually, not much difference, but the pushpin popup says it all.</p>
<h2></h2>
<h2></h2>
<h2>Summary</h2>
<p>I don’t know the performance difference between these approaches – I guess there must be an overhead in having to marshal postMessage calls between iframes but I don’t know if its significant or not. Anecdotally, using Leaflet in the local context seems to be more responsive than the Bing Maps iframe approach. </p>
<p>Better performing or not, the main thing to note is that it’s a lot easier to write manageable code using the second approach. And there’s the irony – until (if?) a native WinRT maps control comes out (or unless you can figure a better way to handle the local/web context dilemma than me), if you’re looking to develop a slippy map interface for a Windows Metro application it’s actually much easier to do so using a third-party map API such as Leaflet than it is to use Microsoft’s own Bing Maps API… </p></p>
<br />  <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gocomments/alastaira.wordpress.com/1079/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/comments/alastaira.wordpress.com/1079/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godelicious/alastaira.wordpress.com/1079/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/delicious/alastaira.wordpress.com/1079/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gofacebook/alastaira.wordpress.com/1079/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/facebook/alastaira.wordpress.com/1079/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gotwitter/alastaira.wordpress.com/1079/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/twitter/alastaira.wordpress.com/1079/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/gostumble/alastaira.wordpress.com/1079/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/stumble/alastaira.wordpress.com/1079/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/godigg/alastaira.wordpress.com/1079/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/digg/alastaira.wordpress.com/1079/" /></a> <a rel="nofollow" href="http://feeds.wordpress.com/1.0/goreddit/alastaira.wordpress.com/1079/"><img alt="" border="0" src="http://feeds.wordpress.com/1.0/reddit/alastaira.wordpress.com/1079/" /></a> <img alt="" border="0" src="http://stats.wordpress.com/b.gif?host=alastaira.wordpress.com&amp;blog=18340120&amp;post=1079&amp;subd=alastaira&amp;ref=&amp;feed=1" width="1" height="1" />]]></content:encoded>
			<wfw:commentRss>http://alastaira.wordpress.com/2011/09/26/creating-a-windows-8-metro-slippy-map-application/feed/</wfw:commentRss>
		<slash:comments>10</slash:comments>
	
		<media:content url="http://1.gravatar.com/avatar/d7c78a79d4ff7e68e2d986ca2f5cc6a7?s=96&#38;d=identicon&#38;r=G" medium="image">
			<media:title type="html">alastaira</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb11.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb12.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb13.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb14.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>

		<media:content url="http://alastaira.files.wordpress.com/2011/09/image_thumb15.png" medium="image">
			<media:title type="html">image</media:title>
		</media:content>
	</item>
	</channel>
</rss>
