Google Maps Directions - Added Waypoints

Topics: Feature Requests, General
Jan 18, 2013 at 10:01 AM

Hi all,

Just thought I'd share an extension to the GMapProviders.GoogleMap.GetDirections() function in the core.dll.  My intention is hopefully for some or all of this to be added in some form to the next build so that it can be maintained with future builds.

Just a simple update to allow you to pass a list of waypoints instead of passing a list of individual a to b routes.  I also split the direction.Distance to DistanceStr and DistanceVal so that it was faster for me to summate multiple calls (this same extension could also be added to directionStep as well). 

Limitations/Drawbacks:

  • Google have restricted it to 8 waypoints for free version (+ rate limit) and 24 waypoints for business api.
  • It treats the whole journey as a single route so you don't get back specific node to node information (distance duration); only the whole journey.

Google also allow for optimsation aka travelling salesman problem (returns an ordered index based upon the original order you sent to them).  Personally, I have already optimised my routes before passing it this data (using the distance matrix service and some custom restrictions) so I don't need it but I may add it in the future.

I provide an example of how I used it in a test at the bottom.

My extension to the top level direction Distance values (durationVal = minutes, distanceVal = metres – it is more accurate than the text representation provided at the minute).

public class GDirections
{
       public string DurationStr;
       public uint DurationVal;
       public string DistanceStr;
       public uint DistanceVal;
}


public abstract class GoogleMapProviderBase : GMapProvider, RoutingProvider, GeocodingProvider, DirectionsProvider
{

//new url format for passing the waypoints, this could be        extended to include via and the optimize (but then we would need to handle the returned optimised index).

static readonly string DirectionUrlFormatWaypoint = "http://maps.googleapis.com/maps/api/directions/xml?origin={0},{1}&waypoints={2}&sensor={3}&language={4}{5}{6}{7}";


//new function - the last index of the waypoints list is the end node
public DirectionsStatusCode GetDirections(out GDirections direction, PointLatLng start, List<PointLatLng> waypoints, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
{
      return GetDirectionsUrl(MakeDirectionsUrl(start, waypoints, LanguageStr, avoidHighways, avoidTolls, walkingMode, sensor, metric), out direction);
}


string MakeDirectionsUrl(PointLatLng start, List<PointLatLng> waypoints, string language, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric)
{
      string av = (avoidHighways ? "&avoid=highways" : string.Empty) + (avoidTolls ? "&avoid=tolls" : string.Empty); // 6
      string mt = "&units=" + (metric ? "metric" : "imperial");���� // 7
      string wk = "&mode=" + (walkingMode ? "walking" : "driving"); // 8

      StringBuilder wpLatLng = new StringBuilder();
      for (int i = 0; i < waypoints.Count; i++) {
           if (i == 0) {
                wpLatLng.Append(waypoints[i].Lat.ToString() + "," + waypoints[i].Lng.ToString());
           }
           else {
                  wpLatLng.Append("|"); wpLatLng.Append(waypoints[i].Lat.ToString() + "," + waypoints[i].Lng.ToString());
           }
      }
      return string.Format(CultureInfo.InvariantCulture, DirectionUrlFormatWaypoint, start.Lat, start.Lng, wpLatLng.ToString(), sensor.ToString().ToLower(), language, av, mt, wk);
}


//grab data from another node in the xml/json
DirectionsStatusCode GetDirectionsUrl(string url, out GDirections direction)
{
     nn = doc.SelectSingleNode("/DirectionsResponse/route/leg/duration");
     if(nn != null) {
           XmlNode nnt = nn.SelectSingleNode("text");
           if(nnt != null){
                  direction.DurationStr = nnt.InnerText;
           }
           XmlNode nnv = nn.SelectSingleNode("value");
           if (nnv != null)
           {
               uint tVal = 0;
               uint.TryParse(nnv.InnerText, out tVal);
               direction.DurationVal = tVal;
           }
     }

      nn = doc.SelectSingleNode("/DirectionsResponse/route/leg/distance");
      if(nn != null)
      {
            XmlNode nnt = nn.SelectSingleNode("text");
            if(nnt != null){
                   direction.DistanceStr = nnt.InnerText;
             }
            XmlNode nnv = nn.SelectSingleNode("value");
            if (nnv != null){
                  uint tVal = 0;
                  uint.TryParse(nnv.InnerText, out tVal);
                  direction.DistanceVal = tVal;
            }
        }
}


//example of how I get round the limit of 8 waypoints per call. Remembering to overlap the last and first node of each list so we get a smooth continuation, I also remove the duplicate point from the end set of route points.

int wpLimit = 8;
GDirections directions = new GDirections();
List<PointLatLng> allPoints = new List<PointLatLng>();
List<List<PointLatLng>> sIndexes = new List<List<PointLatLng>>();

for (int i = 0; i < Math.Ceiling((double)dM.optiRoute.Count / wpLimit); i++){
        List<PointLatLng> nIndex = new List<PointLatLng>();
        for (int j = wpLimit * i; j <= (wpLimit * (i + 1)); j++){
             if (j != 0 && j <= dM.optiRoute.Count){ 
                    nIndex.Add(dM.locations[dM.optiRoute[j-1]].point);
             }
        }
        sIndexes.Add(nIndex);
}
uint tDist = 0;
uint tDur = 0;
for (int k = 0; k < sIndexes.Count; k++){
       List<PointLatLng> tPoints = sIndexes[k].GetRange(1, sIndexes[k].Count - 1);
       Object status = GMapProviders.GoogleMap.GetDirections(out         directions, sIndexes[k][0],tPoints , false, false, false, false, true);
       if (status.Equals(DirectionsStatusCode.OK)){
             tDist += directions.DistanceVal;
             tDur += directions.DurationVal;
             for (int i = 0; i < directions.Steps.Count(); i++){
                   if (directions.Steps[i].Points.Count != 0){
                          if (i == 0 && k != 0){
                                  allPoints.AddRange(directions.Steps[i].Points.GetRange(1, directions.Steps[i].Points.Count - 1)); //remove the initial point due to overlap
                   }
                   else{
                          allPoints.AddRange(directions.Steps[i].Points);
                   }
              }
      }
lblOptiDetails.Text = String.Format("DistVal:{0}� DurVal:{1}", tDist, tDur);
}
else{
dM.errMsgs.Add("Error fetching directions");
}
}

Any questions just ask

Regards,

Pete

 

 

 

 

 

Coordinator
Jan 18, 2013 at 11:12 AM

thanks!

Jan 21, 2013 at 7:41 AM

Hello All,

 

is there any release has this change?

 

Regards

Coordinator
Jan 21, 2013 at 8:51 AM

it's latest hot build, i haven't test it but it should work

Jan 21, 2013 at 9:32 AM

from where can i got it? can you pass the correct url?

thanks in advance

Coordinator
Jan 21, 2013 at 9:43 AM

source code tab is pretty obvious ;}

Jan 21, 2013 at 10:29 AM

thanks man,

 

but an error occur while building mobile version:

 

Error 5 The type or namespace name 'ScaleModes' could not be found (are you missing a using directive or an assembly reference?) \GMap.NET.WindowsForms\GMap.NET.WindowsForms\GMapControl.cs 2578 15 GMap.NET.WindowsMobile (GMap.NET\GMap.NET.WindowsMobile)

here what i did to fix it:

at the end of GmapControl.cs file

 

//////////////////////////// use the PocketPC to define ScaleModes

#if !PocketPC 

//   int x= 0 ;

#else

   public enum HelperLineOptions

   {

      DontShow = 0,

      ShowAlways = 1,

      ShowOnModifierKey = 2 

 }
   public enum ScaleModes   

    /// <summary> 

    /// no scaling 

    /// </summary>   

  Integer,

#if !PocketPC 

 //    int xc = 0;

#else 

    /// <summary> 

    /// scales to fractional level, CURRENT VERSION DOESN'T HANDLE OBJECT POSITIONS CORRECLTY, 

      /// http://greatmaps.codeplex.com/workitem/16046 

    /// </summary> 

    Fractional,

#endif

   }
   public delegate void SelectionChange(RectLatLng Selection, bool ZoomToFit);

#endif

 

 

but a new error occurred:

Error 7 'uint' does not contain a definition for 'TryParse' \Downloads\greatmaps-6150303191b8\greatmaps_6150303191b8\GMap.NET.Core\GMap.NET.MapProviders\Google\GoogleMapProvider.cs 1442 36 GMap.NET.WindowsMobile (GMap.NET\GMap.NET.WindowsMobile)

Coordinator
Jan 21, 2013 at 11:22 AM

i see, so there are 2 compilation errors, thats pretty usual, i'll fix them later

Mar 7, 2013 at 11:33 AM
Poter134
thank you. can you please help me out to use your code.
I want to be able to make a route that consists of more than just two points, and it should create a route between them deciding what should be the most shorter way;
EX:
45 lakewood Av., New York, NY
12 george Av., New York, NY
23 paul Av., New York, NY

it should be able to sort it by distance and make the route so I don't have to return to the same place twice, is there such a feature?if so, can you please post some code I would REALLY appreciate ?
thank you
Mar 7, 2013 at 7:46 PM
Hi,
There is a method from google that calculates the fastest route (for up to 8 points on the free license). The method I submitted to GMaps was for pre-ordered routes only.
You could create a second function based on mine that used the optimise waypoints. You would then have to pass a ‘out List<int> optiRoute ’ for the optimised order and capture that when parsing the xml (you’ll get the optimised route anyways (not including the start location which you still have to specify) but it’d be easier to reference your initial locations by using that.
Should be fairly easy to do if you read the following link and browse through the source. Its explained in the waypoints section and how to parse it near the bottom of the page.
If you get too stuck I could give a few more pointers but i’m pretty busy at the minute so i’m not sure when i’d find the time! 😊
Sent from Windows Mail
From: lbrettsinclair
Sent: ‎07‎ ‎March‎ ‎2013 ‎12‎:‎33
To: p-lea@hotmail.co.uk
Subject: Re: Google Maps Directions - Added Waypoints [greatmaps:430018]

From: lbrettsinclair

Poter134
thank you. can you please help me out to use your code.
I want to be able to make a route that consists of more than just two points, and it should create a route between them deciding what should be the most shorter way;
EX:
45 lakewood Av., New York, NY
12 george Av., New York, NY
23 paul Av., New York, NY

it should be able to sort it by distance and make the route so I don't have to return to the same place twice, is there such a feature?if so, can you please post some code I would REALLY appreciate ?
thank you
Mar 7, 2013 at 8:13 PM
Can you show me how to test your code with those 3 dummy addresses ?
That should help me get started -

thank you for your time and help. Much appreciated.
Mar 7, 2013 at 8:18 PM
quick update: If all you want is to route get the optimised routes you can update the following function. You always need to specify a start location, you could update it to specify an end location &destination=xxxxxxx (but then you have a max of 6 waypoints as all are included in the total up to 8). You don't need to parse the xml for the waypoint order if you don't want.
string MakeDirectionsUrl(PointLatLng start, List<PointLatLng> waypoints, string language, bool avoidHighways, bool avoidTolls, bool walkingMode, bool sensor, bool metric, bool optimiseRoute)

{
          string av = (avoidHighways ? "&avoid=highways" : string.Empty) + (avoidTolls ? "&avoid=tolls" : string.Empty); // 6
          string mt = "&units=" + (metric ? "metric" : "imperial");     // 7
          string wk = "&mode=" + (walkingMode ? "walking" : "driving"); // 8
           
          StringBuilder wpLatLng = new StringBuilder();
          for (int i = 0; i < waypoints.Count; i++) {
              if (i == 0) {
                  if (optimiseRoute) wpLatLng.Append("optimize:true|");
                  wpLatLng.Append(waypoints[i].Lat.ToString() + "," + waypoints[i].Lng.ToString());
              }
              else {
                  wpLatLng.Append("|"); wpLatLng.Append(waypoints[i].Lat.ToString() + "," + waypoints[i].Lng.ToString());
              }
          }
          return string.Format(CultureInfo.InvariantCulture, DirectionUrlFormatWaypoint, start.Lat, start.Lng, wpLatLng.ToString(), sensor.ToString().ToLower(), language, av, mt, wk);
      }
Mar 7, 2013 at 9:47 PM
Yes.
All I want an example of code where I would set a list of addresses, and the most optimized route would be displayed.
If a max of 6 - I would be ok with that.

thank you
Mar 11, 2013 at 11:25 AM
Edited Mar 11, 2013 at 12:29 PM
I noticed you hadn't replied with anything so I'll give a small example of how you can simply use it. I Geocode all my addresses before hand - it saves a lot of hassle imo.
private void example()
            {
                
                PointLatLng startPoint = new PointLatLng(0,0);
                List<PointLatLng> myWaypoints = new List<PointLatLng>();

                //add waypoints

                GMapOverlay ovl = new GMapOverlay("MyTestOverlay");
                GMapRoute rte = new GMapRoute("MyTestRoute");

                GDirections _dir;
                DirectionsStatusCode _code = GMapProviders.GoogleMap.GetDirections(out _dir, startPoint, myWaypoints, false, false, false, false, true);
                if (_code == DirectionsStatusCode.OK)
                {
                    foreach (GDirectionStep _step in _dir.Steps)
                    {
                        rte.Points.AddRange(_step.Points);
                    }
                }

                ovl.Routes.Add(rte);
                gmap.Overlays.Add(ovl);
            }
This should get you started. Edit: Oh yeah :p typed it straight into browser :)
Coordinator
Mar 11, 2013 at 11:53 AM
if (_code == DirectionsStatusCode.OK)
Mar 11, 2013 at 2:57 PM
This would require to have the longitude and latitude of the addresses.
I wanted to use a physical address.
I guess my problem maybe it to find a way to "translate" a physical address into a latitude and longitude points.
Is that where you are trying to lead me to do ?
Mar 11, 2013 at 5:11 PM
yes, I prefer to geocode (the lat/lng of the address) before, then you have no reliance on an address. You could get someone to open up a map and point out a location near a field :)

On the other hand, if you geocode an address, you can then check the location it's returned and verify that its the correct spot and that it's understood the address correctly. Google will always understand your lat/lng points, so you can rule out that as an exception. if one address out of 10 isn't understood or misunderstood it screws the whole journey up.
 string googleGeoStr =    "https://maps.googleapis.com/maps/api/geocode/xml?address={0}&sensor=false&region=gb";

 using(Stream stream = webClient.OpenRead(string.Format(googleGeoStr, tAddress)))
{
      // parse / save whatever data you want here
}
Jan 19 at 7:00 AM
Edited Jan 19 at 7:01 AM
Hello, i'm using poter134 snippet of code to solve cvsp problem
     GMapOverlay ovl = new GMapOverlay("MyTestOverlay");
                GMapRoute rte = new GMapRoute("MyTestRoute");

                GDirections _dir;
                DirectionsStatusCode _code = GMapProviders.GoogleMap.GetDirections(out _dir, startPoint, myWaypoints, false, false, false, false, true);
                if (_code == DirectionsStatusCode.OK)
                {
                    foreach (GDirectionStep _step in _dir.Steps)
                    {
                        rte.Points.AddRange(_step.Points);
                    }
                }
but the _code variable return ExceptionInCode
I'm amateur in programming.