Thus speaks Wikipedia:
Augmented reality (AR) is a term for a live direct or indirect view of a physical real-world environment whose elements are merged with (or augmented by) virtual computer-generated imagery - creating a mixed reality.
Amen. Fact is that AR is currently as hot a nuclear reactor core making its way to China, and that everyone and his aunt are scrambling to get a piece of the action. So why not me ;-)
On November 9th this year, my colleague Jeroen Prins, who did some prototyping with Layar, pushed a HTC Hero in my hands with the words “see if you can do something nice with it”. So in a few evenings I created a little framework for making Layar layers in an easier and consistent way. It is based upon some of Jeroen’s prototype, but since he insisted on not having credits for this I won’t give him any ;-). The framework uses the Enterprise Library, most notably Unity, and I assume you are familiar with it.
Since WCF can be bent in almost every direction as far as generating content is concerned, I decided to use it for my solution. I started, as you always start, with the data contract. The object model is pretty simple: a Layer object has Point-Of-Interest (Poi) objects, a Poi has Action objects. If you study the Layar GetPointsOfInterest page for a few minutes you will see the implementation is WCF 101. Maybe 102 ;-). Contrary to my habits, I forego on the comments – those are all on the GetPointsOfInterest page. First, the Action object:
using System.Runtime.Serialization; namespace LocalJoost.Layar { [DataContract(Name = "Action")] public class Action { [DataMember(Name = "uri")] public string Uri { get; set; } [DataMember(Name = "label")] public string Label { get; set; } } }
The member name is “Uri” (following the .NET coding guidelines) but with adding “Name=uri” in the DataMember attribute I tell WCF to serialize the member as “uri”, without the capital “U”, thus following exactly the Layer API description. This is standard WCF stuff. Then, the Poi class:
using System; using System.Collections.Generic; using System.Runtime.Serialization; namespace LocalJoost.Layar { [DataContract (Name="POI")] public class Poi { public Poi() { Actions = new List(); } [DataMember(Name = "actions")] public List Actions { get; set; } [DataMember(Name = "attribution")] public String Attribution { get; set; } [DataMember(Name = "distance")] public double Distance { get; set; } [DataMember(Name = "id")] public string Id { get; set; } [DataMember(Name = "imageURL")] public string ImageUrl { get; set; } [DataMember(Name = "lat")] public int Latitude { get; set; } [DataMember(Name = "lon")] public int Longitude { get; set; } [DataMember(Name = "line2")] public string Line2 { get; set; } [DataMember(Name = "line3")] public string Line3 { get; set; } [DataMember(Name = "line4")] public string Line4 { get; set; } [DataMember(Name = "title")] public string Title { get; set; } [DataMember(Name = "type")] public int Type { get; set; } } }
and finally, the Layer object itself:
using System.Collections.Generic; using System.Runtime.Serialization; namespace LocalJoost.Layar { [DataContract] public class Layer { public Layer() { Hotspots = new List(); } [DataMember(Name = "nextPageKey")] public string NextPageKey { get; set; } [DataMember(Name = "morePages")] public bool MorePages { get; set; } [DataMember(Name = "hotspots")] public List Hotspots { get; set; } [DataMember(Name = "layer")] public string LayerName { get; set; } [DataMember(Name = "errorCode")] public int ErrorCode { get; set; } [DataMember(Name = "errorString")] public string ErrorString { get; set; } } }
I move on to the whopping complex service contract:
using System.ServiceModel; using System.ServiceModel.Web; namespace LocalJoost.Layar { [ServiceContract(Namespace = "www.yournamespacehere.nl/layar")] public interface ILayarService { [OperationContract] [WebGet(UriTemplate = "Layar/{layerName}/*", ResponseFormat=WebMessageFormat.Json)] Layer GetLayerData(string layerName); } }
which defines the output for this as being JSON, and a custom URI matching pattern which allows us to put the actual layer name in the URL. The * at the end means "and the rest is also accepted". Now the title of this posting says I was doing something with Unity, and here it comes: I define an equally complex interface for a Layar "provider" which will be used by the service implementation:
using System.Collections.Generic; namespace LocalJoost.Layar { public interface ILayarProvider { Layer Get(double? lat, double? lon, int? radius, int? accuracy, IDictionaryrequestParameters); } }
The final piece of real code is the implementation of the ILayarService service contract, with apologies for the crappy layout, but some WCF class names are a wee bit long:
using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Globalization; using System.ServiceModel.Web; using Microsoft.Practices.EnterpriseLibrary.Logging; using LocalJoost.Utilities.Unity; namespace LocalJoost.Layar { /// <summary> /// Layar service implementation /// </summary> public class LayarService : ILayarService { /// <summary> /// Request parameters /// </summary> private static NameValueCollection RequestParams { get { return WebOperationContext.Current != null ? WebOperationContext.Current.IncomingRequest.UriTemplateMatch.QueryParameters : null; } } private static readonly List<string> KeyWordsProcessed = new List<string> { "lat", "lon", "radius", "accuracy" }; /// <summary> /// Gets the layer data. /// </summary> /// <param name="layerName">Name of the layer.</param> /// <returns></returns> public Layer GetLayerData(string layerName) { try { if (WebOperationContext.Current != null ) { Logger.Write("Layar call: " + WebOperationContext.Current.IncomingRequest.UriTemplateMatch.RequestUri); } // Note: layername is lowercase var provider = new UnityResolver( layerName.ToLowerInvariant()).Resolve<ILayarProvider>(); // Collect the other parameters var reqParms = new Dictionary<string, string>(); foreach( var key in RequestParams.Keys) { var keyS = key.ToString(); if (!KeyWordsProcessed.Contains(keyS)) reqParms.Add(keyS, RequestParams[keyS]); }; return provider.Get( GetRequestDouble("lat"), GetRequestDouble("lon"), GetRequestInt("radius"), GetRequestInt("accuracy"), reqParms); } catch( Exception ex) { Logger.Write(ex,"Exceptions"); return null; } } #region Utility methods private double GetRequestDouble(String keyname) { if (!(RequestParams == null || string.IsNullOrEmpty(RequestParams[keyname]))) { return Convert.ToDouble(RequestParams[keyname], CultureInfo.InvariantCulture); } return -1; } private int GetRequestInt(String keyname) { if (!(RequestParams == null || string.IsNullOrEmpty(RequestParams[keyname]))) { return Convert.ToInt32(RequestParams[keyname], CultureInfo.InvariantCulture); } return -1; } #endregion } }
Here comes my little UnityResolver into play, which was described earlier in this blog. What this LayarService basically does is accept a layer name, burp the call into a log file, get the lat, lon, radius, and accuracy from the query string, dump the rest of the query string parameters into a dictionary, use Unity to determine which ILayerProvider implementation is to be loaded, call it’s Get method with the collected data, and return the result.
Now there are only six steps to make this actually work. First, you define a web application project. You reference the LocalJoost.Layar project, System.ServiceModel.dll, System.Runtime.Serialization, every file in the Enterprise Library that starts with "Microsoft.Practices.Unity" (I have 5), and Microsoft.Practices.EnterpriseLibrary.Logging.dll.
The second step is: add a text file to the web application, for instance "LayarService.txt". You enter the following text in it:
<%@ ServiceHost Language="C#" Debug="true" Service="LocalJoost.Layar.LayarService" %>
and rename this the file to "LayerService.svc". The third step is some WCF configuration in the web.config of your web application to host the service as a webHttpBinding, thus making it accept calls via http get:
<services> <service name="LocalJoost.Layar.LayarService"> <endpoint address="" binding="webHttpBinding" behaviorConfiguration="WebHttpBehavior" contract="LocalJoost.Layar.ILayarService" bindingNamespace="http://whatever/layar"> </endpoint> </service> </services> <behaviors> <endpointBehaviors> <behavior name="WebHttpBehavior"> <webHttp/> </behavior> </endpointBehaviors> </behaviors>
The fourth step is to map your implementations of ILayarProvider to your layers. The LayerService class works in such a way that a layer maps directly to a Unity container, so a configuration might look like this:
<unity> <typeAliases> <typeAlias alias="ILayarProvider" type="LocalJoost.ILayarProvider,LocalJoost.Layar"/> <typeAlias alias="SampleProvider" type="SomeAssembly.SampleProvider,SomeAssembly"/> <typeAlias alias="AnotherProvider" type="SomeotherAssembly.AnotherProvider, SomeotherAssembly"/> </typeAliases> <containers> <container name="mylayer"> <types> <type type="ILayarProvider" mapTo="SampleProvider"/> </types> </container> <container name="someotherlayer"> <types> <type type="ILayarProvider" mapTo="AnotherProvider"/> </types> </container> </containers> </unity>
Here I have defined two sample containers, thus layers. If, for instance, your service is hosted as “http://mydomain.com/LayarServer/LayerService.svc” you can register your Layer with the Layar developer portal (which is, incidentally, the fifth step) as “http://mydomain.com/LayarServer/LayerService.svc/Layar/mylayer/” (mind the trailing slash!) and the framework will do the rest.
Now the sixth and final step is the real hard part: writing actual implementations of the ILayarProvider. This depends or what you are actually wanting to show. And this it where my help ends ;-).
1 comment:
Thanks for an interesting read. Very interesting.
Post a Comment