Johannes's profileHannes's Virtual Earth B...BlogListsSkyDrive Tools Help

Hannes's Virtual Earth Blog

November 13

Bing Maps at TechEd Europe

On behalf of our friends from Borchert GeoInfo and the Bing Maps team I would like to thank everybody who visited us at the booth during the TechEd in Berlin or attended one of my sessions. Some of you asked for my presentations and the sample code. You will find everything here on my SkyDrive:

Have a look at the Readme.pdf if you are unsure about what you need.

November 08

Bing Maps at TechEd Europe

Tomorrow the TechEd Europe opens it’s gates in Berlin and Bing Maps will be represented as well. Chris Pendleton will come over from Redmond and join us for the week and I have the privilege of presenting 2 sessions on Bing Maps:

  1. WIA02-IS Bing Maps Silverlight Control, Location Intelligence, and Microsoft SQL Server 2008
    Tue 10th of November 13:30-14:45 Interactive Theatre 2 – Orange
  2. WIA306 Enhancing the Mapping Experience with Microsoft Bing Maps
    Thu 12th of November 17:00-18:15 New York 3 - Hall 7-1a

Here is a quick Bing Maps Collection with some MapCruncher layers that shows the venue and helps you find the Bing Maps booth and the locations for my sessions :-) You can also jump straight into a 3D-tour.

image

I’m looking forward to see some of you in Berlin.

September 02

Bing Maps & SQL Server 2008 R2 Reporting Services

SQL Server 2008 R2 is the next generation of the Microsoft SQL Server platform. The release is planned for the first half of calendar year 2010 but for those who can’t wait there is as always a community technology preview (CTP). The August CTP has lots of new features and from the mapping perspective the most interesting one is the integration of a maps in SQL Server Reporting Services. With just a few mouse-clicks you can generate thematic maps from spatial data stored as GEOMTRY or GEOGRAPHY data types in SQL Server 2008 or from ESRI SHP-files and you can use Bing Maps roads, aerial or hybrid images as background data.

If you are as nosy as me, you can download the CTP here. Check it out it’s really incredible simple.

image

August 21

Bing Maps & Wikipedia

If you have been using the “Explore Collections” feature in the consumer facing implementation of Bing Maps before you may have wondered if it is possible to get this feature into your own Bing Maps implementation as well. Indeed that is possible and there is a quite simple approach. In the following walkthrough we will get specifically Wikipedia content into our Bing Maps.

Let’s start with a closer look how the consumer side does it:

If we go to Bing Maps and search for a location like “Tower of London” we’ll find that we can explore collections for this location.

image

These collections are basically a whole lot of community content that was created in Bing Maps collections, is available as GeoRSS, KML, KMZ or GPX on the internet and was found by the crawlers or is integrated from Wikipedia and Photosynth. We can filter this content, apply different sort criteria such as distance and then we could subscribe to an RSS-feed with the results.

image

A closer look at the RSS-feed will show that it is in fact a GeoRSS-feed and we know of course that we can import GeoRSS-feeds into Bing Maps using the VEMap.ImportShapeLayerData-method.

image

What we really want is however not a static feed, we want to update the results when we pan or zoom the map so let’s have a closer look at the URL of the feed:

http://www.bing.com/maps/GeoCommunity.asjx?action=retrieverss&mkt=en-gb&ss=&bbox=-1.0073968023061534,51.42229465956134,-0.8444901555776242,51.50004927438254&startindex=0&order=distance&tag=Wikipedia

So in fact we are calling a web service that generates the GeoRSS-feed dynamically and the parameter bbox contains the bounding box with the South-West and North-East corner of the area for which we want to retrieve the data. Well that is simple enough to implement but there is one more thing to consider: If we call a GeoRSS-feed that is in a different domain we get an annoying security warning from our browser:

image

To avoid this security warning we can set up a proxy as described by Mike McDougall here.

Our HTML- and JavaScript code could look like this:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
   <head>
      <title></title>
      <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
      <script type="text/javascript" src="http://ecn.dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=6.2"></script>
      <script type="text/javascript">
          var map = null;

          //VEShapeLayer
          var slGeoRSS = new VEShapeLayer();

          function GetMap() {
              map = new VEMap('myMap');
              map.LoadMap(new VELatLong(51.508145,-0.07626), 17, 'h', false);
          }

          function AddShape(control) {
              if (document.getElementById(control).checked == false) {
                  //Delete all Shapes
                  slGeoRSS.DeleteAllShapes();

                  //Detach Map-Events
                  map.DetachEvent("onendpan", LoadData);
                  map.DetachEvent("onendzoom", LoadData);
              }
              else {
                  //Attach Map-Events
                  map.AttachEvent("onendpan", LoadData);
                  map.AttachEvent("onendzoom", LoadData);
                  LoadData();
              }
          }

          function LoadData() {
              map.DeleteAllShapes();

              //Retrieve the boundaries of the mapview
              var nePixel = new VEPixel(600, 0); //North-East corner of the map view
              var swPixel = new VEPixel(0, 400); //South West corner of the map view
              var neLatLon = map.PixelToLatLong(nePixel);
              var neLat = neLatLon.Latitude;
              var neLon = neLatLon.Longitude;
              var swLatLon = map.PixelToLatLong(swPixel);
              var swLat = swLatLon.Latitude;
              var swLon = swLatLon.Longitude;

              //Build URL to call the server
              var url = "./GeoRSS-Proxy.ashx?source=http://www.bing.com/maps/GeoCommunity.asjx?";
              url += "action=retrieverss&mkt=en-gb&ss=&bbox=";
              url += swLon + ",";
              url += swLat + ",";
              url += neLon + ",";
              url += neLat;
              url += "&startindex=0&order=distance&tag=Wikipedia";

              var veLayerSpec = new VEShapeSourceSpecification(VEDataType.GeoRSS, url, slGeoRSS);
              map.ImportShapeLayerData(veLayerSpec, onGeoRSSLoad, false);
          }

          function onGeoRSSLoad(a, b) {
              var numShapes = slGeoRSS.GetShapeCount();
              var numPoints = 0;
              for (var i = 0; i < numShapes; ++i) {
                  var s = slGeoRSS.GetShapeByIndex(i);
                  s.SetCustomIcon("IMG/wikipedia.gif");
              }
          }
      </script>
   </head>
   <body onload="GetMap();">
      <div id='myMap' style="position:absolute; top:0px; left:0px; width:600px; height:400px;"></div><br />
      <div id='divCtrl' style="position:absolute; top:400px; left:0px; width:600px;" >
        <input id="cbGeoRSS" type="checkbox" onclick="AddShape('cbGeoRSS')" /><a>Wikipedia</a><br />
      </div>
   </body>
</html>

And here is the proxy implemented as a Generic WebHandler

<%@ WebHandler Language="VB" Class="GeoRSS_Proxy" %>

Imports System
Imports System.Web
Imports System.Net
Imports System.IO

Public Class GeoRSS_Proxy : Implements IHttpHandler
    
    Public Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        Dim myUrl As String = ""
        myUrl = context.Request.QueryString(0)
        For i = 1 To context.Request.QueryString.Count - 1
            myUrl = myUrl + "&" + context.Request.QueryString.AllKeys(i) + "=" + context.Request.QueryString(i)
        Next
        'Dim source As String = context.Request.QueryString("source")
        context.Response.ContentType = "text/xml"
        context.Response.ContentEncoding = System.Text.Encoding.UTF8

        Dim request As HttpWebRequest = DirectCast(HttpWebRequest.Create(myUrl), HttpWebRequest)
        Dim response As HttpWebResponse = DirectCast(request.GetResponse(), HttpWebResponse)
        Dim stream As StreamReader = New StreamReader(response.GetResponseStream(), Encoding.ASCII)
        context.Response.Write(stream.ReadToEnd())
    End Sub
 
    Public ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

End Class

image

The sample code is available here:

July 01

Route Optimization in Bing Maps powered by OnTerra’s free Stop Optimization Service

OnTerra is a Microsoft Partner specialized on tailored Bing Maps solutions and focussing on but not limited to tracking and fleet management. Recently they launched a free beta version of a Stop Optimization Service. The service allows you to send an unlimited list of stops for your route and receive a string with the order of the stops optimized for the shortest driving distance. Let’s have a quick look at how it works:

Bing Maps supports out-of-the-box Multi-Waypoint Routing for up to 25 stops through the method VEMap.GetDirections. However, the routing algorithm processes the stops always in the order in which they appear in the array of locations. If we want to start for example a trip in the Microsoft Office in Reading and want to visit Swindon, Oxford, Maidenhead and Newbury before we return to the Microsoft office we have to know in which order we want to visit these cities. If we just send the list in the order mentioned above it will guide us from one location to the next in exactly this order and come up with a route that is 185 miles long and takes about 3 hours and 20 minutes of drive time.

image

The free OnTerra Stop Optimization Service figures out in which order we should drive for the shortest distance. In the example above it will suggest that we go to Maidenhead first, then Oxford, Swindon and Newbury. This will save us 45 miles and about 40 minutes of drive time.

image

That’s not bad at all but if you would like to use it for example as a dispatcher in a fleet management application you also need to consider the times when you can make a pickup or a delivery, you may want to optimize for shortest time rather than shortest distance or you may need to consider height and weight restrictions that apply to your trucks. This is not part of the free service but in addition to the free stop optimization, OnTerra also offers such advanced features for a fee. If you are interested in this type of advanced service contact routeopt@onterrasys.com for more details.

To use the free stop optimization service you will need to register and apply for a token. It requires 3 parameters:

  1. the locations which we want to optimize as a string, The string contains a label for the location and the latitude and longitude separated by a comma. Multiple locations are separated by a ‘#’.
  2. a Boolean parameter that indicates weather we do a roundtrip or a one-way trip
  3. our token

You see that we need to geocode the locations before we send them to the stop optimization service. In the sample application above I use the Bing Maps AJAX control and use the callback function for the VEMap.Find-method to concatenate a string with the locations as expected by the stop optimization service, e.g. “txtStop1,51.461179,-0.925943#txtStop2,51.561765,-1.781815#txtStop3,51.522375,-0.727256#txtStop4,51.405876,-1.325891#txtStop5,51.756205,-1.259490”.

Now here is one thing so consider: The optimization service splits the locations-string whenever it finds the character ‘#’. Unfortunately there appears to be a bug(?) which doesn’t process the string correctly when you work with the full number of decimal digits that comes back from the Bing Maps geocoder. In order to work around this bug(?) we can truncate the number of decimal digits to 6. This does actually not have a noticeable impact on the precision of the result but solves our problem.

Once we have our last location we call a JavaScript-function StopOpt which actually creates an AJAX-call

//Build String for Route Optimization
function AppendLocations(layer, resultsArray, places, hasMore, veErrorMessage) {
    i = i + 1;
    if (locations.length > 0) {
        locations=locations+"#"
    }
    locations = locations + "txtStop" + i + "," 
+ places[0].LatLong.Latitude.toFixed(6) + ","
+ places[0].LatLong.Longitude.toFixed(6); if (i == numStops) { StopOpt(); } }

The AJAX-call goes to a web handler which will call the stop optimization service and hands over the locations-string as well as a parameter that indicates if we’re doing a roundtrip or a one-way-trip. The optimized order of the result is received as a string and we process it a bit before we call the VEMap.GetDirections method.

function StopOpt() {
    //Build URL to call the server
    var url = "./06-StopOpt.ashx?";
    url += "locations=" + locations;

    if (document.getElementById("cbRoundtrip").checked == true) {
        url += "&roundtrip=true"
    }
    else {
        url += "&roundtrip=false"
    }

    //Get the appropriate XMLHTTP object for the browser
    var xmlhttp = GetXmlHttp();

    //if we have a valid XMLHTTP object
    if (xmlhttp) {
        xmlhttp.open("GET", url, true); // varAsynx = true

        //set the callback
        xmlhttp.onreadystatechange = function() {
            if (xmlhttp.readyState == 4) //4 is a success
            {
                //web service returns the optimized order of the stops
                var result = xmlhttp.responseText
                var stopArray = result.split(" >> ");
                var stops = new Array();
                var order = "Order (Optimized):<br>";
                for (var i = 0; i < stopArray.length; ++i) {
                    order = order + document.getElementById(stopArray[i]).value + "<br>";
                    stops.push(document.getElementById(stopArray[i]).value);
                }
                if (document.getElementById("cbRoundtrip").checked == true) {
                    order = order + document.getElementById("txtStop1").value;
                    stops.push(document.getElementById("txtStop1").value)
                }

                var options = new VERouteOptions;
                options.RouteCallback = DistTime;

                document.getElementById("pOrder").innerHTML = order;

                map.GetDirections(stops, options);
            }
        }
        xmlhttp.send(null);
    }
}

Finally, here is our web handler that we have been calling with our AJAX-call and which in turn calls the OnTerra stop optimization service:

Imports System.Web
Imports System.Web.Services
Imports BM_Azure_01_WebRole.OnTerra

Public Class _06_StopOpt
    Implements System.Web.IHttpHandler

    Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
        'Get the URL-Parameters
        Dim locations As String = context.Request.Params("locations")
        Dim roundTrip As Boolean = CBool(context.Request.Params("roundtrip"))

        Dim token As String = "YOUR TOKEN"

        Dim svcOT As New OnTerra.OnTerraStopOptClient("basicEndPoint")
        Dim output As String
        output = svcOT.GetStopOpt(locations, roundTrip, token)

        context.Response.ContentType = "text/plain"
        context.Response.Write(output)
    End Sub

    ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
        Get
            Return False
        End Get
    End Property

End Class

 

Visitors

The Latest News From

Chris Pendleton

Loading...Loading...

Richard Brundritt

Loading...Loading...

VE3D-Team

Loading...Loading...

Ed Katibah

Loading...Loading...
No folders have been shared yet.