Marble/Runners/VehicleTracking
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:
Let's think about implementation a bit:
- Init MarbleWidget
- Implement placemarks for cars "Bus" and "Car"
- Change their coordinates using trigonometry functions
- 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:
- Create QThread #1
- Create CarWorker #1 (= worker for cars' coordinates)
- Move CarWorker #1 into thread #1
- 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.
Now we have a nice implementation plan. Let's start coding.
- main() creates an object of the Window (our main QWidget-based window)
- Window creates the MarbleWidget and sets two GeoDataPlacemarks there
- main() calls Window::startCars()
- Window creates two QThreads
- Window creates two CarWorkers
- Window moves car workers into their threads
- 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]>
// Copyright 2012 Torsten Rahn <[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:
/// 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);
} 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! ;)