Package FlightReportManager :: Module FlightReportManager
[hide private]
[frames] | no frames]

Source Code for Module FlightReportManager.FlightReportManager

  1  #!/usr/bin/env python 
  2   
  3  """ 
  4  Created on 17.06.2016 
  5   
  6  @author: johanneskinzig 
  7  @version: 0.3 
  8   
  9  """ 
 10   
 11  from Tkinter import * 
 12  import ttk 
 13  from tkFileDialog import askopenfilename, asksaveasfilename      
 14  from DroneDataConversion import DroneDataConversion # now imported from site-packages because package is provided externally 
 15  import DataStoreController 
 16  import FileStorageController 
 17  import os 
 18  import Plotlib 
 19  import PlotGPSTrack 
 20   
21 -class MainView():
22 - def __init__(self, master):
23 """Setup Tkinter and all necessary properties. Generate instances of classes.""" 24 # TODO: from settings tab <- user can decide where to put the flight db 25 # TODO: use module which provides OS dependent user locations 26 ################################################ 27 # instances from other modules # 28 ################################################ 29 ## FileStorage Controller 30 self.FileStorage = FileStorageController.FileStorageController("FlightReportManager", "JoKi", "0.1_alpha") 31 print("Data directory: " + self.FileStorage.getDataDirectory()) 32 33 ## DataStoreController 34 self.DataStore = DataStoreController.DataStoreController(self.FileStorage.getDBLocation()) 35 36 37 ################################################ 38 # setup tkinter # 39 ################################################ 40 master.title("FlightReportManager Bebop") 41 master.resizable(width=False, height=False) 42 master.grid_columnconfigure(0, weight=1) 43 master.grid_rowconfigure(0, weight=1) 44 #master.geometry("600x320") 45 46 ## not necessary, because I embed the ttk.Notebook inside master and then embed a ttk.Frame inside each Notebook 47 ## but not deleting it from here because of educational purpose 48 # mainWindow = ttk.Frame(master, padding="3 3 12 12") 49 # mainWindow.grid(column=0, row=0, sticky=(N+E+W+S)) 50 51 ## place the widgets where they belong 52 self.initialize_widgets(master) 53 54 ## generate right click context menu 55 self.generateTreeViewContextMenu(master) 56 57 ## update view with data from DB 58 self.updateDataInView()
59
60 - def initialize_widgets(self, master):
61 """Holds all the information necessary to place the widgets in the frame. Including callbacks and methods""" 62 tabbar = ttk.Notebook(master) 63 flight_tab = ttk.Frame(tabbar) # first page, which get widgets gridded into it 64 import_tab = ttk.Frame(tabbar) # second page 65 settings_tab = ttk.Frame(tabbar) 66 tabbar.add(flight_tab, text='Flights') 67 tabbar.add(import_tab, text='Import') 68 tabbar.add(settings_tab, text='Settings') 69 tabbar.grid(row=0, column=0, sticky=(N+E+W+S)) 70 71 ##################################################################### 72 # "Flights" tab Widgets # 73 ##################################################################### 74 75 ################################################ 76 # TreeView for Flight data # 77 ################################################ 78 columns = ("event_name", "city_nearby", "pilot_location", "datetime", "total_distance", "max_altitude", "avg_speed", "flight_duration", "controller_type", "Drone_type", "battery_usage") 79 self.flights_treeView = ttk.Treeview(flight_tab, columns=columns, selectmode='browse', height=15, padding=(0,0,0,0)) 80 self.flights_treeView.grid(row=1, column=0, rowspan = 5, columnspan = 4, sticky=N+S+W) 81 82 ################################################# 83 # Scrollbars for treeview # 84 ################################################# 85 vsb_treeview = ttk.Scrollbar(flight_tab, orient="vertical", command = self.flights_treeView.yview) 86 hsb_treeview = ttk.Scrollbar(flight_tab, orient="horizontal", command = self.flights_treeView.xview) 87 88 ## Link scrollbars activation to top-level object 89 self.flights_treeView.configure(yscrollcommand=vsb_treeview.set, xscrollcommand=hsb_treeview.set) 90 vsb_treeview.grid(row=1, column=4, rowspan = 5, sticky=(N+S)) 91 hsb_treeview.grid(row=6, column=0, columnspan = 4, sticky=(E+W)) 92 93 ## name treeview columns 94 self.flights_treeView.heading("#0", text="FlightID") 95 self.flights_treeView.column("#0", minwidth=50, width=50) 96 97 self.flights_treeView.heading("event_name", text="Event Name") 98 self.flights_treeView.heading("city_nearby", text="City nearby") 99 self.flights_treeView.heading("pilot_location", text="Pilot Location") 100 self.flights_treeView.heading("datetime", text="Time") 101 self.flights_treeView.heading("total_distance", text="Distance (m)") 102 self.flights_treeView.heading("max_altitude", text="max Alt. (m)") 103 self.flights_treeView.heading("avg_speed", text="avg. Speed (m/s)") 104 self.flights_treeView.heading("flight_duration", text="Duration (min)") 105 self.flights_treeView.heading("controller_type", text="Controller Type") 106 self.flights_treeView.heading("Drone_type", text="Drone Type") 107 self.flights_treeView.heading("battery_usage", text="Battery Usage (%)") 108 109 self.flights_treeView.column("event_name", minwidth=90, width=100) 110 self.flights_treeView.column("city_nearby", minwidth=80, width=110) 111 self.flights_treeView.column("pilot_location", minwidth=160, width=170, anchor=CENTER) 112 self.flights_treeView.column("datetime", minwidth=120, width=165) 113 self.flights_treeView.column("total_distance", minwidth=70, width=70, anchor=CENTER) 114 self.flights_treeView.column("max_altitude", minwidth=70, width=70, anchor=CENTER) 115 self.flights_treeView.column("avg_speed", minwidth=95, width=95) 116 self.flights_treeView.column("flight_duration", minwidth=80, width=85) 117 self.flights_treeView.column("controller_type", minwidth=105, width=105, anchor=CENTER) 118 self.flights_treeView.column("Drone_type", minwidth=80, width=80) 119 self.flights_treeView.column("battery_usage", minwidth=95, width=95) 120 121 122 ################################################ 123 # Buttons for flight management # 124 ################################################ 125 ttk.Button(flight_tab, text="Export Track (gpx)", command=self.exportAsGPXInvocation, width = 14).grid(row=0, column = 0) 126 ttk.Button(flight_tab, text="Export Track (kml)", command=self.action, width = 14).grid(row=0, column = 1) 127 ttk.Button(flight_tab, text="Export Flight (csv)", command=self.exportAsCSVInvocation, width = 14).grid(row=0, column = 2) 128 ttk.Button(flight_tab, text="Edit Flight", command=lambda: self.editRowWindowInvocation(master), width = 14).grid(row=0, column = 3) 129 130 ################################################ 131 # Buttons for displaying flight data # 132 ################################################ 133 ttk.Button(flight_tab, text="Plot Track", command=self.plotTrackInvocation, width = 14).grid(row = 7, column = 0) 134 ttk.Button(flight_tab, text="Plot Altitude", command=self.plotAltitudeInvocation, width = 14).grid(row = 7, column = 1) 135 ttk.Button(flight_tab, text="Plot Speed", command=self.plotSpeedInovaction, width = 14).grid(row=7, column = 2) 136 ttk.Button(flight_tab, text="Plot Battery", command=self.plotBatteryUsageInvocation, width = 14).grid(row = 7, column = 3) 137 138 ##################################################################### 139 # "Import" tab Widgets # 140 ##################################################################### 141 142 ################################################ 143 # Label and Textfield to set event name # 144 ################################################ 145 ttk.Label(import_tab, text="Event Name: ").grid(column = 0, row = 0) 146 self.event_name_entry = ttk.Entry(import_tab, width=30) 147 self.event_name_entry.grid(column = 1, row= 0) 148 149 ################################################ 150 # Label, Textentry, Button to get pud/json file# 151 ################################################ 152 ttk.Label(import_tab, text="PUD/JSON File location: ").grid(column=0, row = 1, sticky = W) 153 self.file_location_entry = ttk.Entry(import_tab, width=30) 154 self.file_location_entry.grid(column = 1, row = 1) 155 ttk.Button(import_tab, text = "Choose...", command=self.openFileChooser, width = 7).grid(column = 2, row = 1, sticky = E) 156 157 ################################################ 158 # Label, Textfield, Button to import data # 159 ################################################ 160 ttk.Label(import_tab, text = "Import Log: ").grid(column = 0, row = 2, sticky = W) 161 ttk.Button(import_tab, text = "Import", command = self.importInvocation, width = 7).grid(column = 2, row = 2, sticky = E) 162 self.log_textfield = Text(import_tab, height=15) 163 self.log_textfield.grid(column = 0, row = 3, columnspan = 3, sticky=EW) 164 165 ################################################# 166 # Scrollbars for log_textfield # 167 ################################################# 168 vsb_log = ttk.Scrollbar(import_tab, orient="vertical", command = self.log_textfield.yview) 169 hsb_log = ttk.Scrollbar(import_tab, orient="horizontal", command = self.log_textfield.xview) 170 171 ## Link scrollbars activation to top-level object 172 self.log_textfield.configure(yscrollcommand=vsb_log.set, xscrollcommand=hsb_log.set) 173 vsb_log.grid(row=3, column=4, rowspan = 1, sticky=N+S) 174 hsb_log.grid(row=4, column=0, columnspan = 3, sticky=E+W)
175 176
177 - def generateTreeViewContextMenu(self, master):
178 """Generate Menu which opens when right-clicking on treeview entry""" 179 self.tv_cont_men = Menu(master, tearoff=0) 180 #self.tv_cont_men.add_command(label="Export Takeoff gpx-Wpt", command=self.action) 181 self.tv_cont_men.add_command(label="Export Track (gpx)", command=self.exportAsGPXInvocation) 182 self.tv_cont_men.add_command(label="Export Track (csv)", command=self.exportAsCSVInvocation) 183 self.tv_cont_men.add_command(label="Export Takeoff Waypoint (gpx)", command=self.exportTakeoffGpxWptInvocation) 184 self.tv_cont_men.add_command(label="Export Takeoff Waypoint (csv)", command=self.exportTakeoffCsvWptInvocation) 185 self.tv_cont_men.add_separator() 186 self.tv_cont_men.add_command(label="Edit", command=lambda: self.editRowWindowInvocation(master)) 187 self.tv_cont_men.add_command(label="Delete", command=self.deleteRowInvocation) 188 self.tv_cont_men.add_separator() 189 190 # bind action to mouse button 191 self.flights_treeView.bind("<Button-2>", self.showTreeViewContextMenu)
192
193 - def showTreeViewContextMenu(self, event):
194 ''' Show context menu and store selected row 195 ''' 196 try: 197 #self.tv_cont_men.selection = self.flights_treeView.set(self.flights_treeView.identify_row(event.y)) 198 self.tv_cont_men.selection = self.flights_treeView.item(self.flights_treeView.focus()) 199 self.tv_cont_men.post(event.x_root, event.y_root) 200 finally: 201 # release the grab (version 8.0 and higher only) 202 self.tv_cont_men.grab_release()
203 204
205 - def editRowWindowInvocation(self, master):
206 """Generate second Tkinter window, directly generate a ttk-Frame to have similar styles for all windows""" 207 def editRowInvocation(window, flight_id, newName): 208 """Actively edit the selected row (treeview) in DB""" 209 # update 210 self.DataStore.updatRowNameInTable(flight_id, str(newName.get())) 211 # quit window 212 window.destroy() 213 self.updateDataInView()
214 215 def editRowUpdateCityInvocation(window, flight_id, newCity): 216 """Update city nearby, in case geolocation service was unavailable""" 217 # update 218 self.DataStore.updateRowCityInTable(flight_id, newCity) 219 # quit window 220 window.destroy() 221 self.updateDataInView()
222 223 ################################### 224 # Subwindow: Edit Flight # 225 ################################### 226 editNameWindowTL = Toplevel(master) 227 editNameWindowTL.resizable(width=False, height=False) 228 editNameWindowTL.title("Edit Flight") 229 editNameWindowTL.minsize(60,30) 230 editNameWindowFr = ttk.Frame(editNameWindowTL, padding="3 3 12 12") 231 editNameWindowFr.grid(column=0, row=0, sticky=(N+E+W+S)) 232 ttk.Label(editNameWindowFr, text="New Name:").grid(column=0, row=0) 233 newName = ttk.Entry(editNameWindowFr) 234 newName.grid(column=1, row = 0, columnspan = 3, sticky=(N+E+W+S)) 235 236 ################################### 237 # Identify selected item # 238 ################################### 239 # get selected row --> used as primary key in DB 240 # flight_id = int(self.tv_cont_men.selection['text']) # 241 flight_id = int(self.flights_treeView.item(self.flights_treeView.focus())['text']) 242 print("Selected Row --> Flight_ID: " + str(flight_id)) 243 # get filename for selected item 244 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem()) 245 246 ################################### 247 # Map Buttons to invocations # 248 ################################### 249 ttk.Button(editNameWindowFr, text="Exit", command=editNameWindowTL.destroy).grid(column=1, row=1) 250 ttk.Button(editNameWindowFr, text="Rename", command=lambda: editRowInvocation(editNameWindowTL, flight_id, newName)).grid(column=2, row=1) 251 ttk.Button(editNameWindowFr, text="Refresh City", command=lambda: editRowUpdateCityInvocation(editNameWindowTL, flight_id, str(flightManager.get_nearest_city()))).grid(column=3, row=1) 252
253 - def deletePudFileInvocation(self):
254 """Delte pud file from directory""" 255 self.FileStorage.deleteFile(self.getFilenameForSelectedItem())
256
257 - def deleteRowInvocation(self):
258 """Delete selected row from table and delete according files""" 259 # delete file 260 self.deletePudFileInvocation() 261 # delete row from table 262 self.DataStore.deleteRowFromTable(int(self.flights_treeView.item(self.flights_treeView.focus())['text'])) 263 self.updateDataInView()
264
265 - def updateDataInView(self):
266 """Update data in GUI""" 267 ## sample to see how treeview is filled with data 268 #self.flights_treeView.insert("", 0, "dir1", text="Dir 1", values=("1A","1b")) 269 #self.flights_treeView.insert("", 1, "dir2", text="Dir 2", values=("2A","2b")) 270 271 ## delete all fields 272 self.flights_treeView.delete(*self.flights_treeView.get_children()) 273 ## and fill treeview with data 274 data = self.DataStore.readDataFromTable() 275 for data_set in data: 276 # TODO: include in logger 277 # print data_set 278 # insert at next position 279 #self.flights_treeView.insert("", int(data_set[0]), str(data_set[0]), text=str(data_set[0]), values=(data_set[1], data_set[2], data_set[3], data_set[4], data_set[5], data_set[6], data_set[7], data_set[8], data_set[9], data_set[10], data_set[11])) 280 # insert at position 0 (= first row) 281 self.flights_treeView.insert("", 0, str(data_set[0]), text=str(data_set[0]), values=(data_set[1], data_set[2], data_set[3], data_set[4], round(data_set[5], 2), data_set[6], round(data_set[7], 2), round(data_set[8], 2), data_set[9], data_set[10], data_set[11]))
282
283 - def importInvocation(self):
284 """Get the user input for the "event_name_entry" and "file_location_entry" text entry field - Store data inside DB""" 285 event_name_value = self.event_name_entry.get() 286 file_location_value = self.file_location_entry.get() 287 288 # TODO: check if file is already existing 289 # TODO: include in log file 290 print("EVENT NAME USER INPUT: " + event_name_value) 291 print("PUD/JSON FILE LOCATION: " + file_location_value) 292 293 ## copy pudfile to application data 294 # extract filename from complete file path 295 pud_path, pud_name = os.path.split(file_location_value) 296 self.raw_file_name = pud_name 297 298 file_location_destination_value = self.FileStorage.getPudfileDataDirectory() + pud_name 299 print("PUD/JSON FILE DESTINATION: " + file_location_destination_value) 300 301 self.FileStorage.copyFileTo(file_location_value, file_location_destination_value) 302 303 ## then parse it! 304 ## generate instance of BebopFlightDataManager 305 flightManager = DroneDataConversion.BebopFlightDataManager(file_location_destination_value) 306 ## debug output for console 307 flightManager.display_diagnostic_information_debug() 308 ## import data into DB 309 self.DataStore.insertIntoTable(event_name_value, flightManager.diagnostic_information_raw()) 310 ## clear log 311 self.log_textfield.delete('1.0', END) 312 ## and insert into log afterwards 313 self.log_textfield.insert(END, ("\n".join(flightManager.display_diagnostic_information_debug()))) 314 ## refresh view which presents data 315 self.updateDataInView()
316
317 - def openFileChooser(self):
318 """Open tkaskopenfilename and return filename/absolute filepath of user's selection""" 319 # define file options 320 file_options = {} 321 file_options['defaultextension'] = '.json' 322 file_options['filetypes'] = [('JSON Files', '.json'), ('PUD Files', '.pud')] 323 file_options['title'] = "Choose Bebop's json/pud File..." 324 325 # open file chooser dialog and get filename (just get the path, this is not an object of type file) 326 filepath_absolute = askopenfilename(**file_options) 327 # TODO: include in log file 328 print("FILENAME: " + filepath_absolute) 329 # clear textfield before filling it again 330 self.file_location_entry.delete(0, END) 331 self.file_location_entry.insert(END, filepath_absolute)
332
333 - def getFilenameForSelectedItem(self):
334 """Makes a request to the DB and returns the location/filename for the selected item""" 335 # get selected item 336 flight_id = int(self.flights_treeView.item(self.flights_treeView.focus())['text']) 337 # get filename of selected item 338 # request from DB 339 row = self.DataStore.getRowFromID(flight_id) 340 print("PUD/JSON File for parsing: " + str(row[12])) 341 file_location = self.FileStorage.getPudfileDataDirectory() + str(row[12]) 342 return str(file_location)
343
344 - def getEventnameForSelectedItem(self):
345 """Makes a request to the DB and returns the event name for the selected item""" 346 # get selected item 347 flight_id = int(self.flights_treeView.item(self.flights_treeView.focus())['text']) 348 # get event name of selected item 349 # request from DB 350 row = self.DataStore.getRowFromID(flight_id) 351 print("Eventname: " + str(row[1])) 352 event_name = str(row[1]) # TODO anpassen 353 return event_name
354
355 - def saveToFileDialog(self, filetype):
356 """Open Tkinter save file dialog and return path to file""" 357 filetype = str(filetype) 358 event_name = self.getEventnameForSelectedItem() 359 # define file options 360 file_options = {} 361 if filetype == "gpx": 362 file_options['defaultextension'] = '.gpx' 363 file_options['title'] = "Export GPX to ..." 364 file_options['initialfile'] = event_name + str(file_options['defaultextension']) 365 elif filetype == "csv": 366 file_options['defaultextension'] = '.csv' 367 file_options['title'] = "Export CSV to ..." 368 file_options['initialfile'] = event_name + str(file_options['defaultextension']) 369 # ask filename to save to 370 filepath = asksaveasfilename(**file_options) 371 return filepath
372
373 - def exportAsGPXInvocation(self):
374 """Export track as GPX file invocation""" 375 # generate instance of DroneDataConversion 376 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem()) 377 filelocation = self.saveToFileDialog("gpx") 378 # convert to gpx 379 flightManager.export_as_gpx(filelocation, gpx_track_name=str(os.path.basename(os.path.splitext(filelocation)[0]))) 380 print("Done")
381
382 - def exportTakeoffGpxWptInvocation(self):
383 """Export takeoff location as gpx waypoint invocation""" 384 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem()) 385 filelocation = self.saveToFileDialog("gpx") 386 # convert to gpx 387 flightManager.takeoff_location_as_gpx(filelocation, waypoint_name=str(os.path.basename(os.path.splitext(filelocation)[0]))) 388 print("Done")
389 390
391 - def exportAsKMLInvocation(self):
392 """Export track as kml file invocation""" 393 pass
394 # TODO: implement 395
396 - def exportTakeoffKMLWptInvocation(self):
397 """Export takeoff location as kml waypoint invocation""" 398 pass
399 # TODO: implement 400
401 - def exportAsCSVInvocation(self):
402 """Export track as csv file invocation""" 403 # generate instance of DroneDataConversion 404 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem()) 405 # ask filename to save to 406 filelocation = self.saveToFileDialog("csv") 407 # convert to csv 408 flightManager.export_as_csv(filelocation) 409 print("Done")
410
411 - def exportTakeoffCsvWptInvocation(self):
412 """Export the takeoff location as csv waypoint""" 413 # generate instance of DroneDataConversion 414 flightManager = DroneDataConversion.BebopFlightDataManager(self.getFilenameForSelectedItem()) 415 # ask filename to save to 416 filelocation = self.saveToFileDialog("csv") 417 # convert to csv 418 flightManager.takeoff_location_as_csv(filelocation, waypoint_name=str(os.path.basename(os.path.splitext(filelocation)[0]))) 419 print("Done")
420
421 - def plotBatteryUsageInvocation(self):
422 """Invocation of battery plot""" 423 ## plotting instance 424 plot_flight = Plotlib.Plotlib(self.getFilenameForSelectedItem()) 425 plot_flight.plotBatteryUsage()
426
427 - def plotAltitudeInvocation(self):
428 """Invocation of altitude plot""" 429 ## plotting instance 430 plot_flight = Plotlib.Plotlib(self.getFilenameForSelectedItem()) 431 plot_flight.plotAltitude()
432
433 - def plotSpeedInovaction(self):
434 """Invocation of speed Plot""" 435 ## plotting instance 436 plot_flight = Plotlib.Plotlib(self.getFilenameForSelectedItem()) 437 plot_flight.plotSpeed()
438
439 - def plotTrackInvocation(self):
440 """Plot the flight track - experimental only!""" 441 ## plot the flight gps track 442 plot_flight = PlotGPSTrack.PlotGPSTrack(self.getFilenameForSelectedItem()) 443 plot_flight.plotTrackOnMap_MP()
444
445 - def action(self):
446 print "Not yet implemented"
447 448 if __name__ == '__main__': 449 root = Tk() 450 root.columnconfigure(0, weight=2) 451 root.rowconfigure(0, weight=2) 452 app=MainView(root) 453 root.mainloop() 454