Skip to content

06 Navigating a route

Overview

In this tutorial you will learn how to implement route navigation and how to interpret the maneuvers given by the NavigationSDK. We also show you an easy way to simulate a navigation along the current route for testing.

Base

Use Tutorial05 as a starting point for this project.

Prerequisites

Please add/replace the following files from the Tutorial06 folder to your project directory:

  • NaviInfo.cpp
  • NaviInfo.h
  • NextManeuverControlWidget.cpp
  • NextManeuverControlWidget.h
  • nextmaneuvercontrolwidget.ui
  • InfoControlWidget.cpp
  • InfoControlWidget.h
  • infocontrolwidget.ui
  • NavigationLoop.cpp
  • NavigationLoop.h
  • mainwindow.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
    ...
    nextmaneuvercontrolwidget.ui
    infocontrolwidget.ui
)

set(Tutorial_SRCS
    ...
    NextManeuverControlWidget.cpp
    InfoControlWidget.cpp
    NaviInfo.cpp
)

set(Tutorial_HEADER
    ... 
    NextManeuverControlWidget.h
    InfoControlWidget.h
    NaviInfo.h
)

Also, copy the resources below from the Tutorial06/res directory to your projects /res folder:

  • arw_fwd.png
  • arw_fwd_left.png
  • arw_fwd_right.png
  • arw_left.png
  • arw_right.png
  • arw_uturn_left.png
  • arw_uturn_right.png

Add the resource files to the main.qrc file:

main.qrc:

<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>
  </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 rebuilt.

Getting guidance information

To get maneuvers for our current guidance, we have to call SDK_GetGuidanceInformation() in our NavigationLoop. We only have to call it when we are navigating a route. So we add an inNavigation flag to our NavigationLoop to determine the status of our app. We use a std::atomic<bool> to guarantee thread safe access to it. We added the methods startNavigation(), stopNavigation() and navigationActive() to set the flag and to get the status. We also have to subclass the NavigationLoop additionally from QObject, because we like to send the progress and guidance info via the signal slot mechanism later:

NavigationLoop.h:

...
#include "NaviInfo.h"

class NavigationLoop : public QObject, public QRunnable
{   
    Q_OBJECT
public:
    NavigationLoop(MapWidget * mapWidget);
    ~NavigationLoop();
    void run();

    static SDK_INT4 SDK_API progressCallback(SDK_INT4 current, SDK_INT4 total, SDK_INT4 job);

    void startNavigation();
    void stopNavigation();
    bool navigationActive();

private:
    MapWidget * mMap;
    std::atomic<bool> inNavigation;
    NaviInfo mNaviInfo;

signals:
    void progress(int);
    void newGuidanceInfo(NaviInfo info);
};

In the MainWindow::onCalcRouteFinished() we call mNavigationLoop->startNavigation() if the route calculation succeeded:

Add the following to MainWindow::onCalcRouteFinished():

MainWindow.cpp:

void MainWindow::onCalcRouteFinished(int error)
{
    ..
    if ((SDK_ERROR)error == SDK_ERROR_Ok)
    {
        ..
        mNavigationLoop->startNavigation();
    }
}

Now we can get the guidance information in the navigation loop. Because SDK_GetGuidanceInformation() is a potentially long running operation, we would usually call it in a QRunnable and push it to our SDKJobQueue. But since we are already in a QRunnable - which is called in the SDKJobQueue - there is no need for this. If we have no route, it makes no sense to get guidance information. So we check this and return early from the loop. At the end, we check the DestinationReached flag of the returned SDK_NavigationInformation result to see if we have to stop the navigation.

NavigationLoop.cpp:

...
void NavigationLoop::run()
{
    ...

    if (rc == SDK_ERROR_Ok || rc == SDK_ERROR_SamePosition)
    {
        ...

        if (navigationActive())
        {
            SDK_ERROR rc = SDK_GetGuidanceInformation(&gps, &navInfo, 0, progressCallback);
            if (rc == SDK_ERROR_Ok)
            {
                mNaviInfo.setNaviInfo(navInfo);
                ...
                emit newGuidanceInfo(mNaviInfo);
            }
        }
        ...

        if (navInfo.DestinationReached == SDK_TRUE)
        {
            stopNavigation();
        }
    }
}
...

Rerouting

So what happens if we deviate from the given route? Do we have to check and calculate a new route? No we don't have to, SDK_GetGuidanceInformation() will take care of that. If we deviate only a few meters from our current route, the call of the function will automatically pull us back to our current route. If we deviate further from our current route it will calculate a complete new route to our destination. Because of this, we can call SDK_GetGuidanceInformation() with a callback as parameter to get progress information in this case. To send this progress information to our MainWindow, we created a signal progress(), which we connect to our MainWindow::onProgress() slot (which we have already implemented for the route calculation).

NavigationLoop.h:

...
signals:
    void progress(int);
    ...

NavigationLoop.cpp:

SDK_INT4 SDK_API NavigationLoop::progressCallback(SDK_INT4 current, SDK_INT4 total, SDK_INT4 job)
{
    emit pThis->progress(current);
    return 1;
}
To connect this signal, we add the following code to the MainWindow constructor. Be sure to add this behind the creation of our mNavigationLoop, otherwise you get a crash on startup.

Add the following to the MainWindow class:

MainWindow.cpp:

MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    ...
    connect(mNavigationLoop, SIGNAL(progress(int)), this, SLOT(onProgress(int)));
}

Maneuver building

To inform the MainWindow about new guidance information, we emit the signal newGuidanceInfo() in our NavigationLoop. Qt can only send structures with the signal/slot connections over threads that are previously registered to Qt. Therefore, we have to wrap the SDK_NavigationInformation struct in a new class, so that Qt can send it over. This class has to be subclassed from QObject and must have a copy constructor. Q_DECLARE_METATYPE(NaviInfo) declares this class for Qt.

Finally, we also have to register the class to make it visible to Qt:

Add the following to the MainWindow class:

MainWindow.cpp:

...
#include "NaviInfo.h"

MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    qRegisterMetaType<NaviInfo>();
    ...
}

The NaviInfo class itself looks like this:

NaviInfo.h:

#pragma once

#include "../navigationsdk/SDKInterface.h"
#include <QObject>

class NaviInfo
{   

public:
    NaviInfo();
    NaviInfo(const SDK_NavigationInformation& info);
    ~NaviInfo();

    void getNaviInfo(SDK_NavigationInformation& info);
    void setNaviInfo(const SDK_NavigationInformation& info);

private: 
    SDK_NavigationInformation mNaviInfo;
};

Q_DECLARE_METATYPE(NaviInfo);

NaviInfo.cpp:

#include "../navigationsdk/SDKInterface.h"
#include "NaviInfo.h"

NaviInfo::NaviInfo()
{

}

NaviInfo::NaviInfo(const SDK_NavigationInformation& info)
{
    mNaviInfo = info;
}

NaviInfo::~NaviInfo()
{

}

void NaviInfo::getNaviInfo(SDK_NavigationInformation& info)
{
    info = mNaviInfo;
}

void NaviInfo::setNaviInfo(const SDK_NavigationInformation& info)
{
    mNaviInfo = info;
}

As you can see, the class simply wraps a SDK_NavigationInformation struct so we can use it with the signal/slot mechanism.

To show the guidance information on the MainWindow, we created a small widget which shows the distance, the street name and an icon representation of the next maneuver called NextManeuverControlWidget. We also created a control called InfoControlWidget to show the arrival time, the distance to the next station and the time to the next station. The NextManeuverControlWidget will be shown on top of our map, the InfoControlWidget at the bottom.

Open the nextmaneuvercontrolwidget.ui and the infocontrolwidget.ui in the Qt designer:

The next maneuver control widget has three labels, two on the left side and one one the right. The upper left one will show a direction arrow, the lower one the distance to the next maneuver. The right label shows the street name.

The info control widget has three labels to show the arrival time, the driving time and the distance to the next station.

We added the widgets to our mainwindow.ui, so that the next maneuver control is placed above the map in the upper part of the window and the info control is in the lower part:

To manage the content and visibility of the two widgets, we implemented two classes, the NextManeuverControlWidget class and the InfoControlWidget class.
Both classes are based on a QWidget.

NextManeuverControlWidget.cpp:

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

    mArrowForward.load(":/res/arw_fwd.png");
    mArrowForwardLeft.load(":/res/arw_fwd_left.png");
    mArrowForwardRight.load(":/res/arw_fwd_right.png");
    mArrowLeft.load(":/res/arw_left.png");
    mArrowRight.load(":/res/arw_right.png");
    mArrowUTurnLeft.load(":/res/arw_uturn_left.png");
    mArrowUTurnRight.load(":/res/arw_uturn_right.png");

    ui.labelIcon->setPixmap(mArrowForward);
    ui.labelIconText->setText("500m");
    ui.labelText->setText("Streetname");
}

void NextManeuverControlWidget::update(const SDK_NavigationInformation& navInfo)
{
    ...
}

In the constructor, we load the bitmaps of the direction arrows. The update function will change the arrow and texts in the widget dependent on the SDK_NavigationInformation struct it receives. We will show details of this function later.

InfoControlWidget.cpp:

...
InfoControlWidget::InfoControlWidget(QWidget *parent)
    : QWidget(parent)
{   
    ui.setupUi(this);
}

...

void InfoControlWidget::update(const SDK_NavigationInformation& info)
{
    if (!isVisible())
        setVisible(true);


    // labelDistance shows the distance to the next station
    SDK_WCHAR_T *distanceText = NULL;
    SDK_ERROR rc = SDK_GetLocalizedDistanceString(info.DistToNextStation, SDK_FALSE, &distanceText, SDK_FALSE, L"", L'.', SDK_FALSE);
    ui.labelDistance->setText(QString::fromWCharArray(distanceText));

    // labelArrivalTime shows the arrival time
    QDateTime date = QDateTime::currentDateTime();
    date = date.addSecs(info.TimeToNextStation);
    QString format = "hh:mm";
    QString t = date.toString(format);
    ui.labelArrivalTime->setText(t);

    // labelDuration shows the driving time                     
    int hours = (info.TimeToNextStation / 60 / 60) % 24;
    int minutes = (info.TimeToNextStation / 60) % 60;

    QString  duration;
    duration.sprintf("%02d:%02d", hours, minutes);
    ui.labelDuration->setText(duration);
}

In the update() method, we fill the labels with the distance, the arrival time and the duration.
The DistToNextStation value is in [m], so we format it with SDK_GetLocalizedDistanceString() to get a string with a localized and meaningful unit.
We also format the arrival time and the duration.

The NextManeuverControlWidget and the InfoControlWidget will be updated in the MainWindow in the slot onGuidanceInfo(). This slot is connected to the NavigationLoop signal NavigationLoop::newGuidanceInfo() and will receive the current navigation information.

Add/Change the following in the MainWindow class:

MainWindow.h:

...
#include "NaviInfo.h"
...
protected slots: 
    ...
    void onGuidanceInfo(NaviInfo info);
    ...

MainWindow.cpp:

MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    ...
    ui.nextManeuverWidget->setVisible(false);    
    ui.infoControlWidget->setVisible(false);
    connect(mNavigationLoop, SIGNAL(newGuidanceInfo(NaviInfo)), this, SLOT(onGuidanceInfo(NaviInfo)));
}

void MainWindow::onGuidanceInfo(NaviInfo navInfo)
{
    SDK_NavigationInformation info;
    navInfo.getNaviInfo(info);

    if (info.DestinationReached)
    {
        ui.nextManeuverWidget->setVisible(false);
        ui.infoControlWidget->setVisible(false);
    }
    else
    {
        ui.nextManeuverWidget->update(info);
        ui.infoControlWidget->update(info);
    }
}

We initially set the widgets to invisible, because we do not have any guidance information on startup.

When new guidance information is available, we call ui.nextManeuverWidget->update(info) and ui.infoControlWidget->update(info) with the current navigation information.

The struct SDK_NavigationInformation 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_NavigationInformationVERSION
SDK_Position Pos Current position [Mercator]
SDK_Position PosOnRoute Current position on route [Mercator]
SDK_Position CorrectedPosOnRoute Current position on route [Mercator] (+forecast, but that's not implemented)
SDK_INT4 Course Current course [0..360[, head east, counter-clockwise
SDK_INT4 CourseOnRoute Current course on route [0..360[, 0 heading east, counterclockwise
SDK_INT4 SpeedLimitCar Speed limit on the current segment for cars [km/h]
SDK_INT4 SpeedLimitTruck Speed limit on the current segment for trucks [km/h]
SDK_BOOL SpeedLimitCarIsImplicit Flag whether the speed limit for cars is implicit or explicit.
SDK_BOOL SpeedLimitTruckIsImplicit Flag whether the speed limit for trucks is implicit or explicit.
SDK_INT4 NextSpeedLimitCar Value of next different speedlimit for cars ahead on the route from the current position, if the speedlimit doesn't change until reaching the target, the value will be set to -1
SDK_INT4 NextSpeedLimitTruck Value of next different speedlimit for trucks ahead on the route from the current position, if the speedlimit doesn't change until reaching the target, the value will be set to -1
SDK_UINT4 DistToNextSpeedLimitCar Distance to the next different speedlimit for cars on the route.
SDK_UINT4 DistToNextSpeedLimitTruck Distance to the next different speedlimit for trucks on the route.
SDK_INT4 RoadTypeOfNextSpeedLimitCar Road type of the segment where the next speedlimit for cars changes
SDK_INT4 RoadTypeOfNextSpeedLimitTruck Road type of the segment where the next speedlimit for trucks changes
SDK_INT4 RoadType Road type of the current segment [0..14]
SDK_INT4 Category Road category of the current segment [0..4]
SDK_INT2 CountryCode The country code attached to the current segment. 0 when no cc is attached
SDK_BOOL SegmentBorderPassed Has a segment border been passed since the last position?
SDK_BOOL ManeuverPointPassed Has a maneuver point been passed since the last position?
SDK_BOOL NextCrossingInTown Is the next crossing inside a town area?
SDK_BOOL NoTownUntilNextManeuver Only Non-City-Roads until next crossing?
SDK_UINT4 DistToNextManeuver Distance [m] to next maneuver
SDK_UINT4 TimeToNextManeuver Time [s] to next maneuver
SDK_UINT4 RouteLengthToNextManeuver Distance [m] from previous maneuver to next maneuver
SDK_UINT4 DistToDestination Distance [m] to destination
SDK_UINT4 TimeToDestination Time [s] to destination without delays caused by traffic on the route
SDK_UINT4 CrowFlightDistToDestination Crow flight distance [m] from last matched position on the route to destination
SDK_UINT4 DistToNextStation Distance [m] to next station
SDK_UINT4 TimeToNextStation Time [s] to next station
SDK_INT2 VoiceOutputManeuverHint 0 if no voice output of the next maneuver is suggested. > 0 if voice output is suggested. Value is the amount of suggestions including this up to next maneuver. May be set for a finite period of time, see generatemaneuverhints for details
SDK_BOOL AnnounceNextManeuver True, if the next maneuver is closer than 600m in urban and closer than 3000m in rural areas
SDK_BOOL AnnounceAtOnceForNextManeuver True, if the next and second next maneuver are nearby crossings and the time between the two will not exceed a certain period
SDK_BOOL AnnounceSecondNextManeuver True if the maneuver is a composite maneuver
SDK_WCHAR_T CurrentStreetName[128] Name of the current street
SDK_ManeuverDescription NextManeuver Description of the next maneuver
SDK_ManeuverDescription SecondNextManeuver Description of the second next maneuver
SDK_CrossingView NextManeuverCrossingView Information for drawing the crossing view for the next maneuver
SDK_CrossingView SecondNextManeuverCrossingView Information for drawing the crossing view for the second next maneuver
SDK_INT4 RoundedDistToNextManeuver Distance to the next maneuver [unit]. The unit is given by the current set metric
SDK_INT4 RoundedDistFromNextToSecondNextManeuver Distance from the next to the second next maneuver [unit]. The unit is given by the current set metric
SDK_BOOL InTunnel Are we in a tunnel?
SDK_BOOL OnBridge Not yet implemented
SDK_BOOL WrongRoad Are we on the wrong road? Early indicator that the user has left the road
SDK_BOOL OffRoad Set to SDK_TRUE when driving offroad
SDK_BOOL MissRouting Are we in a missrouting case? System will try to calculate a new route next time, if it wasn't done just now
SDK_BOOL DestinationReached Destination reached? Set to SDK_TRUE while near the destination. User should stop navigation/calling SDK_GetGuidanceInformation then!
SDK_BOOL ClosedArea Indicates if the vehicle is on a "forbidden" road
SDK_INT4 reserved0 Reserved for future applications
SDK_INT4 ReRouteStatus Did the ReRoute status change? (see ReRoutingModes)
SDK_UINT4 RouteTraceHnd Reserved for future applications
SDK_INT4 GuidanceKind Guidance mode. See GuidanceKinds
SDK_INT4 NextGuidanceKind Next Guidance mode. See GuidanceKinds
SDK_INT2 CrowFlightDirection Direction to target [0..360] 0=N, 90=E
SDK_UINT4 CrowFlightGPSDistToDestination Crow flight distance [m] from current GPS position on route to destination
SDK_TruckWarnings TruckWarningsCurrentSegment Truck warnings on current segment
SDK_TruckWarnings TruckWarningsNextSegment Truck warnings on next segment
SDK_INT4 TrafficDelayOnRoute Current traffic delay on the complete route
SDK_INT4 TrafficDelayOnRouteForCurrentPosition Current traffic delay on the route from the current position
SDK_BOOL TrafficBlockingEventsOnRoute Any blocking traffic events exist on route.
SDK_BOOL TrafficBlockingEventsOnRouteForCurrentPosition Any blocking traffic events exist on route for or after current position.

As you can see, there is a lot of information we get. For our control, we only use the DistToNextManeuver for the distance label and the NextManeuver for determining the arrow and for setting the street name.
To update the label with the correct maneuver arrow, we have to interpret the NextManeuver field, which is of type SDK_ManeuverDescription.

The struct SDK_ManeuverDescription 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_ManeuverDescriptionVERSION
SDK_Position Pos Coordinates of the maneuver
SDK_INT4 DrivingDirection [0..360], 0 is East
SDK_INT4 RoadType Road type of road after maneuver [0..14]
SDK_INT4 Category Road category of road after maneuver [0..4]
SDK_INT4 CountryID Country id. Maybe 0 for some roads due to internal limitations
SDK_INT4 DistFromStart Distance from start [m]
SDK_INT4 TimeFromStart Time from start [s]
SDK_INT4 TunnelMode Usually set to 0. 1 indicates a maneuver inside a tunnel. 2 indicates a maneuver immediately after leaving a tunnel
SDK_BOOL RoundaboutManeuver Is this a maneuver related to a roundabout?
SDK_BOOL TBaseCrossing Is this a maneuver at a T-like crossing (i.e. two streets leaving the current street, one about 90 degrees left, one about 90 degrees right)
SDK_INT2 ManeuverType A value of type NavigationManeuverTypes
SDK_INT2 TurnOrient A value of type NavigationManeuverDirections
SDK_INT2 TurnWeight A value of type NavigationManeuverWeights
SDK_INT2 ExitNr Exit number in a roundabout. It counts only for valid/legal exits and counting is in driving direction (i.a. counter-clockwise if driving on RHS (most parts of europe, USA, etc.) and clockwise if driving on LHS (GB, AUS, RSA, etc.)
SDK_INT4 GuidanceKind Kind of current guidance as of next menoeuvre. See GuidanceKinds
SDK_INT4 NextGuidanceKind Kind of next guidance as after next maneuver. See GuidanceKinds
SDK_BOOL Border Is maneuver a border crossing
SDK_BOOL EnterFerry Is maneuver entering a ferry
SDK_BOOL LeaveFerry Is maneuver leaving a ferry
SDK_BOOL HasSignPostInfo Signpost info is available
SDK_UINT1 JunctionType The type of junction this maneuver description belongs to. Possible values are:
  • 0 -> standard value (no special junction type)
  • 1 -> AS standard junction type
  • 2 -> AS 1st junction type
  • 3 -> AS 2nd junction type
  • 4 -> AK 1st junction type
  • 5 -> AK 2nd junction type
  • 6 -> AD right junction type
  • 7 -> AD left junction type
SDK_WCHAR_T StreetName[128] Street name of segment after passing the maneuver
SDK_SignPostInfo SignPostInfo Signpost information, only set, if HasSignPostInfo is SDK_TRUE
SDK_INT4 DelayOnRoute The traffic delay on the route

We implement a simple (but not complete) example how to map these informations into a maneuver arrow in NextManeuverControlWidget::update().

void NextManeuverControlWidget::update(const SDK_NavigationInformation& navInfo)
{
    ...
    SDK_ManeuverDescription nextManeuver = navInfo.NextManeuver;
    SDK_CrossingView crossingView = navInfo.NextManeuverCrossingView;
    bool drivingRHS = crossingView.DrivingRHS;
    ...

First, we retrieve the next maneuver information and the crossing info. The next maneuver field provides information about the direction of the next maneuver and the type of it. The crossing info is used to retrieve info about the driving side. We need this info to paint the correct arrow for turn around maneuvers.

    ...
    SDK_WCHAR_T *distanceText = NULL;
    SDK_ERROR rc = SDK_GetLocalizedDistanceString(navInfo.DistToNextManeuver, SDK_TRUE, &distanceText, SDK_TRUE, L"now", L'.', SDK_FALSE);
    ui.labelIconText->setText(QString::fromWCharArray(distanceText));
    ui.labelText->setText(QString::fromWCharArray(nextManeuver.StreetName));
    ...

To get a formatted distance string with the correct unit, we call SDK_GetLocalizedDistanceString(). Next we determine the arrow for the maneuver:

    ...
    switch (nextManeuver.ManeuverType)
    {
        case SDK_eMT_Error:
        case SDK_eMT_Further:
            ui.labelIcon->setPixmap(mArrowForward);
            return;

        case SDK_eMT_Keep:
        case SDK_eMT_Enter:
        case SDK_eMT_Change:
        case SDK_eMT_Turn:
        {
            switch (nextManeuver.TurnOrient)
            {
                case SDK_eTO_Left:
                {
                    switch (nextManeuver.TurnWeight)
                    {
                    case SDK_eTW_Weak:
                        ui.labelIcon->setPixmap(mArrowForwardLeft);
                        return;
                    case SDK_eTW_Error:
                    case SDK_eTW_Strong:
                    case SDK_eTW_Normal:
                        ui.labelIcon->setPixmap(mArrowLeft);
                        return;
                    }
                }
                case SDK_eTO_Right:
                {
                    switch (nextManeuver.TurnWeight)
                    {
                    case SDK_eTW_Weak:
                        ui.labelIcon->setPixmap(mArrowForwardRight);
                        return;
                    case SDK_eTW_Error:
                    case SDK_eTW_Strong:
                    case SDK_eTW_Normal:
                        ui.labelIcon->setPixmap(mArrowRight);
                        return;
                    }
                }
                case SDK_eTO_Back:
                {
                    if (drivingRHS)
                        ui.labelIcon->setPixmap(mArrowUTurnLeft);
                    else
                        ui.labelIcon->setPixmap(mArrowUTurnRight);
                    return;
                }
                default:
                    ui.labelIcon->setPixmap(mArrowForward);
                    return;
            }
        }

        case SDK_eMT_UTurn:
            if (drivingRHS)
                ui.labelIcon->setPixmap(mArrowUTurnLeft);
            else
                ui.labelIcon->setPixmap(mArrowUTurnRight);
            break;

        case SDK_eMT_Exit:
        case SDK_eMT_EnterRoundabout:
        case SDK_eMT_StayRoundabout:
        case SDK_eMT_ExitRoundabout:
        case SDK_eMT_DestinationReached:
        case SDK_eMT_EnterRoad:
        case SDK_eMT_LeaveRoad:
        case SDK_eMT_StopOver:
        default:
            ui.labelIcon->setPixmap(mArrowForward);
            return;
    }
}
To handle the showing and hiding of the control, we add the following to the MainWindow class:

MainWindow.cpp:

void MainWindow::onCalcRouteFinished(int error)
{
    ...

    if ((SDK_ERROR)error == SDK_ERROR_Ok)
    {
        ...
    }
    else
    {
        ui.nextManeuverWidget->setVisible(false);
    }
}

void MainWindow::actionSearchAddressTriggered(bool triggered)
{
    ...

    if (retVal == QDialog::Accepted && rc == SDK_ERROR_Ok)
    {
        ui.map->denyUpdates();
        ui.nextManeuverWidget->setVisible(false);
        ...
    }
}

We hide the controls if a route calculation returned an error, when we go into tracking mode or when a new address was searched.

Simulate driving

For easy testing, the NavigationSDK brings its own simulation mechanism. To use it, we simply generate a simulation file by calling SDK_CreateGPSSimulation() after a route calculation. We then close our current GPSDevice by calling GPSManager::getInstance()->closeGPS() and reopen a simulated GPSDevice with GPSManager::getInstance()->openGPS() passing the name of the created simulation file.

Attention

Don't push locations to the SDK by calling GPSManager::getInstance()->positionUpdated()! Only get the current position with GPSManager::getInstance()->getCurrentPosition(). The simulation will step to the next position with each call to getCurrentPosition().

To start and stop a simulation, we added a new menu entry to our MainWindow with the action actionToggleSimulation. When triggered, the slot actionSimulationToggled() will be called. We also add a new atomic bool flag for getting the current simulation status.

Add the following to the MainWindow class:

MainWindow.h:

...
#include <atomic>

class MainWindow : public QMainWindow
{
    ...
protected slots:
    void actionSimulationToggled(bool toggled);
private:
    ...
    std::atomic<bool> mIsSimulation;
    ...
}

MainWindow.cpp:

MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    ..
    mIsSimulation.store(false);
    connect(ui.actionToggleSimulation, SIGNAL(toggled(bool)), this, SLOT(actionSimulationToggled(bool)));
}

void MainWindow::actionSimulationToggled(bool checked)
{   
    if (!checked)
    {
        mIsSimulation.store(false);
        GPSManager::getInstance()->closeGPS();
        GPSManager::getInstance()->openGPS(0);
    }
    else
    {
        mIsSimulation.store(true);
        QString simFileName = Constants::getDataPath();
        simFileName += "simulation.dat";
        SDK_ERROR rc = SDK_CreateGPSSimulation(simFileName.toStdWString().c_str());
        mGPSManager.closeGPS();
        mGPSManager.openGPS(simFileName.toStdWString().c_str());
        ui.map->setScale(250);
    }
}

In the constructor, we set the mIsSimulation flag initially to false and connect the menu action actionToggleSimulation to the slot actionSimulationToggled().

If the simulation was toggled by clicking the menu entry, we either stop or start the simulation. Stopping is easy, we close the GPS device and reopen it with the simulation file name set to 0. Starting the simulation is done by calling

SDK_ERROR rc = SDK_CreateGPSSimulation(simFileName.toStdWString().c_str());
This will generate a simulation file with the name "simFileName" from the current calculated route. If there is no current route, nothing will be generated and an error will be returned. To play the file, we close our GPS device and reopen it with the simulation file name as parameter. If the call is succesful, the simulation will start immediately.

As mentioned above, we have to avoid pushing GPS data to the SDK by calling GPSManager::getInstance()->positionUpdated(gpsData) if we are in a simulation, so we check the flag mIsSimulation before pushing the GPS data.

Add/Change the following in the MainWindow class:

MainWindow.cpp:

void MainWindow::onPositionUpdated(QGeoPositionInfo info)
{
    ...
    if(!mIsSimulation.load())
        GPSManager::getInstance()->positionUpdated(gpsData);
}
...
void MainWindow::onCalcRouteFinished(int error)
{
    ...

    if ((SDK_ERROR)error == SDK_ERROR_Ok)
    {
        ...
        ui.actionToggleSimulation->setEnabled(true);
    }
    else
    {
        ui.actionToggleSimulation->setEnabled(false);
        ...
    }
}

The menu entry for starting a simulation should only be active if the route calculation was successful, so we set the action to enabled or disabled according to the error returned from the route calculation.

Now we have the possibility to navigate:

Auto zooming

The SDK offers the possibility to auto zoom the map depending on the current driving situation. If a maneuver is ahead, it will be zoomed in to show the street situation at the maneuver in detail. If no maneuver is ahead, a proper zoom level will be calculated dependent on the driving speed and the segment speed the car is on. On motorways the auto zoom will zoom out a lot more than on urban streets for a better overview. The margins for the scalings and the speed intervals with the correspondent scales can be set by the functions SDK_SetAutoZoomSpeedScales() and SDK_SetAutoZoomScaleMargins(). The scaling and speed values are all preset internally to meaningful values, so you only have to change them if you are not satisfied with the default values.

Zooming is done by calling SDK_DoAutoZoom(). We call this function in our MapWidget class, so we have to add the following code:

MapWidget.h:

class MapWidget : public QWidget
{
    Q_OBJECT

public:
    ...
    void DoAutoZoom(const SDK_NavigationInformation * navInfo, const SDK_GPSData * gps);
...
}

MapWidget.cpp:

...
void MapWidget::DoAutoZoom(const SDK_NavigationInformation * navInfo, const SDK_GPSData * gps)
{
    SDK_DoAutoZoom(mMapId, navInfo, gps, true);
}

SDK_DoAutoZoom() will calculate a proper scale value for the current driving situation. If the parameter navInfo is 0, it will calculate values suitable for tracking mode, if an SDK_NavigationInformation struct is given, it will calculate a scale for navigation (for instance by considering the maneuver information additionally). The third parameter is for using the smoothing functionality of the zoom. The calculated scale values of the different zooming situations may vary a lot. For instance, if you drive onto a motorway, the scale for the maneuver will be very low to show details of the street situation. After the maneuver, the scale for driving on the motorway is much higher. You usually don't need many details when driving on a motorway, so the standard behaviour is to zoom out for a better overview. To avoid a jump in the zoom level, the auto zoom will ramp up or down the actual scale value by a given step size until it reaches the calculated zoom value for the situation. Therefore, you have to call SDK_DoAutoZoom() in the NavigationLoop to constantly change the scale value. If you immediately want to switch to the calculated zoom value without smoothing (maybe because you are switching from manual to auto zoom), you can set the third parameter to false. This will result in an immediate switch to the zoom value.

To switch the auto zoom on and off, we added a menu entry in the main window, which we now connect to a slot in our NavigationLoop (the action is already defined in the mainwindow.ui, you don't have to add this):

Add/Change the following in the MainWindow class:

MainWindow.cpp:

MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    ...
    connect(ui.actionAutoZoom, SIGNAL(toggled(bool)), mNavigationLoop, SLOT(actionAutoZoomToggled(bool)));
}

We connect the actionAutoZoom to the slot actionAutoZoomToggled() in the NavigationLoop. Now we have to add this function to the NavigationLoop:

NavigationLoop.h:

class NavigationLoop : public QObject, public QRunnable
{  
    ...
private:
    bool mDoAutoZoom;
    ...

protected slots: 
    void actionAutoZoomToggled(bool);
}    

NavigationLoop.cpp:

NavigationLoop::NavigationLoop(MapWidget * map) : QObject(), QRunnable()
{
    ...
    mDoAutoZoom = false;
}

...

void NavigationLoop::run()
{
    ...
    if (navigationActive())
    {
        SDK_ERROR rc = SDK_GetGuidanceInformation(&gps, &navInfo, 0, progressCallback);
        if (rc == SDK_ERROR_Ok)
        {
            ...
            if(mDoAutoZoom)
                mMap->DoAutoZoom(&navInfo, &gps);
        }
    }
    else
    {
        if (gps.fix > 0)
        {
            ...           
            if(mDoAutoZoom)
                mMap->DoAutoZoom(0, &gps);          
        }
        else
            ...
    }
    ...

}
...
void NavigationLoop::actionAutoZoomToggled(bool checked)
{
    mDoAutoZoom = checked;
}

The auto zooming is done by calling the previously implemented function DoAutoZoom(0, &gps) of our MapWidget. We call this in the NavigationLoop every time we get new guidance or new gps information. The SDK function will calculate a proper scale value and sets it automatically.

Tunnel extrapolation

When driving through a tunnel, we normally have no valid GPS position. Our navigation software therefore stops at the tunnel entry and will go on when leaving the tunnel. Most of the time, this behaviour is not wanted, we want to drive through the tunnel as in normal navigation and we also want updated navigation information in the tunnel.

To circumvent this situation, the SDK offers the method SDK_CheckIfTunnel(). It has to be called right after getting navigation information by calling SDK_GetGuidanceInformation() and will check if we entered or leaved a tunnel. The parameter 'pIsTunnel' will be set accordingly. Now, we know if we are in a tunnel or not, but we still haven't a valid GPS signal and the navigation will not continue in the tunnel.

SDK_CheckIfTunnel() also has a solution for this problem. It not only checks if we enter a tunnel, it also starts a position extrapolator, which emulates a GPS signal in the tunnel. We can get this extrapolated position by reading out the values extraPolatedPos, extraPolatedVelocity and extraPolatedCourse in our SDK_GPSData struct. Disabling the extrapolator is also done by SDK_CheckIfTunnel. If we receive more than 5 continuous valid gps data sets, the extrapolator will be deactivated automatically.

To enable the extrapolating, we have to add the following to the NavigationLoop:

NavigationLoop.cpp:

NavigationLoop::NavigationLoop(MapWidget * map) : QObject(), QRunnable()
{
    ...
    mDoAutoZoom = false;
}

...

void NavigationLoop::run()
{
    ...
    if (navigationActive())
    {
        SDK_ERROR rc = SDK_GetGuidanceInformation(&gps, &navInfo, 0, progressCallback);
        if (rc == SDK_ERROR_Ok)
        {
            ...
            SDK_BOOL isTunnel = false;
            SDK_CheckIfTunnel(&navInfo, &gps, &isTunnel);
            if (!isTunnel)
            {
                mMap->setGreyMode(false);
                mMap->setCenter(navInfo.PosOnRoute);
                mMap->setMapMarker(navInfo.PosOnRoute, navInfo.CourseOnRoute, false, SDK_mm_Directed);
            }
            else
            {
                mMap->setGreyMode(true);
                mMap->setCenter(gps.extraPolatedPos);
                mMap->setMapMarker(gps.extraPolatedPos, gps.extraPolatedCourse, false, SDK_mm_Directed);
            }
            ...
        }
    }
    else
    {
        mMap->setGreyMode(false);
        ...
    }
    ...

}

We check for a tunnel right after calling SDK_GetGuidanceInformation. If we are not in a tunnel, we set the center of the map and the map marker to the real GPS position and course, otherwise we use the extrapolated position and course (we also render the map in grey mode if we are in a tunnel to give the user the feedback that the position is extrapolated and not real GPS).

Tracking Mode

Now we can navigate, but how to stop and reset the navigation? For this, the SDK offers the function SDK_TrackingMode(). It will reset the navigation and remove the route trace from the map. We switch to tracking mode by triggering the menu action actionTrackingMode.

Add the following to the MainWindow class:

MainWindow.h:

    ...
protected slots:
    void actionTrackingModeTriggered(bool triggered);
    ...

Mainwindow.cpp

MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    ...
    connect(ui.actionTrackingMode, SIGNAL(triggered(bool)), this, SLOT(actionTrackingModeTriggered(bool)));
}

void MainWindow::actionTrackingModeTriggered(bool triggered)
{
    SDK_TrackingMode(SDK_TRUE, 0, 0);   
    ui.map->removeAllPins();
    mNavigationLoop->stopNavigation();
    ui.nextManeuverWidget->setVisible(false);
    ui.infoControlWidget->setVisible(false);
}
If triggered, we call SDK_TrackingMode(), remove the pins from the map and set the inNavigation flag of our NavigationLoop to false. Also, we hide the next maneuver control and the info control.