In the bus-stop detection section, we have applied the Density-Based Spatial Clustering of Applications with Noise (DBSCAN) based clustering algorithm to detect the bus-stops on a route. Now for developing the arrival time predictor scheme, we will need the travel time information of a bus at different bus-stops or junctions / crossroads. These travel time information will be used in the subsequent unit to built the arrival time predictor based on historical bus trajectories.

Travel time information extraction

[1]:
'''Imports'''
from pymongo import MongoClient

import os
import sys
import pprint
import pandas as pd
#sys.path.append("/".join(os.getcwd().split('/')) +'/Codes/LibCodes')
sys.path.append("/".join(os.getcwd().split('/')) +'/LibCode')

'''Import project specific library'''
import Preprocessing

'''Initialize MongoClient'''
con = MongoClient()

RouteName='Git_ISCON_PDPU'
[2]:
#'''
ProjectDataUsed = True
UsedPreTrained = False
UseMongoDB = True
#'''
'''
ProjectDataUsed = True
UsedPreTrained = True
UseMongoDB = False
'''
[2]:
'\nProjectDataUsed = True\nUsedPreTrained = True\nUseMongoDB = False\n'

We begin by extracting the bus-stops and junction or crossroads on a route for both the direction i.e. from ISCON to PDPU (North bound) and PDPU to ISCON (South bound).

[3]:
if UseMongoDB==True:
    '''BusStops, use of ProcessStatus collection and record: BusStops:True'''
    BusStopsListNorth = [BusStop for BusStop in con[RouteName]['BusStops.NorthBound'].find().sort([('id',1)])]
    #New Addition for Dist_th
    BusStopsListSouth = [BusStop for BusStop in con[RouteName]['BusStops.SouthBound'].find().sort([('id',1)])]
    Dist_TH = 50

Now, to compute travel time we compare the filtered location records with three consecutive bus-stops on a route. Because, we have observed that if the location record corresponding to a particular bus-stop is missing due to GPS outage, then the travel time extraction module would get stuck waiting for the location record corresponding to the bus-stop location. In order to cater with these types of an occasional GPS outage, we compare the location records with three consecutive bus-stop. If the distance between the bus-stop location and location record is less than \(D_{th}\) meters (\(50 m\)), then the travel time extraction module marks the corresponding location record of the bus as the record at a bus-stop.

We need to emphasize that the id of bus-stop increases as the bus moves during its trip in the case of north bound whereas in the case of south bound the id of bus-stop decreases as the bus moves during its trip. Let us print the BusStopsListNorth and BusStopsListSouth to observe this point.

[4]:
if UseMongoDB==True:
    pprint.pprint([(BusStop['Name'],BusStop['id']) for BusStop in BusStopsListSouth])
[('ISCON', 0),
 ('Pakwaan', 1),
 ('Gurudwara', 2),
 ('Thaltej', 3),
 ('Zydus', 4),
 ('Kargil', 5),
 ('Sola', 6),
 ('Gota', 7),
 ('Vaishnodevi', 8),
 ('Khoraj', 9),
 ('Adalaj-uvarsad', 10),
 ('Sargasan', 11),
 ('RakshaShakti', 12),
 ('Bhaijipura', 13),
 ('PDPU', 14)]
[5]:
if UseMongoDB==True:
    pprint.pprint(
        [(BusStop['Name'],BusStop['id']) for BusStop in con[RouteName]['BusStops.SouthBound'].find().sort([('id',-1)])])
[('PDPU', 14),
 ('Bhaijipura', 13),
 ('RakshaShakti', 12),
 ('Sargasan', 11),
 ('Adalaj-uvarsad', 10),
 ('Khoraj', 9),
 ('Vaishnodevi', 8),
 ('Gota', 7),
 ('Sola', 6),
 ('Kargil', 5),
 ('Zydus', 4),
 ('Thaltej', 3),
 ('Gurudwara', 2),
 ('Pakwaan', 1),
 ('ISCON', 0)]

Therefore, we have formulated two functions separately for North bound and South bound to compute travel time estimates. One must emphasize on the condition and index using for North bound and South bound.

For north bound in function ExtractTimeStampNorthBound,

if (BusStopIndex+j) < BusStopsCount:
'''and'''
BusStopsListNorth[BusStopIndex+j],

and for south bound in function ExtractTimeStampSouthBound,

if BusStopsCount-BusStopIndex-1-j >=0:
'''and'''
BusStopsListSouth[BusStopsCount-BusStopIndex-1-j]
[6]:
def ExtractTimeStampNorthBound(LocationRecords, BusStopsListNorth, Dist_TH):
    '''
    input: Location records of the trip, bus-stop list, and distance threshold
    output: The dictionary of location records corresponding to bus
    function: Compares the location records of the trip with three consecutive
              bus-stop and if distance is less than Dist_TH then marks the corresponding
              record as a location record at a bus-stop.
    '''
    BusStopsTimeStampList = []
    BusStopIndex = 0
    LocationRecordsCount = len (LocationRecords)
    BusStopsCount = len (BusStopsListNorth)

    for i in range(0, LocationRecordsCount):
        for j in range(0,3):
            if (BusStopIndex+j) < BusStopsCount:
                DistanceFromStop = Preprocessing.mydistance(LocationRecords[i]['Latitude'],
                                              LocationRecords[i]['Longitude'],
                                              BusStopsListNorth[BusStopIndex+j]['Location'][0],
                                              BusStopsListNorth[BusStopIndex+j]['Location'][1])

                if DistanceFromStop < Dist_TH:
                    BusStopDict = {}
                    BusStopIndex += j
                    BusStopDict['id'] = BusStopIndex
                    BusStopDict['epoch'] = LocationRecords[i]['epoch']
                    BusStopDict['Latitude'] = LocationRecords[i]['Latitude']
                    BusStopDict['Longitude'] = LocationRecords[i]['Longitude']
                    BusStopDict['Name'] = BusStopsListNorth[BusStopIndex]['Name']
                    BusStopsTimeStampList.append(BusStopDict)
                    BusStopIndex +=1
                    break

        if BusStopIndex == BusStopsCount:
            break
    return(BusStopsTimeStampList)
[7]:
def ExtractTimeStampSouthBound(LocationRecords, BusStopsListSouth):
    '''
    input: Location records of the trip, bus-stop list, and distance threshold
    output: The dictionary of location records corresponding to bus
    function: Compares the location records of the trip with three consecutive
              bus-stop and if distance is less than Dist_TH then marks the corresponding
              record as a location record at a bus-stop.
    '''
    BusStopsTimeStampList = []
    BusStopIndex = 0
    LocationRecordsCount = len (LocationRecords)
    BusStopsCount = len (BusStopsListSouth)

    for i in range(0, LocationRecordsCount):
        for j in range(0,3):
            if BusStopsCount-BusStopIndex-1-j >=0:
                DistanceFromStop = Preprocessing.mydistance(LocationRecords[i]['Latitude'],
                                              LocationRecords[i]['Longitude'],
                                              BusStopsListSouth[BusStopsCount-BusStopIndex-1-j]['Location'][0],
                                              BusStopsListSouth[BusStopsCount-BusStopIndex-1-j]['Location'][1])
                if DistanceFromStop < Dist_TH:
                    BusStopIndex +=j
                    BusStopDict = {}
                    BusStopDict['id'] = BusStopsCount-BusStopIndex-1
                    BusStopDict['epoch'] = LocationRecords[i]['epoch']
                    BusStopDict['Latitude'] = LocationRecords[i]['Latitude']
                    BusStopDict['Longitude'] = LocationRecords[i]['Longitude']
                    BusStopDict['Name'] = BusStopsListNorth[BusStopIndex]['Name']
                    BusStopsTimeStampList.append(BusStopDict)
                    BusStopIndex +=1
                    break
        if BusStopIndex == BusStopsCount:
            break
    return(BusStopsTimeStampList)

We will update the travel time of a trip in the MongoDB with the collection name dd_mm_yyyy__hh_mm_ss.BusStopsRecord in the function addTravelTimeInformationToMongoDB. Additionally, we update the BusStopRecordExtracted flag of a trip to True in the TripInfo collection. It would be used to retrieve only those trips for which the travel time information related to bus-stop is extracted. Furthermore, one should observe the update of TripStartTimeAggregate collection.

'''Create collection to store the trip aggregate information'''
con [RouteName]['TripStartTimeAggregate'].update_one({},{'$addToSet':
                                                         {'TripStartTimeBound':
                                                          (TripInfoList[0]['TripStartHour'], Bound)}},True)

TripStartTimeAggregate maintains the starting time of all the trips on a particular bound using the tuple (TripStartHour, Bound).

[8]:
def addTravelTimeInformationToMongoDB(SingleTripInfo, BusStopsTimeStampList, Bound):
    '''
    input: Trip name, bus-stop location record and bound
    output: void
    function: Stores the bus-stop location record in the MongoDB database with collection name
              SingleTripInfo.BusStopsRecord. It also updates the flag Bound and BusStopRecordExtracted
              in TripInfo collection. Further, the function updates the TripStartTimeAggregate to
              maintains the starting time of all the trips on a particular bound using the tuple
              (TripStartHour, Bound).
    '''
    TripInfoList = [Trip for Trip in
                    con[RouteName]['TripInfo'].find({'SingleTripInfo':SingleTripInfo}).limit(1)]

    '''If travel time record of trip is not available'''
    if len(BusStopsTimeStampList) == 0:
        con [RouteName]['TripInfo'].update_one({'SingleTripInfo':SingleTripInfo},
                                               {'$set':{'Bound': Bound, 'BusStopRecordExtracted':False}})
    else:

        '''Drop if any previous records are stored in MongoDB collection'''
        con [RouteName].drop_collection(SingleTripInfo+'.BusStopsRecord')
        con [RouteName][SingleTripInfo+'.BusStopsRecord'].insert_many(BusStopsTimeStampList)
        con [RouteName]['TripInfo'].update_one({'SingleTripInfo':SingleTripInfo},
                                               {'$set':{'Bound': Bound, 'BusStopRecordExtracted':True}})

        '''Create collection to store the trip aggregate information'''
        con [RouteName]['TripStartTimeAggregate'].update_one({},{'$addToSet':
                                                                 {'TripStartTimeBound':
                                                                  (TripInfoList[0]['TripStartHour'], Bound)}},True)

Now, given that we have built the required functions for travel time information, we can execute it for the trips on North bound and South bound.

[9]:
if UseMongoDB==True:
    '''For Morning trips'''
    SingleTripsInfoNorthBound = [rec['SingleTripInfo'] for rec in con[RouteName]['TripInfo'].find({'$and':
                                                                       [ {'filteredLocationRecord':True},
                                                                        {'TripStartHour':'07'} ] })]

    for SingleTripInfo in SingleTripsInfoNorthBound:
        print('Extracting travel time for trip: '+ SingleTripInfo)
        LocationRecords = [LocationRecord for LocationRecord in
                           con[RouteName][SingleTripInfo+'.Filtered'].find().sort([('epoch',1)])]
        BusStopsTimeStampList = ExtractTimeStampNorthBound(LocationRecords, BusStopsListNorth, Dist_TH)

        addTravelTimeInformationToMongoDB(SingleTripInfo, BusStopsTimeStampList, 'North')
Extracting travel time for trip: 22_12_2017__07_38_21
Extracting travel time for trip: 26_12_2017__07_32_35
Extracting travel time for trip: 20_12_2017__07_38_14
Extracting travel time for trip: 21_12_2017__07_52_59
Extracting travel time for trip: 08_01_2018__07_41_43
Extracting travel time for trip: 09_01_2018__07_40_01
Extracting travel time for trip: 18_01_2018__07_38_10
Extracting travel time for trip: 19_01_2018__07_38_47
Extracting travel time for trip: 22_01_2018__07_41_04
Extracting travel time for trip: 27_12_2017__07_55_48
Extracting travel time for trip: 29_12_2017__07_37_27
Extracting travel time for trip: 01_01_2018__07_38_27
Extracting travel time for trip: 05_04_2018__07_38_07
Extracting travel time for trip: 14_02_2018__07_41_04
Extracting travel time for trip: 22_02_2018__07_42_45
Extracting travel time for trip: 16_02_2018__07_45_41
Extracting travel time for trip: 19_02_2018__07_46_19
Extracting travel time for trip: 20_02_2018__07_41_48
Extracting travel time for trip: 21_02_2018__07_42_42
Extracting travel time for trip: 13_03_2018__07_29_52
Extracting travel time for trip: 14_03_2018__07_35_46
Extracting travel time for trip: 20_03_2018__07_28_45
Extracting travel time for trip: 15_02_2018__07_45_52
Extracting travel time for trip: 03_04_2018__07_38_31
Extracting travel time for trip: 21_03_2018__07_32_39
Extracting travel time for trip: 22_03_2018__07_38_43
Extracting travel time for trip: 12_02_2018__07_40_14
Extracting travel time for trip: 30_01_2018__07_42_30
Extracting travel time for trip: 01_02_2018__07_39_12
Extracting travel time for trip: 02_02_2018__07_38_50
Extracting travel time for trip: 29_01_2018__07_39_47
[10]:
if UseMongoDB==True:
    '''For Evening trips'''
    SingleTripsInfoSouthBound = [rec['SingleTripInfo'] for rec in con[RouteName]['TripInfo'].find({'$and':
                                                                       [ {'filteredLocationRecord':True},
                                                                        {'TripStartHour':'18'} ] })]
    for SingleTripInfo in SingleTripsInfoSouthBound:
        print('Extracting travel time for trip: '+ SingleTripInfo)
        LocationRecords = [LocationRecord for LocationRecord in
                           con[RouteName][SingleTripInfo+'.Filtered'].find().sort([('epoch',1)])]

        BusStopsTimeStampList = ExtractTimeStampSouthBound(LocationRecords, BusStopsListSouth)

        addTravelTimeInformationToMongoDB(SingleTripInfo, BusStopsTimeStampList, 'South')
Extracting travel time for trip: 22_12_2017__18_38_34
Extracting travel time for trip: 19_12_2017__18_41_16
Extracting travel time for trip: 20_12_2017__18_31_19
Extracting travel time for trip: 08_01_2018__18_37_49
Extracting travel time for trip: 04_04_2018__18_34_54
Extracting travel time for trip: 28_03_2018__18_30_02
Extracting travel time for trip: 21_02_2018__18_28_29
Extracting travel time for trip: 15_02_2018__18_33_19
Extracting travel time for trip: 20_02_2018__18_31_07
Extracting travel time for trip: 14_02_2018__18_30_22
Extracting travel time for trip: 03_04_2018__18_32_45
Extracting travel time for trip: 21_03_2018__18_32_40

Now let us look at the .BusStopsRecord for one of the trips, for which BusStopRecordExtracted is True.

[11]:
if UseMongoDB==True:
    SingleTripsInfo = [rec['SingleTripInfo'] for rec in
                                 con[RouteName]['TripInfo'].find({'BusStopRecordExtracted':True})]

    for SingleTripInfo in SingleTripsInfo:
        BusStopTimeStamp = [LocationRecord for LocationRecord in
                            con[RouteName][SingleTripInfo+'.BusStopsRecord'].find().sort([('epoch',1)])]

        #pprint.pprint(BusStopTimeStamp)
        break

    print(pd.DataFrame(BusStopTimeStamp))
[11]:
_id id epoch Latitude Longitude Name
0 6340c9908847a9267b8e7fa6 1 1.513909e+12 23.038356 72.511578 Pakwaan
1 6340c9908847a9267b8e7fa7 2 1.513909e+12 23.045993 72.515400 GuruDwara
2 6340c9908847a9267b8e7fa8 3 1.513909e+12 23.049837 72.517080 Thaltej
3 6340c9908847a9267b8e7fa9 4 1.513909e+12 23.058542 72.519877 Zydus
4 6340c9908847a9267b8e7faa 5 1.513909e+12 23.076625 72.525225 Kargil
5 6340c9908847a9267b8e7fab 6 1.513909e+12 23.086095 72.527949 Sola
6 6340c9908847a9267b8e7fac 7 1.513910e+12 23.098790 72.531800 Gota
7 6340c9908847a9267b8e7fad 8 1.513910e+12 23.136531 72.542583 Vaishnodevi
8 6340c9908847a9267b8e7fae 9 1.513910e+12 23.160555 72.556536 Khoraj
9 6340c9908847a9267b8e7faf 10 1.513910e+12 23.176090 72.583962 Adalaj-Uvarsad
10 6340c9908847a9267b8e7fb0 11 1.513911e+12 23.192540 72.614802 Sargasan
11 6340c9908847a9267b8e7fb1 12 1.513911e+12 23.185766 72.637598 Raksha-shakti circle
12 6340c9908847a9267b8e7fb2 13 1.513911e+12 23.160927 72.635840 Bhaijipura
13 6340c9908847a9267b8e7fb3 14 1.513911e+12 23.154663 72.664302 PDPU

Here, the field epoch gives the time stamp corresponding to the bus-stop or a junction / crossroad.