Routing with ADAS Data |
In this tutorial we will build a simple routing application, based on ADAS information and the Telogis.GeoBase.Adas namespace, to display the varying gradients of a road along a route. As an example, in the image below, the red sections of the route indicate an incline (in the direction of 'reference' to 'non-reference' along the street link), the green sections indicate a decline, and the yellow sections indicate that the road is level (that is, within ±0.5°).
Note |
---|
GeoBase identifies gradient changes -- the slope of the road surface -- using street links. The two ends of any street link (short sections of street along a route, typically running between two intersections) are designated as being either 'reference' or 'non-reference' nodes. The left and right sides of a street link are also defined based on a theoretical observer standing on the 'reference' end of the link, looking out toward the 'non-reference' end. The ADAS gradient data used by GeoBase assumes travel from the reference end toward the non-reference end of a street link. That is, if the altitude of the reference end is less than the non-reference end, the gradient of the link is inclined. However, this does not take into account the actual direction of travel. It is entirely possible that this will be in the opposite direction (non-reference to reference), in which case this incline will actually be a downward slope to the traveler. For this reason it is important to compare the points along the direction of travel with the ADAS points and adjust the data presented accordingly. Information displayed to the end-user will then better reflect their anticipated navigation experience. The tutorial below demonstrates one approach to ensure slopes are correctly displayed to users based on route direction. |
ADAS stands for Advanced Driver Assistance Systems. An ADAS data file can be incorporated with the GeoBase platform to provide useful information for both the driver and in-vehicle safety systems, allowing for a more safer driving experience and environment.
Note |
---|
ADAS information currently only includes road slope information. Other ADAS information is expected to become available in the future. |
The Telogis.GeoBase.Adas namespace provides classes to represent and query ADAS information. Namely:
AdasInfo
The AdasInfo class provides the following useful properties:
AdasQuery
The AdasQuery class provides the QueryLink(UInt64) method to retrieve ADAS information for a given street link; the street link is specified by the link ID.
The main purpose of this tutorial is to demonstrate how to use ADAS information with the classes from the Telogis.GeoBase.Adas namespace. We will build a simple routing application, comprising a map control and a fixed route. The route will be displayed on the map with sections of the route colored according to the section's slope. For this tutorial, and to keep things simple, we will use just three colors: a red section of the route indicates an incline, a green section of the route will indicate a decline, and a yellow section will indicate that the road is level, to within ±0.5°.
To retrieve the slope value from a section of the road, we will call the AdasQuery's QueryLink method with the section's link ID, to return an AdasInfo, where, as mentioned above, each element of the Slopes array describes the slope of the road at the location specified by the corresponding element in the Points array.
We will split the tutorial into three steps:
Open a new instance of Visual Studio, and add geobase.net.dll as a reference.
Add a map control to the form. Rename the map control to 'mapMain', and set the 'Zoom' property to 3. Change the CenterLat and CenterLon properties to:
That's all there is to this step.
In this section we will write the code to create:
Before we create the route, change to the code view in Visual Studio. To make the code simpler and more readable, add the following using directives to the top of the form:
using Telogis.GeoBase; using Telogis.GeoBase.Routing; using Telogis.GeoBase.Adas; using Telogis.GeoBase.Repositories;
Next, add the following code to the Form1 constructor, just after the InitializeComponent() statement. Ensure that your data files are located as per the parameter to the 'new MultiRepository()' statement. In this example, our data files are stored at 'C:\GeoBase\Data'.
Note |
---|
It is important to note that ADAS data is stored within specific ADAS map data files. ADAS data is not typically included in generic .gbfs map data files. If you do not currently have an ADAS file, an example can be downloaded from the Verizon Connect Spatial website. |
//Add start and stop route points RouteStop rsStart = new RouteStop(new LatLon(34.00102,-118.27779)); RouteStop rsEnd = new RouteStop(new LatLon(34.07787,-118.23342)); //Set repository - this repository will include both map and ADAS data MultiRepository mr = new MultiRepository(@"C:\GeoBase\Data\"); Repository.CurrentThreadRepository = mr; //Calculate route Route myRoute = new Route(rsStart, rsEnd); //Get directions Directions myDirs = myRoute.GetDirections(); //Add directions to map RendererList rList = new RendererList(); rList.Add(myDirs); //Add rendererlist mapMain.Renderer = rList;
And that concludes step 2. You can build and run the application as it stands so far - you should see a map centered on LA, with a route displayed along the Harbor Freeway, as shown in the image below.
In this section we will add the code to re-color the route depending on the road's gradient. This section comprises one class, AdasRenderer, which implements the IMapRenderer interface. To implement the IMapRenderer interface, we will add code for both Render(), and RequiredRenderModes(). RequiredRenderModes() simply tells the renderer when to render our images, within its three-stage render process, onto the map. For this application, we will select rendering to occur 'pre-labelling' - that is, map labels will be placed on the map after we have re-colored the route. The Render method will be responsible for the rest of the work of re-coloring the route according to the slope.
To begin with, add the AdasRenderer class as shown below underneath Form1. Notice that the RequiredRenderModes method has been implemented with the read-only RenderMode property being set to PreLabelling.
public class AdasRenderer : IMapRenderer { public void Render(System.Drawing.Graphics graphics, RenderContext rc) { } public RenderMode RequiredRendermodes { get { return RenderMode.PreLabelling; } } }
In section 2, we created a route, and obtained the Directions from that route to display on the map. The Directions class provides a GetDirectionLinks() method from which we can obtain a list of DirectionLinks, and thus providing us with the necessary link IDs that will be used to find, ultimately, the slopes of the individual sections of the route. For that reason, we will need to instantiate the AdasRenderer with these Directions. Add the following code and constructor to the AdasRenderer class:
public AdasRenderer(Directions myDirs) {
DirectionLink[] dirlinks = myDirs.GetDirectionLinks();
}
The render method will perform the following tasks:
Next we determine whether the slope of the street link is incline or decline relative to the direction of travel.
This code should be placed in the AdasRenderer class, immediately below the 'DirectionLink[] dirlinks = myDirs.GetDirectionLinks();' statement.
// This part determines the directionality of the route for (int i = 0; i < dirlinks.Length - 1; i++) { var curr = dirlinks[i]; // Get the first point of the directionLink which // has the same direction as the route double routeHeading = MathUtil.GetHeading(curr.Points.First(), curr.Points.Last()); // Query the AdasInfo for the points AdasInfo[] info = AdasQuery.QueryLink(curr.LinkId); // Create the list of points and slopes from the AdasInfo List<LatLon> points = new List<LatLon>(); List<float> slopes = new List<float>(); foreach (var datum in info) { points.AddRange(datum.Points); slopes.AddRange(datum.Slopes); } DrawingInfo di = new DrawingInfo(); if (info.Length != 0) { // If the points in the AdasInfo is in a different order from the // direction described by the direction link, we reverse the order double adasHeading = MathUtil.GetHeading(info[0].Points.First(), info[0].Points.Last()); double adasHeadingBackwards = MathUtil.GetHeading(info[0].Points.Last(), info[0].Points.First()); if (Math.Abs(routeHeading - adasHeading) > Math.Abs(routeHeading - adasHeadingBackwards)) { ReverseSlopes(slopes); } // Create the drawing info } di.points = points.ToArray(); di.slopes = slopes.ToArray(); dis.Add(di); }
Copy the following code above the Render() method. The ReverseSlopes method performs the inversion of slope values when called.
The DrawingInfo structure is used to create an array of point locations and slope values which is saved to a list, then used by ReverseSlopes. This information is used to compare the slope of street links with the direction of travel along the route, allowing link color (green or red) to be switched when appropriate (ensuring a green decline is displayed as a red incline if traveling in the opposite direction).
struct DrawingInfo { public LatLon[] points; public float[] slopes; } List<DrawingInfo> dis = new List<DrawingInfo>(); void ReverseSlopes(List<float> slopes) { for (int i = 0; i < slopes.Count; i++) { slopes[i] = -slopes[i]; } }
The following code should be added into the Render() method.
It set the Pens to be used for coloring the route. If the slope value is positive, then the section of the route is on an incline, and we will re-color it red. If the slope value is negative, then we will re-color the route green to indicate a decline. To indicate that the road is level, we will re-color the route yellow. For this example, we have chosen an arbitrary value, levelBand, of ±0.5°.
//Set pen's line width float lineWidth = 15 - (float)rc.Map.Zoom; //Set band at which gradient is considered level (0.5 degrees in this example) float levelBand = 0.5F; //Set pen's colors Pen inclinePen = new Pen(Color.Red, lineWidth); Pen declinePen = new Pen(Color.Green, lineWidth); Pen levelPen = new Pen(Color.Yellow, lineWidth);
Immediately below this, add the following code to derive the direction of the slope for each point of the route from each AdasInfo:
foreach (DrawingInfo info in dis) { int nPoints = info.points.Length; Point[] points = new Point[nPoints]; for (int idx = 0; idx < nPoints; idx++){ int x; int y; rc.Map.LatLontoXY(out x, out y, info.points[idx]); points[idx] = new Point(x, y); } for (int idx = 1; idx < nPoints - 1; idx++) { //Check if slope is within the level band if (Math.Abs(info.slopes[idx]) < levelBand){ graphics.DrawLines(levelPen, points); } else { graphics.DrawLines(info.slopes[idx] > 0 ? inclinePen : declinePen, points); } } }
That concludes the coding for the AdasRenderer class. Before we test the application, we still need to tell the map to use our AdasRenderer. Add the following code in the Form1 constructor, just before the 'mapMain.Renderer = rList;' statement.
//Add ADAS Info to renderer list AdasRenderer ar = new AdasRenderer(myDirs); rList.Add(ar);
Build and run your application. You will be presented with the screen as shown below. Red sections of the route indicate the road is on an incline; green indicates a decline, and yellow indicates that the section is level. If any part of the route is not colored red, green, or yellow, then no ADAS information is available for that part of the route.
The complete code for this example project is included below:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Telogis.GeoBase; using Telogis.GeoBase.Routing; using Telogis.GeoBase.Adas; using Telogis.GeoBase.Repositories; namespace ADAS { public partial class Form1 : Form { public Form1() { InitializeComponent(); //Add start and stop route points RouteStop rsStart = new RouteStop(new LatLon(34.00102, -118.27779)); RouteStop rsEnd = new RouteStop(new LatLon(34.07787, -118.23342)); //Set repository - this repository will include both map and ADAS data MultiRepository mr = new MultiRepository(@"C:\GeoBase\Data\"); Repository.CurrentThreadRepository = mr; //Calculate route Route myRoute = new Route(rsStart, rsEnd); //Get directions Directions myDirs = myRoute.GetDirections(); //Add directions to map RendererList rList = new RendererList(); rList.Add(myDirs); //Add ADAS Info to renderer list AdasRenderer ar = new AdasRenderer(myDirs); rList.Add(ar); //Add rendererlist mapMain.Renderer = rList; } public class AdasRenderer : IMapRenderer { public AdasRenderer(Directions myDirs) { DirectionLink[] dirlinks = myDirs.GetDirectionLinks(); // This part determines the directionality of the route for (int i = 0; i < dirlinks.Length - 1; i++) { var curr = dirlinks[i]; // Get the first point of the directionLink which // has the same direction as the route double routeHeading = MathUtil.GetHeading(curr.Points.First(), curr.Points.Last()); // Query the AdasInfo for the points AdasInfo[] info = AdasQuery.QueryLink(curr.LinkId); // Create the list of points and slopes from the AdasInfo List<LatLon> points = new List<LatLon>(); List<float> slopes = new List<float>(); foreach (var datum in info) { points.AddRange(datum.Points); slopes.AddRange(datum.Slopes); } DrawingInfo di = new DrawingInfo(); if (info.Length != 0) { // If the points in the AdasInfo is in a different order from the // direction described by the direction link, we reverse the order double adasHeading = MathUtil.GetHeading(info[0].Points.First(), info[0].Points.Last()); double adasHeadingBackwards = MathUtil.GetHeading(info[0].Points.Last(), info[0].Points.First()); if (Math.Abs(routeHeading - adasHeading) > Math.Abs(routeHeading - adasHeadingBackwards)) { ReverseSlopes(slopes); } // Create the drawing info } di.points = points.ToArray(); di.slopes = slopes.ToArray(); dis.Add(di); } } void ReverseSlopes(List<float> slopes) { for (int i = 0; i < slopes.Count; i++) { slopes[i] = -slopes[i]; } } struct DrawingInfo { public LatLon[] points; public float[] slopes; } List<DrawingInfo> dis = new List<DrawingInfo>(); public void Render(System.Drawing.Graphics graphics, RenderContext rc) { //Set pen's line width float lineWidth = 15 - (float)rc.Map.Zoom; //Set band at which gradient is considered level (0.5 degrees in this example) float levelBand = 0.5F; //Set pen's colors Pen inclinePen = new Pen(Color.Red, lineWidth); Pen declinePen = new Pen(Color.Green, lineWidth); Pen levelPen = new Pen(Color.Yellow, lineWidth); foreach (DrawingInfo info in dis) { int nPoints = info.points.Length; Point[] points = new Point[nPoints]; for (int idx = 0; idx < nPoints; idx++) { int x; int y; rc.Map.LatLontoXY(out x, out y, info.points[idx]); points[idx] = new Point(x, y); } for (int idx = 1; idx < nPoints - 1; idx++) { //Check if slope is within the level band if (Math.Abs(info.slopes[idx]) < levelBand) { graphics.DrawLines(levelPen, points); } else { graphics.DrawLines(info.slopes[idx] > 0 ? inclinePen : declinePen, points); } } } } public RenderMode RequiredRendermodes { get { return RenderMode.PreLabelling; } } } } }