Using Geospatial Data

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.

Before I illustrate use of these features, I'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'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.

Second, this CTP does not allow null values in geospatial properties. Nulls will be added by RTM.

OK, enough on what it doesn't do. Let's interact with some geospatial data!

Adding Geospatial Data to the Model

https://gist.github.com/1293201 is a simple OData service which lets a user find people and businesses near them. I'll describe the key geospatial parts here.

First, the entities each have a property of type GeometricPoint (one of the new geospatial types):

[DataServiceKey("BusinessId")]
public class Business
{
public int BusinessId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public GeographicPoint Location { get; set; }
}

[DataServiceKey("Username")]
public class User
{
public User()
{
this.Friends = new List<User>();
}

public string Username { get; set; }
public IList<User> Friends { get; set; }
public GeographicPoint LastKnownLocation { get; set; }
}

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):

new User { Username = "Chai", LastKnownLocation = GeographyFactory.Point(47.7035614013672, -122.329437255859) }

Finally, geospatial data is only supported in V3 of the OData protocol:

public static void InitializeService(DataServiceConfiguration config)
{
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V3;
}

That'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.

Writing the client

Just use Add Service Reference to codegen a client. Consuming geospatial data is no different than consuming any other V3 OData service.

Reading and writing geospatial values

There'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().

Enabling geo.distance queries

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().

Unfortunately, the October CTP does not include an in-memory implementation for distance. Computing distance on a round earth is complicated. You'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.

public static void InitializeService(DataServiceConfiguration config)
{
// ...

// Register my operations
SpatialOperations.Register(2.0, new MyOperations());
}

internal class MyOperations : SpatialOperations
{
public override double Distance(Geometry operand1, Geometry operand2)
{
// TODO: Put your code here.
throw new NotImplementedException();
}

public override double Distance(Geography operand1, Geography operand2)
{
// TODO: Put your code here.
throw new NotImplementedException();
}
}

Making a distance query

Now that you've got a service that supports geo.distance(), we want to query it. Here are a couple of queries we can run:

var localStuff = new LocalStuff(new Uri("https://localhost/LocalStuff.svc", UriKind.Absolute));

var me = localStuff.Users.First(u => u.Username == "Chang");

var myNearbyFriends = me.Friends
.Where(friend => friend.LastKnownLocation.Distance(me.LastKnownLocation) < 1000.0);
var moviesNearMe = localStuff.Businesses
.Where(b => b.Description.Contains("movie"))
.OrderBy(b => b.Location.Distance(me.LastKnownLocation))
.Take(3);

Have fun with the new geospatial data features. Please provide any feedback on the OData.org mailing list.