summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dashboard_website/__pycache__/db.cpython-312.pycbin12755 -> 13656 bytes
-rw-r--r--dashboard_website/__pycache__/router.cpython-312.pycbin18499 -> 19073 bytes
-rw-r--r--dashboard_website/dashboard.py26
-rw-r--r--dashboard_website/db.py83
-rw-r--r--dashboard_website/router.py32
-rw-r--r--dashboard_website/savefile.csv1
-rw-r--r--dashboard_website/static/js/dashboard.js60
-rw-r--r--dashboard_website/templates/index.html4
8 files changed, 151 insertions, 55 deletions
diff --git a/dashboard_website/__pycache__/db.cpython-312.pyc b/dashboard_website/__pycache__/db.cpython-312.pyc
index 1d7188c..b5db03b 100644
--- a/dashboard_website/__pycache__/db.cpython-312.pyc
+++ b/dashboard_website/__pycache__/db.cpython-312.pyc
Binary files differ
diff --git a/dashboard_website/__pycache__/router.cpython-312.pyc b/dashboard_website/__pycache__/router.cpython-312.pyc
index c55cd00..3c20b6e 100644
--- a/dashboard_website/__pycache__/router.cpython-312.pyc
+++ b/dashboard_website/__pycache__/router.cpython-312.pyc
Binary files differ
diff --git a/dashboard_website/dashboard.py b/dashboard_website/dashboard.py
index 77832aa..757324d 100644
--- a/dashboard_website/dashboard.py
+++ b/dashboard_website/dashboard.py
@@ -73,16 +73,18 @@ def requestRoute():
if not ( (type(content['longitude']) is float ) and (type(content['latitude']) is float)):
return jsonify({'status' : "ERROR 2"})
-
+
+ bike, clue = db.getBikeCluePair(content['team_index'])
+ if clue == None: # bike disabled or without clue -- should not ping bike yet
+ return jsonify({'status' : "ERROR 5"})
+
if db.pingBike(content['team_index'], content['latitude'], content['longitude']) == 4:
return jsonify({'status' : "ERROR 4"})
if db.currently_updating:
return jsonify({'status' : "ERROR 3"})
- bike, clue = db.getBikeCluePair(content['team_index'])
- if clue == None:
- return jsonify({'status' : "ERROR 5"})
+
route = router.getRouteFullJSON(bike, clue)
if route['code'] != 'Ok': # or some other code indicating routing problem?
@@ -257,6 +259,7 @@ def getLatestInfo():
'clues_changed' : False,
'home_changed' : False,
'routes_changed' : False,
+ 'routes_to_commit' : db.routesToCommit,
'route_update_staged' : db.is_route_update_required() }
cl = db.getCluesJSON(last_timestamp)
if cl != False:
@@ -266,10 +269,11 @@ def getLatestInfo():
if h != False:
data['home_changed'] = True
data['homebase'] = h
- r = db.getRoutesJSON(last_timestamp)
- if r != False:
+ p, r = db.getRoutesJSON(last_timestamp)
+ if p != False:
data['routes_changed'] = True
data['routes'] = r
+ data['preview_routes'] = p
data['calculating_routes'] = db.currently_updating
data['bikes'] = db.getBikesJSON()
data['status'] = "OK"
@@ -328,7 +332,15 @@ def siteControls():
elif cmd == "generateRoutes":
if db.updateRoutes() != 0:
return jsonify({"status" : "ERROR"})
- return jsonify({"status" : "OK"})
+ return jsonify({"status" : "OK"})
+ elif cmd == "stopGenerating":
+ db.force_stop()
+ return jsonify({"status" : "OK"})
+ elif cmd == "commitRoutes":
+ if db.routesToCommit:
+ db.applyPreviewRoutes()
+ return jsonify({"status" : "OK"})
+
return render_template("controls.html")
diff --git a/dashboard_website/db.py b/dashboard_website/db.py
index eec587d..6142fed 100644
--- a/dashboard_website/db.py
+++ b/dashboard_website/db.py
@@ -17,7 +17,7 @@ clues = []
bikes = []
clusters = []
routes = {"clusters": [], "cluster_times": {},
- "individual_routes": []} # geojson polylines, both between all the clusters
+ "individual_routes": [], "clue_clusters" : []} # geojson polylines, both between all the clusters
preview_routes = {"clusters": [], "cluster_times": {}, # geojson polylines, both between all the clusters
"individual_routes": [], "clue_clusters" : []} # except clue clusters which is a list of a list of clues
assigned_clues = []
@@ -27,20 +27,28 @@ routes_last_changed = time.time()
route_update_required = False
currently_updating = False
minimalRouting = False # if false, currently assigned clues
+routesToCommit = False # if there exist preview routes to commit
_bikes_changed = False # In case bikes or clues are enabled/disabled
_clues_changed = False # during route calculation
+_force_stop = False
startup = False #
def should_cancel_routing():
- global _bikes_changed, _clues_changed
- if _bikes_changed or _clues_changed:
- _bikes_changed = True
- _clues_changed = True
+ global _bikes_changed, _clues_changed, _force_stop
+ if _bikes_changed or _clues_changed or _force_stop:
+ _bikes_changed = False
+ _clues_changed = False
+ _force_stop = False
return True
return False
+def force_stop():
+ global _force_stop, currently_updating
+ currently_updating = False
+ _force_stop = True
+
def is_route_update_required():
return route_update_required
@@ -48,14 +56,20 @@ def is_route_update_required():
# a bike is added/removed
# determines/assigns clusters, and assigns routes to bikes
def updateRoutes_background(): # run in thread due to long runtime
- global currently_updating, routes_last_changed, route_update_required, preview_routes, clusters, routes
+ global currently_updating, routes_last_changed, route_update_required, preview_routes, clusters, routesToCommit, _bikes_changed, _clues_changed
print("Calculating clusters...")
+ _bikes_changed = False
+ _clues_changed = False
+ _force_stop = False
preview_routes = {"clusters": [], "cluster_times": {}, "individual_routes": [], "clue_clusters" : []} # reset
status, clusters, paths, times = router.getClusters(bikes, clues, homeBase, minimalRouting)
if status != 0:
print("Aborted routing.")
+ currently_updating = False
+ return -1
+
- preview_routes['individual_routes'] = paths.copy()
+ preview_routes['individual_routes'] = [[] for bike in bikes]
preview_routes['cluster_times'] = times
preview_routes['clusters'] = paths
preview_routes['clue_clusters'] = clusters
@@ -63,16 +77,9 @@ def updateRoutes_background(): # run in thread due to long runtime
if bikes[i].status != "DISABLED":
preview_routes['individual_routes'][i] = router.getRouteHDPolyline(bikes[i], clusters[i][0])
- # set routes - TESTING
- routes['individual_routes'] = paths.copy()
- routes['cluster_times'] = times
- routes['clusters'] = paths
- for i, bike in enumerate(bikes):
- if bike.status == "DISABLED": continue
- bike.setCluster(clusters[i])
-
routes_last_changed = time.time()
print("Finished calculating clusters/routes.")
+ routesToCommit = True
currently_updating = False
route_update_required = False
@@ -87,6 +94,22 @@ def updateRoutes(): # if necessary
return 0
+def applyPreviewRoutes():
+ global routes_last_changed, routesToCommit, preview_routes, routes
+ # set routes
+ routes['individual_routes'] = preview_routes['individual_routes'].copy()
+ routes['cluster_times'] = preview_routes['cluster_times'].copy()
+ routes['clusters'] = preview_routes['clusters'].copy()
+ routes['clue_clusters'] = preview_routes['clue_clusters'].copy()
+ for i, bike in enumerate(bikes):
+ if bike.status == "DISABLED": continue
+ bike.setCluster(routes['clue_clusters'][i])
+ preview_routes = {"clusters": [], "cluster_times": {}, "individual_routes": [], "clue_clusters" : []} # reset
+
+
+ routesToCommit = False
+ routes_last_changed = time.time()
+
# interface functions
def getTime():
return time.time()
@@ -125,8 +148,12 @@ def getBikeCluePair(team_index):
def getRoutesJSON(timestamp):
if timestamp < 0 or routes_last_changed - timestamp > 0:
- return routes
- return False
+ preview_routes_t = preview_routes.copy()
+ routes_t = routes.copy()
+ del preview_routes_t["clue_clusters"]
+ del routes_t["clue_clusters"]
+ return preview_routes_t, routes_t
+ return False, False
def pingBike(team_index, latitude, longitude):
@@ -136,9 +163,10 @@ def pingBike(team_index, latitude, longitude):
return 0
def setBikeEnabled(team_index, enabled):
- global route_update_required
+ global route_update_required, _bikes_changed
bike = bikes[team_index-1]
route_update_required = True
+ _bikes_changed = True
if enabled:
bike.enable()
else:
@@ -163,7 +191,7 @@ def getCluesJSON(timestamp):
def addClue(clue_name, clue_info, longitude, latitude, status="UNVISITED"):
- global clues_last_changed, route_update_required
+ global clues_last_changed, route_update_required, _clues_changed
for clue in clues:
if clue.name == clue_name:
return -1 # clue already exists
@@ -171,21 +199,23 @@ def addClue(clue_name, clue_info, longitude, latitude, status="UNVISITED"):
clues.append(newClue)
clues_last_changed = time.time()
route_update_required = True
+ _clues_changed = True
return 0
def deleteClue(clue_name):
- global route_update_required
+ global route_update_required, _clues_changed
for i, clue in enumereate(clues):
if clue.name == clue_name:
clues.pop(i)
clues_last_changed = time.time()
route_update_required = True
+ _clues_changed = True
break
def visitClue(clue_name, unvisit=False):
- global clues_last_changed, route_update_required
+ global clues_last_changed, route_update_required, _clues_changed
for clue in clues:
if clue.name == clue_name:
if clue.status == "VISITED":
@@ -196,7 +226,8 @@ def visitClue(clue_name, unvisit=False):
return 3 # already visited
clue.visit()
clues_last_changed = time.time()
- route_update_required
+ route_update_required = True
+ _clues_changed = True
return 0 # OK
return 2 # no clue
@@ -261,6 +292,7 @@ def visitClueTeam(team_index, clue_name):
def load(filename=None):
+ global route_update_required
"""
Loads all clues from a CSV file
:param filename: name of CSV file
@@ -290,11 +322,11 @@ def load(filename=None):
except:
return 1
clues_last_changed = time.time()
- updateRoutes()
+ route_update_required = True
return 0
def loadDirty(filename=None):
- global clues_last_changed
+ global clues_last_changed, route_update_required
if filename == None:
return
@@ -312,13 +344,12 @@ def loadDirty(filename=None):
if len(latlong) != 2: continue
latitude = float(latlong[0])
longitude = float(latlong[1])
- print(longitude)
clues.append(Clue(latitude, longitude, name, info, "UNVISITED"))
except:
return 1
clues_last_changed = time.time()
- updateRoutes()
+ route_update_required = True
return 0
diff --git a/dashboard_website/router.py b/dashboard_website/router.py
index 4840c8d..70af4c6 100644
--- a/dashboard_website/router.py
+++ b/dashboard_website/router.py
@@ -72,7 +72,7 @@ def getClusters(bikes, clues, endpoint, minimal=True):
# utility functions (internal)
def cluster_and_optimize(
- clues: [Clue], bikes: [Bike], end: Point, minimal : bool, time_diff=0.25, max_time=24
+ clues: [Clue], bikes: [Bike], end: Point, minimal : bool, time_diff=0.25
):
"""
Takes a dataframe of gps coordinates, a list of centroids, and an end point and returns a dataframe with a cluster
@@ -86,8 +86,8 @@ def cluster_and_optimize(
"""
status = 0
# OVERRIDE MAX TIME
- max_time = datetime.datetime.now() - endtime
- max_time = max_time.seconds / 3600
+ max_max_time = endtime - datetime.datetime.now()
+ max_max_time = max_max_time.seconds / 3600
# Create a new column with normalized gps coordinates and centroids
active_indices = [i for i in range(len(bikes)) if bikes[i].status != "DISABLED"]
@@ -124,13 +124,17 @@ def cluster_and_optimize(
# Remove waypoints from the longest route until the trip time is less than the max time
for i in range(len(routes)):
- routes[i] = __remove_longest_waypoints(routes[i], bikes[i], end, max_time)
-
+ resp = __remove_longest_waypoints(routes[i], bikes[i], end, max(bikes[i].timeTilDeadline()/3600, max_max_time))
+ if resp == -1:
+ return -1, [], [], []
+ routes[i] = resp
+
# Get the json of the routes
route_waypoints = []
geometries = []
times = []
for i, route in enumerate(routes):
+ if db.should_cancel_routing(): return -1, [], [], []
route_json = __get_json(
__clues_to_string(route),
__clues_to_string([bikes[i].target_clue if (minimal and (bikes[i].target_clue != None)) else bikes[i]]),
@@ -257,15 +261,16 @@ def __minimize_route_time_diff(
# Find the difference between the longest trip time and the average trip time
time_difference = times[sorted_indices[-1]] - average_time
-
+ print(times)
# If the difference is greater than the time difference, move a coordinate from the longest route to the shortest route
if time_difference > time_diff:
# Move a coordinate from the longest route to the shortest route
- closest_coordinate = __find_closest_coordinate(
- routes[sorted_indices[-1]], __mean_center(routes[sorted_indices[0]])
- )
- routes[active_indices[sorted_indices[0]]].append(closest_coordinate)
- routes[active_indices[sorted_indices[-1]]].remove(closest_coordinate)
+ for _ in range(max(1,int(time_difference/0.12))): # roughly estimate how many points must be moved to equalize
+ closest_coordinate = __find_closest_coordinate(
+ routes[active_indices[sorted_indices[-1]]], __mean_center(routes[sorted_indices[0]])
+ )
+ routes[active_indices[sorted_indices[0]]].append(closest_coordinate)
+ routes[active_indices[sorted_indices[-1]]].remove(closest_coordinate)
# Recursively minimize the time difference between the routes
return __minimize_route_time_diff(routes, bikes, end, minimal, time_diff)
@@ -288,6 +293,10 @@ def __remove_longest_waypoints(
if len(route_coordinates) < 1:
return []
+ if db.should_cancel_routing(): return -1
+
+ print(max_time)
+
if start.status != "DISABLED" and start.target_clue != None: # already assigned a clue
start = start.target_clue
# Find the trip time for the route
@@ -300,6 +309,7 @@ def __remove_longest_waypoints(
# If the trip time is greater than the max time, remove the waypoint with the longest distance from the mean
if route_time > max_time:
+ print(route_time)
route_mean = __mean_center(route_coordinates)
farthest_coordinates = __find_farthest_coordinates(route_coordinates, route_mean)
for farthest_coordinate in farthest_coordinates:
diff --git a/dashboard_website/savefile.csv b/dashboard_website/savefile.csv
index 314a385..a5ce1e0 100644
--- a/dashboard_website/savefile.csv
+++ b/dashboard_website/savefile.csv
@@ -54,3 +54,4 @@ A52,42.342764551151554,-71.12168301640884,Brookline Booksmith,UNVISITED
A53,42.31487124359676,-71.22693938547769,Echo Bridge,UNVISITED
A54,42.32485823589608,-71.16189103410989,Longyear Museum,UNVISITED
A55,42.3320010747946,-71.15563010527421,Waterworks Museum,UNVISITED
+SAVS,42.356,-71.06,savenors,UNVISITED
diff --git a/dashboard_website/static/js/dashboard.js b/dashboard_website/static/js/dashboard.js
index a816327..4ab8670 100644
--- a/dashboard_website/static/js/dashboard.js
+++ b/dashboard_website/static/js/dashboard.js
@@ -10,8 +10,9 @@ var unvisited_clues_m = L.layerGroup([]); // subset of all clues - unvisited clu
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 p_routes_m = L.layerGroup([]); //
-var homemarker, homebase, clues, clue_rels, bikes, routes, previewmarker;
+var homemarker, homebase, clues, clue_rels, bikes, routes, p_routes, previewmarker;
var latest_timestamp = -1; // initially -1, otherwise set to value given by server in last successful info update
@@ -75,7 +76,7 @@ function filterClues(text){
}
}
-function drawRoute(route_coords_osrm, team_color) {
+function drawRoute(route_coords_osrm, team_color, layer) {
//osrm lat/long are swapped
for (var i = 0; i < route_coords_osrm.length; i++){
var t = route_coords_osrm[i][1];
@@ -83,25 +84,44 @@ function drawRoute(route_coords_osrm, team_color) {
route_coords_osrm[i][0] = t;
}
var route = new L.polyline(route_coords_osrm, {color: team_color});
- routes_m.addLayer(route);
+ layer.addLayer(route);
}
function drawRoutes() {
routes_m.clearLayers();
+ p_routes_m.clearLayers();
+ for (var i = 0; i < p_routes['clusters'].length; i++){
+ if(p_routes['clusters'][i].length > 0){
+ var color;
+ var shade = i*35+100;
+ var r = shade;
+ var g = shade; // grey previews
+ var b = shade;
+ color= "rgb("+r+","+g+","+ b+")";
+ console.log(color);
+ drawRoute(p_routes['clusters'][i], color, p_routes_m);
+ }
+ }
+ for (var i = 0; i < p_routes['individual_routes'].length; i++){
+ if(p_routes['individual_routes'][i].length > 0){
+ drawRoute(p_routes['individual_routes'][i], 'white', p_routes_m);
+ }
+ }
for (var i = 0; i < routes['clusters'].length; i++){
if(routes['clusters'][i].length > 0){
var color;
- var r = Math.floor(Math.random() * 155+100);
+ var shade = i*50+100;
+ var r = shade;
var g = 0; // no greens -- avoid yellow
- var b = Math.floor(Math.random() * 155+100);
+ var b = shade-50;
color= "rgb("+r+","+g+","+ b+")";
console.log(color);
- drawRoute(routes['clusters'][i], color);
+ drawRoute(routes['clusters'][i], color, routes_m);
}
}
for (var i = 0; i < routes['individual_routes'].length; i++){
if(routes['individual_routes'][i].length > 0){
- drawRoute(routes['individual_routes'][i], 'yellow');
+ drawRoute(routes['individual_routes'][i], 'yellow', routes_m);
}
}
}
@@ -313,7 +333,7 @@ function requestLatestInfo(){
// process routes
if(json['routes_changed']){
routes = json['routes'];
- console.log(routes);
+ p_routes = json['preview_routes'];
drawRoutes();
}
// process bikes
@@ -360,7 +380,8 @@ function requestLatestInfo(){
updateClueStats();
}
- document.getElementById("routeinfo").innerHTML = json['calculating_routes'] ? "ROUTE INFO | <span style='color:orange'>(Calculating...)</span>" : "ROUTE INFO";
+ document.getElementById("routeinfo").innerHTML = json['calculating_routes'] ? "ROUTE INFO | <span style='color:orange'>(Calculating... <a href='#' onclick='stopRouting()'>(Force Stop)</a>)</span>" : "ROUTE INFO";
+ if(json['routes_to_commit'])document.getElementById("routeinfo").innerHTML += "| <a href='#' onclick='commitRoutes()'>Commit preview routes</a>"
}
fetch(host+'/getLatestInfo', {
method: "POST",
@@ -373,6 +394,24 @@ function requestLatestInfo(){
.then((json) => handleLatestInfo(json));
}
+function stopRouting(){
+ var formData = new FormData();
+ formData.append("command", "stopGenerating");
+ fetch(host+'/controls', {
+ method: "POST",
+ body: formData
+ }).then( res => requestLatestInfo());
+}
+
+function commitRoutes(){
+ var formData = new FormData();
+ formData.append("command", "commitRoutes");
+ fetch(host+'/controls', {
+ method: "POST",
+ body: formData
+ }).then( res => requestLatestInfo());
+}
+
function onDeadlineChange(element){
var team_index = parseInt(element.id.split("_")[1]);
var new_deadline = element.value;
@@ -426,10 +465,13 @@ window.onload = function() {
map.addLayer(unvisited_clues_m);
map.addLayer(bikes_m);
map.addLayer(routes_m);
+ map.addLayer(p_routes_m);
var layerControl = L.control.layers(null,null,{collapsed:false});
layerControl.addOverlay(unvisited_clues_m, "Unvisited Clues");
layerControl.addOverlay(visited_clues_m, "Visited Clues");
layerControl.addOverlay(bikes_m, "Bikes");
+ layerControl.addOverlay(routes_m, "Routes");
+ layerControl.addOverlay(p_routes_m, "Route Previews");
layerControl.addTo(map);
requestLatestInfo();
}
diff --git a/dashboard_website/templates/index.html b/dashboard_website/templates/index.html
index 9c604e0..60e6eaf 100644
--- a/dashboard_website/templates/index.html
+++ b/dashboard_website/templates/index.html
@@ -2,7 +2,7 @@
<html style="height:100%">
<head>
- <title>MMCH HQ</title>
+ <title>MMCHQ</title>
<link rel="icon" type="image/x-icon" href="/static/img/favicon.ico">
<link rel="stylesheet" href="{{ url_for('static', filename='css/leaflet.css') }}"
@@ -185,7 +185,7 @@
id="new_clue_info"
autocomplete=off
style="flex:1;"
- placeholder="e.g., Savenor's Butcher"/>
+ placeholder="e.g., Savenor's Butcher (RIP)"/>
</div>
<div style="margin:5px;display:flex;flex-direction:row"><span>Longitude: </span> <input autocomplete=off
type="input"