Skip to content

09 Alternatives

Overview

This tutorial will show you how to calculate a route with two additional alternative route proposals.

Base

You can use your results from the previous Tutorial08 as a base for our new project.

Prerequisites

Please copy the following files from the Tutorial09 folder to your project directory:

  • RouteOverviewDialog.cpp
  • RouteOverviewDialog.h
  • routeoverviewdialog.ui

Note

When replacing an .ui file, the build system sometimes doesn't recognize the file has change. As a workaround simply modify the file (for example add and remove a space) and save it so that its time stamp changes.

Add the new files to the CMakeLists.txt file:

set(Tutorial_RES
    ...
    routeoverviewdialog.ui
)

set(Tutorial_SRCS
    ...
    RouteOverviewDialog.cpp
)

set(Tutorial_HEADER
    ... 
    RouteOverviewDialog.h
)

Also, we need some new resources, so copy the following items from the Tutorial09/res folder to your /res directory:

  • arw_destination.png
  • arw_destination_dist.png
  • icon_toll.png
  • icon_traffic_blocked.png

And add them to the main.qrc file:

<RCC>
  <qresource>
    <file>res/ccp.png</file>
    <file>res/arw_fwd.png</file>
    <file>res/arw_fwd_left.png</file>
    <file>res/arw_fwd_right.png</file>
    <file>res/arw_left.png</file>
    <file>res/arw_right.png</file>
    <file>res/arw_uturn_left.png</file>
    <file>res/arw_uturn_right.png</file>
    <file>res/arw_destination.png</file>
    <file>res/arw_destination_dist.png</file>
    <file>res/icon_toll.png</file>
    <file>res/icon_traffic_blocked.png</file>
  </qresource>
</RCC> 

Call cmake ../ in the build directory or build the ALL_BUILD project of the solution to update it.
Also, always trigger a rebuild after copying to ensure that replaced files will be newly built.

Route alternatives

In Tutorial05, we showed you how to calculate a route with SDK_CalculateTour(). But there is more. Th SDK can calculate a route with alternative routes in one step using the same function.
In this tutorial, We will show how to activate the alternative route calculation.

Creating a route overview

First we add a new dialog called RouteOverviewDialog. Open the routeoverviewdialog.ui file in the Qt designer:

This dialog will be shown right after a route calculation. Because the SDK can calculate up to three alternative routes, we have three buttons on the top of the dialog to switch between the alternatives. If the SDK calculated less than the three routes, unused buttons will be removed.
The selected route will be shown in a MapWidget. Also, some information about the selected route will be shown:

  • The length of the selected alternative
  • The arrival time
  • The traffic delay
  • The toll distance

So let's take a look at the RouteOverviewDialog class:

RouteOverviewDialog.cpp:

...
RouteOverviewDialog::RouteOverviewDialog(QWidget *parent)
    : QDialog(parent)
{
    ui.setupUi(this);

    // the designer didn't want to set these automatically, so we have to do it...
    connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
    connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(reject()));

    SDK_InitNavigationInformation(&mInfo);
    ui.map->setNavigationInformation(&mInfo);

    connect(ui.map, SIGNAL(resized()), this, SLOT(onMapResize()));

    mCurrentAlternative = 0;

    SDK_INT4 routeCount = 0;
    SDK_GetAlternativeRouteCount(&routeCount);

    if (routeCount == 1)
    {
        ui.pushButton2->setVisible(false);
        ui.pushButton3->setVisible(false);
        connect(ui.pushButton1, SIGNAL(clicked()), this, SLOT(onPushButton1Clicked()));
    }
    else if (routeCount == 2)
    {
        ui.pushButton3->setVisible(false);
        connect(ui.pushButton1, SIGNAL(clicked()), this, SLOT(onPushButton1Clicked()));
        connect(ui.pushButton2, SIGNAL(clicked()), this, SLOT(onPushButton2Clicked()));
    }
    else if(routeCount == 3)
    {
        connect(ui.pushButton1, SIGNAL(clicked()), this, SLOT(onPushButton1Clicked()));
        connect(ui.pushButton2, SIGNAL(clicked()), this, SLOT(onPushButton2Clicked()));
        connect(ui.pushButton3, SIGNAL(clicked()), this, SLOT(onPushButton3Clicked()));
    }

    ui.map->initialize();

    bool rcLoad = mTimeIcon.load(":/res/arw_destination.png");
    rcLoad = mDistIcon.load(":/res/arw_destination_dist.png");
    rcLoad = mTrafficIcon.load(":/res/icon_traffic_blocked.png");
    rcLoad = mTollIcon.load(":/res/icon_toll.png");


    ui.labelDist->setPixmap(mDistIcon);
    ui.labelTime->setPixmap(mTimeIcon);
    ui.labelTraffic->setPixmap(mTrafficIcon);
    ui.labelToll->setPixmap(mTollIcon);

    ui.pushButton1->setChecked(true);
    updatePushButtons();
    updateView(0);
}
In the constructor, we set a SDK_NavigationInformation struct to the MapWidget so it can show a route trace. We connect the signal resized() of the MapWidget to the slot onMapResized() to handle size changes of the map correctly.

To be able to emit the signal, add the following to the MapWidget class:

MapWidget.h:

...
signals:
    ...
    void resized();
...

MapWidget.cpp:

...
void MapWidget::resizeEvent(QResizeEvent *ev)
{
    ...
    emit resized();
}
...

We want to show only as much buttons as alternative routes are available, so we first get the number of alternative routes by calling:

    SDK_INT4 routeCount = 0;
    SDK_GetAlternativeRouteCount(&routeCount);

This will give us the number of routes the SDK has calculated. We adapt the number of buttons to the number of routes, load the four icons and set them to the correct labels in our layout. Then we check the first button so that the first route will be shown.

The buttons on top of the dialog should show the duration of the particular alternative, so we update the text of them in the function updatePushButtons():

void RouteOverviewDialog::updatePushButtons()
{
    SDK_INT4 routeCount = 0;
    SDK_GetAlternativeRouteCount(&routeCount);

    SDK_RouteInformation info;
    SDK_InitRouteInformation(&info);

    for (SDK_INT4 i = 0; i < routeCount; i++)
    {
        SDK_GetRouteInformation(&info, 0, i);
        SDK_INT4 hours = info.Duration / 3600;
        SDK_INT4 minutes = (info.Duration % 3600) / 60;
        QString durationText = QString("%1 h %2 min").arg(hours).arg(minutes);
        switch (i)
        {
        case 0:
            ui.pushButton1->setText(durationText);
            break;
        case 1:
            ui.pushButton2->setText(durationText);
            break;
        case 2:
            ui.pushButton3->setText(durationText);
            break;
        default:
            break;
        }
    }
}
To get the duration information for the alternatives, we call SDK_GetAlternativeRouteCount() again to get the number of alternatives. We then iterate over the alternatives and call
SDK_GetRouteInformation(&info, 0, i);
to get particular information about the alternative with the given index. The parameters for this function are:

Type Parameter Description
SDK_RouteInformation* pRouteInformation Will be filled by the function with the current route information
SDK_INT1 routeIdx The index of the route in a multi tour routing (only used if a tour was calculated with SDK_CalculateTour(), see Tutorial10 for more details)
SDK_INT4 alternativeRouteNumber The alternative index we want the route information for (starts at 0)

The information will be returned by a SDK_RouteInformation struct.

The struct SDK_RouteInformation has the following members:

Type Name Description
SDK_HDRINFO size Size of the structure - for controlling
SDK_HDRINFO version Version of the structure - for controlling. Use SDK_RouteInformationVERSION
SDK_UINT4 Length Length of the route [m]
SDK_UINT4 Duration Driving duration [s]
SDK_BOOL HasRestrictionsAtStart Indicates whether the current route has restricted segments at the start.
SDK_BOOL HasRestrictionsOnRoute Indicates whether the current route has restricted segments in it - excluding start and destination.
SDK_BOOL HasRestrictionsAtDestination Indicates whether the current route has restricted segments at the destination.
SDK_BOOL HasLegalRestrictions Indicates whether the current route has legal restricted segments.
SDK_TollInformation TollInformation Toll information

We format the Duration value to a show us the time in hours and minutes and set the text of the particular button to it.

In the onMapResize() slot, we zoom to the route and update the map:

void RouteOverviewDialog::onMapResize()
{
    ui.map->zoom2Route();
    ui.map->update();
}

To switch between the alternatives, we connect the buttons in our dialog to three functions which will call updateView() with the alternative index:

void RouteOverviewDialog::onPushButton1Clicked()
{
    mCurrentAlternative = 0;
    updateView(0);
}

void RouteOverviewDialog::onPushButton2Clicked()
{
    mCurrentAlternative = 1;
    updateView(1);
}

void RouteOverviewDialog::onPushButton3Clicked()
{
    mCurrentAlternative = 2;
    updateView(2);
}

The updateView() function will alter the info fields of the dialog (dependent on the alternative index). We show from left to right the distance to destination, the estimated time of arrival, the delay caused by traffic and the covered distance on toll roads.
To access the traffic delay, we simply call SDK_GetTrafficDelayOnRoute().

It has the following parameters:

Type Parameter Description
SDK_INT4* pDelay Will be set to the current delay on the route or to 0, if no route is given
SDK_BOOL* pBlockingAhead Will be set to true, if a blocking traffic jam is ahead
SDK_BOOL bComplete If set to true, the delay on the whole route will be returned, otherwise the delay from the current position (in this case, a navigation must have been started to get the delay. If not, 0 will be returned)
SDK_INT4 routeIdx The index of the tour route about which to return the traffic result information for a multi station tour. '0' returns the traffic delay of the current route. '1' returns the traffic delay of the first inactive route, '2' returns the traffic delay of the second inactive route, and so on. For Standard A-B routings, use '0'.
SDK_INT4 alternativeIdx The index of the corresponding alternative route. Set to 0 if you don't use alternative routes

Like in Tutorial06 we format the distances with

SDK_GetLocalizedDistanceString() 
This function sets the correct unit for the current set metric and formats the string in some variants.

It has the following parameters:

Type Parameter Description
SDK_INT4 value The value to transform and localize
SDK_BOOL quantify Flag whether the distance should be quantified to a reasonable value for the current set metric
SDK_WCHAR_T** valueString Will be set to the transformed, localized and quantified value string. The memory allocated must be freed by calling SDK_FreeString()!
SDK_BOOL allowNow Allow the function to return the given nowString instead of the distance, used for the now maneuver (only useful when manually generating maneuver text output)
SDK_WCHAR_T* nowString The string to return, if allowNow is set and the distance is below or equal the "now" distance (only useful when manually generating maneuver text output)
SDK_WCHAR_T decimalSeparator The standard '.' decimal separator will be replaced with decimalSeparator if set
SDK_BOOL ommitDecimalNull If set to true, integral distances are not given as floating value (e.g. 2 km instead of 2.0 km)

At last, we set the desired route alternative as the active route with:

SDK_SetAlternativeRouteActive(SDK_INT4 routeNumber) 
This will internally set the alternative with the given route number as active and highlight the trace of this alternative on the map.

void RouteOverviewDialog::updateView(SDK_INT4 alternative)
{
    SDK_RouteInformation routeInfo;
    SDK_InitRouteInformation(&routeInfo);
    SDK_ERROR rc = SDK_GetRouteInformation(&routeInfo, 0, alternative);

    SDK_WCHAR_T * length;

    rc = SDK_GetLocalizedDistanceString(routeInfo.Length, false, &length, SDK_FALSE, L"", L'.', SDK_TRUE);
    ui.labelDistText->setText(QString::fromWCharArray(length));

    SDK_WCHAR_T * toll;
    rc = SDK_GetLocalizedDistanceString(routeInfo.TollInformation.TotalLengthTruckToll, false, &toll, SDK_FALSE, L"", L'.', SDK_TRUE);
    ui.labelTollText->setText(QString::fromWCharArray(toll));

    SDK_FreeString(length);
    SDK_FreeString(toll);

    QDateTime date = QDateTime::currentDateTime();
    date = date.addSecs(routeInfo.Duration);
    QString format = "hh:mm";
    QString t = date.toString(format);
    ui.labelTimeText->setText(t);

    SDK_INT4 delay = 0;
    SDK_BOOL blockingAhead = SDK_FALSE;
    rc = SDK_GetTrafficDelayOnRoute(&delay, &blockingAhead, SDK_TRUE, 0, alternative);
    SDK_INT4 hours = delay / 3600;
    SDK_INT4 minutes = (delay % 3600) / 60;

    QString trafficText = QString("+ %1 h %2 min").arg(hours).arg(minutes);
    ui.labelTrafficText->setText(trafficText);
    rc = SDK_SetAlternativeRouteActive(alternative);

    ui.map->update();
}

If a traffic update occurred while this dialog is open (which may have changed the traffic delay on some routes), we alter the info fields, the buttons and the map.

void RouteOverviewDialog::onTrafficFinished(int error)
{
    updateView(mCurrentAlternative);
    updatePushButtons();
}

Enabling alternative routes

Now, as we have a new dialog, we will enable alternative route calculation. We do also show the RouteOverviewDialog when a route calculation has finished.

Add/Change the following in the MainWindow class:

MainWindow.cpp:

void MainWindow::setDefaultRoutingOptions()
{   
    ...
    routeOptions.RouteCalculationType = SDK_RouteAlternative;
    ...

We set the route calculation type of the SDK_RouteOptions to SDK_RouteAlternative. Be sure that setDefaultRoutingOptions() is called in actionSearchAddressTriggered() as mentioned in Tutorial05.

That's all to get alternative routes! You can also use SDK_RouteClassic if you want to disable alternative routes as long as you request information for route 0 only, otherwise you will get an error.

To show the dialog, we have to add some code to our MainWindow.

Add/Change the following in the MainWindow class:

MainWindow.h:

...
class MainWindow : public QMainWindow
{
    ...
protected slots: 
    ...
    void showRouteOverviewDialog();

MainWindow.cpp:

...
#include "RouteOverviewDialog.h"
...
void MainWindow::onCalcRouteFinished(int error)
{
    showRouteOverviewDialog();
    ...
}

void MainWindow::showRouteOverviewDialog()
{
    disableNavigationLoopTimer();

    RouteOverviewDialog dialog(this);
    connect(mNavigationLoop, SIGNAL(trafficFinished(int)), &dialog, SLOT(onTrafficFinished(int)));
    int retVal = dialog.exec();
    if (retVal == QDialog::Rejected)
    {
        ui.map->setNavigationInformation(0);
        ui.map->removeTargetPin();
    }
    else
    {
        ui.map->setNavigationInformation(mNavigationInformation);
    }

    enableNavigationLoopTimer();
}

We call the new function showRouteOverviewDialog() in our onCalcRouteFinished() right at the beginning. showRouteOverviewDialog() will create a new modal RouteOverviewDialog and connect the signal trafficFinished() from the NavigationLoop to the slot onTrafficFinished() of our dialog. So the dialog gets called when new traffic items arrive (this is only the case if a traffic search was started and has not been finished before the dialog has raised). In case the user clicked on the "Ok" button of the dialog, we set our MainWindow map an SDK_NavigationInformation struct to enable showing a route trace. If the user rejected the dialog, we will remove the route trace by setting the navigation information to 0 and remove the set target pin. Again our timer guard makes sure that our NavigationLoop will not be executed while showing the dialog.

Voilà we should have our final result:

Attention

As soon as SDK_GetGuidanceInformation() is called for the first time, the currently selected alternative will be the only selectable alternative. The SDK will remove the unwanted alternatives automatically.

Implications on navigation

Starting a navigation with an alternative route (i.e. bigger than 0) has implications to navigation. Because each of the alternative routes is not the arithmetical optimal route, there is a risk that rerouting will switch back to the optimal route. Therefore a special mechanism is implemented: The chosen route will be stored during the whole navigation. Its underlying segments get a small preference while rerouting. This should lead to a good trade-off finding reasonable reroutes, but also most likely keeps the chosen alternative route.