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.