08 Traffic information

Overview

In this tutorial, you will learn how to retrieve traffic information from the PTV.

Base

Use Tutorial07 as a starting point for this project.

Prerequisites

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

  • TrafficDialog.cpp
  • TrafficDialog.h
  • trafficdialog.ui
  • TrafficModel.cpp
  • TrafficModel.h

Also replace

  • mainwindow.ui

with the one from Tutorial08.

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
    ...
    trafficdialog.ui
)

set(Tutorial_SRCS
    ...
    TrafficDialog.cpp
    TrafficModel.cpp
)

set(Tutorial_HEADER
    ...
    TrafficDialog.h
    TrafficModel.h
)

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.

Obtaining a key

For using the traffic information functionality in the SDK you will need an API key. Please contact the PTV Navigator product management to obtain such a key if you haven't already got one.

Retrieving traffic informations

Traffic incidents can be fetched by the SDK from an external server. The incidents hold informations about what has happend, where it happend, how long the incident will last and many more. Retrieved traffic results will be shown automatically as icons and traces on the map. To consider traffic in a route calculation (and therefore to probably bypass a traffic jam), set the SDK_RouteOptions member "TrafficFactor" to > 0.

The struct SDK_TrafficSearchResult has the following members:

Type Name Description
SDK_HDRINFO size Size of struct - for controlling
SDK_HDRINFO version Version of struct - for controlling. Use SDK_TrafficSearchResultVERSION
SDK_CHAR_T description[512] Description of the incident
SDK_CHAR_T reportId[100] Unique identifier for an incident
SDK_CHAR_T providerName[100] Name of the content provider
SDK_CHAR_T countryCode[5] Country code (ISO 3166-1-alpha-2)
SDK_CHAR_T stateCode[5] Country sub state code
SDK_CHAR_T startTime[50] Start time of the incident, in utc time
SDK_CHAR_T endTime[50] End time of the incident, in utc time
SDK_INT4 tmcEventCode TMC event code of the incident
SDK_INT4 category Category of the incident see TrafficSearchCategories
SDK_INT4 delay Delay of the incident in [s]
SDK_INT4 delayOnRoute Delay of the parts of the incident which lies on the route in [s]
SDK_INT4 delayOnOtherEvent Therefrom (see above, event part on route) also on an other event with higher restriction
SDK_BOOL delayDeliveredByServer Get info if the delay was supplied by the server or internally calculated
SDK_INT4 averageSpeed Average speed on this incident [km/h]
SDK_INT4 eventLength Length of the event in [m]
SDK_INT4 eventLengthOnRoute Length of the event parts on the route in [m]
SDK_INT4 distanceFromStart Distance of the first segment of the event to the start of the route [m]
SDK_DOUBLE distance Distance of the first polypoint to the search midpoint [m]
SDK_INT4 status Status of the incident in relation to the last search, see TrafficStatus
SDK_Position startPosition First point in the incident polygon
SDK_BOOL onRoute Event is part of route
SDK_BOOL routingRelevant Event is part of route and considered while routing, otherwise probably it is out of time
SDK_BOOL info Event is only an information, advice or warning without a delay
SDK_BOOL onlyTruck Event is only relevant for trucks
SDK_INT4 simplifiedCategory Simplified traffic category (SDK_TI_DEFAULT, SDK_TI_JAM, SDK_TI_HEAVY, SDK_TI_BLOCKED)

To retrieve traffic informations the following steps are necessary:

  • Initialize the web search by calling SDK_SetTrafficSearchWebConfiguration()
  • Call SDK_SearchTrafficInformation() to start an asynchronous call to a traffic REST service
  • Implement a callback that will be notified when a traffic search request has finished

First, we initialize the search by setting the search request url and some other data for starting a web request.
We call SDK_SetTrafficSearchWebConfiguration() with an SDK_TrafficSearchWebConfiguration struct.

The struct SDK_TrafficSearchWebConfiguration has the following members:

Type Name Description
SDK_HDRINFO size Size of struct - for controlling
SDK_HDRINFO version Version of struct - for controlling. Use SDK_TrafficSearchWebConfigurationVERSION
const SDK_WCHAR_T* url web server url to use for the search, must be set to L"http://tidemo.ptvgroup.com/wfs"
const SDK_WCHAR_T* proxy proxy server address, only used under Win32
const SDK_WCHAR_T* token server token, set to the token given by PTV
const SDK_WCHAR_T* service service name, must be set to L"WFS"
const SDK_WCHAR_T* serviceVersion service version, must be set to L"1.0.0"
const SDK_WCHAR_T* request service request, must be set to L"GetFeature"
const SDK_WCHAR_T* typeName service provider name to use. Set to L"ptvgroup:tomtom_incidents" for tomtom or L"ptvgroup:nokia_incidents" for nokia
const SDK_WCHAR_T* outputFormat service output format, must be set to L"JSON"
SDK_BOOL useProxy if true, use the given proxy server
SDK_BOOL useGZIP if true, use gzip
SDK_UINT4 timeout server timeout [s]
SDK_BOOL doGetVolumeStatistics retrieve server traffic volume statistics when set to true, not supported on every platform, see also SDK_GetTrafficSearchResultStatistics
SDK_BOOL doLogging if true, all incoming traffic data will get logged into files
SDK_BOOL doSimulation if true, the traffic data will be read from log files instead of fetching it from the server
const SDK_WCHAR_T* directoryLogging the directory where the traffic data should be logged, set to NULL if not used
const SDK_WCHAR_T* directorySimulation the directory from where the simulation data should be read, set to NULL if not used

As you can see, there are many options for configuring the server access, but most of the fields should be set to their default value that is given in the table.

After setting the web configuration, we call SDK_SearchTrafficInformation() with an SDK_TrafficSearchRequest struct. This struct holds information necessary for the upcoming search, such as the position the search should be started from, the range of the search, the type of search and the language of the retrieved messages.

The struct SDK_TrafficSearchRequest has the following members:

Type Name Description
SDK_HDRINFO size Size of struct - for controlling
SDK_HDRINFO version Version of struct - for controlling. Use SDK_TrafficSearchRequestVERSION
SDK_Position pos Center position to search traffic info [merc]
SDK_Position targetPos Optional target position for spanning a rectangle from start to target if type == SDK_TI_SEARCH_BOX
SDK_UINT4 distance First distance [km] to search from the given point, see comment below for details
SDK_UINT4 distance2 Second distance [km] to search from the given point, see comment below for details
SDK_UINT4 distance3 Third distance [km] to search from the given point, see comment below for details
SDK_UINT4 radius First radius [km] to search from the given point, see comment below for details
SDK_UINT4 radius2 Second radius [km] to search from the given point, see comment below for details
SDK_UINT4 radius3 Third radius [km] to search from the given point, see comment below for details
const SDK_WCHAR_T* filter internal use only, must be set to NULL
const SDK_WCHAR_T* filter2 internal use only, must be set to NULL
const SDK_WCHAR_T* filter3 internal use only, must be set to NULL
SDK_INT4 languageCode Language code used to set the message text, see TrafficIsoCode for details
SDK_INT4 type Type of the search, see TrafficSearchMode for more details.

The distance and radius values describe the search distances in different situations. If type is set to SDK_TI_SEARCH_NORMAL, one search request will be sent to the server, if type is SDK_TI_SEARCH_COMPLEX, three requests will be sent.

In complex mode, these three requests differ in the expansion of the search and the roads which will be searched for traffic.
The road filtering is done by the three filter parameters. These parameters will be set internally so that in a complex search, the farer the distance is, only incidents on greater roads are searched to minimize the data volume of a search. Manual filter setting needs a knowledge of the traffic server API, so set the filters to NULL to use the internal predefined values.

There are two main scenarios:

  • There is actually no route available: Traffic is searched in one or more circles around the given pos. When SDK_TI_SEARCH_NORMAL is set, only one circle search is used with the value of "radius". When SDK_TI_SEARCH_COMPLEX is used, for every given radius a search request will be started with the approriate filter.
  • There is a route available: When SDK_TI_SEARCH_NORMAL is set, we search in a rectangle box around the route with a diagonal size from the startpoint towards the route of "distance". This box will be extended about 5%. When SDK_TO_SEARCH_COMPLEX is set, three boxes are created, everyone like in SDK_TI_SEARCH_NORMAL, box1 with "distance", box2 with "distance2" and box3 with "distance3". The boxes will be extended with the appropriate value of the radius parameters. The boxes and circles can be visualized by calling SDK_SetUserParam() with the first parameter set to SDK_SUP_DRAW_TRAFFIC_REQUEST_BOUNDINGS.

For a start, we will set the missing request parameters to:

  • distance = 30
  • distance2 = 60
  • distance3 = 100
  • radius = 50
  • radius2 = 100
  • radius3 = 300
  • type = SDK_TI_SEARCH_COMPLEX
  • languageCode = SDK_TI_ISO_EN

To set the web configuration, the initial search request parameters and to start a search, we

add the following in our NavigationLoop class:

NavigationLoop.h:

...
class NavigationLoop : public QObject, public QRunnable
{   
    Q_OBJECT
public:
    ...
    static SDK_INT4 SDK_API trafficCallback(SDK_INT4 current, SDK_INT4 total, SDK_INT4 job, SDK_INT4 error);
    void setTrafficWebConfig();
    void initTrafficRequest();
    void setTrafficSearchPosition(const SDK_Position& pos);
private:
    ...
    SDK_TrafficSearchRequest mTrafficRequest;
    SDK_TrafficSearchWebConfiguration mTrafficWebConfiguration;
signals:
    ...
    void trafficFinished(int);

NavigationLoop.cpp:

NavigationLoop::NavigationLoop(MapWidget * map, TTSEngine * ttsEngine) : QObject(), QRunnable()
{
    ...
    setTrafficWebConfig();
    initTrafficRequest();
}
...
long loop = 0;
void NavigationLoop::run()
{
    SDK_GPSData gps;
    SDK_ERROR rc = GPSManager::getInstance()->getCurrentPosition(gps);
    if (rc == SDK_ERROR_Ok || rc == SDK_ERROR_SamePosition)
    {               
        if ((loop % 100) == 0)
        {
            setTrafficSearchPosition(gps.GPSPositionMerc);
            rc = SDK_SearchTrafficInformation(&mTrafficRequest, trafficCallback);
        }
        ...
    }
    ...
    loop++;
}

void NavigationLoop::setTrafficWebConfig()
{
    SDK_InitTrafficSearchWebConfiguration(&mTrafficWebConfiguration);

    mTrafficWebConfiguration.url = L"http://ti.ptvgroup.com/wfs";
    mTrafficWebConfiguration.proxy = 0;
    //mTrafficWebConfiguration.token = L"<YOUR TOKEN HERE PLEASE>";
    mTrafficWebConfiguration.service = L"WFS";
    mTrafficWebConfiguration.serviceVersion = L"1.0.0";
    mTrafficWebConfiguration.request = L"GetFeature";
    mTrafficWebConfiguration.typeName = L"ptvgroup:tomtom_incidents";
    mTrafficWebConfiguration.outputFormat = L"JSON";
    mTrafficWebConfiguration.timeout = 30;
    mTrafficWebConfiguration.useProxy = SDK_FALSE;
    mTrafficWebConfiguration.useGZIP = SDK_TRUE;
    mTrafficWebConfiguration.doLogging = SDK_FALSE;
    mTrafficWebConfiguration.doSimulation = SDK_FALSE;
    mTrafficWebConfiguration.doGetVolumeStatistics = SDK_TRUE;

    mTrafficWebConfiguration.directoryLogging = 0;
    mTrafficWebConfiguration.directorySimulation = 0;
}

void NavigationLoop::initTrafficRequest()
{
    SDK_InitTrafficSearchRequest(&mTrafficRequest);

    mTrafficRequest.distance = 30;
    mTrafficRequest.distance2 = 60;
    mTrafficRequest.distance3 = 100;
    mTrafficRequest.radius = 50;
    mTrafficRequest.radius2 = 100;
    mTrafficRequest.radius3 = 300;
    mTrafficRequest.type = SDK_TI_SEARCH_COMPLEX;
    mTrafficRequest.languageCode = SDK_TI_ISO_EN;
}

void NavigationLoop::setTrafficSearchPosition(const SDK_Position& pos)
{
    mTrafficRequest.pos = pos;
}

We initialize the web configuration with a valid url, your given Traffic token and the standard parameters given in the table above in setTrafficWebConfig(). Also, we initialize the search request with some example parameters. We won't change the language or the search type in this tutorial, so we only have to update the GPS position in our NavigationLoop::run() function before calling SDK_SearchTrafficInformation(). We do this by calling setTrafficSearchPosition() with the current position. After that, we start the search. To avoid searching every time the function is called (that would be around 200ms) we add a variable called "loop" to reduce the update interval. SDK_SearchTrafficInformation() will return immediately because it itself spawns a new thread in which the traffic fetching will be done.

The traffic callback

The SDK will call our callback function trafficCallback() when a previously triggered search has finished. The parameters of the callback are:

  • current: Always set by the SDK to 100, no need to check
  • total: Always set by the SDK to 100, no need to check
  • job: The job id. Can be either SDK_TI_JOB_SEARCH or SDK_TI_JOB_UPDATE. The first determines that the callback was triggered after a real search has taken place. The second will be set when the traffic items are updated without a search.
  • error: The error code the search returned

The only thing we do in our callback is to emit the signal trafficFinished() so that we can react in our MainWindow (and in the GUI thread) to the incoming events.

Add the following to the NavigationLoop class:

NavigationLoop.cpp

...
SDK_INT4 SDK_API NavigationLoop::trafficCallback(SDK_INT4 current, SDK_INT4 total, SDK_INT4 job, SDK_INT4 error)
{    
    emit pThis->trafficFinished(error);
    return 1;
}

The receiver of this signal is in our MainWindow class and is called void onTrafficFinished(int error), so

add the following to the MainWindow class:

MainWindow.h:

...
protected slots:
    ...
    void onTrafficFinished(int error);
...

MainWindow.cpp:

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

void MainWindow::onTrafficFinished(int error)
{
    SDK_TrafficSearchResult * trafficResult;
    SDK_UINT4 trafficCount = 0;
    SDK_GetTrafficSearchResult(&trafficResult, &trafficCount, 0);

    for (size_t i = 0; i < trafficCount; i++)
    {
        qDebug(trafficResult[i].description);
    }
}

We connect the signal trafficFinished() from the NavigationLoop with the onTrafficFinished() slot in the MainWindow constructor. The onTrafficFinished() function will retrieve the search results by calling SDK_GetTrafficSearchResult(). We then iterate over the returned results and output the description of the current result to the console.

Start the program and take a look at the Output window. When the search has finished, the descriptions of the traffic incidents will be outputted. Also, the incidents will be shown on the map.

Traffic dialog

Now it is time to show the traffic search results in a separate dialog.

Let's take a look at the trafficdialog.ui file in the designer:

As you can see, this is a very simple dialog with only one listview as the central widget. The implementation of the class looks like this:

TrafficDialog.cpp:

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

TrafficDialog::TrafficDialog(QWidget *parent)
    : QDialog(parent)
{
    ui.setupUi(this);
    ui.listView->setModel(&mModel);
}

TrafficDialog::~TrafficDialog()
{
}

void TrafficDialog::setData(const std::vector<SDK_TrafficSearchResult>& result)
{
    mModel.setData(result);
}

To show the traffic incidents in the listview, we created a model called TrafficModel. We set this model to the listview and add a function TrafficDialog::setData() to add data to this model.

The model itself is subclassed from QAbstractListModel. In the data() function, it will return a QString representation of the description of a particular incident for showing in our listview:

TrafficModel.cpp:

...
void TrafficModel::setData(const std::vector<SDK_TrafficSearchResult>& result)
{
    removeRows(0, mResult.size());
    mResult = result;
    QModelIndex topLeft = createIndex(0, 0);
    QModelIndex topBottomRight = createIndex(mResult.size() - 1, 0);
    emit dataChanged(topLeft, topBottomRight);
}

int TrafficModel::rowCount(const QModelIndex & /*parent*/) const
{
    return mResult.size();
}

QVariant TrafficModel::data(const QModelIndex &modelIndex, int role) const
{

    int index = modelIndex.row();

    if (role == Qt::DisplayRole)
    {
        return QString(mResult[index].description);
    }

    return QVariant();
}

To show the traffic dialog, we added a new action (in the mainwindow.ui) called actionShowTrafficResults to our MainWindow menu. We connect this action to the new slot actionShowTrafficResultsTriggered() which will create a new traffic dialog.
The traffic dialog needs the traffic search result for showing the incidents. Therefore, we save the search result to the variable mTrafficResult in MainWindow::onTrafficFinished().

Add/Change the following to the MainWindow class:

MainWindow.h:

...
#include "TrafficDialog.h"

class MainWindow : public QMainWindow
{
...
protected slots:
    ...
    void actionShowTrafficResultsTriggered(bool triggered);
private:
    std::vector<SDK_TrafficSearchResult> mTrafficResult;
    TrafficDialog * mTrafficDialog;
}

MainWindow.cpp:

MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent)
{
    ...
    mTrafficDialog = 0;
    connect(ui.actionShowTrafficResults, SIGNAL(triggered(bool)), this, SLOT(actionShowTrafficResultsTriggered(bool)));
}
...

void MainWindow::actionShowTrafficResultsTriggered(bool checked)
{
    disableNavigationLoopTimer();
    if (mTrafficDialog)
    {
        mTrafficDialog->setData(mTrafficResult);
        mTrafficDialog->exec();
    }       
    else
    {
        mTrafficDialog = new TrafficDialog(this);
        mTrafficDialog->setData(mTrafficResult);
        mTrafficDialog->exec();
    }
    enableNavigationLoopTimer();
}

void MainWindow::onTrafficFinished(int error)
{
    SDK_TrafficSearchResult * trafficResult;
    SDK_UINT4 trafficCount = 0;
    SDK_GetTrafficSearchResult(&trafficResult, &trafficCount, 0);
    mTrafficResult.clear();
    mTrafficResult.reserve(trafficCount);
    for (size_t i = 0; i < trafficCount; i++)
    {
        mTrafficResult.push_back(trafficResult[i]);
    }

    if (trafficCount > 0)
        ui.actionShowTrafficResults->setEnabled(true);
    else
        ui.actionShowTrafficResults->setEnabled(false);
    ui.map->update();
}

MainWindow:: ~MainWindow()
{
    ...
    if (mTrafficDialog)
        delete mTrafficDialog;
}

In the destructor of our MainWindow, we delete the traffic dialog.

Now we can see the traffic results in our traffic dialog: