diff options
Diffstat (limited to 'dashboard_website')
| -rw-r--r-- | dashboard_website/__pycache__/datastructs.cpython-311.pyc | bin | 0 -> 4422 bytes | |||
| -rw-r--r-- | dashboard_website/__pycache__/db.cpython-311.pyc | bin | 10348 -> 6886 bytes | |||
| -rw-r--r-- | dashboard_website/__pycache__/router.cpython-311.pyc | bin | 16116 -> 17589 bytes | |||
| -rw-r--r-- | dashboard_website/dashboard.py | 7 | ||||
| -rw-r--r-- | dashboard_website/datastructs.py | 81 | ||||
| -rw-r--r-- | dashboard_website/db.py | 95 | ||||
| -rw-r--r-- | dashboard_website/router.py | 53 | ||||
| -rw-r--r-- | dashboard_website/static/js/dashboard.js | 17 |
8 files changed, 147 insertions, 106 deletions
diff --git a/dashboard_website/__pycache__/datastructs.cpython-311.pyc b/dashboard_website/__pycache__/datastructs.cpython-311.pyc Binary files differnew file mode 100644 index 0000000..62fbcab --- /dev/null +++ b/dashboard_website/__pycache__/datastructs.cpython-311.pyc diff --git a/dashboard_website/__pycache__/db.cpython-311.pyc b/dashboard_website/__pycache__/db.cpython-311.pyc Binary files differindex cafda27..b1e752a 100644 --- a/dashboard_website/__pycache__/db.cpython-311.pyc +++ b/dashboard_website/__pycache__/db.cpython-311.pyc diff --git a/dashboard_website/__pycache__/router.cpython-311.pyc b/dashboard_website/__pycache__/router.cpython-311.pyc Binary files differindex 584aea1..57b79d2 100644 --- a/dashboard_website/__pycache__/router.cpython-311.pyc +++ b/dashboard_website/__pycache__/router.cpython-311.pyc diff --git a/dashboard_website/dashboard.py b/dashboard_website/dashboard.py index 9ccad9f..59fa0df 100644 --- a/dashboard_website/dashboard.py +++ b/dashboard_website/dashboard.py @@ -92,7 +92,8 @@ def getLatestInfo(): last_timestamp = content['info_age'] data = {'timestamp' : db.getTime(), 'clues_changed' : False, - 'home_changed' : False} + 'home_changed' : False, + 'routes_changed' : False} cl = db.getCluesJSON(last_timestamp) if cl != False: data['clues_changed'] = True @@ -101,6 +102,10 @@ def getLatestInfo(): if h != False: data['home_changed'] = True data['homebase'] = h + r = db.getRoutesJSON(last_timestamp) + if r != False: + data['routes_changed'] = True + data['routes'] = r data['bikes'] = db.getBikesJSON() data['status'] = "OK" diff --git a/dashboard_website/datastructs.py b/dashboard_website/datastructs.py new file mode 100644 index 0000000..6870521 --- /dev/null +++ b/dashboard_website/datastructs.py @@ -0,0 +1,81 @@ +import time +# time since last ping before deactivating/deleting +BIKE_TIMEOUT = 60 +BIKE_DELETE = 1800 # time before bike deletes itself + +# data structures +class Point: + def __init__(self, lat, long): + self.longitude = long + self.latitude = lat + + def toJSON(self): + json_dict = {'longitude' : self.longitude, + 'latitude' : self.latitude} + return json_dict + + def setCoords(self, lat, long): + self.longitude = long + self.latitude = lat + + def move(self, d_lat, d_long): + self.longitude += d_long + self.latitude += d_lat + + def __str__(self): + return f"{self.longitude},{self.latitude}" + +class Clue(Point): + def __init__(self, lat, long, name, info, status): + self.longitude = long + self.latitude = lat + self.name = name + self.info = info + self.status = status # UNVISITED | ASSIGNED | VISITED + + def visit(self): + self.status = "VISITED" + + def toJSON(self): + json_dict = {'longitude' : self.longitude, + 'latitude' : self.latitude, + 'clue_name' : self.name.replace('"', "'"), + 'clue_info' : self.info.replace('"', "'"), + 'clue_status' : self.status} + return json_dict + +class Bike(Point): + def __init__(self, lat, long, name, status): + self.longitude = long + self.latitude = lat + self.name = name + self.last_contact = time.time() + self.target = "N/A" + self.route_to_next = [] # list of coords if target isnt' N/A + self.status = status # ACTIVE | INACTIVE + + def setTarget(self, clue_name): + self.target = clue_name + + def ping(self): + if self.status != "ACTIVE": + updateRoutes() + self.status = "ACTIVE" + self.last_contact = time.time() + + def checkStatus(self): + if time.time() - self.last_contact > BIKE_TIMEOUT: + self.status = "INACTIVE" + self.target = "N/A" + if time.time() - self.last_contact > BIKE_DELETE: + return -1 + return 0 + + def toJSON(self): + json_dict = {'longitude' : self.longitude, + 'latitude' : self.latitude, + 'time_since_last_contact' : time.time()-self.last_contact, + 'team_name' : self.name, + 'team_status' : self.status, + 'target_clue' : self.target} + return json_dict
\ No newline at end of file diff --git a/dashboard_website/db.py b/dashboard_website/db.py index a071671..892fa0b 100644 --- a/dashboard_website/db.py +++ b/dashboard_website/db.py @@ -1,100 +1,27 @@ # stores and manages clue DB # also manages currently available bike teams +from datastructs import * import router import csv, time -# time since last ping before deactivating/deleting -BIKE_TIMEOUT = 60 -BIKE_DELETE = 1800 # time before bike deletes itself - -# data structures -class Point: - def __init__(self, lat, long): - self.longitude = long - self.latitude = lat - - def toJSON(self): - json_dict = {'longitude' : self.longitude, - 'latitude' : self.latitude} - return json_dict - - def setCoords(self, lat, long): - self.longitude = long - self.latitude = lat - - def move(self, d_lat, d_long): - self.longitude += d_long - self.latitude += d_lat - - def __str__(self): - return f"{self.longitude},{self.latitude}" - -class Clue(Point): - def __init__(self, lat, long, name, info, status): - self.longitude = long - self.latitude = lat - self.name = name - self.info = info - self.status = status # UNVISITED | ASSIGNED | VISITED - - def visit(self): - self.status = "VISITED" - - def toJSON(self): - json_dict = {'longitude' : self.longitude, - 'latitude' : self.latitude, - 'clue_name' : self.name.replace('"', "'"), - 'clue_info' : self.info.replace('"', "'"), - 'clue_status' : self.status} - return json_dict - -class Bike(Point): - def __init__(self, lat, long, name, status): - self.longitude = long - self.latitude = lat - self.name = name - self.last_contact = time.time() - self.target = "N/A" - self.route_to_next = [] # list of coords if target isnt' N/A - self.status = status # ACTIVE | INACTIVE - - def setTarget(self, clue_name): - self.target = clue_name - - def ping(self): - self.status = "ACTIVE" - self.last_contact = time.time() - - def checkStatus(self): - if time.time() - self.last_contact > BIKE_TIMEOUT: - self.status = "INACTIVE" - self.target = "N/A" - if time.time() - self.last_contact > BIKE_DELETE: - return -1 - return 0 - - def toJSON(self): - json_dict = {'longitude' : self.longitude, - 'latitude' : self.latitude, - 'time_since_last_contact' : time.time()-self.last_contact, - 'team_name' : self.name, - 'team_status' : self.status, - 'target_clue' : self.target} - return json_dict # variables homeBase = Point(42.340226, -71.088395) # krentzman, can be changed on dashboard clues = [] bikes = [] +routes = {"clusters" : [], "individual_routes" : []} #geojson polylines, both between all the clusters assigned_clues = [] clues_last_changed = time.time() home_last_changed = time.time() +routes_last_changed = time.time() # called every time a node is added # a bike is added/removed # determines/assigns clusters, and assigns routes to bikes def updateRoutes(): - clusters = router.getClusters(bikes, clues, homeBase) + clusters, paths = router.getClusters(bikes, clues, homeBase) + routes['clusters'] = paths + routes_last_changed = time.time() # interface functions def getTime(): @@ -110,6 +37,12 @@ def setHomeBase(latitude, longitude): home_last_changed = time.time() +def getRoutesJSON(timestamp): + if timestamp < 0 or routes_last_changed - timestamp > 0: + return routes + return False + + def addBike(team_name, latitude, longitude): for bike in bikes: if bike.name == team_name: # already exists @@ -128,6 +61,7 @@ def pingBike(team_name, latitude, longitude): else: # bike team does not exist yet newBike = Bike(latitude, longitude, team_name, "ACTIVE") bikes.append(newBike) + updateRoutes() def getBikesJSON(): @@ -174,6 +108,7 @@ with open("all_clues.csv", newline='') as f: bike1 = Bike(42.340226, -71.088395, 'speedster', 'ACTIVE') bike2 = Bike(42.320226, -71.100395, 'slowpoke', "ACTIVE") bike1.setTarget("Clue #6") +bikes.append(bike1); bikes.append(bike2) +updateRoutes() def moveBike2Test(): bike1.move(0, -0.001); bike1.ping(); -bikes.append(bike1); bikes.append(bike2)
\ No newline at end of file diff --git a/dashboard_website/router.py b/dashboard_website/router.py index 99c83d2..5bafd1e 100644 --- a/dashboard_website/router.py +++ b/dashboard_website/router.py @@ -2,7 +2,7 @@ import numpy as np import requests from sklearn.cluster import KMeans -from dashboard_website import db +from datastructs import * host = "http://acetyl.net:5000" # queries acetyl.net:5000, the OSRM engine @@ -37,21 +37,27 @@ def getZSP(bike, home, clue_cluster): # determines clusters based on current bikes and clues def getClusters(bikes, clues, endpoint): - clusters = [[] for bike in bikes] + + clusters = [[] for bike in bikes ] + route_geos = [[] for bike in bikes ] + active_indices = [i for i in range(len(bikes)) if bikes[i].status == "ACTIVE"] active_bikes = [bike for bike in bikes if bike.status == "ACTIVE"] + if len(active_bikes) == 0: + return clusters, route_geos active_clues = [clue for clue in clues if clue.status == "UNVISITED"] # select only active bikes # select only unvisited clues - clusters_t, route_geo = cluster_and_optimize(active_clues, active_bikes, endpoint) - for cluster in clusters_t: - clusters[i] = cluster + clusters_t, route_geos_t = cluster_and_optimize(active_clues, active_bikes, endpoint) + for i in range(len(active_indices)): + route_geos[active_indices[i]] = route_geos_t[i] + clusters[active_indices[i]] = clusters_t[i] # return list of clue clusters corresponding to bikes - pass + return clusters, route_geos # utility functions (internal) -def cluster_and_optimize(clues: [db.Clue], bikes: [db.Bike], end: db.Point, time_diff=0.25, max_time=24, n=2): +def cluster_and_optimize(clues: [Clue], bikes: [Bike], end: Point, time_diff=0.25, max_time=24, n=2): """ Takes a dataframe of gps coordinates, a list of centroids, and an end point and returns a dataframe with a cluster :param clues: a list of clues @@ -65,7 +71,7 @@ def cluster_and_optimize(clues: [db.Clue], bikes: [db.Bike], end: db.Point, time # Create a new column with normalized gps coordinates and centroids normalized_points, norm_centroids = __normalize_points(clues, bikes) - + print(norm_centroids) # Cluster the coordinates kmeans = KMeans(n_clusters=len(norm_centroids), init=norm_centroids) kmeans.fit(normalized_points) @@ -82,21 +88,22 @@ def cluster_and_optimize(clues: [db.Clue], bikes: [db.Bike], end: db.Point, time routes[i] = __remove_longest_waypoints(routes[i], bikes[i], end, max_time) # Get the json of the routes - route_geo = [] + route_waypoints = [] + geometries = [] for i, route in enumerate(routes): - route_geo.append( - __get_json(__clues_to_string(route), __clues_to_string([bikes[i]]), __clues_to_string([end])[:-1])[ - 'waypoints']) + route_json = __get_json(__clues_to_string(route), __clues_to_string([bikes[i]]), __clues_to_string([end])[:-1]) + geometries.append(route_json['trips'][0]['geometry']['coordinates']) + route_waypoints.append(route_json['waypoints']) # Use the waypoint_index to reorder each route for i, route in enumerate(routes): - route = [route[j] for j in route_geo[i][0]['waypoint_index']] + route = [ route[ j['waypoint_index']-1 ] for j in route_waypoints[i] if route_waypoints[i].index(j) < (len(route_waypoints[i])-1) ] routes[i] = route - return routes + return routes, geometries -def __clues_to_string(points: [db.Clue]): +def __clues_to_string(points: [Clue]): """ Takes a list of points and returns a string of the list of points :param points: a list of points @@ -146,7 +153,7 @@ def __get_trip_time(coordinate_string, num_waypoints, start, end, time_per_waypo return total_time_hours -def __minimize_route_time_diff(routes: [db.Clue], starts: [db.Point], end: db.Point, time_diff, n): +def __minimize_route_time_diff(routes: [Clue], starts: [Point], end: Point, time_diff, n): """ Takes a list of lists of coordinates, a list of start points, an end point, a time difference, and a number of routes :param routes: the list of lists of coordinates @@ -186,7 +193,7 @@ def __minimize_route_time_diff(routes: [db.Clue], starts: [db.Point], end: db.Po return routes -def __remove_longest_waypoints(route_coordinates: [db.Clue], start: db.Bike, end: db.Point, max_time): +def __remove_longest_waypoints(route_coordinates: [Clue], start: Bike, end: Point, max_time): """ Takes a list of coordinates, a start point, an end point, and a maximum time and returns a list of coordinates :param route_coordinates: the list of coordinates @@ -210,7 +217,7 @@ def __remove_longest_waypoints(route_coordinates: [db.Clue], start: db.Bike, end return route_coordinates -def __normalize_points(clues: [db.Clue], bikes: [db.Bike]): +def __normalize_points(clues: [Clue], bikes: [Bike]): """ Takes a list of coordinates and a list of centroids and returns a list of normalized coordinates and a list of normalized centroids @@ -254,7 +261,7 @@ def __min_max_normalize(value, min_value, max_value): return (value - min_value) / (max_value - min_value) -def __find_closest_coordinate(clues: [db.Clue], centroid: db.Point): +def __find_closest_coordinate(clues: [Clue], centroid: Point): """ Takes a list of coordinates and a centroid and returns the clue in the list that is closest to the centroid :param clues: the list of coordinates @@ -273,7 +280,7 @@ def __find_closest_coordinate(clues: [db.Clue], centroid: db.Point): return closest_coordinate -def __find_farthest_coordinate(clues: [db.Clue], centroid: db.Point): +def __find_farthest_coordinate(clues: [Clue], centroid: Point): """ Takes a list of coordinates and a centroid and returns the clue in the list that is farthest from the centroid :param clues: the list of coordinates @@ -291,17 +298,17 @@ def __find_farthest_coordinate(clues: [db.Clue], centroid: db.Point): return farthest_coordinate -def __mean_center(clues: [db.Clue]): +def __mean_center(clues: [Clue]): """ Takes a list of coordinates and returns the mean center of the coordinates :param clues: the list of coordinates :return: the mean center of the coordinates """ - return db.Point(np.mean([coordinate.latitude for coordinate in clues]), + return Point(np.mean([coordinate.latitude for coordinate in clues]), np.mean([coordinate.longitude for coordinate in clues])) -def __distance(coordinate1: db.Clue, coordinate2: db.Point): +def __distance(coordinate1: Clue, coordinate2: Point): """ Takes two coordinates and returns the distance between them :param coordinate1: the first coordinate diff --git a/dashboard_website/static/js/dashboard.js b/dashboard_website/static/js/dashboard.js index c1f1428..0248844 100644 --- a/dashboard_website/static/js/dashboard.js +++ b/dashboard_website/static/js/dashboard.js @@ -9,8 +9,9 @@ var visited_clues_m = L.layerGroup([]); // all clues var unvisited_clues_m = L.layerGroup([]); // subset of all clues - unvisited clues var destination_clues_m = L.layerGroup([]); // clues bikers are currently destined for var bikes_m = L.layerGroup([]); // bike markers +var routes_m = L.layerGroup([]); // polyline routes -var homemarker, homebase, clues, clue_rels, bikes, previewmarker; +var homemarker, homebase, clues, clue_rels, bikes, routes, previewmarker; var latest_timestamp = -1; // initially -1, otherwise set to value given by server in last successful info update @@ -58,6 +59,13 @@ function drawRoute(route_coords_osrm, team_color) { } var route = new L.polyline(route_coords_osrm, {color: team_color}).addTo(map); } +function drawRoutes() { + for (var i = 0; i < routes['clusters'].length; i++){ + if(routes['clusters'][i].length > 0){ + drawRoute(routes['clusters'][i], i%2 == 0 ? 'red' : 'yellow'); + } + } +} function updateBikeStatus(){ var table = document.getElementById("bike-teams-table"); @@ -170,6 +178,11 @@ function requestLatestInfo(){ drawClues(); updateClueStats(); } + // process routes + if(json['routes_changed']){ + routes = json['routes']; + drawRoutes(); + } } fetch(host+'/getLatestInfo', { method: "POST", @@ -192,7 +205,7 @@ var clockINterval = window.setInterval(function(){ // RUN ON PAGE LOAD window.onload = function() { - clues = {}; bikes = {}; + clues = {}; bikes = {}; routes = {}; map = L.map('map').setView([42.3626081,-71.0620591], 13); L.tileLayer('https://tiles.stadiamaps.com/tiles/alidade_smooth_dark/{z}/{x}/{y}{r}.{ext}', { |
