diff options
| author | Anson Bridges <bridges.anson@gmail.com> | 2024-11-14 01:20:05 -0500 |
|---|---|---|
| committer | Anson Bridges <bridges.anson@gmail.com> | 2024-11-14 01:20:05 -0500 |
| commit | 3f68ac783948c7d47974993854a1d317399d3b04 (patch) | |
| tree | b4c5413f54a5435b279977ba6835581bb4f36567 | |
| parent | 5d013db5968b4b1989a5b2eabf2ad651540240d9 (diff) | |
bug fixes from server testing, route previews, slight routing improvements
| -rw-r--r-- | dashboard_website/__pycache__/db.cpython-312.pyc | bin | 12755 -> 13656 bytes | |||
| -rw-r--r-- | dashboard_website/__pycache__/router.cpython-312.pyc | bin | 18499 -> 19073 bytes | |||
| -rw-r--r-- | dashboard_website/dashboard.py | 26 | ||||
| -rw-r--r-- | dashboard_website/db.py | 83 | ||||
| -rw-r--r-- | dashboard_website/router.py | 32 | ||||
| -rw-r--r-- | dashboard_website/savefile.csv | 1 | ||||
| -rw-r--r-- | dashboard_website/static/js/dashboard.js | 60 | ||||
| -rw-r--r-- | dashboard_website/templates/index.html | 4 |
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 Binary files differindex 1d7188c..b5db03b 100644 --- a/dashboard_website/__pycache__/db.cpython-312.pyc +++ b/dashboard_website/__pycache__/db.cpython-312.pyc diff --git a/dashboard_website/__pycache__/router.cpython-312.pyc b/dashboard_website/__pycache__/router.cpython-312.pyc Binary files differindex c55cd00..3c20b6e 100644 --- a/dashboard_website/__pycache__/router.cpython-312.pyc +++ b/dashboard_website/__pycache__/router.cpython-312.pyc 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" |
