{"id":833,"date":"2011-10-17T17:11:21","date_gmt":"2011-10-17T17:11:21","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/odatateam\/2011\/10\/17\/using-geospatial-data\/"},"modified":"2011-10-17T17:11:21","modified_gmt":"2011-10-17T17:11:21","slug":"using-geospatial-data","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/using-geospatial-data\/","title":{"rendered":"Using Geospatial Data"},"content":{"rendered":"<p>This CTP of WCF Data Services adds support for geospatial data. The release allows use of all of the OData geospatial data types and the geo.distance() canonical function. This enables two key scenarios:\n<\/p>\n<ul>\n<li>Read and write geospatial data (all types supported by Sql Server 2008 R2).\n<\/li>\n<li>Find all entities (i.e. coffee shops) near a location.\n<\/li>\n<\/ul>\n<p>Before I illustrate use of these features, I&#8217;d like to mention some limitations of this CTP. First, (and most significantly) WCF Data Services providers only support geospatial data with custom or reflection providers. You can&#8217;t use Entity Framework at this time. OData will support geospatial data over EF as soon as there is an EF release that supports geospatial data.\n<\/p>\n<p>Second, this CTP does not allow null values in geospatial properties. Nulls will be added by RTM.\n<\/p>\n<p>OK, enough on what it doesn&#8217;t do. Let&#8217;s interact with some geospatial data!\n<\/p>\n<h2>Adding Geospatial Data to the Model\n<\/h2>\n<p><a href=\"https:\/\/gist.github.com\/1293201\">https:\/\/gist.github.com\/1293201<\/a> is a simple OData service which lets a user find people and businesses near them. I&#8217;ll describe the key geospatial parts here.\n<\/p>\n<p>First, the entities each have a property of type GeometricPoint (one of the new geospatial types):\n<\/p>\n<p>[DataServiceKey(<span style=\"color:#a31515\">&#8220;BusinessId&#8221;<\/span>)]<br \/><span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">class<\/span> Business<br \/>{<br \/><span style=\"color:blue\">  public<\/span>\n\t\t<span style=\"color:blue\">int<\/span> BusinessId { <span style=\"color:blue\">get<\/span>; <span style=\"color:blue\">set<\/span>; }<br \/>\n\t\t<span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">string<\/span> Name { <span style=\"color:blue\">get<\/span>; <span style=\"color:blue\">set<\/span>; }<br \/>\n\t\t<span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">string<\/span> Description { <span style=\"color:blue\">get<\/span>; <span style=\"color:blue\">set<\/span>; }<br \/>\n\t\t<span style=\"color:blue\">public<\/span> GeographicPoint Location { <span style=\"color:blue\">get<\/span>; <span style=\"color:blue\">set<\/span>; }<br \/>}<\/p>\n<p>[DataServiceKey(<span style=\"color:#a31515\">&#8220;Username&#8221;<\/span>)]<br \/><span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">class<\/span> User<br \/>{<br \/>\n\t\t<span style=\"color:blue\">public<\/span> User()<br \/>  {<br \/>\n\t\t<span style=\"color:blue\">this<\/span>.Friends = <span style=\"color:blue\">new<\/span> List&lt;User&gt;();<br \/>  }<\/p>\n<p>\t\t<span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">string<\/span> Username { <span style=\"color:blue\">get<\/span>; <span style=\"color:blue\">set<\/span>; }<br \/>\n\t\t<span style=\"color:blue\">public<\/span> IList&lt;User&gt; Friends { <span style=\"color:blue\">get<\/span>; <span style=\"color:blue\">set<\/span>; }<br \/>\n\t\t<span style=\"color:blue\">public<\/span> GeographicPoint LastKnownLocation { <span style=\"color:blue\">get<\/span>; <span style=\"color:blue\">set<\/span>; }<br \/>}\n<\/p>\n<p>To create sample data values for geospatial types, I use the GeographyFactory (the data creation API is likely to change before the RTM, but this correct for now):\n<\/p>\n<p><span style=\"color:blue\">new<\/span>\n\t\t<span style=\"color:#2b91af\">User<\/span> { Username = <span style=\"color:#a31515\">&#8220;Chai&#8221;<\/span>, LastKnownLocation = <span style=\"color:#2b91af\">GeographyFactory<\/span>.Point(47.7035614013672, -122.329437255859) }\n<\/p>\n<p>Finally, geospatial data is only supported in V3 of the OData protocol:\n<\/p>\n<p><span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">static<\/span>\n\t\t<span style=\"color:blue\">void<\/span> InitializeService(<span style=\"color:#2b91af\">DataServiceConfiguration<\/span> config)<br \/>{<br \/>  config.DataServiceBehavior.MaxProtocolVersion = <span style=\"color:#2b91af\">DataServiceProtocolVersion<\/span>.V3;<br \/>}\n<\/p>\n<p>That&#8217;s it. Geospatial data values are just primitive data values like any other. It requires as little effort to use them as it does to use a DateTime.\n<\/p>\n<h2>Writing the client\n<\/h2>\n<p>Just use Add Service Reference to codegen a client. Consuming geospatial data is no different than consuming any other V3 OData service.\n<\/p>\n<h2>Reading and writing geospatial values\n<\/h2>\n<p>There&#8217;s nothing special about geospatial values. For example, to update your last known location, you would query for your User entity, set the value of its LastKnownLocation, and call SaveChanges().\n<\/p>\n<h2>Enabling geo.distance queries\n<\/h2>\n<p>This sample service is interesting because it allows users to find nearby friends and businesses. We want to write queries that filter or orderby geo.distance().\n<\/p>\n<p>Unfortunately, the October CTP does not include an in-memory implementation for distance. Computing distance on a round earth is complicated. You&#8217;ll need to find a good implementation for this operation (Sql Server has such an implementation). Once you have it, you can use the following glue code to hook it up.\n<\/p>\n<p><span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">static<\/span>\n\t\t<span style=\"color:blue\">void<\/span> InitializeService(DataServiceConfiguration config)<br \/>{<br \/>\n\t\t<span style=\"color:green\">\/\/ &#8230;<\/span><\/p>\n<p><span style=\"color:green\">  \/\/ Register my operations<\/span><br \/><span style=\"color:green\">\n\t\t<\/span>SpatialOperations.Register(2.0, <span style=\"color:blue\">new<\/span> MyOperations());<br \/>}<\/p>\n<p><span style=\"color:blue\">internal<\/span>\n\t\t<span style=\"color:blue\">class<\/span> MyOperations : SpatialOperations<br \/>{<br \/>\n\t\t<span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">override<\/span>\n\t\t<span style=\"color:blue\">double<\/span> Distance(Geometry operand1, Geometry operand2)<br \/>  {<br \/>\n\t\t<span style=\"color:green\">\/\/ <span style=\"color:darkblue\"><strong>TODO: Put your code here.<\/strong><\/span><br \/><\/span>\n\t\t<span style=\"color:blue\">throw<\/span>\n\t\t<span style=\"color:blue\">new<\/span>\n\t\t<span style=\"color:darkblue\"><strong>NotImplementedException<\/strong><\/span>();<br \/>  }<\/p>\n<p>\t\t<span style=\"color:blue\">public<\/span>\n\t\t<span style=\"color:blue\">override<\/span>\n\t\t<span style=\"color:blue\">double<\/span> Distance(Geography operand1, Geography operand2)<br \/>  {<br \/>\n\t\t<span style=\"color:green\">\/\/ <\/span>TODO: Put your code here.<br \/>\n\t\t<span style=\"color:blue\">throw<\/span>\n\t\t<span style=\"color:blue\">new<\/span> NotImplementedException();<br \/>  }<br \/>}\n<\/p>\n<h2>Making a distance query\n<\/h2>\n<p>Now that you&#8217;ve got a service that supports geo.distance(), we want to query it. Here are a couple of queries we can run:\n<\/p>\n<p><span style=\"color:blue\">var<\/span> localStuff = <span style=\"color:blue\">new<\/span>\n\t\t<span style=\"color:#2b91af\">LocalStuff<\/span>(<span style=\"color:blue\">new<\/span>\n\t\t<span style=\"color:#2b91af\">Uri<\/span>(<span style=\"color:#a31515\">&#8220;http:\/\/localhost\/LocalStuff.svc&#8221;<\/span>, <span style=\"color:#2b91af\">UriKind<\/span>.Absolute));<\/p>\n<p><span style=\"color:blue\">var<\/span> me = localStuff.Users.First(u =&gt; u.Username == <span style=\"color:#a31515\">&#8220;Chang&#8221;<\/span>);<\/p>\n<p><span style=\"color:blue\">var<\/span> myNearbyFriends = me.Friends<br \/>  .Where(friend =&gt; friend.LastKnownLocation.Distance(me.LastKnownLocation) &lt; 1000.0);<br \/><span style=\"color:blue\">var<\/span> moviesNearMe = localStuff.Businesses<br \/>  .Where(b =&gt; b.Description.Contains(<span style=\"color:#a31515\">&#8220;movie&#8221;<\/span>))<br \/>  .OrderBy(b =&gt; b.Location.Distance(me.LastKnownLocation))<br \/>  .Take(3);\n<\/p>\n<p>Have fun with the new geospatial data features. Please provide any feedback on the OData.org mailing list.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This CTP of WCF Data Services adds support for geospatial data. The release allows use of all of the OData geospatial data types and the geo.distance() canonical function. This enables two key scenarios: Read and write geospatial data (all types supported by Sql Server 2008 R2). Find all entities (i.e. coffee shops) near a location. [&hellip;]<\/p>\n","protected":false},"author":512,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[35,78],"class_list":["post-833","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata","tag-geospatial","tag-wcf-data-services"],"acf":[],"blog_post_summary":"<p>This CTP of WCF Data Services adds support for geospatial data. The release allows use of all of the OData geospatial data types and the geo.distance() canonical function. This enables two key scenarios: Read and write geospatial data (all types supported by Sql Server 2008 R2). Find all entities (i.e. coffee shops) near a location. [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/833","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/512"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=833"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/833\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3253"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=833"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=833"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=833"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}