10 Guided navigation
Overview
This tutorial will show you how to calculate a tour with multiple destinations. Parts of the tour can be standard A to B routings, but they also can be guided navigation routings. Guided navigation routings are routings that lead over a list of SVPs (silent via points).
To accomplish this we will develop a simple JSON file format, which can be loaded by the tutorial from an e-mail attachment or from an open file intent.
Base
As always you can use your results from the previous Tutorial09
as a base for our new project.
Prerequisites
Please copy the following files from the Tutorial10 folder to your project directory:
- CalcTourRunnable.cpp
- CalcTourRunnable.h
Also replace the file
- mainwindow.ui
with the one from Tutorial10.
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_SRCS ... CalcTourRunnable.cpp ) set(Tutorial_HEADER ... CalcTourRunnable.h )
Also, we need some new resources, so copy the following file from the Tutorial10/res folder to your /res directory:
- tour.json
And add it 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> <file>res/tour.json</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.
Tour calculation
The NavigationSDK offers extended capabilites to calculate tours with the help of the following functions:
- SDK_CreateTour: Create a tour with the given coordinate as start coordinate.
- SDK_AddStationToTour: Add a regular station to a tour.
- SDK_AddSVPsToTour: Add an SVP station to a tour.
- SDK_CalculateTour: Calculate a tour.
And the following structs:
- SDK_Waypoint(): A point describing a regular destination
- SDK_SVPWaypoint(): A point describing a silent via point in a SVP destination
As we described in Tutorial05, a tour is defined to be a route with one or multiple stations. A tour can contain "regular" stations and SVP stations. Regular stations are stations which are defined solely by a geo-coordinate. SVP stations are defined by a set of coordinates with directions (and other attributes) which describe a way to get to a destination.
To calculate a tour, do the following:
- Create a tour with SDK_CreateTour (this sets also the start position)
- Add as many stations as you like to the former created tour by calling SDK_AddStationToTour or SDK_AddSVPsToTour
- Call SDK_CalculateTour to calculate the tour
- Delete the tour if not needed anymore by calling SDK_DeleteTour
Example JSON data format
For laoding a tour from a file, we created a simple JSON data format.
Our example data format is a simple json array containing regular and/or SVP destinations. A regular destination is basically a destination at point x,y in mercator coordinates. A SVP destination is a trace of points describing the route from the previous destination to the last point of the SVPs. Our default ending will be .tour
.
Attention
SVP stations require a course. For this example we assume the course is given with North = 0 degrees and clockwise orientation. Please note that the SDK only accepts the course with East = 0 and counter clockwise orientation. We implemented a helper function called convertGeoHeading
in the CalcTourRunnable. For details see below.
[ { "type" : "svp", "points" : [ {"x":751322,"y":6696841,"course":193}, {"x":753691,"y":6692971,"course":218}, {"x":753590,"y":6692930,"course":257}, {"x":753486,"y":6692925,"course":307}, {"x":753464,"y":6692964,"course":354}, {"x":753479,"y":6693010,"course":38}, {"x":756197,"y":6692896,"course":128} ] }, { "type" : "regular", "point" : { "x": 756107, "y": 6658347 } } ]
CalcTourRunnable
The implementation of our CalcTourRunnable is straight forward. It parses the destinations from the file and passes them as a new tour to the NavigationSDK. First we call SDK_CreateTour() with our current position. Then we add all the destinations with SDK_AddStationToTour() and SDK_AddSVPsToTour(). Finally we call SDK_CalculateTour()
. The parameters of this function are a progress callback to get progress information and an additional error code. If the route calculation fails, this error code gives information about the section of the tour the error belongs to. At the end, we delete the created tour with SDK_DeleteTour().
#include "../navigationsdk/SDKInterface.h" #include "CalcTourRunnable.h" #include "GPSManager.h" #include <QApplication> #include <QFile> #include <QJsonDocument> #include <QJsonObject> #include <QJsonArray> static CalcTourRunnable * pThis = 0; CalcTourRunnable::CalcTourRunnable(MapWidget * map) : QObject(), QRunnable() { pThis = this; mMap = map; map->removeTargetPin(); } void CalcTourRunnable::run() { QFile file; file.setFileName(":/res/tour.json"); file.open(QIODevice::ReadOnly | QIODevice::Text); QString value = file.readAll(); file.close(); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(value.toUtf8(), &error); QJsonObject obj = doc.object(); QJsonArray array = doc.array(); SDK_Waypoint start; SDK_InitWaypoint(&start); getStart(start); SDK_Tour * tour = 0; SDK_CreateTour(&tour, start); for (int i = 0; i < array.count(); i++) { QJsonObject point = array[i].toObject(); if (point["type"].toString().toUpper() == "SVP") { QJsonArray points = point["points"].toArray(); int numPoints = points.count(); SDK_SVPWaypoint * waypoints = new SDK_SVPWaypoint[numPoints]; SDK_InitSVPWaypoints(waypoints, numPoints); for (int j = 0; j < numPoints; j++) { QJsonObject p = points[j].toObject(); waypoints[j].x = p["x"].toInt(); waypoints[j].y = p["y"].toInt(); waypoints[j].course = convertGeoHeading(p["course"].toInt()); } SDK_AddSVPsToTour(tour, numPoints, waypoints); mDestination.x = waypoints[numPoints - 1].x; mDestination.y = waypoints[numPoints - 1].y; } else { SDK_Waypoint wayPoint; SDK_InitWaypoint(&wayPoint); QJsonObject p = point["point"].toObject(); wayPoint.x = p["x"].toInt(); wayPoint.y = p["y"].toInt(); wayPoint.course = convertGeoHeading(p["couse"].toInt()); SDK_AddStationToTour(tour, wayPoint); mDestination.x = wayPoint.x; mDestination.y = wayPoint.y; } } SDK_INT2 sectionError = 0; SDK_ERROR rc = SDK_CalculateTour(tour, progressCallback, §ionError); SDK_DeleteTour(tour); if (rc == SDK_ERROR_Ok) mMap->setTargetPin(mDestination); emit finished((int)rc); } SDK_INT2 CalcTourRunnable::convertGeoHeading(int heading) { heading = 90 - heading; if (heading < 0) heading += 360; return (SDK_INT2)heading; } void CalcTourRunnable::getStart(SDK_Waypoint& start) { SDK_GPSData gps; SDK_InitGPSData(&gps); // get the current gps position SDK_ERROR rc = GPSManager::getInstance()->getCurrentPosition(gps); start.course = gps.course; start.x = gps.GPSPositionMerc.x; start.y = gps.GPSPositionMerc.y; } SDK_INT4 SDK_API CalcTourRunnable::progressCallback(SDK_INT4 current, SDK_INT4 total, SDK_INT4 job) { emit pThis->progress(current); return 1; }
In our MainWindow, we added a new menu entry to start a tour calculation. The menu entry triggers the action actionLoadSVPTour
. So we have to create a new function and connect this signal with this new function in our MainWindow.
Add/Change the following in the MainWindow class:
MainWindow.h:
... protected slots: void actionLoadSVPTourTriggered(bool triggered); ...
MainWindow.cpp:
... #include "CalcTourRunnable.h" ... MainWindow::MainWindow(QMainWindow * parent) : QMainWindow(parent) { ... connect(ui.actionLoadSVPTour, SIGNAL(triggered(bool)), this, SLOT(actionLoadSVPTourTriggered(bool))); } ... void MainWindow::actionLoadSVPTourTriggered(bool checked) { disableNavigationLoopTimer(); ui.map->denyUpdates(); if (!mProgressDialog) { mProgressDialog = new QProgressDialog("Calculating tour...", "Cancel", 0, 100, this, Qt::WindowSystemMenuHint | Qt::WindowTitleHint); mProgressDialog->setWindowModality(Qt::WindowModal); mProgressDialog->setCancelButton(0); } else mProgressDialog->show(); CalcTourRunnable * runnable = new CalcTourRunnable(ui.map); connect(runnable, SIGNAL(progress(int)), this, SLOT(onProgress(int))); connect(runnable, SIGNAL(finished(int)), this, SLOT(onCalcRouteFinished(int))); SDKJobQueue::getInstance()->push(runnable); }
First we connect the signal with the function actionLoadSVPTourTriggered(bool)
in the constructor.
In actionLoadSVPTourTriggered()
we do nearly the same as what do in the "normal" route calculation in actionSearchAddressTriggered()
.
We disable the NavigationLoop and the map updates, show the progress dialog, create a new CalcTourRunnable, connect the progress and finished signals of our runnable to the appropriate slots in our MainWindow and push the runnable to our SDKJobQueue.
Alternatives
Attention
Alternative routes are currently only supported for the first destination of your tour and only if it is a regular destination.
Switch to next destination
Currently you have to create a new tour, add all destinations except the first and call SDK_CalculateTour() again. This will probably change in a future release.