locked
Draggable Route RRS feed

  • Question

  • Hello All:

    I am currently building a map control, using Bing Maps. The problem I am running into: the map has the ability to create a route and it draws a line from point A to point B. However, I was reading that Bing Maps now allows dragging the route line to another spot to stop at. I see that Bing Maps website has this capability. But I can't seem to find any suggestions on how to do this through the API in custom maps. Can anyone point me in the right direction?

     

    Thank You,

    Josh

    • Moved by Ricky_Brundritt Friday, March 9, 2012 1:33 PM (From:Bing Maps: Map Control and Web services Development)
    Thursday, May 13, 2010 9:21 PM

Answers

  • I've done this in a couple apps (AJAX and Silverlight). The concept is the same for both. Add a mouse down event to the route line. When clicked determine the closest waypoints in your current route (Haversine furmula). You want to do this to determine were this new waypoint you create is going to be inserted. Then drag the mouse on the map. When the mouse is released you will want to insert the new coordinate into the route waypoints at the index you determined from the initial mouse down event. When dragging you will have to lock the may by either disabling the default behavior of the mouse (ajax, return true from the event handler) or by putting something between the mouse and the map such as a pushpin (useful in silverlight). When you have your new coordinate added to your list of waypoints you can then make a new request for a route.
    Windows Live Developer MVP - http://rbrundritt.spaces.live.com
    Thursday, May 13, 2010 10:54 PM

All replies

  • I've done this in a couple apps (AJAX and Silverlight). The concept is the same for both. Add a mouse down event to the route line. When clicked determine the closest waypoints in your current route (Haversine furmula). You want to do this to determine were this new waypoint you create is going to be inserted. Then drag the mouse on the map. When the mouse is released you will want to insert the new coordinate into the route waypoints at the index you determined from the initial mouse down event. When dragging you will have to lock the may by either disabling the default behavior of the mouse (ajax, return true from the event handler) or by putting something between the mouse and the map such as a pushpin (useful in silverlight). When you have your new coordinate added to your list of waypoints you can then make a new request for a route.
    Windows Live Developer MVP - http://rbrundritt.spaces.live.com
    Thursday, May 13, 2010 10:54 PM
  • Hello:

     

    Thank you for responding. Do you know of any examples that I could view for this? I checked out your blog, but could not find anything on this. Do you know of any examples on the internet? I am developing in Silverlight/C#, so examples in those technologies would be very helpful.

     

    Thank You.

    Friday, May 14, 2010 4:24 PM
  • Richard,

    You said that "When clicked determine the closest waypoints in your current route (Haversine furmula)."

    How can I determine closest waypoints in current route? In Haversine formula If we use startaddress lat/long for comparision with waypoints lat/long then It will not work.

    So how can I use Haversine formula to determine the closest waypoint in current route? Can you please provide some steps to use Haversine formula to determine closest waypoint in current route?

    Monday, December 20, 2010 7:12 PM
  • Haversine allows you to estimate the distance between two sets of lat/long coordinates.

    - You know the coordinates where the user clicked (the pixel coordinates are passed to the mouseclick event handler method, and you can convert these to lat/long using the VEMap.PixelToLatLong() method).

    - You also know the lat/long coordinates of each point in the route.

    So, use haversine to work out the distance from the clicked point to all the points in the route, and insert the new point between the two closest points.


    Beginning Spatial with SQL Server http://www.apress.com/book/view/1430218290
    Monday, December 20, 2010 8:46 PM
  • Thanks for reply. I am trying to implement what you said. Will let you know once I done.
    Monday, December 20, 2010 9:13 PM
  • How will I determine distance from the clicked point to all the points in the route & insert the new point between the two closest points.

    Following is my code in javascritpt:

    Suppose  pointArray contains Lat/Long for the start address & destination address. Now when user dragged the route & put the waypoint. Then how can I determine where do I need insert waypoint using following code.

      var LL = map.PixelToLatLong(new VEPixel(x, y)); //coordinates where the user clicked

        var temp;
        var distanceArray;

        temp = pointArray;
        distanceArray = new Array(temp.length);
        var WaypointLatLong;
        for (x = 0; x < temp.length; x++) {
            WaypointLatLong = new VELatLong(temp[x].Latitude, temp[x].Longitue, null, null);
            distanceArray[x] = parseFloat(getDistance(LL, WaypointLatLong));
        }

     

    function getDistance(latlon1, latlon2) {
        try {

            var lat1 = latlon1.Latitude;
            var lon1 = latlon1.Longitude;
            var lat2 = latlon2.Latitude;
            var lon2 = latlon2.Longitude;

            var R = 6371; // Radius of the earth in km
            var dLat = toRad(lat2 - lat1);  // Javascript functions in radians
            var dLon = toRad(lon2 - lon1);
            var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
            var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
            var d = R * c; // Distance in km
            return d;
        }
        catch (e) {
            alert("Inside getDistance : " + e);
        }

    }

    function toRad(deg) {
        return deg * Math.PI / 180;
    }

    Monday, December 20, 2010 9:42 PM
  • Can anyone please answer my above question?
    Tuesday, December 21, 2010 5:13 PM
  • Is Anybody there who can answer my above question?
    Wednesday, December 22, 2010 2:26 PM
  • x3.........................................?.....x2...x1..x0

     

    Maybe I am misunderstanding something but doesn't the route returned by the route service give you just enough points to draw a route on the map. Let's say that the line returned by the route service is perfectly straight and the x's above represent existing waypoints that the user must go through. If the only points returned by the call to the route service are the x's then if I click on the line that is drawn on the map (represented by the dots) where the ? mark is wouldn't this method place the new waypoint between  x1 and x2? I guess my question is if this is true "You also know the lat/long coordinates of each point in the route."

    Wednesday, December 22, 2010 3:04 PM
  • Thanks for reply. Can you please suggest where should I modify the code?<br/>
    <br/>
    Following is my code:<br/>
    <br/>
    function GetDirections() {
     slRoute.DeleteAllShapes();
     pointArray = new Array();
     var options = new VERouteOptions();
     options.DrawRoute = false;
     options.RouteCallback = onGotRoute;
     map.GetDirections([document.getElementById("txtStart").value, document.getElementById("txtEnd").value], options);
    
    }
    
    function onGotRoute(route) {
     map.SetMapView(route.ShapePoints);
     var shape = new VEShape(VEShapeType.Polyline, route.ShapePoints);
     shape.SetLineColor(new VEColor(255, 165, 0,0.5));
     shape.SetLineWidth(5);
     shape.HideIcon();
     shape.SetTitle("MyRoute");
     shape.SetZIndex(1000, 2000);
     slRoute.AddShape(shape);
     map.AttachEvent("onmouseover", HandleMouseOverRoute);
    }
    
    function HandleMouseOverRoute(e) {
     if (e.elementID != null) {
      if (map.GetShapeByID(e.elementID).GetTitle().match("MyRoute"
    
    )) {
       map.DetachEvent("onmouseover", HandleMouseOverRoute);
       var x = e.mapX;
       var y = e.mapY;
       var LL = map.PixelToLatLong(new VEPixel(x, y));
       dragPoint = new VEShape(VEShapeType.Pushpin, LL);
       dragPoint.SetCustomIcon("./IMG/drag.png");
       dragPoint.SetZIndex(1000, 2000);
       slDragPoint.AddShape(dragPoint);
       map.AttachEvent("onmousemove"
    , HandleMouseOverRouteMove);
      }
     }
    }
    
    function HandleMouseOverRouteMove(e) {
     if (e.elementID != null) {
      if 
    (map.GetShapeByID(e.elementID).GetTitle().match("MyRoute"
    
    )) {
       var x = e.mapX;
      var y = e.mapY;
       var LL = map.PixelToLatLong(new VEPixel(x, y));
       dragPoint.SetPoints(LL);
       map.AttachEvent("onmousedown", HandleMouseDown);
      }
     }
     else 
    {
      map.DetachEvent("onmousedown", HandleMouseDown);
      map.DetachEvent("onmousemove"
    , HandleMouseOverRouteMove);
      slDragPoint.DeleteAllShapes();
      map.AttachEvent("onmouseover"
    , HandleMouseOverRoute);
     }
    }
    
    function HandleMouseDown(e) {
     map.DetachEvent("onmousemove"
    , HandleMouseOverRouteMove);
    
     map.AttachEvent("onmousemove", HandleDragPointMove);
    
     map.AttachEvent("onmouseup", HandleMouseUp);
    
    }
    <br/>
    <br/>
    But this code doesn't generate route in order like bing map does. Look at the following image:<br/>
    <br/>
    <br/>
    Start Address: New York, NY Destination Address: Boston, MA<br/>
    <br/>
    <br/>
    First I dragged the route to GreenField Then I dragged route to Hyde Park. Route should get generate<br/>
    <br/>
    <br/>
    from New York, NY->Hyde Park->GreenField->Boston, MA like Bing Map does. But it doesn't generates in <br/>
    <br/>
    <br/>
    order. 
    
    
    Wednesday, December 22, 2010 5:01 PM
  • I can't see the images.

    I'm a little confused. Where are you calling the distance formula and where are you generating the route with your new waypoints. You only call GetDirections with the original locations from what I can see. I'm still questioning the proposed method, but if you go with that you need to 1) call GetDirections, 2) subscribe to the user clicking on the route and then dragging the mouse and releasing the mouse, 3) measure the distance of where the mouse was released to all points on the route, 4) insert the waypoint between the two waypoints closest to it, 5) call the getdirections method again and give it a new array of points (the first time it would be original start, new waypoint, original end). I don't see steps 4 and 5 being done, so you would never expect anything other than the original route.

    Wednesday, December 22, 2010 5:09 PM
  • function
     GetDirections() {
     slRoute.DeleteAllShapes();
     pointArray = new Array();
     var options = new VERouteOptions();
     options.DrawRoute = false;
     options.RouteCallback = onGotRoute;
     map.GetDirections([document.getElementById("txtStart").value, document.getElementById("txtEnd").value], options);
    }
    
    function onGotRoute(route) {
     map.SetMapView(route.ShapePoints);
     var shape = new VEShape(VEShapeType.Polyline, route.ShapePoints);
     shape.SetLineColor(new VEColor(255, 165, 0,0.5));
     shape.SetLineWidth(5);
     shape.HideIcon();
     shape.SetTitle("MyRoute");
     shape.SetZIndex(1000, 2000);
     slRoute.AddShape(shape);
     map.AttachEvent("onmouseover", HandleMouseOverRoute);
    }
    
    
    function HandleMouseOverRoute(e) {
     if (e.elementID != null) {
     if (map.GetShapeByID(e.elementID).GetTitle().match("MyRoute"
    )) {
     map.DetachEvent("onmouseover", HandleMouseOverRoute);
     var x = e.mapX;
     var y = e.mapY;
     var LL = map.PixelToLatLong(new VEPixel(x, y));
     dragPoint = new VEShape(VEShapeType.Pushpin, LL);
     dragPoint.SetCustomIcon("./IMG/drag.png");
     dragPoint.SetZIndex(1000, 2000);
     slDragPoint.AddShape(dragPoint);
     map.AttachEvent("onmousemove"
    , HandleMouseOverRouteMove);
    }
     }
    }
    
    function HandleMouseOverRouteMove(e) {
     if (e.elementID != null) {
     if(map.GetShapeByID(e.elementID).GetTitle().match("MyRoute"
    )) {
     var x = e.mapX;
     var y = e.mapY;
     var LL = map.PixelToLatLong(new VEPixel(x, y));
     dragPoint.SetPoints(LL);
     map.AttachEvent("onmousedown", HandleMouseDown);
    }
     }
     else
     {
     map.DetachEvent("onmousedown", HandleMouseDown);
     map.DetachEvent("onmousemove", HandleMouseOverRouteMove);
     slDragPoint.DeleteAllShapes();
     map.AttachEvent("onmouseover", HandleMouseOverRoute);
    }
    }
    
    function HandleMouseDown(e) {
     map.DetachEvent("onmousemove", HandleMouseOverRouteMove);
     map.AttachEvent("onmousemove", HandleDragPointMove);
     map.AttachEvent("onmouseup", HandleMouseUp);
    }
    
    function HandleDragPointMove(e) {
     var x = e.mapX;
     var y = e.mapY;
     var LL = map.PixelToLatLong(new VEPixel(x, y));
     dragPoint.SetPoints(LL);
     return true;
    }
    
    
    function HandleMouseUp(e) {
     map.DetachEvent("onmousemove", HandleDragPointMove);
     map.DetachEvent("onmouseup", HandleMouseUp);
     var x = e.mapX;
     var y = e.mapY;
     var LL = map.PixelToLatLong(new VEPixel(x, y)); //coordinates where the user clicked
    
     /*--logic to find distance of LL with other waypoints(that user added)
     /*-- on the route using haversine formula -- */
    var temp;
     var distanceArray; 
     temp = pointArray;
     distanceArray = new Array(temp.length);
     var WaypointLatLong;
     for (x = 0; x < temp.length; x++) {
     WaypointLatLong = new VELatLong(temp[x].Latitude, temp[x].Longitue, null, null);
     distanceArray[x] = parseFloat(getDistance(LL, WaypointLatLong));
     }
    
    /*---sort distance array -------------/
     var tmpDistanceArray = distanceArray.slice();
     tmpDistanceArray.sort(sortNumber);
    
     for (x = 0; x < distanceArray.length; x++) {
     ind = getIndex(tmpDistanceArray, distanceArray[x]);
     tmpDistanceArray[ind] = x;
     }
    /*******************************************
    
     /* determine where to add new waypoint to pointArray */
     var insertWaypointIndex;
     if ((tmpDistanceArray[1] + 1) == tmpDistanceArray[0]) {
     insertWaypointIndex = tmpDistanceArray[1] + 1;
     }
     else if ((tmpDistanceArray[0] + 1) == tmpDistanceArray[1]) {
     insertWaypointIndex = tmpDistanceArray[1];
     }
     else {
     insertWaypointIndex = tmpDistanceArray[0] + 1;
     }
    
     pointArray.splice(insertWaypointIndex, 0, LL); 
    
     Reroute();
    }
    
    
    function Reroute() {
     slRoute.DeleteAllShapes();
     var options = new VERouteOptions();
     options.DrawRoute = false;
     options.RouteCallback = onGotRoute;
     map.GetDirections(pointArray, options);
    }
    
    function getDistance(latlon1, latlon2) {
     try {
      var lat1 = latlon1.Latitude;
      var lon1 = latlon1.Longitude;
      var lat2 = latlon2.Latitude;
      var lon2 = latlon2.Longitude;
      var R = 6371; // Radius of the earth in km
      var dLat = toRad(lat2 - lat1); // Javascript functions in radians
      var dLon = toRad(lon2 - lon1);
      var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + 
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
      var d = R * c; // Distance in km 
      return d;
     }
     catch (e) {
      alert("Inside getDistance : " + e);
     }
    }
    
    
    function toRad(deg) {
     return deg * Math.PI / 180;
    }
    Wednesday, December 22, 2010 6:03 PM
  • Sasha dos Santos you said following two points:

    4) insert the waypoint between the two waypoints closest to it, 5) call the getdirections method again and give it a new array of points (the first time it would be original start, new waypoint, original end).

     

    For step 5) Suppose Initially there are start address  & destination address. Then user wants to add new waypoint to the route. Here you are saying that I need to measure the distance of where the mouse was released to all points on the route(which is returned in call back function of map.GetDirection In my given code It is onGotRoute function).

    If I find the distance of where the mouse was released to all points on the route then I add that new waypoint to the two closest point on the route. In my code In HandleMouseUp function I add this to pointArray then I pass this array to ReRoute function which calls the map.GetDirection again.

    But next time when user wants to add second waypoint then do we have pass all the points(returned in map.GetDirection callback function , here onGotRoute function) along with new waypoint to map.GetDirection function( here in ReRoute function)?

     

     

    Wednesday, December 22, 2010 6:34 PM
  • All I have tried what you all said but it still generates route in different order.

    E.g. Suppose we have route between start address - New York, NY to destination address- Boston, MA. Now suppose we drag route to Keene, MA & put the waypoint. Then we drag route to Hyde Park, NY & put the waypoint. Then we drag route to Norwich, CT & put the waypoint. Then we drag route betweeen Norwich & Keene to  Pittsfield. Then whole should be from New York -> Hyde Park -> Norwich-> Pittsfield->Keene->Boston. Bing Map site generates route in this order.

    But my code which uses Haversine formula doesn't generate route in above order. It generates route from New York->Hyde Park->Pittssfield->Norwich->Keene->Boston.

    In Haversine formula, Pittsfield location is near to Hyde park so we get Hyde Park as nearest location.

    I want to show you all screenshot of what bing map site generates & what my code generates. How can I upload image here?

    Wednesday, December 22, 2010 8:26 PM
  • Well you might be encountering the issue I posted a few posts ago, that I thought the proposed method of dragging routes is flawed because you can come up with scenarios where a waypoint is closest to two points in the route and that doesn't necessarily mean that the waypoint should be inserted between them. It's the cases where order matters and not just find a route that goes thru all points that are the problem, I think. Now unfortunately, this thread is marked as answered, so I don't know if the original posters will notice that there are some questions about the method. Maybe it works and I am misinterpreting what would happen.

     

    Now all that being said, I have implemented draggable routes in a much harder way than what was proposed, but it seems to work (I was pulled off into another task so I had to stop my work). And sorry, I cannot give out the code because it's literally hundreds of lines of proprietary code paid for by my employer, but I think it is OK to give the general idea:

    1) I keep track of all waypoints in an array. Note that when I say waypoint, I am including the original start and end points (your first two points in the array)

    2) When the person drags a route (by mousing down on it, dragging it, and releasing it), I place the new waypoint in between the originals 2 points in the array and I calculate the new route with these 3 points, then I split the resulting route in 2 by splitting the route at the waypoint (note that there are some rounding issues between the waypoint you give Bing and the route that is returned). So at this point I actually have two lines: start to waypoint and waypoint to end. I assign each line segment a # starting at 0. You can just assign that new property to the VEShape object itself (ex. line1.linesegmentID = 0;)

    3) If the person wants to drag the route further, then they are going to click on one of the two lines. I do #2 again, inserting the new waypoint in the waypoints array between the start and end points of that line segment. If I am working on line with linesegmentID = 0 then the new waypoint is inserted into the array in index 1. I call GetDirections - but only for the line segment that is being changed and split the line again, increasing the line segment numbers of the lines segments past that one.

    4) If printing of directions is required, make a call to GetDirections with the waypoints array to get a text description that should take you thru all waypoints in the proper order. I did not get this far, but I think this part will work.

    Easy? No. But that's life, or should I say, that's Microsoft (I'm still annoyed by version 7 of the API, sorry). I have been able to successfully, insert/delete waypoints and modify start and end points in this manner. Now, if we can be convinced that the original idea works then go for that, but like you have seen and I had observed before, it may be to simplistic for scenarios such as ours.

    Wednesday, December 22, 2010 9:35 PM
  • Please any body can answer my above question?
    Wednesday, December 22, 2010 9:35 PM
  • Thanks for the steps you provided. I will try to implement it.
    Wednesday, December 22, 2010 9:54 PM
  • then I split the resulting route in 2 by splitting the route at the waypoint (note that there are some rounding issues between the waypoint you give Bing and the route that is returned ).

    How to resolve this rounding issues? We have to compare lat/long of the waypoints with lat/long in ShapePoints, right?

    Wednesday, December 22, 2010 11:27 PM
  • Correct. I find that when I get the route back and look at the RouteLegs, they are truncating the coordinates to 6 decimal places after the decimal point. I think that is the case; I will have to check my code a little more and read it. I have a note about that but maybe it's not applicable to you.

     

    This is my method if you need it:

     

            //Helper function to return a truncated decimal number with the given number of
            //digits after the decimal point.
            function truncate(number, decimalPosition)
            {
                var boolNegative = (number < 0);
                var numStr = number.toString();
                var decPos = numStr.indexOf(".");
                if (decPos == -1)
                    return number; //no truncation needed
                else
                {
                    return parseFloat(numStr.substr(0, decPos + decimalPosition + 1));
                }
               
            };


    Sasha dos Santos Software Engineer Pro Tech Monitoring, Inc. http://www.ptm.com
    Thursday, December 23, 2010 6:07 PM
  • Thanks.

    I am finding it difficult to implement step3

    3) If the person wants to drag the route further, then they are going to click on one of the two lines. I do #2 again, inserting the new waypoint in the waypoints array between the start and end points of that line segment. If I am working on line with linesegmentID = 0 then the new waypoint is inserted into the array in index 1. I call GetDirections - but only for the line segment that is being changed and split the line again, increasing the line segment numbers of the lines segments past that one.

    Any help would be appreciated?

    Thursday, December 23, 2010 8:07 PM
  • In my application I have provided facility to add waypoints using mouse drag as well as through add new destination functionality(i.e. by entering waypoint address in the textbox).

    I have  to differentiate waypoints added using mouse drag from waypoints added using add new destination functionality(i.e. by entering waypoint address in the textbox) using custom flags. For this In the callback function of map.GetDirection function I am checking the lat/long of the route legs. But In some cases I am not getting exact lat/long for waypoints added using add new destination functionality in the route legs.

    Lat/Long of the route leg returned in the callback function is not matching with lat/long of the waypoint added using add new destination functionality (even first 2 digits after decimal places is not matching).

    Monday, January 3, 2011 4:52 PM