summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dashboard_website/__pycache__/datastructs.cpython-311.pycbin0 -> 4422 bytes
-rw-r--r--dashboard_website/__pycache__/db.cpython-311.pycbin10348 -> 6886 bytes
-rw-r--r--dashboard_website/__pycache__/router.cpython-311.pycbin16116 -> 17589 bytes
-rw-r--r--dashboard_website/dashboard.py7
-rw-r--r--dashboard_website/datastructs.py81
-rw-r--r--dashboard_website/db.py95
-rw-r--r--dashboard_website/router.py53
-rw-r--r--dashboard_website/static/js/dashboard.js17
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
new file mode 100644
index 0000000..62fbcab
--- /dev/null
+++ b/dashboard_website/__pycache__/datastructs.cpython-311.pyc
Binary files differ
diff --git a/dashboard_website/__pycache__/db.cpython-311.pyc b/dashboard_website/__pycache__/db.cpython-311.pyc
index cafda27..b1e752a 100644
--- a/dashboard_website/__pycache__/db.cpython-311.pyc
+++ b/dashboard_website/__pycache__/db.cpython-311.pyc
Binary files differ
diff --git a/dashboard_website/__pycache__/router.cpython-311.pyc b/dashboard_website/__pycache__/router.cpython-311.pyc
index 584aea1..57b79d2 100644
--- a/dashboard_website/__pycache__/router.cpython-311.pyc
+++ b/dashboard_website/__pycache__/router.cpython-311.pyc
Binary files differ
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}', {