GEOG 489
Advanced Python Programming for GIS

4.10.2.2 Setting up the BusTrackAnalyzer

PrintPrint

Next, we create the BusTrackAnalyzer object to be used for the event detection in line 28 of main.py providing the bus dictionary and depot list as parameters to the constructor together with a list of class names for the bus event classes that we want the analyzer to detect. This list is defined in line 17 of main.py.

eventClasses = [ LeavingDepotEvent, EnteringDepotEvent, BusStoppedEvent, BusEncounterEvent ] # list of event classes to detect 

... 







# create main BusTrackAnalyzer object 
analyzer = BusTrackAnalyzer(busData, depotData, eventClasses) 

If you look at lines 12 to 21 of bus_track_analyzer.py, you will see that the constructor takes these parameters and stores them in its own instance variables for performing analysis steps later on (lines 16, 17 and 19).

def __init__(self, busData, depotData, eventClasses):  
         self.allBusTrackers = []            # list of BusTracker objects for all buses currently being processed 
         self.allEvents = []                 # used for storing all Event objects created during an analysis run 
         self.lastProcessedTimepoint = None  # Timepoint of the last Observation that has been processed 
         self._busData = busData             # dictionary mapping bus Id strings to Bus objects with GPS data 
         self._depotData = depotData         # list of Depot objects used for Event detection 
         self._observationQueue = []         # priority queue of next Observation objects to be processed for each bus  
         self._eventClasses = eventClasses   # list of instantiable subclasses of BusEvent that should be detected 

         self.reset()   # initialize variables for new analysis run

In addition, the constructor sets up some more instance variables that will be needed when the analysis is run: a list of bus trackers (one for each bus) in variable allBusTrackers, a list of events detected in variable allEvents, a variable lastProcessedTimepoint for the Timepoint of the last observation processed, and a list in variable _observationQueue that will serve as the priority queue of Observation objects to be processed next. Then in the last line, we call the method reset() of BusTrackAnalyzer defined in lines 23 to 40 whose purpose is to reset the value of these instance variables to what they need to be before the first analysis step is performed, allowing the analysis to be reset and repeated at any time.

def reset(self): 
         """reset current analysis run and reinitialize everything for a new run""" 
         self.allBusTrackers = [] 
         self.allEvents = [] 
         self.lastProcessedTimepoint = None 
         self._observationQueue = [] 

         for busId, bus in self._busData.items():  # go through all buses in the data 
             busTracker = BusTracker(bus)          # create new BusTracker object for bus 

             # set initial BusTracker status to "IN DEPOT" if bus is inside bounding box of one of the depots 
             isInDepot, depot = Depot.inDepot(bus.timepoints[0].lat, bus.timepoints[0].lon, self._depotData) 
             if isInDepot: 
                 busTracker.status = BusTracker.STATUS_INDEPOT 
                 busTracker.depot = depot 

             self.allBusTrackers.append(busTracker) # add new BusTracker to list of all BusTrackers 
             heapq.heappush(self._observationQueue, Observation(busTracker, 0)) # create Observation for first Timepoint of this bus
                                                                                # and add to Observation priority queue 

The main thing the method does is go through the dictionary with all the Bus objects and, for each, create a new BusTracker object that will be placed in the allBusTrackers list, set the initial status of that BusTracker to STATUS_INDEPOT if the first Timepoint for that bus is inside one of the depots (else the status will be the default value STATUS_DRIVING), and create an Observation object with that BusTracker for the first Timepoint from the Timepoint list of the corresponding bus that will be put into the observation priority queue via the call of the heapq.headpush(…) function (line 40). The image below illustrates how the main instance variables of the BusTrackAnalyzer object may look after this initialization for an imaginary input data set.

Screen shot illustrating text above image.      
Figure 4.25 Python objects in memory after data has been read and main BusTrackAnalyzer has been initialized

The buses with IDs 5, 2145, and 270 are the ones with earliest GPS observations in our imaginary data but there can be more busses that we are not showing in the diagram. We are also not showing all instance variables for each object, just the most important ones. Furthermore, Timepoint objects are shown as simple date + time values in the diagram not as objects of class Timepoint. The arrows indicate which objects the different instance variables contain starting with the _observationQueue, allBusTrackers, and allEvents instance variables of the single BusTrackAnalyzer object that we have.

The Bus objects at the top that contain the GPS data read from the input file will not change anymore and we are not showing here that these are actually maintained in a dictionary. The list of BusTracker objects (one for each Bus object) will also not change anymore but the properties of the individual BusTracker objects in it will change during the analysis. The observation queue list is the one that will change the most during the analysis because it will always contain the Observation objects to be processed ordered by the time point information. The event list is still empty because we have not detected any events yet.