Source code for taxi_simulator.utils

import asyncio
import json
import os
import sys
import time
import logging
import socket
import uuid
from importlib import import_module
from abc import ABCMeta

from spade.message import Message
from spade.behaviour import CyclicBehaviour, OneShotBehaviour
from spade.template import Template

from .helpers import distance_in_meters, kmh_to_ms

logger = logging.getLogger()

TAXI_WAITING = "TAXI_WAITING"
TAXI_MOVING_TO_PASSENGER = "TAXI_MOVING_TO_PASSENGER"
TAXI_IN_PASSENGER_PLACE = "TAXI_IN_PASSENGER_PLACE"
TAXI_MOVING_TO_DESTINATION = "TAXI_MOVING_TO_DESTINATION"
TAXI_WAITING_FOR_APPROVAL = "TAXI_WAITING_FOR_APPROVAL"

PASSENGER_WAITING = "PASSENGER_WAITING"
PASSENGER_IN_TAXI = "PASSENGER_IN_TAXI"
PASSENGER_IN_DEST = "PASSENGER_IN_DEST"
PASSENGER_LOCATION = "PASSENGER_LOCATION"
PASSENGER_ASSIGNED = "PASSENGER_ASSIGNED"


[docs]def status_to_str(status_code): """ Translates an int status code to a string that represents the status Args: status_code (int): the code of the status Returns: str: the string that represents the status """ statuses = { 10: "TAXI_WAITING", 11: "TAXI_MOVING_TO_PASSENGER", 12: "TAXI_IN_PASSENGER_PLACE", 13: "TAXI_MOVING_TO_DESTINATION", 14: "TAXI_WAITING_FOR_APPROVAL", 20: "PASSENGER_WAITING", 21: "PASSENGER_IN_TAXI", 22: "PASSENGER_IN_DESTINATION", 23: "PASSENGER_LOCATION", 24: "PASSENGER_ASSIGNED" } if status_code in statuses: return statuses[status_code] return status_code
[docs]class StrategyBehaviour(CyclicBehaviour, metaclass=ABCMeta): """ The behaviour that all parent strategies must inherit from. It complies with the Strategy Pattern. """ pass
[docs]class RequestRouteBehaviour(OneShotBehaviour): """ A one-shot behaviour that is executed to request for a new route to the route agent. """ def __init__(self, msg: Message, origin: list, destination: list, route_agent: str): """ Behaviour to request a route to a route agent Args: msg (Message): the message to be sent origin (list): origin of the route destination (list): destination of the route route_agent (str): name of the route agent """ self.origin = origin self.destination = destination self._msg = msg self.route_agent = route_agent self.result = {"path": None, "distance": None, "duration": None} super().__init__()
[docs] async def run(self): try: self._msg.to = self.route_agent self._msg.set_metadata("performative", "route") content = {"origin": self.origin, "destination": self.destination} self._msg.body = json.dumps(content) await self.send(self._msg) logger.debug("RequestRouteBehaviour sent message: {}".format(self._msg)) msg = await self.receive(20) logger.debug("RequestRouteBehaviour received message: {}".format(msg)) if msg is None: logger.warning("There was an error requesting the route (timeout)") self.exit_code = {"type": "error"} return self.kill(json.loads(msg.body)) except Exception as e: logger.error("Exception requesting route: " + str(e))
[docs]async def request_path(agent, origin, destination, route_id): """ Sends a message to the RouteAgent to request a path Args: agent: the agent who is requesting the path origin (list): a list with the origin coordinates [longitude, latitude] destination (list): a list with the target coordinates [longitude, latitude] route_id (str): name of the route agent Returns: list, float, float: a list of points (longitude and latitude) representing the path, the distance of the path in meters, a estimation of the duration of the path Examples: >>> path, distance, duration = request_path(an_agent, origin=[0,0], destination=[1,1]) >>> print(path) [[0,0], [0,1], [1,1]] >>> print(distance) 2.0 >>> print(duration) 3.24 """ if origin[0] == destination[0] and origin[1] == destination[1]: return [[origin[1], origin[0]]], 0, 0 msg = Message() msg.thread = str(uuid.uuid4()).replace("-", "") template = Template() template.thread = msg.thread behav = RequestRouteBehaviour(msg, origin, destination, route_id) agent.add_behaviour(behav, template) while not behav.is_killed(): await asyncio.sleep(0.01) if behav.exit_code is {} or "type" in behav.exit_code and behav.exit_code["type"] == "error": return None, None, None else: return behav.exit_code["path"], behav.exit_code["distance"], behav.exit_code["duration"]
[docs]def unused_port(hostname): """Return a port that is unused on the current host.""" s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((hostname, 0)) port = s.getsockname()[1] s.close() return port
[docs]def chunk_path(path, speed_in_kmh): """ Splits the path into smaller chunks taking into account the speed. Args: path (list): the original path. A list of points (lon, lat) speed_in_kmh (float): the speed in km per hour at which the path is being traveled. Returns: list: a new path equivalent (to the first one), that has at least the same number of points. """ meters_per_second = kmh_to_ms(speed_in_kmh) length = len(path) chunked_lat_lngs = [] for i in range(1, length): _cur = path[i - 1] _next = path[i] if _cur == _next: continue distance = distance_in_meters(_cur, _next) factor = meters_per_second / distance if distance else 0 diff_lat = factor * (_next[0] - _cur[0]) diff_lng = factor * (_next[1] - _cur[1]) if distance > meters_per_second: while distance > meters_per_second: _cur = [_cur[0] + diff_lat, _cur[1] + diff_lng] distance = distance_in_meters(_cur, _next) chunked_lat_lngs.append(_cur) else: chunked_lat_lngs.append(_cur) chunked_lat_lngs.append(path[length - 1]) return chunked_lat_lngs
[docs]def load_class(class_path): """ Tricky method that imports a class form a string. Args: class_path (str): the path where the class to be imported is. Returns: class: the class imported and ready to be instantiated. """ sys.path.append(os.getcwd()) module_path, class_name = class_path.rsplit(".", 1) mod = import_module(module_path) return getattr(mod, class_name)
[docs]def avg(array): """ Makes the average of an array without Nones. Args: array (list): a list of floats and Nones Returns: float: the average of the list without the Nones. """ array_wo_nones = list(filter(None, array)) return (sum(array_wo_nones, 0.0) / len(array_wo_nones)) if len(array_wo_nones) > 0 else 0.0