Projects/Marble/Runners/VehicleTracking

< Projects‎ | Marble
Revision as of 15:10, 13 January 2013 by Tucnak (Talk | contribs)

Jump to: navigation, search
Search
Tutorial Series   Marble C++ Tutorial
Previous   Tutorial 12 - Displaying Places the proper way (via GeoDataDocuments)
What's Next   Tutorial 14 - Yet missing
Further Reading   n/a

We want to present you another C++ example of Marble Runnable usage, here on KDE TechBase. It's about the cars. Imagine that there are two completely drunk car drivers. They are driving around ukranian city Kiev (EURO-2012). They are driving circles around it with set radius and speed. At the end of tutorial you will have an application with smth like:

Marble-VehicleTracking-1.png


Let's think about implementation a bit:

  1. Init MarbleWidget
  2. Implement placemarks for cars "Bus" and "Car"
  3. Change their coordinates using trigonometry functions
  4. Apply new coordinates


We need to change coordinates with really small interval (~10 ms). In this way, to not block GUI thread we need to use multi-threading. For that we suggest that model:

  1. Create QThread #1
  2. Create CarWorker #1 (= worker for cars' coordinates)
  3. Move CarWorker #1 into thread #1
  4. The same for Car #2 (1-3)


Then we will have three threads - GUI, Calculations #1, Calculations #2. To connect calc. threads with GUI we should use Qt S/S (Signal/Slot) connection.

noframe
 
Important
Use here connection type Qt::BlockingQueuedConnection, another way GUI thread will be blocked


Now we have a nice implementation plan. Let's start coding.

  1. main() creates an object of the Window (our main QWidget-based window)
  2. Window creates the MarbleWidget and sets two GeoDataPlacemarks there
  3. main() calls Window::startCars()
  4. Window creates two QThreads
  5. Window creates two CarWorkers
  6. Window moves car workers into their threads
  7. Threads are started

Then each CarWorker creates a QTimer with interval x. On timeout() it calls the CarWorker::iterate() - function to calculate new position of placemark and notify Window (another thread) about it.

Everything is going on until the application finishes.

The code with comments:

//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
// Copyright 2012 Torsten Rahn      <tackat@kde.org>
//
 
#include <QtGui/QApplication>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtCore/QHash>
#include <QtCore/qmath.h>
#include <QtCore/QDebug>
#include <QtGui/QVBoxLayout>
 
#include <marble/MarbleWidget.h>
#include <marble/MarbleGlobal.h>
#include <marble/GeoDataDocument.h>
#include <marble/GeoDataPlacemark.h>
#include <marble/GeoDataLineString.h>
#include <marble/GeoDataTreeModel.h>
#include <marble/MarbleModel.h>
 
 
using namespace Marble;
 
// CarWorker Class
class CarWorker : public QObject
{
    Q_OBJECT
public:
    CarWorker(const GeoDataCoordinates& city, qreal radius, qreal speed);
 
signals:
    /// This signal will be emitted when we need to
    /// move the placemark.
    void coordinatesChanged(GeoDataCoordinates coord);
 
public slots:
    void startWork();
    void finishWork();
 
private slots:
    void iterate();
 
private:
    QTimer *m_timer;
    GeoDataCoordinates m_city;
    qreal m_radius;
    qreal m_speed;
    qreal m_alpha;
};
 
CarWorker::CarWorker(const GeoDataCoordinates &city, qreal radius, qreal speed) :
    QObject(),
    m_timer(new QTimer(this)),
    m_city(city),
    m_radius(radius),
    m_speed(speed),
    m_alpha(0.0)
{}
 
void CarWorker::startWork()
{
    m_timer->setInterval(0);
    connect(m_timer, SIGNAL(timeout()), this, SLOT(iterate()));
    m_timer->start();
}
 
/// Single timer loop iteration:
/// Calculates new coordinates for the car
void CarWorker::iterate()
{
    /// A bit of math
    qreal lon = m_city.longitude(GeoDataCoordinates::Degree) + m_radius * qCos(m_alpha * DEG2RAD);
    qreal lat = m_city.latitude(GeoDataCoordinates::Degree) + m_radius * qSin(m_alpha * DEG2RAD);
 
    GeoDataCoordinates coord(lon, lat, 0.0, GeoDataCoordinates::Degree);
    emit coordinatesChanged(coord);
 
    /// Iteration is finished. We need to increase angle
    m_alpha += m_speed;
}
 
void CarWorker::finishWork()
{
    m_timer->stop();
}
 
// Window Class
class Window : public QWidget
{
    Q_OBJECT
public:
    Window(QWidget *parent = 0);
    void startCars();
 
public slots:
    void setCarCoordinates(const GeoDataCoordinates &coord);
 
private:
    MarbleWidget *m_marbleWidget;
    CarWorker *m_firstWorker;
    CarWorker *m_secondWorker;
    GeoDataPlacemark *m_carFirst;
    GeoDataPlacemark *m_carSecond;
    QThread *m_threadFirst;
    QThread *m_threadSecond;
};
 
Window::Window(QWidget *parent) :
    QWidget(parent),
    m_marbleWidget(new MarbleWidget)
{
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->addWidget(m_marbleWidget);
    setLayout(layout);
 
    // Load the OpenStreetMap map
    m_marbleWidget->setMapThemeId("earth/openstreetmap/openstreetmap.dgml");
    m_marbleWidget->setProjection( Mercator );
    setGeometry(80, 60, 1000, 800);
    GeoDataCoordinates Kiev(30.523333, 50.45, 0.0, GeoDataCoordinates::Degree);
    m_marbleWidget->centerOn(Kiev);
    m_marbleWidget->setZoom(2300);
 
    m_carFirst = new GeoDataPlacemark("Bus");
    m_carSecond = new GeoDataPlacemark("Car");
 
    GeoDataDocument *document = new GeoDataDocument;
 
    document->append(m_carFirst);
    document->append(m_carSecond);
 
    m_marbleWidget->model()->treeModel()->addDocument(document);
 
    show();
}
 
void Window::startCars()
{
    GeoDataCoordinates Kiev(30.523333, 50.45, 0.0, GeoDataCoordinates::Degree);
 
    m_threadFirst = new QThread;
    m_firstWorker = new CarWorker(Kiev, (qreal)0.1, (qreal)0.7);
    m_firstWorker->moveToThread(m_threadFirst);
 
    connect(m_firstWorker, SIGNAL(coordinatesChanged(GeoDataCoordinates)),
            this, SLOT(setCarCoordinates(GeoDataCoordinates)), Qt::BlockingQueuedConnection);
 
    m_threadSecond = new QThread;
    m_secondWorker = new CarWorker(Kiev, (qreal)0.2, (qreal)-0.5);
    m_secondWorker->moveToThread(m_threadSecond);
 
    connect(m_secondWorker, SIGNAL(coordinatesChanged(GeoDataCoordinates)),
            this, SLOT(setCarCoordinates(GeoDataCoordinates)), Qt::BlockingQueuedConnection);
 
    connect(m_threadFirst, SIGNAL(started()), m_firstWorker, SLOT(startWork()));
    connect(m_threadFirst, SIGNAL(finished()), m_firstWorker, SLOT(finishWork()));
 
    connect(m_threadSecond, SIGNAL(started()), m_secondWorker, SLOT(startWork()));
    connect(m_threadSecond, SIGNAL(finished()), m_secondWorker, SLOT(finishWork()));
 
    m_threadFirst->start();
    m_threadSecond->start();
}
 
void Window::setCarCoordinates(const GeoDataCoordinates &coord)
{
    CarWorker *worker = qobject_cast<CarWorker*>(sender());
    if (worker == m_firstWorker) {
        m_carFirst->setCoordinate(coord);
        m_marbleWidget->model()->treeModel()->updateFeature(m_carFirst);
    } else if (worker == m_secondWorker) {
        m_carSecond->setCoordinate(coord);
        m_marbleWidget->model()->treeModel()->updateFeature(m_carSecond);
    }
}
 
// Main
int main(int argc, char** argv)
{
    QApplication app(argc,argv);
 
    Window window;
    window.startCars();
 
    return app.exec();
}
 
#include "vehicletracking.moc"


Find your way and explore the world! ;)


KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V.Legal