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: