Marble/Runners/VehicleTracking: Difference between revisions

From KDE TechBase
(Created)
 
 
(7 intermediate revisions by 3 users not shown)
Line 6: Line 6:
name=Search|
name=Search|


pre=[[Projects/Marble/Runners/DisplayGeoDataPlacemark|Tutorial 12 - Displaying Places the proper way (via GeoDataDocuments)]]|
pre=[[Projects/Marble/Runners/DisplayGeoDataPlacemark|Tutorial 6 - Displaying Places the proper way (via GeoDataDocuments)]]|


next=[[Projects/Marble/Runners/YetMissing|Tutorial 14 - Yet missing]]|
next=[[Projects/Marble/OnlineServices|Tutorial 8 - Creating new online services]]|
}}
}}


We want to present you another C++ example of Marble Runnable usage, here on KDE TechBase. It's about the cars. Imaging 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:
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 flying cars in the air. 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:


[[File:Marble-VehicleTracking-1.png]]
[[File:Marble-VehicleTracking-1.png]]
Line 24: Line 24:




As far as you understood, 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:
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:


# Create [http://qt-project.org/doc/qt-5.0/qtcore/qthread.html QThread] #1
# Create [http://qt-project.org/doc/qt-5.0/qtcore/qthread.html QThread] #1
Line 36: Line 36:
{{Warning|2=Important|1=Use here connection type Qt::BlockingQueuedConnection, another way GUI thread will be blocked}}
{{Warning|2=Important|1=Use here connection type Qt::BlockingQueuedConnection, another way GUI thread will be blocked}}


Now we have nice implementation plan, let's start coding. The code listed under this text is doing:
Now we have a nice implementation plan. Let's start coding.


# main() creates object of the Window (our main QWidget-based window)
# main() creates an object of the Window (our main QWidget-based window)  
# Window creates the [http://api.kde.org/stable/kdeedu-apidocs/marble/html/classMarbleWidget.html MarbleWidget] and sets two GeoDataPlacemarks there
# Window creates the [http://api.kde.org/stable/kdeedu-apidocs/marble/html/classMarbleWidget.html MarbleWidget] and sets two GeoDataPlacemarks there
# main() calls Window::startCars()
# main() calls Window::startCars()
Line 46: Line 46:
# Threads are started
# Threads are started


Then each CarWorker creates a [http://qt-project.org/doc/qt-5.0/qtcore/qtimer.html 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.


Then each CarWorker creates [http://qt-project.org/doc/qt-5.0/qtcore/qtimer.html QTimer] with interval x. On timeout() it calls CarWorker::iterate() - function to calculate new position of placemark and notify Window (another thread) about it.
Everything is going on until the application finishes.
 
Everything is going on until application finishes.


'''The code with comments:'''
'''The code with comments:'''
Line 62: Line 61:
//
//
// Copyright 2012 Illya Kovalevskyy <[email protected]>
// Copyright 2012 Illya Kovalevskyy <[email protected]>
// Copyright 2012 Torsten Rahn      <[email protected]>
//
//


Line 92: Line 90:


signals:
signals:
    /// This signal will be emitted when we need to
    /// move the placemark.
     void coordinatesChanged(GeoDataCoordinates coord);
     void coordinatesChanged(GeoDataCoordinates coord);


Line 127: Line 123:
}
}


/// Single timer loop iteration:
/// Calculates new coordinates for the car
void CarWorker::iterate()
void CarWorker::iterate()
{
{
    /// A bit of math
     qreal lon = m_city.longitude(GeoDataCoordinates::Degree) + m_radius * qCos(m_alpha * DEG2RAD);
     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);
     qreal lat = m_city.latitude(GeoDataCoordinates::Degree) + m_radius * qSin(m_alpha * DEG2RAD);
Line 138: Line 131:
     emit coordinatesChanged(coord);
     emit coordinatesChanged(coord);


    /// Iteration is finished. We need to increase angle
     m_alpha += m_speed;
     m_alpha += m_speed;
}
}
Line 230: Line 222:
     if (worker == m_firstWorker) {
     if (worker == m_firstWorker) {
         m_carFirst->setCoordinate(coord);
         m_carFirst->setCoordinate(coord);
        m_marbleWidget->model()->treeModel()->updateFeature(m_carFirst);
     } else if (worker == m_secondWorker) {
     } else if (worker == m_secondWorker) {
         m_carSecond->setCoordinate(coord);
         m_carSecond->setCoordinate(coord);
Line 236: Line 229:
}
}


// Main
// Main (start point)
int main(int argc, char** argv)
int main(int argc, char** argv)
{
{
Line 250: Line 243:
</source>
</source>


Before running qmake, create a [http://doc.trolltech.com/qmake-tutorial.html qmake project file]
<syntaxhighlight lang="text">
TEMPLATE = app
TARGET = vehicletracking
DEPENDPATH += .
INCLUDEPATH += .
SOURCES += vehicletracking.cpp
LIBS += -lmarblewidget
</syntaxhighlight>
Save this file as "vehicletracking.pro" in the same directory and run
<source lang = "bash">
qmake
make
</source>
then execute <tt>./vehicletracking</tt>. If everything goes fine, marble widget will appear as shown in the screenshot at the start of the tutorial.


'''Find your way and explore the world! ;)'''
'''Find your way and explore the world! ;)'''
The latest source code of this example can be found at [https://quickgit.kde.org/?p=marble.git&a=blob&f=examples%2Fcpp%2Fvehicletracking%2Fvehicletracking.cpp vehicletracking.cpp] and [https://quickgit.kde.org/?p=marble.git&a=blob&f=examples%2Fcpp%2Fvehicletracking%2Fvehicletracking.h vehicletracking.h].

Latest revision as of 09:30, 21 October 2016

Search
Tutorial Series   Marble C++ Tutorial
Previous   Tutorial 6 - Displaying Places the proper way (via GeoDataDocuments)
What's Next   Tutorial 8 - Creating new online services
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 flying cars in the air. 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:


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.

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 <[email protected]>
//

#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:
    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();
}

void CarWorker::iterate()
{
    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);

    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 (start point)
int main(int argc, char** argv)
{
    QApplication app(argc,argv);

    Window window;
    window.startCars();

    return app.exec();
}

#include "vehicletracking.moc"

Before running qmake, create a qmake project file

TEMPLATE = app
TARGET = vehicletracking
DEPENDPATH += .
INCLUDEPATH += .
SOURCES += vehicletracking.cpp
LIBS += -lmarblewidget

Save this file as "vehicletracking.pro" in the same directory and run

qmake
make

then execute ./vehicletracking. If everything goes fine, marble widget will appear as shown in the screenshot at the start of the tutorial.

Find your way and explore the world! ;)

The latest source code of this example can be found at vehicletracking.cpp and vehicletracking.h.