Following on from Rich's post introducing the Bing Maps Service in the WP7Contrib I'm going to explain in more detail how we built this service and how to use the API.
Before I get into the details let’s get some background on what is provided by Microsoft out of the box. Microsoft provides multiple APIs for programmatically integrating maps and map data into your WP7 applications as well as the UI based controls included in the Microsoft.Phone.Controls.Maps namespace. The APIs are designed to used in conjunction with the UI controls to annotate the visual elements of Bing Maps.
The Bing Maps has it's own area up on MSDN, here.
Microsoft supports both SOAP and REST APIs for accessing location, imagery, route and services information. Each implementation has its own taxonomy and these are heavily influenced by the underlying protocol (as much as you can call REST a protocol). The only advantage we can currently see with using the SOAP implementation over REST is the ease with which you can integrate this into your application. In my opinion this is not a reason to use the SOAP implementation, although it may appear to be quicker and easier.
The important point to remember about using a SOAP based API (over HTTP) is the fact that SOAP is based around the HTTP POST method and is not utilising the power of the HTTP headers and caching when retrieving read-only data. Since the Bing Maps are a read-only service, why would I want to use a protocol on top of HTTP that does not utilise the potentials of HTTP for caching out in the web?
After using both Bing Maps API implementations, we believe the REST version is cleaner, easier to understand and more powerful from a caching perspective. After using this on several WP7 apps we found ourselves either repeating the same steps to use the REST API or copy-pasting the code project to project. We wanted to get away from this - say hello to the WP7Contrib Bing Maps Service!
Simply put, the Bing Maps service is a client-side wrapper for the Microsoft Bing Maps REST API for use in WP7 applications. It abstracts the developer away from having to deal with REST or HTTP concerns. It utilises Rx (reactive extensions) for exposing the request data as well as handling caching, timeouts and retry strategies. We also provide a criteria factory to help create the required search criterion - the number of configurations a developer can use is mind boggling and the criteria factory is there to help reduce the information overload. The complexity of a criterion and the results can be seen from the number of classes required to support searching for locations, routes, imagery and services shown below:
The Bing Maps service does not expose these resource classes to developer, this is done via our model classes, see below. The resources are mapped into the model classes by the service - the reason for this distinction is that we see them as falling into two distinct areas, one fulfilling the communications side (resources) and the other supporting the application and UI side (models) via binding, equality and cloning.
The next thing to look at is the service interfaces and the methods exposed. As I said earlier we use Rx to expose the results because all operations over HTTP are asynchronous and Rx is very well suited to handling this requirement. The interfaces are separated into roles - an interface for location methods, an interface for route method etc. These are then aggregated under a single interface called IBingMapsService which is implemented by the BingMapsService class.
We also have a settings interface. This is to allow the developer to setup the Bing Maps Service with the required config values. This is passed to the constructor of the service and the service extracts the required values, thus preventing a constructor with 20 parameters! The interface definition has read-only properties but the implementation class Settings allows the setting of properties as well.
The last set of interfaces of interest is Criteria such as ILocationSearchAddressCriterion and IRouteSearchCriterion. You can see in the above code snippets these are used as parameters to the Bing Map service APIs. They encapsulate all the parameters required for making the call to the backend service. These interfaces are returned by the CriterionFactory class; this is a helper class and is designed to make creating a criterion easier. The code below for the IRouteSearchCriterion inteface shows the number of properties that can be used for calculating a route. The criterion factory makes creating these easier because it has overloaded parameter signatures for specific search requirements, e.g. search by address only, route search using only way points, or route search using way points with mode of travel and route optimisation. Shown below are some of the criterion interfaces.
The criterion factory also has simple validation rules to prevent calling the Bing Maps service with an invalid criterion, see screen shot below.
The following screen shots and code snippet bring all of the above together. This is an example application to get the detailed address information for a post code (zip code).
This code is taken from the BingMapsLocationDemo in the Spikes directory of the WP7Contrib code base. If you use any of the Bing Map demos in the Spike directory you will have to register with Microsoft for a Bing Maps account - this can be done here.
See how simply the location can be determined for a post code - 4 lines of code!.
Before I get into the details let’s get some background on what is provided by Microsoft out of the box. Microsoft provides multiple APIs for programmatically integrating maps and map data into your WP7 applications as well as the UI based controls included in the Microsoft.Phone.Controls.Maps namespace. The APIs are designed to used in conjunction with the UI controls to annotate the visual elements of Bing Maps.
The Bing Maps has it's own area up on MSDN, here.
Microsoft supports both SOAP and REST APIs for accessing location, imagery, route and services information. Each implementation has its own taxonomy and these are heavily influenced by the underlying protocol (as much as you can call REST a protocol). The only advantage we can currently see with using the SOAP implementation over REST is the ease with which you can integrate this into your application. In my opinion this is not a reason to use the SOAP implementation, although it may appear to be quicker and easier.
The important point to remember about using a SOAP based API (over HTTP) is the fact that SOAP is based around the HTTP POST method and is not utilising the power of the HTTP headers and caching when retrieving read-only data. Since the Bing Maps are a read-only service, why would I want to use a protocol on top of HTTP that does not utilise the potentials of HTTP for caching out in the web?
After using both Bing Maps API implementations, we believe the REST version is cleaner, easier to understand and more powerful from a caching perspective. After using this on several WP7 apps we found ourselves either repeating the same steps to use the REST API or copy-pasting the code project to project. We wanted to get away from this - say hello to the WP7Contrib Bing Maps Service!
Simply put, the Bing Maps service is a client-side wrapper for the Microsoft Bing Maps REST API for use in WP7 applications. It abstracts the developer away from having to deal with REST or HTTP concerns. It utilises Rx (reactive extensions) for exposing the request data as well as handling caching, timeouts and retry strategies. We also provide a criteria factory to help create the required search criterion - the number of configurations a developer can use is mind boggling and the criteria factory is there to help reduce the information overload. The complexity of a criterion and the results can be seen from the number of classes required to support searching for locations, routes, imagery and services shown below:
The Bing Maps service does not expose these resource classes to developer, this is done via our model classes, see below. The resources are mapped into the model classes by the service - the reason for this distinction is that we see them as falling into two distinct areas, one fulfilling the communications side (resources) and the other supporting the application and UI side (models) via binding, equality and cloning.
The next thing to look at is the service interfaces and the methods exposed. As I said earlier we use Rx to expose the results because all operations over HTTP are asynchronous and Rx is very well suited to handling this requirement. The interfaces are separated into roles - an interface for location methods, an interface for route method etc. These are then aggregated under a single interface called IBingMapsService which is implemented by the BingMapsService class.
public interface IBingLocationService
{
IObservable SearchForLocationUsingAddress(ILocationSearchAddressCriterion criterion);
IObservable SearchForLocationUsingPoint(ILocationSearchPointCriterion criterion);
IObservable FindLocationUsingQuery(ILocationSearchQueryCriterion criterion);
}
public interface IBingRouteService
{
IObservable CalculateARoute(IRouteSearchCriterion criterion);
IObservable CalculateRoutesFromMajorRoads(IRouteSearchMajorRoadCriterion criterion);
}
public interface IBingImageryService
{
Uri ImageryUrlForCenterPoint(IImageryUrlForCenterPointCriterion criterion);
Uri ImageryUrlForCenterPointWithRoute(IImageryUrlForCenterPointWithRouteCriterion criterion);
Uri ImageryUrlForQuery(IImageryUrlForQueryCriterion criterion);
Uri ImageryUrlForSpecificArea(IImageryUrForSpecificAreaCriterion criterion);
Uri ImageryUrlForSpecificAreaWithRoute(IImageryUrForSpecificAreaWithRouteCriterion criterion);
Uri ImageryUrlForRoute(IImageryUrlForRouteCriterion criterion);
}
public interface IBingImageryMetadataService
{
IObservable GetImageryMetadataForAnImagerySet(ImagerySearchCriterion criterion);
IObservable GetImageryMetadataForAnImagerySetAtASpecificLocation(ImagerySearchCriterion criterion);
IObservable GetImageryMetadataForABasicImagerySetAtASpecificLocation(ImagerySearchCriterion criterion);
}
public interface IBingSearchService
{
IObservable SearchForServices(ISearchCriterion criterion);
}
public interface IBingMapsService : IBingLocationService,
IBingRouteService,
IBingSearchService,
IBingImageryService,
IBingImageryMetadataService
{
}
We also have a settings interface. This is to allow the developer to setup the Bing Maps Service with the required config values. This is passed to the constructor of the service and the service extracts the required values, thus preventing a constructor with 20 parameters! The interface definition has read-only properties but the implementation class Settings allows the setting of properties as well.
public interface ISettings
{
string AppId { get; }
string CalculateRouteUrl { get; }
string CalculateRoutesFromMajorRoadsUrl { get; }
string CredentialsId { get; }
string ImageryCenterPointUrl { get; }
string ImageryCenterPointWithRouteUrl { get; }
string ImageryMapAreaUrl { get; }
string ImageryMapAreaWithRouteUrl { get; }
string ImageryMapRouteUrl { get; }
string ImageryQueryUrl { get; }
string FindLocationUsingQueryUrl { get; }
string SearchLocationUsingAddressUrl { get; }
string SearchLocationUsingPointUrl { get; }
string SearchUrl { get; }
int CacheTimeout { get; }
int Timeout { get; }
int Retry { get; }
}
The last set of interfaces of interest is Criteria such as ILocationSearchAddressCriterion and IRouteSearchCriterion. You can see in the above code snippets these are used as parameters to the Bing Map service APIs. They encapsulate all the parameters required for making the call to the backend service. These interfaces are returned by the CriterionFactory class; this is a helper class and is designed to make creating a criterion easier. The code below for the IRouteSearchCriterion inteface shows the number of properties that can be used for calculating a route. The criterion factory makes creating these easier because it has overloaded parameter signatures for specific search requirements, e.g. search by address only, route search using only way points, or route search using way points with mode of travel and route optimisation. Shown below are some of the criterion interfaces.
public interface ILocationSearchAddressCriterion : ICloneable<ILocationSearchAddressCriterion>
{
Address Address { get; }
bool HasAddress { get; }
}
public interface ILocationSearchPointCriterion : ICloneable<ILocationSearchPointCriterion>
{
GeoCoordinate Point { get; }
ObservableCollection<LocationEntity> IncludedEntities { get; }
bool HasPoint { get; }
bool HasIncludedEntities { get; }
}
public interface IRouteSearchCriterion : ICloneable<IRouteSearchCriterion>
{
Avoid Avoid { get; }
ObservableCollection<WayPoint> WayPoints { get; }
int? Heading { get; }
Optimize Optimize { get; }
RoutePathOutput PathOutput { get; }
DistanceUnit DistanceUnit { get; }
DateTime? DateTime { get; }
TimeType TimeType { get; }
int MaxSolutions { get; }
ModeOfTravel TravelMode { get; }
string PointOfInterest { get; }
bool HasAvoid { get; }
bool HasWayPoints { get; }
}
public interface IRouteSearchMajorRoadCriterion : ICloneable<IRouteSearchMajorRoadCriterion>
{
RouteDestination Destination { get; }
RoutePathOutput PathOutput { get; }
RouteExclude Exclude { get; }
DistanceUnit DistanceUnit { get; }
bool HasDestination { get; }
}
The criterion factory also has simple validation rules to prevent calling the Bing Maps service with an invalid criterion, see screen shot below.
The following screen shots and code snippet bring all of the above together. This is an example application to get the detailed address information for a post code (zip code).
This code is taken from the BingMapsLocationDemo in the Spikes directory of the WP7Contrib code base. If you use any of the Bing Map demos in the Spike directory you will have to register with Microsoft for a Bing Maps account - this can be done here.
See how simply the location can be determined for a post code - 4 lines of code!.
private void getAddress_Click(object sender, RoutedEventArgs e)
{
var criterion = CriterionFactory.CreateLocationSearchForAddress(this.postCode.Text);
this.bingMapsService.SearchForLocationUsingAddress(criterion)
.ObserveOnDispatcher()
.Subscribe(result =>
{
this.address.Text = result.Locations[0].Address.Locality;
this.address.Text += Environment.NewLine;
this.address.Text += result.Locations[0].Address.PostalCode;
this.address.Text += Environment.NewLine;
this.address.Text += result.Locations[0].Address.AdminDistrict;
this.address.Text += Environment.NewLine;
this.address.Text += result.Locations[0].Address.CountryRegion;
});
}
The code below shows how the service and its dependencies are created. The WP7Contrib is based around the idea of using an IoC container for dependencies and injecting all dependencies via constructors. The example application does not use an IoC container but you can see how we pass the dependencies to the Bing Maps service.
private readonly ILog log = new DebugLog();
private readonly IBingMapsService bingMapsService = null;
// Constructor
public MainPage()
{
InitializeComponent();
this.log = new DebugLog();
this.bingMapsService = new BingMapsService(new ResourceClientFactory(this.log),
new UrlEncoder(),
new Settings("MyApplicationId", "MyCredentialId", 10000, 5));
}
The last to area to cover is the implementation of the SearchForLocationUsingAddress() method inside the Bing Maps service. This uses a standard approach to making a query to the backend service. First of all we check to see if the search has already been executed and if so return the value from the cache; if not we then instantiate the resource client and then construct the search parameters. Finally we execute the search and persist the results into the cache. You can also see the timeout and retry strategy.
public IObservable<LocationSearchResult> SearchForLocationUsingAddress(ILocationSearchAddressCriterion criterion)
{
this.log.Write("BingMapsService: SearchForLocationUsingAddress...");
try
{
var keyTuple = new CacheTuple<string, ILocationSearchAddressCriterion>
{
Val1 = "SearchForLocationUsingAddress",
Val2 = criterion.DeepClone()
};
var locationResult = this.cacheProvider.Get<CacheTuple<string, ILocationSearchAddressCriterion>, LocationSearchResult>(keyTuple);
if (locationResult != null)
{
this.log.Write("BingMapsService: SearchForLocationUsingAddress results retrieved from cache, hash code - {0}", criterion.GetHashCode());
return Observable.Return(locationResult).AsObservable();
}
var resourceHandler = resourceHandlerFactory.Create()
.ForType(ResourceType.Json)
.UseUrlForGet(this.settings.SearchLocationUsingAddressUrl);
var @params = new[]
{
// path parameters...
// query string parameters...
propertyEncoder.Encode(criterion.Address.CountryRegion),
propertyEncoder.Encode(criterion.Address.AdminDistrict),
propertyEncoder.Encode(criterion.Address.Locality),
propertyEncoder.Encode(criterion.Address.PostalCode),
propertyEncoder.Encode(criterion.Address.AddressLine),
this.settings.CredentialsId
};
return resourceHandler.Get<Resources.Location.Result>(@params)
.Timeout(this.timeout)
.Retry(this.retry)
.Select(response => {
var locations = ProcessResponse(response);
this.cacheProvider.Add(keyTuple, locations, this.cacheTimeout);
return locations;
});
}
catch (Exception exn)
{
var message = string.Format(FailedLocationSearch, exn.Message);
this.log.Write(message);
throw new ServiceException(message, exn);
}
}
As I said before you can find the demo application BingMapsLocationDemo in the Spikes directory of the WP7Contrib code base.
Comments
Post a Comment