We have seen various capabilities of Find Service to find places, addresses, nearby entities, and points of interest, but what happens if you have an address in an unstructured or unformatted form? What if you want to create an application where your users can type their address in a textbox without worrying about the formatting? How do you parse the address field to understand various parts of the address? You can use the FindServiceSoap.ParseAddress method for these purposes. The ParseAddress method takes two arguments, input address as a string and an optional country/region name, and returns an Addressobject for valid addresses. For example, if you have the address 1 Microsoft Way, Redmond, WA in string format, you can use theParseAddressmethod to parse it into anAddressobject:
//Create a web service proxy FindServiceSoap findService = new FindServiceSoap(); //Assign credentials . . .
//Parse a string into a valid address object Address address = findService.ParseAddress("1 Microsoft Way, Redmond", "United States");
One of the greatest advantages of this method is that you can implement one user interface that can perform both find place and find address depending on what users input without having to design two different UIs for two different purposes.
Asynchronous Programming with Find Service
When developing applications using Web Service, keep in mind that you are making a network round-trip with every method call, which has serious implications on your application’s performance in terms of responsiveness. For example, since calls over the network take a long time to return, you don’t want to block the UI thread for your Windows application. This is where the asynchronous programming patterns come to the rescue. Using the .NET framework, it is easy to call Web Service methods asynchronously. So, let’s see how you would implement a FindServiceSoap.Find method call asynchronously.
Asynchronous Programming for Windows Applications
When you generate the MapPoint Web Service proxy class using Visual Studio .NET, it also generates the necessary methods for asynchronous programming. For example, if you look for the FindServiceSoap.Find method, you also find the FindServiceSoap.BeginFind and FindServiceSoap.EndFind methods in the proxy class. TheBeginandEndmethod pairs enable the asynchronous programming patterns for your web service client applications. Using these methods is really easy; in a synchronous scenario, yourFindcall looks like the following code:
If this code is running on the UI thread, it does not get to theDisplayFindResultsmethod until theFindmethod call completes and returns thefindresultsvalue; during this period, users of your application may find it unresponsive. To avoid this situation, create a worker thread and call theFindmethod using it so that your UI thread is free during this long network round-trip. In fact, that’s exactly what theBeginFindandEndFindmethods do behind the scenes. To implement the previous code using asynchronous methods, you would do something similar to the following:
First, define a callback method for your asynchronous method calls:
Next, modify your find call to become an asynchronousBeginFindcall:
//Async call to find AsyncCallback callback = new AsyncCallback(FindServiceCallback); findsoap.BeginFind(findspec, callback, findsoap);
TheBeginFindinvokes theFindmethod on a different (worker) thread and passes a pointer to theFindSeviceCallbackmethod as a callback method; when theFindmethod returns aFindResultsinstance, the callback delegate is invoked so that theFindServiceCallbackmethod gets executed on the UI thread again. In theFindServiceCallbackmethod, you need to obtain theFindResultsreturned by theFindmethod by calling theEndFindmethod and displaying them. Keep in mind that the HTTP session is kept alive during this asynchronous operation behind the scenes—this pattern is asynchronous at your application thread level but not at the HTTP communication level.
Asynchronous Programming for Web Applications
Multithreaded programming works well for Windows applications if you are calling web services, but wouldn’t it be nice to adopt this asynchronous programming model for web applications as well? Wouldn’t it be convenient to develop more responsive applications without doing a complete page refresh? You can do these things with a combination of JavaScript and Msxml2.XMLHTTP ActiveX control. The use of JavaScript with asynchronous XML messaging is called Asynchronous JavaScript and XML, or simply AJAX. While AJAX terminology is fairly new to web application development, the use of JavaScript and XMLHTTP is not. In this section, I will go over some scenarios where AJAX can be used in your web applications to improve the overall user experience.
In theory, AJAX is no different from any other web application that uses HTTP request and response—however, adding asynchronous calls from your web page to the web server that uses JavaScript dramatically improves the user’s experience.
To understand how to leverage AJAX in your MapPoint Web Service web applications, you need to understand how AJAX works, which is explained in the following section.
For AJAX to work, you need three core components:
A web page (htm, aspx, etc.) that hosts JavaScript containing asynchronous calls to the web server
XMLHTTP ActiveX control enabled from the client web browser
A server-side component that can process HTTP GET requests using the query string parameters
All three of these components together make an AJAX implementation. Usually, the server-side component is an HTTP Handler, since it renders only the script instead of conventional HTML. An HTTP Handler is similar to an ISAPI extension, and you need to implement the IHTTPHandler interface to develop an ASP.NET HTTP Handler. These concepts are shown pictorially in Figure 6-3.
Since designing a web page and writing JavaScript are straightforward tasks, let me delve into the server-side components that are required for AJAX.
For example, you want to develop a web application that implements “Find a place” functionality; in this application, when users search for a place, generally MapPoint Web Service comes back with a list of possible matches to be displayed for disambiguation. Conventionally, you would implement this process using the following series of actions:
Figure 6-3. AJAX architecture for MapPoint Web Service applications
Have the user type in a place and click a button to find it.
Post the request to the server page that invokes the MapPoint Find Service calls to find the place.
If there is more than one match to the input string, display them in a list box where the user can select the place that he is looking for.
Post that selection back to the server page that invokes MapPoint Render Service to get a map.
The same application can be implemented with AJAX to improve the overall user experience:
As the user types each character into the input place textbox, make an asynchronous call to the server to fetch matching places and display them in a dynamic drop-down list.
Have the user select the place she is looking for.
Post that selection back to the server page to display a map.
An implementation of this application results in the user interface shown in Figure 6-4.
The experience of using the application is far richer with AJAX when compared to a traditional MapPoint find web application. Next, let’s see how to implement this application.
Figure 6-4. Place lookup using MapPoint Web Service and AJAX
The input textbox"place"has theonkeyupevent wired up to theDoLookup()method, which will be called each time the user types in a character.
Also define adivelement to hold the results; this element acts as a dynamic list box to show results. If no results are found, thedivis hidden as an invisible element on the page, and if search results are found, thedivis visible for the user to select the desired find result:
The next step is to implement the HTTP Handler that processes find requests on HTTP GET.
Developing the HTTP Handler. To develop an HTTP Handler, you need to implement theSystem.Web.IHttpHandlerinterface, which requires overriding theProcessRequestmethod that handles the incoming request.
You can read more about implementing HTTP Handlers on MSDN at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/ html/frlrfsystemwebihttphandlerclassprocessrequesttopic. asp?frame=true.
In this method, you check an incoming request’s query string to see whether there is any place name; if a place name exists, make aFindServiceSoap.Findmethod call to return matching find results. The results need to be formatted so that the calling JavaScript can understand and display the contents. So, I decided to do the result formatting on the server side in the HTTP handler and simply return the find results that are display-ready. The following code shows theProcessRequestmethod implementation:
public void ProcessRequest(HttpContext context) { //See incoming place query string parameter string input = context.Request["place"] as string;
//If there is a valid input call MapPoint Web Service try { //See if this has a cache item: string response = context.Cache[input] as string; if(response != null) { context.Response.Write("ShowLocation(" + response + ");"); return; } else { String[] findings = null; //Call MapPoint Web Service and get the place names into //the findings string array . . . .
System.Text.StringBuilder sb = new System.Text.StringBuilder(); int count = findings.Length; sb.Append("""); for(int i =0; i< count; i++) { //Open div and add id sb.Append("<div id='findresult_" + i.ToString() + "' "); //Add unselectable = on sb.Append(" UNSELECTABLE='on' "); //Add findresult content sb.Append("<img align=absmiddle src=arrow.gif border=0> " + findings[i].Trim().Replace("'", "\'")); //close div sb.Append("</div>"); } sb.Append(""");
//Write back the necessary JavaScript and //add this entry to cache so that we don't have to hit MapPoint //Web Service if(sb.Length > 5) { //Write JavaScript context.Response.Write("ShowLocation(" + sb.ToString() + ");"); //Add to cache context.Cache.Add(input, sb.ToString(), null, System.DateTime.MaxValue, TimeSpan.FromHours(6), System.Web.Caching.CacheItemPriority.Default, null); } else { context.Response.Write("var zeroOutputFound = 0;"); } }
This HTTP handler writes appropriate JavaScript back to the client as a response to any incoming request. For example, an incoming request such as the following:
http://yourHTTPHandlerUrl?place=New%20York
results in the following JavaScript being sent back as response:
ShowLocation("<div id='findresult_0' UNSELECTABLE='on' ><img align=absmiddle src=arrow.gif border=0> New York, New York, United States</div><div id='findresult_1' UNSELECTABLE='on'><img align=absmiddle src=arrow.gif border=0> New York (state), United States</div><div id='findresult_2' UNSELECTABLE='on'><img align=absmiddle src=arrow.gif border=0> New York (county), New York, United States</div><div id='findresult_3' UNSELECTABLE='on'><img align=absmiddle src=arrow.gif border=0> New York, Santa Rosa, Florida, United States</div><div id='findresult_4' UNSELECTABLE='on'><img align=absmiddle src=arrow.gif border=0> New York, Wayne, Iowa, United States</div>");
You can also simply return an array of strings in JavaScript and let the client code do the formatting for the display, but I chose to implement it on the server side since it simplifies the JavaScript implementation.
The next step is to develop the JavaScript to glue these pieces together.
Developing the JavaScript. You might have noticed a couple of JavaScript functions that I have been using so far:
DoLookup Captures the user’s key inputs and sends an asynchronous call to the HTTP Handler.
ShowLocation Displays the find match results to the user in a dynamic drop-down list.
Let’s see how to implement these two functions.
TheDoLookupfunction uses XMLHTTP functionality to send the request that we developed in the previous step asynchronously to the HTTP handler; a simplified version ofDoLookupis shown in the following code:
//Define a global variable to hold http request //object var xmlhttp=null;
Function DoLookup() { //Create a valid url with user typed place value var url = "your http handler url"? + place.value;
//Create an instance of xmlHttp xmlhttp=new ActiveXObject("Msxml2.XMLHTTP"); if(xmlhttp) { //Now open a request and assign callback xmlhttp.open("GET",url,true); xmlhttp.onreadystatechange= FindPlaceRequestCallBack; //Send the request xmlhttp.send(null); } }
A callback function,FindPlaceRequestCallBack, is assigned to handle the response and any other state changes in the input request; the implementation of theFindPlaceRequestCallBackis as follows:
function FindPlaceRequestCallBack() { if(xmlhttp) { if(xmlhttp.readyState==4&&xmlhttp.responseText) { //Get the response content var placecontent = xmlhttp.responseText; //Just execute the response content eval(placecontent); xmlhttp = null; } } }
The callback function receives the response text from the HTTP handler and executes the resulting JavaScript using theevalmethod. The resulting JavaScript is nothing but theShowLocation function call withFindresult matches for the input place. Next, we need to implement theShowLocationfunction:
function ShowLocation(findResults) { if(!findResults) { return; }
In this function, the results are assigned to thedivelement to be displayed to the user. Your AJAX application for place lookup is now ready. With minimal effort, you can AJAX-enable your MapPoint Web Service web application to provide a great user experience.
Obviously, you can extend these functions more to optimize your client-server communication by limiting the requests to strings more than three characters in length. Also, you can implement more features such as scroll enabling with arrow keys, and mouse-select enabling in JavaScript to add more depth to the application. The sample application included in the companion material has all these features implemented in JavaScript.
Finally, because you are charged a fee for each MapPoint Web ServiceFindcall, you need to evaluate this design appropriately; a more cost-effective variation to this implementation might be to do a lookup asynchronously when the user clicks Enter, instead of performing a lookup for each character typed.
One of the advantages of working with web services is that you can invoke methods over the wire using XML, but due to the remote nature of this method and XML’s inherent behavior to bloat the packet size, it may pose performance and end user experience issues. For example, say you are building a mapping application for a handheld device that depends on GPRS for connectivity; usually the users of these connected handheld devices pay the network service provider for the data plans (the number of bytes downloaded over the air using the GPRS connections).
To find a place, your MapPoint Web Service application sends a request SOAP XML message and receives the response SOAP XML message from the Web Service. Because the XML bloats the size of the request and response packet, this could cost your application users a lot of money. Not only could having large request and response messages slow down your application for network transfer, but it also may result in poor end user experience. There are multiple ways to optimize your MapPoint Web Service applications for SOAP XML size and response speed; let’s look at them in detail.
Optimizing the SOAP Response Size
Always write your applications to receive only the information you need and filter out all unnecessary noise. There are three elements you can limit to do this:
Result-set size
If you are looking for a place and are certain of its name, request only one find result using the FindRange object:
//Create a FindServiceSoap object. FindServiceSoap findservicesoap = new FindServiceSoap(); //Assign credentials go here. . . . //Create a FindSpecification object. FindSpecification findspecification = new FindSpecification(); //Assign a valid data source name. findspecification.DataSourceName = "MapPoint.NA"; //Specify a place to find. findspecification.InputPlace = "Redmond, WA"; //Create a FindOptions object. findspecification.Options = new FindOptions(); //Create a Range object. findspecification.Options.Range = new FindRange(); //Assign the Range StartIndex and the result count to be returned. findspecification.Options.Range.StartIndex = 0; findspecification.Options.Range.Count = 1; //Invoke the Find method. FindResults findresults = findservicesoap.Find(findspecification);
Result information
Usually, all find methods returnFindResultobjects with location, entity, and best map view information, leaving it up to you to pick and choose what information you need and don’t need. If you are looking only for latitude/longitude information for one particular place, get only that information by filtering all other information using theFindResultMaskenumeration:
//Create a FindServiceSoap object. FindServiceSoap findservicesoap = new FindServiceSoap(); //Assign credentials go here. . . . //Create a FindSpecification object. FindSpecification findspecification = new FindSpecification(); //Assign a valid data source name. findspecification.DataSourceName = "MapPoint.NA"; //Specify a place to find. findspecification.InputPlace = "Redmond, WA"; //Set ResultMask to retrieve only map view information. findspecification.Options.ResultMask = FindResultMask.BestMapViewFlag; //Invoke the Find method. FindResults findresults = findservicesoap.Find(findspecification);
Limit entity information
Even though this is only applicable to point of interest methods, such asFindNearby,FindById,FindByProperty, andFindNearRoute, it is always a good practice to request entity attributes using theFindFilterobject. This object has a property,PropertyNamesof type string array, that allows you to define which attributes you want to see on returned entities. If an entity (such as a coffee shop) has 200 properties and you plan to use only 2 properties (say, Name and PhoneNumber), you can specify that in yourFindNearbyrequest using theFindFilter.PropertyNamesso that your response SOAP XML contains only 2 properties instead of all 200. The only caveat to this approach is that your definition for property names must also include the names used in filter expression in theFindFilterExpression.Expressionobject. The following code snippet shows the usage of theFindFilter.PropertyNamesproperty:
//Declare a find nearby specification object //and assign all required information FindNearbySpecification findNearbySpec = new FindNearbySpecification(); findNearbySpec.DataSourceName = "MapPoint.FourthCoffeeSample"; findNearbySpec.Distance = 1; findNearbySpec.LatLong = new LatLong(); findNearbySpec.LatLong.Latitude = 47.6; findNearbySpec.LatLong.Longitude = -122.33; findNearbySpec.Filter = new FindFilter(); findNearbySpec.Filter.EntityTypeName = "FourthCoffeeShops"; //Minimize the properties on returned entities by //specifying the property names field //Define the properties you plan to use //in your application string[] returnProperties = new string[2]; returnProperties[0] = "Name"; returnProperties[1] = "Phone"; //Assign it to find nearby specification findNearbySpec.Filter.PropertyNames = returnProperties;
Whenever possible, try to apply proper metadata for your find queries, including applying proper entity type information and adding search contexts. For example, if you are searching for Redmond, WA in the United States, you can improve your application’s performance by adding the entity type information of PopulatedPlace (using the entity type name for cities means that you are looking only for a city) and a context of 244 (the entity ID of the United States narrows your search to the United States) during your find call:
//Create a find specifications object FindSepcification findspecification = new FindSepcification();
//Assign the EntityTypeNames value findspecification.EntityTypeNames = new string[] {"PopulatedPlace"};
//Assign search context findspecification.Options = new FindOptions(); //Add context for United States findspecification.Options.SearchContext = 244;
Use Asynchronous Programming Patterns
Since any web service call involves a network round-trip, using asynchronous programming improves the user’s experience dramatically. With MapPoint Web Service Find Service, you can use asynchronous programming paradigms provided by Microsoft .NET Framework. If you are building a web or Windows application using MapPoint Web Service, you can use the MapPoint Web Service asynchronous methods to perform tasks such as Find or FindAddress; however, in order to use asynchronous methods, you use the Begin and End pair methods instead of the actual method itself. For example, to call the Find method in asynchronous patterns, use the BeginFind and EndFindmethods, which internally use theSoapHttpClientProtocolobject’sBeginInvokeandEndInvokemethods.
When you are building an enterprise-level application using MaPoint Web Service, obviously performance is not the only thing you need to keep in mind; you also need to think about supporting multiple languages and cultures, so globalizing your applications to support local languages is an essential part of your application. In the next section, let's see how to leverage some of the MapPoint Web Service features to build global applications.
Globalizing Find
MapPoint Web Service currently supports 10 different languages, meaning that when you use the Find Service with a desired (and supported) language, the Find results are returned in that language. It is important to note that in the case of the Find service, only city names and other entity information are localized to display in that specific language.
Table 6-10 shows the list of languages currently supported in MapPoint Web Service:
Table 6-10. Languages supported in MapPoint Web Service
Language
Language Code
Lcid
Dutch
nl
19
English
en
9
English-United States
en-us
1033
French
fr
12
German
de
7
Italian
it
16
Portuguese
pt
22
Spanish
es
10
Swedish
sv
29
The data sourcesMapPoint.MoonandMapPoint.Worldsupport all of these languages as well as Japanese (language code ja and LCID 1041).
To send your desired language information to the MapPoint Web Service during your Find Service calls, MapPoint Web Service provides SOAP Headers. With Find Service, these settings use theFindServiceSoap.UserInfoFindHeaderValuefield. TheUserInfoFindHeaderValueis of typeUserInfoFindHeader, and it and provides fields to set values for the location search context (theUserInfoFindHeader.Contextfield), user preferred culture (theUserInfoFindHeader.Culturefield), and default distance unit as eithermilesorkilometers(theUserInfoFindHeader.DefaultDistanceUnit field). To use Find Service with a search context for Canada (whose country entity ID is 39) with a preferred language of French (name fr), you would have to provide the user information header to theFindServiceSoap:
//Create a find service soap object FindServiceSoap findService = new FindServiceSoap(); //Create a user header UserInfoFindHeader userInfoFindHeader = new UserInfoFindHeader(); //Set the country context to Canada userInfoFindHeader.Context = new CountryRegionContext(); userInfoFindHeader.Context.EntityID = 39;
//Set the language preference userInfoRenderHeader.Culture = new CultureInfo(); userInfoRenderHeader.Culture.Name = "fr";
//Then assign the header to the find service proxy findService.UserInfoFindHeaderValue = userInfoFindHeader;
The entity ID and language name are assigned to the find header before making any find calls. To set the desired culture information, you could also use theLcidID for French instead of using the language name. One thing to remember while using the user information header to obtain localized information from Find Service is that the addresses returned by theFindServiceSoap.FindAddressmethod are never localized.
Where Are We?
Find Service is one of the core components of the MapPoint Web Service, providing many features such as finding places, addresses, and points of interest around a given location. Find Service also provides a way to convert any latitude/longitude information to an entity that contains the geographic information about that given point; it also provides necessary tools to parse addresses to see whether a given string is a place or an address so that you can call the appropriate method to find information.
Since Find Service methods are web service methods, it is important to think about performance optimization and asynchronous programming patters where applicable. Finally, Find Service also provides a way to get information in a specific localized language (among the 10 supported languages).
In the next chapter, we’ll look at MapPoint Web Service Route and Render Service components.