This article is written for an old version of the Virtual Earth platform. While still available for reference purposes, it is unlikely to work if implemented.
This article starts with the web page created at the end of the Getting Started Part 2 article. In this article we will create a popup context menu to set the start and end points for a journey. We will then discover how to build the code to allow us to retrieve the directions for the journey. Finally we will plot out the route on the map.
This article is also available in PDF format.
Getting the start and end points
In order to get the start and end point of the journey we are going to use a popup menu that will allow the user to set the start and end point and then get the driving directions.
The first step to getting a popup menu to display is to define some styles for how the menu will look. We will do this at the top of the HTML file but in a real world application it would be preferable to use a CSS file.
ul, li
{margin:0;padding:0;}
ul.pmenu
{
position:absolute;
margin: 0;
padding: 1px;
list-style: none;
width: 150px; /* Width of Menu Items */
border: 1px solid #ccc;
background:white;
display:none;
z-index:10;
}
ul.pmenu li
{ position: relative; }
/* Styles for Menu Items */
ul.pmenu li a
{
display: block;
text-decoration: none;
color: black;
padding: 2px 5px 2px 20px;
}
ul.pmenu li a:hover
{
background:#335EA8;
color:white;
}
/* IE \*/
* html ul.pmenu li { float: left; height: 1%; }
* html ul.pmenu li a { height: 1%; }
* html ul.pmenu li ul {left:147px;}
/* End */
In the body section of the HTML code we can now define the menu as:
<ul id="popupmenu" class="pmenu">
<li><a href="#" onclick='setstart()'>Set Start</a></li>
<li><a href="#" onclick='setend()'>Set End</a></li>
<li><a href="#" onclick="getdirections()">Get Directions</a></li>
</ul>
You can see we have define 3 methods that will get called when the menu items get clicked. We will need to implement those.
First let�s implement the code needed to show and hide the menu.
function removepopupmenu(e)
{
var menu = document.getElementById('popupmenu').style.display='none';
}
var popuplat;
var popuplon;
function popupmenu(e)
{
var menu = document.getElementById('popupmenu');
menu.style.display='block'; //Showing the menu
popuplat = e.view.latlong.latitude;
popuplon = e.view.latlong.longitude;
menu.style.left = map.GetX(popuplon)+10; //Positioning the menu
menu.style.top = map.GetY(popuplat)+50;
}You will notice that the location for the popup menu is being stored in 2 global variables; popuplat and popuplon. We will use these in the setstart and setend methods to place the pushpins on the map.
var startpt;
var endpt;
function setstart()
{
map.RemovePushpin('start');
map.AddPushpin('start',
popuplat,
popuplon,
100,
34,
'pin',
'Start',
1);
startpt = new Msn.VE.LatLong();
startpt.latitude = popuplat;
startpt.longitude = popuplon;
}
function setend()
{
map.RemovePushpin('end');
map.AddPushpin('end',
popuplat,
popuplon,
88,
34,
'pin',
'End',
1);
endpt = new Msn.VE.LatLong();
endpt.latitude = popuplat;
endpt.longitude = popuplon;
}
In order to display the menu we need to attach the popupmenu function to the oncontextmenu event that is raised on the mapcontrol. We can also attach the removepopupmenu function to the onclick event so that the menu is hidden when the user clicks elsewhere on the map.
Attaching the events should happen in the method called by onLoad, in this example it is the OnPageLoad function.
map.AttachEvent("onclick", removepopupmenu);
map.AttachEvent("oncontextmenu", popupmenu);
We now have a popup menu that appears when the user right clicks on the map. This menu allows the user to set a start and end point for the route for which they want directions.
Building the Directions Code
In order to retrieve the directions for the route we will need to build a server side component that can query the directions from the Windows Live Local directions component. This is similar to building the proxy code for the Search or Birds Eye that can be found on in the MSDN documentation.
Microsoft�s directions code is located on the Local.Live.com site: http://local.live.com/directions.ashx.
The directions.ashx page takes 4 parameters; the start latitude, start longitude, end latitude and end longitude. It then returns the JScript code to execute on the local.live.com site. We will need to extract the information from this code to use on our page.
For this example we will build the server side code using C# in ASP.NET 1.1.
Driving directions consist of a set of instructions, each instruction is for a particular location. This can be represented by a simple class in C#:
public class RoutePoint
{
public string instruction;
public decimal lat;
public decimal lon;
}
The first thing we will do in our directions code is create an HttpWebRequest for the local.live.com/directions.ashx page.
private HttpWebRequest CreateDirectionsRequest()
{
HttpWebRequest dirRequest = (HttpWebRequest)WebRequest.Create("http://local.live.com/directions.ashx");
dirRequest.Method = "POST";
dirRequest.ServicePoint.Expect100Continue = false;
dirRequest.ContentType = "application/x-www-form-urlencoded";
return dirRequest;
}
We can then write a method that will place the results from our call to the directions code into a string.
private string GetDirectionResults(HttpWebRequest dirRequest)
{
string results = string.Empty;
HttpWebResponse dirResponse;
dirResponse = (HttpWebResponse)dirRequest.GetResponse();
Stream receiveStream = dirResponse.GetResponseStream();
System.Text.Encoding encode = System.Text.Encoding.GetEncoding("utf-8");
StreamReader readStream = new StreamReader(receiveStream, encode);
Char[] read = new Char[256];
// Reads 256 characters at a time.
int count = readStream.Read( read, 0, 256 );
while (count > 0)
{
String str = new String(read, 0, count);
results += str;
count = readStream.Read(read, 0, 256);
}
dirResponse.Close();
return results;
}
We can then use these two methods to call the Microsoft directions code and return the results in a string that we can work with.
private string BuildParams()
{
string dirParams = string.Empty;
dirParams += "&startlat=" + startLat + "&startlon=" + startLon;
dirParams += "&endlat=" + endLat + "&endlon=" + endLon;
return dirParams;
}
private string GetDirections()
{
string results = string.Empty;
string dirParams = BuildParams();
HttpWebRequest dirRequest = CreateDirectionsRequest();
System.Text.UTF8Encoding encoding = new System.Text.UTF8Encoding();
byte[] bytes = encoding.GetBytes(dirParams);
dirRequest.ContentLength = bytes.Length;
Stream requestStream = dirRequest.GetRequestStream();
requestStream.Write(bytes, 0, bytes.Length);
requestStream.Close();
try
{
results = GetDirectionResults(dirRequest);
}
catch(Exception ex)
{
//not a good idea for production system - log it
Console.Out.WriteLine(ex.Message);
}
return results;
}
Breaking down the route
We can extract the route instructions from the directions string (that contains Jscript specific for Local.live.com) returned from the previous method calls. We are going to do it using a simple string search and replace. (There are probably smarter and more efficient ways to this, but the aim is to explain what we are doing here).
private ArrayList routePoints;
private ArrayList GetRoutePoints(string directions)
{
routePoints = new ArrayList();
// look for new VE_RouteInstruction
string routePtStart = "new VE_RouteInstruction('";
int currentIndex = 0;
do
{
currentIndex = directions.IndexOf(routePtStart, currentIndex);
if (currentIndex!=-1)
{
RoutePoint routePt = new RoutePoint();
int startIndex = currentIndex+routePtStart.Length;
int endIndex = directions.IndexOf("'",startIndex);
routePt.instruction =
directions.Substring(startIndex,
endIndex - startIndex);
startIndex = directions.IndexOf(',', endIndex);
startIndex++;
endIndex = directions.IndexOf(',', startIndex);
routePt.lat = decimal.Parse(directions.Substring(startIndex,
endIndex-startIndex));
startIndex = directions.IndexOf(',', endIndex);
startIndex++;
endIndex = directions.IndexOf(',', startIndex);
routePt.lon = decimal.Parse(directions.Substring(startIndex, endIndex-startIndex));
routePoints.Add(routePt);
currentIndex = endIndex;
}
}
while(currentIndex >0);
return routePoints;
}
We now have the code to create an array list of RoutePoint objects that we can use to plot on the map.
Plotting the route points
We are going to use the same technique that Microsoft uses to display the driving direction on the map. The local.live.com/directions.ashx page returns Jscript that can be executed (using the eval function) on the client side. From the array of RoutePoint objects we are going to build the Jscript to populate the panel and place the points on the map.
private string startLat;
private string startLon;
private string endLat;
private string endLon;
private void Page_Load(object sender, System.EventArgs e)
{
if(Request["startLon"] != null
&& Request["startLat"] != null
&& Request["endLon"] != null
&& Request["endLat"] != null
)
{
Response.Clear();
startLat = Request["startLat"];
startLon = Request["startLon"];
endLat = Request["endLat"];
endLon = Request["endLon"];
string directions = GetDirections();
routePoints = GetRoutePoints(directions);
//first output the jscript to add to the panel
int index = 1;
Response.Write("p.body.innerHTML = \" ");
foreach(RoutePoint routePt in routePoints)
{
string direction = string.Empty;
direction +=
"<a href='javascript:map.SetCenterAndZoom(";
direction += routePt.lat.ToString()
+ "," + routePt.lon.ToString();
direction += ", 18);'>" + index.ToString();
direction += "</a>: "
+ routePt.instruction + "<br/>";
Response.Write(direction);
index++;
}
Response.Write("\";");
//now write out the jscript to add the route points
index = 1;
foreach(RoutePoint routePt in routePoints)
{
Response.Write("map.AddPushpin('" + index.ToString() + "',");
Response.Write(routePt.lat.ToString() + "," + routePt.lon.ToString() );
Response.Write(",30, 34, 'pin', ");
Response.Write(index.ToString() + ",40);");
index++;
}
}
}
The full listing for our directions.aspx code can be found here http://www.viavirtualearth.com/MyVirtualEarth/v2/directions.aspx.cs.txt.
Putting it on the Map
The final step is to call our new directions code and run the code returned (eval) to fill the panel and place the points on the map.
var xmlhttp=false;
function getdirections()
{
if (startpt && endpt)
{
try
{
xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
}
catch (e)
{
try
{
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
}
catch (E)
{
xmlhttp = false;
}
}
if (!xmlhttp && typeof XMLHttpRequest!='undefined')
{
xmlhttp = new XMLHttpRequest();
}
xmlhttp.onreadystatechange=directionsHandler;
xmlhttp.open("GET","Directions.aspx?startLat="
+ startpt.latitude
+ "&startLon="
+ startpt.longitude
+ "&endLat="
+ endpt.latitude
+ "&endLon="
+ endpt.longitude
,true);
xmlhttp.send(null);
}
else
{
alert("you need to set a start and end point");
}
removepopupmenu(0);
}
function directionsHandler()
{
if (xmlhttp.readyState==4)
{
ClearPanel();
//remove any existing pushpins
//(could be left from previous directions search)
map.ClearPushpins();
//add the start and end back to the map
map.AddPushpin('start',
startpt.latitude,
startpt.longitude,
100,
34,
'pin',
'Start',
40);
map.AddPushpin('end',
endpt.latitude,
endpt.longitude,
100,
34,
'pin',
'End',
40);
var directions = xmlhttp.responseText;
//use this to examine the returned JScript
//alert(directions);
eval(directions);
}
}
Conclusion
We have now added the ability to get driving directions between 2 locations and plot them on the map. The full sample can be viewed here http://www.viavirtualearth.com/MyVirtualEarth/v2/gettingstartedpt3.htm
One thing that is missing is the ability to plot the path of the route along the roads. This is more complicated than simply drawing lines between the points as roads are generally not that straight. If you require functionality such as plotting the lines you can either delve deeper into the local.live.com directions code or (and I would recommend this) explore a product such as MapPoint Web Service (http://msdn.microsoft.com/MapPoint) that provides this functionality.