Skip to content

10 Guided navigation

Overview

This tutorial will show you how to calculate a tour with multiple destinations. Parts of the tour can be normal A to B routings and also can be guided navigation routings. Guided navigation routings are routings that go through 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 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_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:

And the following structs:

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:

Example JSON data format

To be able to load 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. Then again an 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 need a course. For this example we assume the course is given in the following format: North = 0 and clockwise orientation. The SDK only accepts the course in the following format: East = 0 and counter clockwise orientation. For this we implemented a small 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, &sectionError);

    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 mainly the same as the we 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

Alternatives are currently only supported for the first destination of your tour. And only if it is a regular destination.

Switch to next destination

Currently yout have to create a new tour with one less destination and call SDK_CalculateTour(). This will probably change in a future release.