Skip to content

09 Alternatives

Overview

This tutorial will show you how to calculate a route with up to 3 alternatives.

Base

As always 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 that the file has changed. So edit the file (for example add and remove a space) and save it so that it's 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 added the possibility to calculate a route with SDK_CalculateTour(). The SDK can do even more. It can calculate a route with alternative routes in one step with 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 max. 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 the MapWidget an SDK_NavigationInformation struct to be able to 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 in it - excluding start and destination.
SDK_BOOL HasRestrictionsOnRoute Indicates whether the current route has restricted segments at the start.
SDK_BOOL HasRestrictionsAtDestination Indicates whether the current route has restricted segments at the destination.
SDK_BOOL HasLegalRestrictions Indicates whether the current route has leagal 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 connected 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 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 route alternative we want to active 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, 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 also will 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, nothing more to do to get alternative routes! You can also use SDK_RouteClassic without changing anything in the dialog if you want to disable alternative routes as long as you request information for route 0, 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 we remove the set target pin. Around all is again our timer guard so 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

If a navigation is started with an alternative route higher than 0, this has implications to navigation. Because any of these alternative routes are not the arithmetical optimal route, there is a risk, that reroutes will switch to the optimal one. Therefore a special mechanism is implemented: The chosen route will be stored during the whole navigation and its underlying segments get a small preference in reroutings. This should lead to a good trade-off finding reasonable reroutes, but also keeping the chosen alternative.