This script creates an dashboard that shows upcoming departures from Munich's Public Transport Agency (MVG: Münchner Verkehrsgesellschaft) API.
Technologies Used: Python 3.X (mvg_api, tkinter)
This app creates a GUI (Graphical User Interface) with dynamically generated data from Munich's Public Transport API. The user is prompted to (optionally) enter a station and the departures are generated for that station in a retro-UI. If nothing is entered, the dashboard will show the departures for Munich Central Station (Hauptbahnof) by default. The app also has an option to Refresh the departures, as needed.
Install the python modules listed above, and simply run: fetch_data.py
Enter the station name (e.g.: Lehel, Münchener Freiheit) and hit enter to see the UI.
fetch_data.py
import mvg_api
import tkinter as tk
from GUI import *
def formatDepartures(departures):
departureT=[]
for item in departures:
bus=item['label']
destination = item['destination']
minutes = item['departureTimeMinutes']
departureT.append([bus,destination,minutes])
return departureT
# pdb.set_trace()
station = str(input("Enter your requested station:"))
station_id = mvg_api.get_id_for_station(station) #
# pdb.set_trace()
if station_id=='de:06412:10' or station_id=='' or station_id is None:
station_id = 'de:09162:6' #Haupybahnof station ID as default
print("Station name not found, default to Hauptbahnof...")
rawData = mvg_api.get_departures(station_id)
departures=formatDepartures(rawData)
#Get data for a particular station and create GUI
root = tk.Tk()
gui = GUI(root,station_id)
gui.populateTable(departures)
root.mainloop()
GUI.py
:
import tkinter as tk
import mvg_api
onPi=True
sideColumnMinSize = 100
departureFontSize = 40
destinationFontSize = 40
headerFontSize = 25
subHeaderFontSize = 20
maxFutureDepartureTime = 120 # The maximum amount of time (in minutes) left for a departure that is displayed
class GUI:
def __init__(self, master, station_id,**kwargs):
self.master = master
self.station_id = station_id
# A list that will hold the temporary departure frames so to destroy them upon refreshing
self.departureRowFrames = []
self.currentlyDisplayedDepartures = [] # Used to decide whether to refresh the display
self.master.grid()
self.master.title("Departures")
# A new frame inside master with boarder
headerFrame = tk.Frame(master, bd="0")
# Set the header frame's background to black
headerFrame.configure(background='black')
# Use the grid layout and expand the frame
headerFrame.grid(row=0, sticky=tk.E + tk.W)
# Label inside header frame
headerLbl = tk.Label(headerFrame, text="Departures", font=("Helvetica bold", headerFontSize), bg="black", fg="white")
# Place label on grid layout, row 0 and do not expand
headerLbl.grid(row=0)
# Center column 0 inside header frame
headerFrame.grid_columnconfigure(0, weight=1)
subHeadersFrame = tk.Frame(master)
subHeadersFrame.configure(background='black')
subHeadersFrame.grid(row=1, sticky=tk.E + tk.W)
busNoLbl = tk.Label(subHeadersFrame, text="Line Number", font=("Courier", subHeaderFontSize), bg="black", fg="white")
busDestLbl = tk.Label(subHeadersFrame, text="Destination", font=("Courier", subHeaderFontSize), bg="black", fg="white")
minsLeftLbl = tk.Label(subHeadersFrame, text="Minutes Left", font=("Courier", subHeaderFontSize), bg="black", fg="white")
busNoLbl.grid(row=0, column=0)
busDestLbl.grid(row=0, column=1)
minsLeftLbl.grid(row=0, column=2)
# Expand the middle column so the other two move to the sides
subHeadersFrame.grid_columnconfigure(1, weight=1)
# The frame that will contain the departures
departuresFrame = tk.Frame(master)
departuresFrame.grid(row=2, sticky=tk.E + tk.W)
departuresFrame.grid_columnconfigure(1, weight=1)
self.departuresFrame = departuresFrame # Class variable to hold the container frame for all the departures
b = tk.Button(self.departuresFrame, text="Refresh",command=self.refreshDepartures).grid(row=13, column=2) #,command=self.refreshDepartures()
q = tk.Button(self.departuresFrame, text="Exit",command=self.master.destroy).grid(row=14, column=2)
# Keep everything in column 0 of master centered/expanded
master.grid_columnconfigure(0, weight=1)
# Make it full screen
# w, h = master.winfo_screenwidth(), master.winfo_screenheight()
# master.overrideredirect(onPi) # Set to 1 to force full window mode
# master.geometry("%dx%d+0+0" % (w, h))
def refreshDepartures(self):
departures = mvg_api.get_departures(self.station_id)
departureT=[]
for item in departures:
bus=item['label']
destination = item['destination']
minutes = item['departureTimeMinutes']
departureT.append([bus,destination,minutes])
self.populateTable(departureT)
def populateTable(self, departures):
currentRow = 0
for count,departure in enumerate(departures):
(bus, destination, minutes) = departure
# If a departure is too far in the future, don't display it
if minutes > maxFutureDepartureTime:
continue
# Change background color for each row
bgColor = "#FFF289" if currentRow % 2 else "white"
# The frame that will contain each departure
rowFrame = tk.Frame(self.departuresFrame)
rowFrame.grid(row=currentRow, columnspan=3, sticky=tk.E + tk.W)
rowFrame.configure(background=bgColor)
currentRow += 1
# After we have created the frame that will hold each departure, create the labels
busNo = tk.Label(rowFrame, text=bus, font=("Helvetica", departureFontSize), bg=bgColor)
busDest = tk.Label(rowFrame, text=destination, font=("Helvetica", destinationFontSize), bg=bgColor)
minsLeft = tk.Label(rowFrame, text=int(minutes) if int(minutes) != 0 else "Now", font=("Helvetica", departureFontSize), bg=bgColor)
busNo.grid(row=0, column=0)
busDest.grid(row=0, column=1)
minsLeft.grid(row=0, column=2)
# Expand the middle column to push the other two to the sides
rowFrame.grid_columnconfigure(1, weight=1)
# Set the minimum size of the side columns so the middle column text is always at the same position
rowFrame.grid_columnconfigure(0, minsize=sideColumnMinSize)
rowFrame.grid_columnconfigure(2, minsize=sideColumnMinSize)
# Add the newly created frame to a list so we can destroy it later when we refresh the departures
self.departureRowFrames.append(rowFrame)
if count==(len(departures)-20): #Normally returns 30 last departures, so show only 10 (30-20=10)
# The frame that will contain each departure
rowFrame = tk.Frame(self.departuresFrame)
rowFrame.grid(row=currentRow, columnspan=3, sticky=tk.E + tk.W)
rowFrame.configure(background='#FFA500')
currentRow += 1
busNo.grid(row=0, column=0)
busDest.grid(row=0, column=1)
minsLeft.grid(row=0, column=2)
# Expand the middle column to push the other two to the sides
rowFrame.grid_columnconfigure(1, weight=1)
# Set the minimum size of the side columns so the middle column text is always at the same position
rowFrame.grid_columnconfigure(0, minsize=sideColumnMinSize)
rowFrame.grid_columnconfigure(2, minsize=sideColumnMinSize)
# Add the newly created frame to a list so we can destroy it later when we refresh the departures
self.departureRowFrames.append(rowFrame)
break
# Destroy any existing frames containing departures that already exist
def resetDepartures(self):
for frame in self.departureRowFrames:
frame.destroy()
# Empty the list as we have destroyed everything that was included in it
self.departureRowFrames = []
def main():
root = tk.Tk()
gui = GUI(root)
root.mainloop()
if __name__ == "__main__":
main()
Back to my projects →