Thanks to everyone who attended my Virtual Earth / Live.com Search speaking session and visited me at the Live.com booth @ Tech Ed this year. During my discussion I had promised I would post my app code for converting SOAP XML to JavaScript, so you'll find step-by-step instructions below. I had originally written this on the B2B blog and have since updated it for Version 5 of the API. Specifically, this application will show you how to use MapPoint Web Service's FindNearRoute() method to find the nearest locations with .1 miles of a route then display the results on a Virtual Earth map. Enjoy!

An application hitting MapPoint Web Service (MWS) for algorithmic searching (in order to be presented atop Virtual Earth (VE) requires some code-behind programming. The following example will illustrate how to do this in C#.

The application consists of several parts, some of which can be consolidated for ease of use but I broke them up as this would be the best architecture for application develpment. Also, I should not that I don't do much error handling and you definately want to add that! This is quick and dirty - it is a blog after all. :)

First thing's first - you need a MapPoint Web Service account. Get a free one here.

Once you get your account setup, you'll need to download the WSDL which you can get here. Also, if you get stuck anywhere here's a link to the online SDK for ASP .Net and here's one for those of you who don't use ASP .Net.

So, once you have your WSDL setup you'll want to have a few files as a part of your project.

Default.aspx - This is where you'll display your results on the Virtual Earth map.
MWSServices.cs - Class file for storing methods to connect to MWS.
MWSVE.js - A JavaScript file for wrapping and parsing MWS requests.
FindNearRoute.aspx - The front end for our code behind file - we won't actually use this, but we need it.
FindNearRoute.aspx.cs - Class file storing most of the MWS code.

I've documented much of the project inline for better use. The flow of the application goes like this -

(1) Users are dropped onto a web page with 2 input boxes and a VE map.

(2) Users enter two postal codes (origin and destination). That information is sent to the MWSVE.js file.

(3) The MWSVE.js file will create a URL for posting to FindNearRoute.aspx file.

(4) FindNearRoute.aspx.cs will parse the URL and process the postal codes to MWS (via the MWSServices.cs file). The file will geocode the two points, calculate the route, then calculate the locations along the route. Once the calculation is complete, I create a StringBuilder to post to the MWSVE.js file.

(5) The MWSVE.js file will parse the StringBuilder and post it (eval) to default.aspx and viola we have a map with a route and locations along the route.

////////////////////////////////////////
//Default.aspx
////////////////////////////////////////
<%@ Page Language="C#" AutoEventWireup="true"  CodeFile="Default.aspx.cs" Inherits="_Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<!--
<script runat="server">

</script>
-->
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
<!--PUSHPIN STYLE SHEET-->
      <style type="text/css">
      .titleStyle
      {
         font-family:Verdana;
         font-size:12pt;
         font-weight:bold;
      }
      .iconStyle
      {
         position:relative;
         top:-15px;
      }
      .detailsStyle
      {
         font-family:Verdana;
         font-size:10pt;
         font-weight:normal;
         text-align:left;
      }
      </style>
<title>Sample Virtual Integration Page</title>     
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">     
<script src="http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5"></script>
<script src="MWSVE.js"></script>
<!-- Get Map and load onto page-->
<script>     

  var map = null;                 
  function GetMap()     
  {        
    map = new VEMap('myMap');        
    map.LoadMap();     
  }        

</script>
</head>
<body onload="GetMap();">     
    <br />
    <form id="form1" runat="server">
<!--Plain old text input box for Origin-->
      Origin: <asp:TextBox ID="TextBox1" runat="server" Text="98052"></asp:TextBox>
<!--Plain old text input box for Destination-->
      Destination: <asp:textbox ID="TextBox2" runat="server" Text="90210"></asp:textbox>
<!--Plain old button input type-->
      <input type=button onclick="ShowRoute();FindNearRoute();" ID="Button1" value="Find Locations!" />
    </form>
    <div id='myMap' style="position:absolute; width:800px; height:600px;"></div>
</body>
</html>

////////////////////////////////////////
//MWSServices.cs
////////////////////////////////////////

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using MWSStaging; //WSDL file name

/// <summary>
/// Summary description for MWSServices
/// </summary>
public class MWSServices
{
    public static FindServiceSoap findservice()
    {
        FindServiceSoap findService = new FindServiceSoap();
        findService.Credentials = new System.Net.NetworkCredential(Constants.batman, Constants.robin);
        findService.PreAuthenticate = true;
        findService.UserInfoFindHeaderValue = new UserInfoFindHeader();
        findService.UserInfoFindHeaderValue.DefaultDistanceUnit = DistanceUnit.Mile;
        return findService;
    }

    public static RouteServiceSoap routeservice()
    {
        RouteServiceSoap routeService = new RouteServiceSoap();
        routeService.Credentials = new System.Net.NetworkCredential(Constants.batman, Constants.robin);
        routeService.PreAuthenticate = true;
        routeService.UserInfoRouteHeaderValue = new UserInfoRouteHeader();
        routeService.UserInfoRouteHeaderValue.DefaultDistanceUnit = DistanceUnit.Mile;
        return routeService;
    }

    public static RenderServiceSoap renderservice()
    {
        RenderServiceSoap renderService = new RenderServiceSoap();
        renderService.Credentials = new System.Net.NetworkCredential(Constants.batman, Constants.robin);
        renderService.PreAuthenticate = true;
        renderService.UserInfoRenderHeaderValue = new UserInfoRenderHeader();
        renderService.UserInfoRenderHeaderValue.DefaultDistanceUnit = DistanceUnit.Mile;
        return renderService;
    }
}

////////////////////////////////////////
//MWSVE.js
// JScript File
////////////////////////////////////////

//Create an ActiveXObject
function GetXmlHttp()
{
 var x = null;
 try { x = new ActiveXObject("Msxml2.XMLHTTP") }
 catch(e)  { try { x = new ActiveXObject("Microsoft.XMLHTTP") }
 catch(e)  { x = null } }
 if(!x && typeof XMLHttpRequest != "undefined") { x = new XMLHttpRequest() }
 return x;
}

//Parse the response from FindNearRoute.aspx.cs and evaluate
function MWSRequest(url)
{
 var xmlhttp = GetXmlHttp();
 if(xmlhttp)
 {
  xmlhttp.open("GET", url, true);
  xmlhttp.onreadystatechange=function()
  {
   if(xmlhttp.readyState==4)
   {
     var html = xmlhttp.responseText;
     eval(html);
   }
  }
  xmlhttp.send(null);
 }
}


//Create a URL string that will hit FindNearRoute.aspx
function FindNearRoute()
{
  var url = "FindNearRoute.aspx?";
  var o = document.getElementById('TextBox1');
  url += "opc=" + o.value;
  var d = document.getElementById('TextBox2');
  url += "&dpc=" + d.value;
  MWSRequest(url);
}

//Show route using Virtual Earth method (not hitting MWS)
function ShowRoute()
{
  var routeOrigin = document.getElementById('TextBox1');
  var routeDestination = document.getElementById('TextBox2');
  var rtRouteOrigin = routeOrigin.value;
  var rtRouteDestination = routeDestination.value;
  map.GetRoute(rtRouteOrigin, rtRouteDestination, VEDistanceUnit.Miles, VERouteType.Quickest);
}

////////////////////////////////////////
//FindNearRoute.aspx - note that I deleted all of the default code but left the header.
////////////////////////////////////////
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="FindNearRoute.aspx.cs" Inherits="FindNearRoute" %>

////////////////////////////////////////
//FindNearRoute.aspx.cs
////////////////////////////////////////
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using MWSStaging;
using System.Text;

public partial class FindNearRoute : System.Web.UI.Page
{
    /*Instance of Find and Route Service respectively connecting to MapPoint Web Service
    * via the MWSServices.cs file. We need this globally. */
    FindServiceSoap findService = MWSServices.findservice();
    RouteServiceSoap routeService = MWSServices.routeservice();
   
    /*We don't have any code on the .aspx portion of this file because we aren't using it.
    * We want to load this function as soon as the method requests it from js methods */
    protected void Page_Load(object sender, EventArgs e)
    {
        //Grab the origin and destination from the URL parameters
        string origin = Request["opc"];
        string destination = Request["dpc"];

        /*Get the coordinates for the origin and destination postal codes using
        * the custom GeocodePostalCodes() method */
        double[] dblGeocodedOrigin = GeocodePostalCodes(origin);
        double[] dblGeocodedDestination = GeocodePostalCodes(destination);
       
        /* We'll use the coordinates from the postal codes to create a route.
         * We need this in order to calculate a route using MapPoint Web Service. We
         * do this because the algorithm for finding points along a route is included
         * in MWS. The coordinates need to be included in the LatLong[] in order to
         * be passed to CalculateSimpleRoute.*/
        LatLong[] routePoints = new LatLong[2];
        routePoints[0] = new LatLong();
        routePoints[0].Latitude = dblGeocodedOrigin[0];
        routePoints[0].Longitude = dblGeocodedOrigin[1];
        routePoints[1] = new LatLong();
        routePoints[1].Latitude = dblGeocodedDestination[0];
        routePoints[1].Longitude = dblGeocodedDestination[1];

        /* Request the route from MWS. We could also use CalculateRoute(), but I have a simple
         * route and don't need the segment information. I'll pass in the coordinates,
         * the geographic regions (MapPoint.NA is North America), and the route calculation
         * preference which is either Quickest or Shortest. You'll want to make sure
         * this aligns with the segment preference you set in your VE route, as well*/
        Route myRoute = routeService.CalculateSimpleRoute(routePoints, "MapPoint.NA", SegmentPreference.Quickest);

        /* We need to find the locations along the route. We do that by creating a routespecifiction.
         * This is not a geographic request since we want to find YOUR CUSTOM locations which
         * you've uploaded into MWS. The FourthCoffee data source is available to everyone
         * with an MWS account and can be used for demo purposes.*/
        FindNearRouteSpecification findNearRouteSpec = new FindNearRouteSpecification();
        findNearRouteSpec.DataSourceName = "MapPoint.FourthCoffeeSample";
        /* Distance from the route in a cylindrical shape following the route. I've
         * set this in mile in the Constants.cs file. It can be changed to kilos as well. */
        findNearRouteSpec.Distance = .1;
        /* Filter is required to specify your EntityTypeName. You're allowed many EntityTypeNames
         * per data source. Think of a data source as a data base and your EntityTypeName
         * as your tables.*/       
        findNearRouteSpec.Filter = new FindFilter();
        findNearRouteSpec.Filter.EntityTypeName = "FourthCoffeeShops";
        /* FindOptions are just that - optional. MWS has defaults for all of these, but you
         * can extend the functionality by specifying values.
         * Options.Range.Count is the most amount of records you want returned in the response.
         * Route is the route object created above my MWS */
        findNearRouteSpec.Options = new FindOptions();
        findNearRouteSpec.Options.Range = new FindRange();
        findNearRouteSpec.Options.Range.Count = 500;
        findNearRouteSpec.Route = myRoute;

        //Now send your routespecification to MWS to get the locations along a route.
        FindResults myFindNearRouteResults = findService.FindNearRoute(findNearRouteSpec);

        /* Take the results for locations near a route and display them on a VE map.
         * There are many ways to do this, but I prefer a StringBuilder. First, I want to delete
         * any pushpins that may be on the map from a previous query. */
        //StringBuilder sbPins = new StringBuilder();
        StringBuilder sbPointsAlongRoute = new StringBuilder();
        //Delete any previous layers
        sbPointsAlongRoute.Append("map.DeleteAllShapeLayers();");
        /* Now, we just build the VEShape object for Virtual Earth and loop through each record
         * in the MWS response. */
        sbPointsAlongRoute.Append("var myPinArray = new Array(");

        //Create array of lat/lons from results
        for(int i = 0; i < myFindNearRouteResults.Results.Length; i++)
        {
            sbPointsAlongRoute.Append("new VELatLong("
            + myFindNearRouteResults.Results[i].FoundLocation.LatLong.Latitude
            + ", "
            + myFindNearRouteResults.Results[i].FoundLocation.LatLong.Longitude
            + ")");
            if(i < (myFindNearRouteResults.Results.Length-1))
            {
                sbPointsAlongRoute.Append(",");
            }
            else
            {
                sbPointsAlongRoute.Append("");
            }
        }
        sbPointsAlongRoute.Append(");");

        sbPointsAlongRoute.Append("for(x in myPinArray){");
        for (int k = 0; k < myFindNearRouteResults.Results.Length; k++)
        {
         //Loop through points creating a shape for each
         //Instantiate the VE Shape - Pins
         sbPointsAlongRoute.Append("var myVEShapePins = new VEShape(VEShapeType.Pushpin, myPinArray[x]);"
         //Set the title for each pushpins
            + "myVEShapePins.SetCustomIcon('http://demo.mappoint.net/tap/html/111-8,8.gif');"
            + "myVEShapePins.SetTitle('Station "
            + Convert.ToString(Math.Abs(myFindNearRouteResults.Results[k].FoundLocation.Entity.ID))
            + "');"
            + "myVEShapePins.SetDescription('<img src=http://www.tfarch.com/cave-gas-low.jpg width=211 height=119><br>"
            + myFindNearRouteResults.Results[k].FoundLocation.Entity.Properties[0].Value.ToString()
            + "<br>" + myFindNearRouteResults.Results[k].FoundLocation.Entity.Properties[1].Value.ToString()
            + ", " + myFindNearRouteResults.Results[k].FoundLocation.Entity.Properties[3].Value.ToString()
            + " " + myFindNearRouteResults.Results[k].FoundLocation.Entity.Properties[5].Value.ToString()
            + "<br>" + myFindNearRouteResults.Results[k].FoundLocation.Entity.Properties[6].Value.ToString()
            + "<br>Manager: " + myFindNearRouteResults.Results[k].FoundLocation.Entity.Properties[7].Value.ToString()
            + "');"
            //Add Pins to the map
         + "map.AddShape(myVEShapePins);");
        }
            sbPointsAlongRoute.Append("}");
       
        //Finally, write the javascript into the page.
        Response.Write(sbPointsAlongRoute);
    }

    /* The following method will request lat/long coordinates from
     * MWS based on a postal code input from the user.*/
    public static double[] GeocodePostalCodes(string postalcode)
    {
        //We use Find service to get postal codes from MWS
        FindServiceSoap findService = MWSServices.findservice();
        //Setup a FindSpecification to create the MWS request
        FindSpecification findSpec = new FindSpecification();
        //This is a geographic search, so we need to specify North America
        findSpec.DataSourceName = "MapPoint.NA";
        //Input postal code
        findSpec.InputPlace = postalcode;
        findSpec.Options = new FindOptions();
        findSpec.Options.Range = new FindRange();
        /* I only need a single lat/long coordinate, so I set Options.Range.Count to 1.
         * This will help with performance by limited the response packet size. */
        findSpec.Options.Range.Count = 1;
        /* I also ONLY need the lat/long coordinate, so I can specify that here. This
         * also help performance be further limiting the response packet size. */      
        findSpec.Options.ResultMask = FindResultMask.LatLongFlag;
        // SearchContext specifies the country. 244 is the US.
        findSpec.Options.SearchContext = 244;
        //Threshold score is an accuracy rating system
        findSpec.Options.ThresholdScore = .85;

        FindResults myResults;
        //Request the response from MWS by sending the FindSpecification
        myResults = findService.Find(findSpec);

        //Place results into a simple array for return to the load function.
        double[] coordinates = new double[] {myResults.Results[0].FoundLocation.LatLong.Latitude, myResults.Results[0].FoundLocation.LatLong.Longitude};
        return coordinates;
    }
}

 

CP