Marble/Runners/MarblePythonVehicleTracking
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:
- 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:
#!/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.