Marble/Runners/MarblePythonVehicleTracking

From KDE TechBase
Vehicle Tracking
Tutorial Series   Marble Python Tutorial
Previous   Tutorial 6 - Displaying Places the proper way (via GeoDataDocuments)
What's Next   Tutorial 13 - GeoPainter: Painting onto the map
Further Reading   n/a

We want to present you another Python 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 looking something 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:

#!/usr/bin/env python
from PyQt4.QtCore import *
from PyQt4.QtGui import *
from PyKDE4.marble import *
import sys
from math import *

class CarWorker(QObject):
    def __init__(self, city, radius, speed):
        QObject.__init__(self)
        # initialize the worker's data
        self.city = city
        self.radius = radius
        self.speed = speed
        self.timer = QTimer(self)
        self.alpha = 0.0

    def startWork(self):
        self.timer.setInterval(0)
        self.connect(self.timer, SIGNAL('timeout()'), self.iterate)
        self.timer.start()

    def iterate(self):
        # update the loaction of the current worker
        lon = self.city.longitude(Marble.GeoDataCoordinates.Degree) + self.radius * cos(radians(self.alpha));
        lat = self.city.latitude(Marble.GeoDataCoordinates.Degree) + self.radius * sin(radians(self.alpha));

        coord = Marble.GeoDataCoordinates(lon, lat, 0.0, Marble.GeoDataCoordinates.Degree);
        self.emit(SIGNAL("coordinatesChanged(PyQt_PyObject)"), coord)

        self.alpha += self.speed;

    def finishWork(self):
        self.timer.stop()

class Window(QWidget):
    def __init__(self):
        QWidget.__init__(self)

        # create the marble widget
        self.marble = Marble.MarbleWidget()

        # resize the widget and add a window title
        self.resize(1000, 800)
        self.setWindowTitle("vehicletracking")

        layout = QVBoxLayout(self)
        layout.addWidget(self.marble)
        self.setLayout(layout)

        # load the OpenStreetMap map
        self.marble.setMapThemeId("earth/openstreetmap/openstreetmap.dgml")
        self.marble.setProjection(Marble.Mercator)
        # center the map on Kiev
        Kiev = Marble.GeoDataCoordinates(30.523333, 50.45, 0.0, Marble.GeoDataCoordinates.Degree)
        self.marble.centerOn(Kiev);
        # set the zoom
        self.marble.setZoom(2300)

        # create the placemarks and their containing document
        self.carFirst = Marble.GeoDataPlacemark("Bus")
        self.carSecond = Marble.GeoDataPlacemark("Car")

        self.document = Marble.GeoDataDocument()

        self.document.append(self.carFirst)
        self.document.append(self.carSecond)

        # add the placemark document to the map
        self.marble.model().treeModel().addDocument(self.document);

        # add the widget to the KMainWindow
        self.show()

    def startCars(self):
        Kiev = Marble.GeoDataCoordinates(30.523333, 50.45, 0.0, Marble.GeoDataCoordinates.Degree)

        # create the thread for the first car
        self.threadFirst = QThread()
        self.firstWorker = CarWorker(Kiev, 0.1, 0.7)
        self.firstWorker.moveToThread(self.threadFirst)

        self.connect(self.firstWorker, SIGNAL("coordinatesChanged(PyQt_PyObject)"),
                self.setCarCoordinates, Qt.BlockingQueuedConnection)


        # create the thread for the second car
        self.threadSecond = QThread()
        self.secondWorker = CarWorker(Kiev, 0.2, -0.5)
        self.secondWorker.moveToThread(self.threadSecond)

        self.connect(self.secondWorker, SIGNAL("coordinatesChanged(PyQt_PyObject)"),
                self.setCarCoordinates, Qt.BlockingQueuedConnection)

        # when both the threads start, begin running the workers
        self.connect(self.threadFirst, SIGNAL("started()"), self.firstWorker.startWork)
        self.connect(self.threadFirst, SIGNAL("finished()"), self.firstWorker.finishWork)

        self.connect(self.threadSecond, SIGNAL("started()"), self.secondWorker.startWork)
        self.connect(self.threadSecond, SIGNAL("finished()"), self.secondWorker.finishWork)

        # start the threads
        self.threadFirst.start()
        self.threadSecond.start()

    def setCarCoordinates(self, coord):
        worker = self.sender()
        # set the coordinates based on which worker emitted the signal
        if (worker == self.firstWorker):
            self.carFirst.setCoordinate(coord)
            self.marble.model().treeModel().updateFeature(self.carFirst)
        elif (worker == self.secondWorker):
            self.carSecond.setCoordinate(coord)
            self.marble.model().treeModel().updateFeature(self.carSecond)

def main():
    app = QApplication(sys.argv)

    window = Window()
    window.startCars()

    app.exec_()

main()

Save this file as "vehicletracking.py" then execute python vehicletracking.py. If everything goes fine, marble widget will appear as shown in the screenshot at the start of the tutorial.