Implementation of Required MapPoint Functionality
In this section we discuss the implementation of several key aspects of Route Factory functionality that rely upon MapPoint.
Time/Distance Evaluations
Route Factory Spreadsheet represents locations (i.e. terminals or customers) as C# objects with the underlying MapPoint.Location as an attribute. Although we only need the time and/or distance between given pairs of points, we still need to construct a MapPoint.Route object to perform the evaluation. The MapPoint.Route object is provided with the two MapPoint.Location objects as in the form of MapPoint “WayPoints” as follows:
We then get the data we need via:Code:private static MapPoint.Route BuildMappointPair(Location locStart, Location locFinish) { MapPoint.Route route = RFGlobals.MyMap.ActiveRoute; route.Waypoints.Add(locStart.MPLocation, "start"); route.Waypoints.Add(locFinish.MPLocation, "finish"); return route; }
Alternatively, the user is allowed to specify that the algorithm use mathematically computed distances between locations. These are computed from the Location’s Latitude/Longitude coordinates using the “Great-Circle” trigonometric equations. The resulting “as the crow flies” distances are then multiplied by an empirically derived fudge factor to account for typical road circuity.Code:route.Calculate(); time = route.DrivingTime * 24; // result returned in fractional days distance = route.Distance;
Geocoding Process
Geocoding is the process of determining a Latitude/Longitude from a Street Address and tying that to an underlying MapPoint.Location. As with any GIS System or even MapQuest, this is not a 100% reliable process. The easiest situation is when the user supplies the Latitude/Longitude coordinates, in which case we merely use:
where MyMap is a MapPoint.Map object. To get the MapPoint.Location directly from the street address we use:Code:foundLocation = MyMap.GetLocation(loc.Latitude, loc.Longitude, 1);
However, even if the address has been correctly specified, the function can sometimes return null or low quality (findresults.ResultsQuality > 2). Our fallback in those instances is to focus on the zip code only. We have integrated a commercial database into our application that maps the zip code to a Latitude/Longitude coordinate representing the centroid of the zip code region. (This same database maps the zip code to time zone data, which we discuss later in this article). If the zip code is also invalid, there is not much else one can do, so we trap and flag the situation for the user so they can try to get a better address.Code:MapPoint.FindResults findResults = MyMap.FindAddressResults(loc.Street, loc.City, string.Empty, region, loc.Zip, country);
Route Display
We use MapPoint to display the routes built by our routing algorithms. At the conclusion of the route building algorithms, the route specification is output to an Excel worksheet as a way of storing the solution between uses of the application. Each stop on the route is represented as a row on the worksheet. To display a route, the user selects a transport ID from a drop down menu and clicks on a Draw Route button. This activates the following steps for each stop (row):
- Geocode the stop by using its Location ID to retrieve the required data from the Locations Worksheet and utilizing the geocoding process described above to obtain a MapPoint.Location
- Add a Waypoint to a MapPoint.Route object corresponding to the MapPoint.Location.
- Create a MapPoint.Pushpin with an identifier indicating the sequence number of the stop
- Populate the MapPoint.Pushpin with descriptive information that will be available to the user using the “More Information” right mouse button selection.
Note that i_RouteData is an object array with data from the Excel row for that stop.Code:MapPoint.Location loc; string waypointName; numStops++; loc = MyMap.GetLocation(locInfo.Latitude, locInfo.Longitude, 1); waypointName = locInfo.ID; route.Waypoints.Add(loc, waypointName); MapPoint.Pushpin pinloc = MyMap.AddPushpin(loc, locInfo.ID); pinloc.Symbol = (short)(217 + numStops); // pushpin with stop sequence number pinloc.Note = locInfo.Description + "\n" + "Address: " + locInfo.GetAddressString(); "\n" + "ETA: " + i_routeData[numStops, 6].ToString() + "\n" + "Loads: " + i_routeData[numStops, 8].ToString() + "\n" + "UnLoads: " + i_routeData[numStops, 9].ToString(); pinloc.BalloonState = MapPoint.GeoBalloonState.geoDisplayNone;
When MapPoint displays a driving route, it also creates a set of turn-by-turn driving instructions where each stop has a listed arrival and departure time. In Route Factory Spreadsheet, we need to manually set the arrival and departure on these instructions for three reasons: 1) the duration of the stop is a function of the load/unload time at that particular stop, 2) the load/unload operation can be delayed by the stop’s time window, that is, the transport may have to wait until the stop is “open for business”, and 3) the stop may involve an “overnight” due to drive time restrictions. These considerations are handled via the following code:
Last we adjust the route.DriverProfile to be consistent with the parameter settings made by the user (details available upon request). Finally, we build and display the MapPoint route:Code:for (int i = 1; i <= numStops; i++) { object index = i; waypoint = route.Waypoints.get_Item(ref index); if (i < numStops) { int goodTimes = 0; if (i_routeData[i, 6] != null) { arrivalTime = (DateTime)i_routeData[i, 6]; goodTimes++; } if (i_routeData[i, 7] != null) { departureTime = (DateTime)i_routeData[i, 7]; waypoint.PreferredDeparture = departureTime; goodTimes++; } if (goodTimes == 2) { if (departureTime.Date > arrivalTime.Date) waypoint.Overnights = (int)(departureTime.Date - arrivalTime.Date).TotalDays; } } }
A sample map is shown below.Code:route.Calculate(); route.ZoomTo(); MyApp.Visible = true;
![]()