Example #1
0
class Router(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(host=mongodb_host, port=mongodb_port)
        log(module_name='Router', log_type='DEBUG', log_message='mongodb_database_connection: established')

    def get_bus_stop(self, name=None, provided_point=None, longitude=None, latitude=None):
        """
        Get a bus_stop_document.

        :param name: string
        :param provided_point: Point
        :param longitude: float
        :param latitude: float
        :return: bus_stop: bus_stop_document
        """
        bus_stop = None

        if name is not None:
            bus_stop = self.mongodb_database_connection.find_bus_stop_document(name=name)
        elif provided_point is not None:
            bus_stop_documents = self.mongodb_database_connection.find_bus_stop_documents()
            bus_stop = self.get_bus_stop_closest_to_point(
                bus_stop_documents=bus_stop_documents,
                provided_point=provided_point
            )
        elif longitude is not None and latitude is not None:
            point = Point(longitude=longitude, latitude=latitude)
            bus_stop_documents = self.mongodb_database_connection.find_bus_stop_documents()
            bus_stop = self.get_bus_stop_closest_to_point(
                bus_stop_documents=bus_stop_documents,
                provided_point=point
            )
        else:
            pass

        return bus_stop

    @staticmethod
    def get_bus_stop_closest_to_point(bus_stop_documents, provided_point):
        """
        Get the bus stop which is closest to a geographic point.

        :param bus_stop_documents: [bus_stop_document]
        :param provided_point: Point
        :return closest_bus_stop: bus_stop_document
        """
        minimum_distance = float('Inf')
        closest_bus_stop = None

        for bus_stop_document in bus_stop_documents:
            bus_stop_document_point = bus_stop_document.get('point')

            current_distance = distance(
                point_one=provided_point,
                longitude_two=bus_stop_document_point.get('longitude'),
                latitude_two=bus_stop_document_point.get('latitude')
            )
            if current_distance == 0:
                closest_bus_stop = bus_stop_document
                break
            elif current_distance < minimum_distance:
                minimum_distance = current_distance
                closest_bus_stop = bus_stop_document
            else:
                pass

        return closest_bus_stop

    def get_bus_stops(self, names):
        """
        Get multiple bus_stop_documents.

        :param names: [string]
        :return: bus_stops: [bus_stop_document]
        """
        bus_stops = []

        # for name in names:
        #     bus_stop = self.get_bus_stop(name=name)
        #     bus_stops.append(bus_stop)

        bus_stop_documents = self.mongodb_database_connection.find_bus_stop_documents(names=names)
        bus_stop_documents_dictionary = {}

        for bus_stop_document in bus_stop_documents:
            bus_stop_document_name = bus_stop_document.get('name')
            bus_stop_documents_dictionary[bus_stop_document_name] = bus_stop_document

        for name in names:
            bus_stop_document = bus_stop_documents_dictionary.get(name)
            bus_stops.append(bus_stop_document)

        return bus_stops

    def get_bus_stops_dictionary(self):
        """
        Retrieve a dictionary containing all the documents of the BusStops collection.

        :return: bus_stops_dictionary: {name -> {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}}}
        """
        bus_stops_dictionary = self.mongodb_database_connection.find_bus_stop_documents(in_dictionary=True)
        return bus_stops_dictionary

    def get_bus_stops_list(self):
        """
        Retrieve a list containing all the documents of the BusStops collection.

        :return: bus_stops_list: [{'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}}]
        """
        bus_stops_list = self.mongodb_database_connection.find_bus_stop_documents()
        return bus_stops_list

    def get_edges_dictionary(self):
        """
        Retrieve a dictionary containing all the documents of the Edges collection.

        :return: {starting_node_osm_id -> [{'_id', 'starting_node': {'osm_id', 'point': {'longitude', 'latitude'}},
                                            'ending_node': {'osm_id', 'point': {'longitude', 'latitude'}},
                                            'max_speed', 'road_type', 'way_id', 'traffic_density'}]}
        """
        edges_dictionary = self.mongodb_database_connection.find_edge_documents(in_dictionary=True)
        return edges_dictionary

    def get_edges_list(self):
        """
        Retrieve a list containing all the documents of the Edges collection.

        :return: edges_list: [{'_id', 'starting_node': {'osm_id', 'point': {'longitude', 'latitude'}},
                               'ending_node': {'osm_id', 'point': {'longitude', 'latitude'}},
                               'max_speed', 'road_type', 'way_id', 'traffic_density'}]
        """
        edges_list = self.mongodb_database_connection.find_edge_documents()
        return edges_list

    def get_points_dictionary(self):
        """
        Retrieve a dictionary containing all the documents of the Points collection.

        :return points_dictionary: {osm_id -> {'_id', 'osm_id', 'point': {'longitude', 'latitude'}}}
        """
        points_dictionary = self.mongodb_database_connection.find_point_documents(in_dictionary=True)
        return points_dictionary

    def get_route_between_two_bus_stops(self, starting_bus_stop=None, ending_bus_stop=None,
                                        starting_bus_stop_name=None, ending_bus_stop_name=None,
                                        edges_dictionary=None):
        """
        Identify the less time-consuming route between two bus_stops.

        :param starting_bus_stop: bus_stop_document
        :param ending_bus_stop: bus_stop_document
        :param starting_bus_stop_name: string
        :param ending_bus_stop_name: string
        :param edges_dictionary: {starting_node_osm_id -> [edge_document]}
        :return response: get_route_between_two_bus_stops
        """
        if starting_bus_stop is None and starting_bus_stop_name is not None:
            starting_bus_stop = self.get_bus_stop(name=starting_bus_stop_name)

        if ending_bus_stop is None and ending_bus_stop_name is not None:
            ending_bus_stop = self.get_bus_stop(name=ending_bus_stop_name)

        if edges_dictionary is None:
            edges_dictionary = self.get_edges_dictionary()

        route = identify_path_with_lowest_cost(
            start=starting_bus_stop,
            end=ending_bus_stop,
            edges_dictionary=edges_dictionary
        )
        response = {
            'starting_bus_stop': starting_bus_stop,
            'ending_bus_stop': ending_bus_stop,
            'route': route
        }
        return response

    def get_route_between_multiple_bus_stops(self, bus_stops=None, bus_stop_names=None):
        """
        Identify the less time-consuming route between multiple bus_stops.

        :param bus_stops: [bus_stop_document]
        :param bus_stop_names: string
        :return response: get_route_between_multiple_bus_stops
        """
        response = []
        edges_dictionary = self.get_edges_dictionary()

        if bus_stops is None and bus_stop_names is not None:
            bus_stops = self.get_bus_stops(names=bus_stop_names)

        for i in range(0, len(bus_stops) - 1):
            starting_bus_stop = bus_stops[i]
            ending_bus_stop = bus_stops[i + 1]

            intermediate_route = self.get_route_between_two_bus_stops(
                starting_bus_stop=starting_bus_stop,
                ending_bus_stop=ending_bus_stop,
                edges_dictionary=edges_dictionary
            )
            response.append(intermediate_route)

        return response

    def get_waypoints_between_two_bus_stops(self, starting_bus_stop=None, ending_bus_stop=None,
                                            starting_bus_stop_name=None, ending_bus_stop_name=None):
        """
        Identify all possible route connections between two bus_stops.

        :param starting_bus_stop: bus_stop_document
        :param ending_bus_stop: bus_stop_document
        :param starting_bus_stop_name: string
        :param ending_bus_stop_name: string
        :return response: get_waypoints_between_two_bus_stops
        """
        if starting_bus_stop is None and starting_bus_stop_name is not None:
            starting_bus_stop = self.get_bus_stop(name=starting_bus_stop_name)

        if ending_bus_stop is None and ending_bus_stop_name is not None:
            ending_bus_stop = self.get_bus_stop(name=ending_bus_stop_name)

        edges_dictionary = self.get_edges_dictionary()

        waypoints = identify_all_paths(
            starting_node_osm_id=starting_bus_stop.get('osm_id'),
            ending_node_osm_id=ending_bus_stop.get('osm_id'),
            edges_dictionary=edges_dictionary
        )
        response = {
            'starting_bus_stop': starting_bus_stop,
            'ending_bus_stop': ending_bus_stop,
            'waypoints': waypoints
        }
        return response

    def get_waypoints_between_multiple_bus_stops(self, bus_stops=None, bus_stop_names=None):
        """
        Identify all possible route connections between multiple bus_stops.

        :param bus_stops: [bus_stop_document]
        :param bus_stop_names: string
        :return response: get_waypoints_between_multiple_bus_stops
        """
        response = []

        if bus_stops is None and bus_stop_names is not None:
            bus_stops = self.get_bus_stops(names=bus_stop_names)

        edges_dictionary = self.get_edges_dictionary()

        for i in range(0, len(bus_stops) - 1):
            starting_bus_stop = bus_stops[i]
            ending_bus_stop = bus_stops[i + 1]

            waypoints = identify_all_paths(
                starting_node_osm_id=starting_bus_stop.get('osm_id'),
                ending_node_osm_id=ending_bus_stop.get('osm_id'),
                edges_dictionary=edges_dictionary
            )
            intermediate_response = {
                'starting_bus_stop': starting_bus_stop,
                'ending_bus_stop': ending_bus_stop,
                'waypoints': waypoints
            }
            response.append(intermediate_response)

        return response
class LookAheadHandler(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(host=mongodb_host, port=mongodb_port)
        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='mongodb_database_connection: established')

    def generate_bus_line(self, bus_stop_names, bus_line_id=None):
        """
        Generate a bus_line, consisted of a bus_line_id and a list of bus_stops, and store it to the corresponding
        collection of the System Database. Moreover, identify all the possible waypoints between the bus_stops
        of the bus_line, and populate the BusStopWaypoints collection.

        :param bus_stop_names: [string]
        :param bus_line_id: int
        :return: None
        """
        # 1: The inputs: bus_line_id and bus_stop_names are provided to the function, so as as a bus_line
        #    with the corresponding bus_line_id and bus_stops to be generated.
        #
        # 2: The Look Ahead connects to the System Database and retrieves the bus_stops which correspond to
        #    the provided bus_stop_names. The function returns None and the bus_line is not generated,
        #    in case there is a bus_stop_name which does not correspond to a stored bus_stop.
        #
        if bus_line_id is None:
            maximum_bus_line_id = self.mongodb_database_connection.get_maximum_or_minimum(collection='bus_line')
            bus_line_id = maximum_bus_line_id + 1

        bus_stops = []

        for bus_stop_name in bus_stop_names:
            bus_stop_document = self.mongodb_database_connection.find_bus_stop_document(name=bus_stop_name)

            if bus_stop_document is None:
                log_message = 'find_bus_stop_document (mongodb_database) - name:', bus_stop_name, '- result: None'
                log(module_name='look_ahead_handler', log_type='DEBUG', log_message=log_message)
                return None
            else:
                bus_stops.append(bus_stop_document)

        # 3: The intermediate waypoints of the bus_routes, which are generated while combining starting and
        #    ending bus_stops of the bus_line, should be stored as bus_stop_waypoints_documents at the System Database.
        #    The Look Ahead checks the existing bus_stop_waypoints_documents and communicates with the Route Generator
        #    in order to identify the waypoints of the bus_routes which are not already stored. The newly generated
        #    bus_stop_waypoints_documents are getting stored to the corresponding collection of the System Database.
        #    The function returns None and the bus_line is not generated, in case the Route Generator can not identify
        #    a possible route in order to connect the bus_stops of the bus_line.
        #
        number_of_bus_stops = len(bus_stops)

        for i in range(0, number_of_bus_stops - 1):
            starting_bus_stop = bus_stops[i]
            ending_bus_stop = bus_stops[i + 1]

            bus_stop_waypoints_document = self.mongodb_database_connection.find_bus_stop_waypoints_document(
                starting_bus_stop=starting_bus_stop,
                ending_bus_stop=ending_bus_stop
            )
            if bus_stop_waypoints_document is None:
                route_generator_response = get_waypoints_between_two_bus_stops(
                    starting_bus_stop=starting_bus_stop,
                    ending_bus_stop=ending_bus_stop
                )
                if route_generator_response is None:
                    log(module_name='look_ahead_handler', log_type='DEBUG',
                        log_message='get_waypoints_between_two_bus_stops (route_generator): None')
                    return None
                else:
                    waypoints = route_generator_response.get('waypoints')

                    if len(waypoints) == 0:
                        log(module_name='look_ahead_handler', log_type='DEBUG',
                            log_message='get_waypoints_between_two_bus_stops (route_generator): None')
                        return None

                    lists_of_edge_object_ids = []

                    for list_of_edges in waypoints:
                        list_of_edge_object_ids = []

                        for edge in list_of_edges:
                            edge_object_id = edge.get('_id')
                            list_of_edge_object_ids.append(edge_object_id)

                        lists_of_edge_object_ids.append(list_of_edge_object_ids)

                    # waypoints: [[edge_object_id]]
                    #
                    waypoints = lists_of_edge_object_ids

                    self.mongodb_database_connection.insert_bus_stop_waypoints_document(
                        starting_bus_stop=starting_bus_stop,
                        ending_bus_stop=ending_bus_stop,
                        waypoints=waypoints
                    )

        # 4: The Look Ahead stores the newly generated bus_line_document, which is consisted of the bus_line_id
        #    and the list of bus_stops, to the corresponding collection of the System Database.
        #    In case there is an already existing bus_line_document, with the same bus_line_id,
        #    then the list of bus_stops gets updated.
        #
        bus_line_document = {'bus_line_id': bus_line_id, 'bus_stops': bus_stops}
        self.mongodb_database_connection.insert_bus_line_document(bus_line_document=bus_line_document)

        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='insert_bus_line_document (mongodb_database): ok')

    def generate_timetables_for_bus_line(self, timetables_starting_datetime, timetables_ending_datetime,
                                         requests_min_departure_datetime, requests_max_departure_datetime,
                                         bus_line=None, bus_line_id=None):
        """
        Generate timetables for a bus_line, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        - The input: timetables_starting_datetime and input: timetables_ending_datetime
          are provided to the function, so as timetables for the specific datetime period
          to be generated.

        - The input: requests_min_departure_datetime and input: requests_max_departure_datetime
          are provided to the function, so as travel_requests with departure_datetime corresponding
          to the the specific datetime period to be evaluated.

        - The input: bus_line or input: bus_line_id is provided to the function,
          so as timetables for the specific bus_line to be generated.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """

        maximum_timetable_id_in_database = self.mongodb_database_connection.get_maximum_or_minimum(
            collection='timetable'
        )

        # 1: The list of bus_stops corresponding to the provided bus_line is retrieved.
        #
        # bus_stop_document: {
        #     '_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}
        # }
        # bus_stops: [bus_stop_document]
        #
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')

        # 2: The Look Ahead retrieves from the System Database the travel_requests with
        #    departure_datetime higher than requests_min_departure_datetime and
        #    lower than requests_max_departure_datetime.
        #
        # travel_request_document: {
        #     '_id', 'client_id', 'bus_line_id',
        #     'starting_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'ending_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'departure_datetime', 'arrival_datetime'
        # }
        # travel_requests: [travel_request_document]
        #
        travel_requests = self.mongodb_database_connection.find_travel_request_documents(
            bus_line_ids=[bus_line_id],
            min_departure_datetime=requests_min_departure_datetime,
            max_departure_datetime=requests_max_departure_datetime
        )

        # 3: (TimetableGenerator is initialized) The Look Ahead sends a request to the Route Generator so as
        #    to identify the less time-consuming bus_route between the bus_stops of bus_line,
        #    while taking into consideration the current levels of traffic density.
        #
        timetable_generator = TimetableGenerator(
            maximum_timetable_id_in_database=maximum_timetable_id_in_database,
            bus_line_id=bus_line_id,
            bus_stops=bus_stops,
            travel_requests=travel_requests
        )

        # The list of bus_stops of a bus_line might contain the same bus_stop_osm_ids more than once.
        # For this reason, each travel_request needs to be related with the correct index in the bus_stops list.
        # So, the values 'starting_timetable_entry_index' and 'ending_timetable_entry_index' are estimated.
        #
        correspond_travel_requests_to_bus_stops(
            travel_requests=timetable_generator.travel_requests,
            bus_stops=timetable_generator.bus_stops
        )

        # 4: Based on the response of the Route Generator, which includes details about the followed bus_route,
        #    and using only one bus vehicle, the Look Ahead generates some initial timetables which cover the
        #    whole datetime period from timetables_starting_datetime to timetables_ending_datetime.
        #    Initially, the list of travel requests of these timetables is empty, and the departure_datetime and
        #    arrival_datetime values of the timetable_entries are based exclusively on the details of the bus_route.
        #    In the next steps of the algorithm, these timetables are used in the initial clustering
        #    of the travel requests.
        #
        timetable_generator.timetables = generate_initial_timetables(
            bus_line_id=bus_line_id,
            timetables_starting_datetime=timetables_starting_datetime,
            timetables_ending_datetime=timetables_ending_datetime,
            route_generator_response=timetable_generator.route_generator_response
        )

        current_average_waiting_time_of_timetables = float('Inf')

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_generator.timetables,
                travel_requests=timetable_generator.travel_requests
            )
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables
            )
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_generator.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_generator.timetables)
            else:
                break

        print_timetables(timetables=timetable_generator.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id')
        )
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_generator.timetables
        )
        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='insert_timetable_documents (mongodb_database): ok')

    def generate_timetables_for_bus_lines(self, timetables_starting_datetime, timetables_ending_datetime,
                                          requests_min_departure_datetime, requests_max_departure_datetime):
        """
        Generate timetables for all bus_lines, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.generate_timetables_for_bus_line(
                bus_line=bus_line,
                timetables_starting_datetime=timetables_starting_datetime,
                timetables_ending_datetime=timetables_ending_datetime,
                requests_min_departure_datetime=requests_min_departure_datetime,
                requests_max_departure_datetime=requests_max_departure_datetime
            )

    def update_timetables_of_bus_line(self, bus_line=None, bus_line_id=None):
        """
        Update the timetables of a bus_line, taking into consideration the current levels of traffic_density.

        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')
        timetables = self.mongodb_database_connection.find_timetable_documents(bus_line_ids=[bus_line_id])
        travel_requests = get_travel_requests_of_timetables(timetables=timetables)

        timetable_updater = TimetableUpdater(
            bus_stops=bus_stops,
            timetables=timetables,
            travel_requests=travel_requests
        )
        update_entries_of_timetables(
            timetables=timetable_updater.timetables,
            route_generator_response=timetable_updater.route_generator_response
        )
        current_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
            timetables=timetable_updater.timetables
        )
        print_timetables(timetables=timetable_updater.timetables)

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_updater.timetables,
                travel_requests=timetable_updater.travel_requests
            )
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables
            )
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_updater.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_updater.timetables)
            else:
                break

        print_timetables(timetables=timetable_updater.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id')
        )
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_updater.timetables
        )
        log(module_name='look_ahead_handler', log_type='DEBUG',
            log_message='update_timetable_documents (mongodb_database): ok')

    def update_timetables_of_bus_lines(self):
        """
        Update the timetables of all bus_lines, taking into consideration the current levels of traffic_density.

        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.update_timetables_of_bus_line(bus_line=bus_line)
Example #3
0
class LookAheadHandler(object):
    def __init__(self):
        self.mongodb_database_connection = MongodbDatabaseConnection(
            host=mongodb_host, port=mongodb_port)
        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='mongodb_database_connection: established')

    def generate_bus_line(self, bus_stop_names, bus_line_id=None):
        """
        Generate a bus_line, consisted of a bus_line_id and a list of bus_stops, and store it to the corresponding
        collection of the System Database. Moreover, identify all the possible waypoints between the bus_stops
        of the bus_line, and populate the BusStopWaypoints collection.

        :param bus_stop_names: [string]
        :param bus_line_id: int
        :return: None
        """
        # 1: The inputs: bus_line_id and bus_stop_names are provided to the function, so as as a bus_line
        #    with the corresponding bus_line_id and bus_stops to be generated.
        #
        # 2: The Look Ahead connects to the System Database and retrieves the bus_stops which correspond to
        #    the provided bus_stop_names. The function returns None and the bus_line is not generated,
        #    in case there is a bus_stop_name which does not correspond to a stored bus_stop.
        #
        if bus_line_id is None:
            maximum_bus_line_id = self.mongodb_database_connection.get_maximum_or_minimum(
                collection='bus_line')
            bus_line_id = maximum_bus_line_id + 1

        bus_stops = []

        for bus_stop_name in bus_stop_names:
            bus_stop_document = self.mongodb_database_connection.find_bus_stop_document(
                name=bus_stop_name)

            if bus_stop_document is None:
                log_message = 'find_bus_stop_document (mongodb_database) - name:', bus_stop_name, '- result: None'
                log(module_name='look_ahead_handler',
                    log_type='DEBUG',
                    log_message=log_message)
                return None
            else:
                bus_stops.append(bus_stop_document)

        # 3: The intermediate waypoints of the bus_routes, which are generated while combining starting and
        #    ending bus_stops of the bus_line, should be stored as bus_stop_waypoints_documents at the System Database.
        #    The Look Ahead checks the existing bus_stop_waypoints_documents and communicates with the Route Generator
        #    in order to identify the waypoints of the bus_routes which are not already stored. The newly generated
        #    bus_stop_waypoints_documents are getting stored to the corresponding collection of the System Database.
        #    The function returns None and the bus_line is not generated, in case the Route Generator can not identify
        #    a possible route in order to connect the bus_stops of the bus_line.
        #
        number_of_bus_stops = len(bus_stops)

        for i in range(0, number_of_bus_stops - 1):
            starting_bus_stop = bus_stops[i]
            ending_bus_stop = bus_stops[i + 1]

            bus_stop_waypoints_document = self.mongodb_database_connection.find_bus_stop_waypoints_document(
                starting_bus_stop=starting_bus_stop,
                ending_bus_stop=ending_bus_stop)
            if bus_stop_waypoints_document is None:
                route_generator_response = get_waypoints_between_two_bus_stops(
                    starting_bus_stop=starting_bus_stop,
                    ending_bus_stop=ending_bus_stop)
                if route_generator_response is None:
                    log(module_name='look_ahead_handler',
                        log_type='DEBUG',
                        log_message=
                        'get_waypoints_between_two_bus_stops (route_generator): None'
                        )
                    return None
                else:
                    waypoints = route_generator_response.get('waypoints')

                    if len(waypoints) == 0:
                        log(module_name='look_ahead_handler',
                            log_type='DEBUG',
                            log_message=
                            'get_waypoints_between_two_bus_stops (route_generator): None'
                            )
                        return None

                    lists_of_edge_object_ids = []

                    for list_of_edges in waypoints:
                        list_of_edge_object_ids = []

                        for edge in list_of_edges:
                            edge_object_id = edge.get('_id')
                            list_of_edge_object_ids.append(edge_object_id)

                        lists_of_edge_object_ids.append(
                            list_of_edge_object_ids)

                    # waypoints: [[edge_object_id]]
                    #
                    waypoints = lists_of_edge_object_ids

                    self.mongodb_database_connection.insert_bus_stop_waypoints_document(
                        starting_bus_stop=starting_bus_stop,
                        ending_bus_stop=ending_bus_stop,
                        waypoints=waypoints)

        # 4: The Look Ahead stores the newly generated bus_line_document, which is consisted of the bus_line_id
        #    and the list of bus_stops, to the corresponding collection of the System Database.
        #    In case there is an already existing bus_line_document, with the same bus_line_id,
        #    then the list of bus_stops gets updated.
        #
        bus_line_document = {
            'bus_line_id': bus_line_id,
            'bus_stops': bus_stops
        }
        self.mongodb_database_connection.insert_bus_line_document(
            bus_line_document=bus_line_document)

        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='insert_bus_line_document (mongodb_database): ok')

    def generate_timetables_for_bus_line(self,
                                         timetables_starting_datetime,
                                         timetables_ending_datetime,
                                         requests_min_departure_datetime,
                                         requests_max_departure_datetime,
                                         bus_line=None,
                                         bus_line_id=None):
        """
        Generate timetables for a bus_line, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        - The input: timetables_starting_datetime and input: timetables_ending_datetime
          are provided to the function, so as timetables for the specific datetime period
          to be generated.

        - The input: requests_min_departure_datetime and input: requests_max_departure_datetime
          are provided to the function, so as travel_requests with departure_datetime corresponding
          to the the specific datetime period to be evaluated.

        - The input: bus_line or input: bus_line_id is provided to the function,
          so as timetables for the specific bus_line to be generated.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """

        maximum_timetable_id_in_database = self.mongodb_database_connection.get_maximum_or_minimum(
            collection='timetable')

        # 1: The list of bus_stops corresponding to the provided bus_line is retrieved.
        #
        # bus_stop_document: {
        #     '_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}
        # }
        # bus_stops: [bus_stop_document]
        #
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(
                bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')

        # 2: The Look Ahead retrieves from the System Database the travel_requests with
        #    departure_datetime higher than requests_min_departure_datetime and
        #    lower than requests_max_departure_datetime.
        #
        # travel_request_document: {
        #     '_id', 'client_id', 'bus_line_id',
        #     'starting_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'ending_bus_stop': {'_id', 'osm_id', 'name', 'point': {'longitude', 'latitude'}},
        #     'departure_datetime', 'arrival_datetime'
        # }
        # travel_requests: [travel_request_document]
        #
        travel_requests = self.mongodb_database_connection.find_travel_request_documents(
            bus_line_ids=[bus_line_id],
            min_departure_datetime=requests_min_departure_datetime,
            max_departure_datetime=requests_max_departure_datetime)

        # 3: (TimetableGenerator is initialized) The Look Ahead sends a request to the Route Generator so as
        #    to identify the less time-consuming bus_route between the bus_stops of bus_line,
        #    while taking into consideration the current levels of traffic density.
        #
        timetable_generator = TimetableGenerator(
            maximum_timetable_id_in_database=maximum_timetable_id_in_database,
            bus_line_id=bus_line_id,
            bus_stops=bus_stops,
            travel_requests=travel_requests)

        # The list of bus_stops of a bus_line might contain the same bus_stop_osm_ids more than once.
        # For this reason, each travel_request needs to be related with the correct index in the bus_stops list.
        # So, the values 'starting_timetable_entry_index' and 'ending_timetable_entry_index' are estimated.
        #
        correspond_travel_requests_to_bus_stops(
            travel_requests=timetable_generator.travel_requests,
            bus_stops=timetable_generator.bus_stops)

        # 4: Based on the response of the Route Generator, which includes details about the followed bus_route,
        #    and using only one bus vehicle, the Look Ahead generates some initial timetables which cover the
        #    whole datetime period from timetables_starting_datetime to timetables_ending_datetime.
        #    Initially, the list of travel requests of these timetables is empty, and the departure_datetime and
        #    arrival_datetime values of the timetable_entries are based exclusively on the details of the bus_route.
        #    In the next steps of the algorithm, these timetables are used in the initial clustering
        #    of the travel requests.
        #
        timetable_generator.timetables = generate_initial_timetables(
            bus_line_id=bus_line_id,
            timetables_starting_datetime=timetables_starting_datetime,
            timetables_ending_datetime=timetables_ending_datetime,
            route_generator_response=timetable_generator.
            route_generator_response)

        current_average_waiting_time_of_timetables = float('Inf')

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_generator.timetables,
                travel_requests=timetable_generator.travel_requests)
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables)
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_generator.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_generator.timetables)
            else:
                break

        print_timetables(timetables=timetable_generator.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id'))
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_generator.timetables)
        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='insert_timetable_documents (mongodb_database): ok')

    def generate_timetables_for_bus_lines(self, timetables_starting_datetime,
                                          timetables_ending_datetime,
                                          requests_min_departure_datetime,
                                          requests_max_departure_datetime):
        """
        Generate timetables for all bus_lines, for a selected datetime period,
        evaluating travel_requests of a specific datetime period.

        :param timetables_starting_datetime: datetime
        :param timetables_ending_datetime: datetime
        :param requests_min_departure_datetime: datetime
        :param requests_max_departure_datetime: datetime
        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.generate_timetables_for_bus_line(
                bus_line=bus_line,
                timetables_starting_datetime=timetables_starting_datetime,
                timetables_ending_datetime=timetables_ending_datetime,
                requests_min_departure_datetime=requests_min_departure_datetime,
                requests_max_departure_datetime=requests_max_departure_datetime
            )

    def update_timetables_of_bus_line(self, bus_line=None, bus_line_id=None):
        """
        Update the timetables of a bus_line, taking into consideration the current levels of traffic_density.

        :param bus_line: bus_line_document
        :param bus_line_id: int
        :return: None
        """
        if bus_line is None and bus_line_id is None:
            return None
        elif bus_line is None:
            bus_line = self.mongodb_database_connection.find_bus_line_document(
                bus_line_id=bus_line_id)
        else:
            bus_line_id = bus_line.get('bus_line_id')

        bus_stops = bus_line.get('bus_stops')
        timetables = self.mongodb_database_connection.find_timetable_documents(
            bus_line_ids=[bus_line_id])
        travel_requests = get_travel_requests_of_timetables(
            timetables=timetables)

        timetable_updater = TimetableUpdater(bus_stops=bus_stops,
                                             timetables=timetables,
                                             travel_requests=travel_requests)
        update_entries_of_timetables(
            timetables=timetable_updater.timetables,
            route_generator_response=timetable_updater.route_generator_response
        )
        current_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
            timetables=timetable_updater.timetables)
        print_timetables(timetables=timetable_updater.timetables)

        while True:
            new_timetables = generate_new_timetables_based_on_travel_requests(
                current_timetables=timetable_updater.timetables,
                travel_requests=timetable_updater.travel_requests)
            new_average_waiting_time_of_timetables = calculate_average_waiting_time_of_timetables_in_seconds(
                timetables=new_timetables)
            if new_average_waiting_time_of_timetables < current_average_waiting_time_of_timetables:
                timetable_updater.timetables = new_timetables
                current_average_waiting_time_of_timetables = new_average_waiting_time_of_timetables
                print_timetables(timetables=timetable_updater.timetables)
            else:
                break

        print_timetables(timetables=timetable_updater.timetables)

        self.mongodb_database_connection.delete_timetable_documents(
            bus_line_id=bus_line.get('bus_line_id'))
        self.mongodb_database_connection.insert_timetable_documents(
            timetable_documents=timetable_updater.timetables)
        log(module_name='look_ahead_handler',
            log_type='DEBUG',
            log_message='update_timetable_documents (mongodb_database): ok')

    def update_timetables_of_bus_lines(self):
        """
        Update the timetables of all bus_lines, taking into consideration the current levels of traffic_density.

        :return: None
        """
        bus_lines = self.mongodb_database_connection.find_bus_line_documents()

        for bus_line in bus_lines:
            self.update_timetables_of_bus_line(bus_line=bus_line)