diff options
| author | Anson Bridges <bridges.anson@gmail.com> | 2024-07-02 16:50:11 +0200 |
|---|---|---|
| committer | Anson Bridges <bridges.anson@gmail.com> | 2024-07-02 16:50:11 +0200 |
| commit | 411b7f175fb81aed6a9b050ce0872b376afe0431 (patch) | |
| tree | 147df07363f43363a8ac0d24f1ee92fed4aed3a2 /dashboard_website | |
| parent | 9690fc0e4a319941f3103b1c43d8509c22c20a4f (diff) | |
| parent | cc2a87f3e9949db97ffad300fdd87afcafed78c8 (diff) | |
Merge branch 'master' of acetyl.net:/home/git/hh
Diffstat (limited to 'dashboard_website')
| -rw-r--r-- | dashboard_website/dashboard.py | 2 | ||||
| -rw-r--r-- | dashboard_website/datastructs.py | 75 | ||||
| -rw-r--r-- | dashboard_website/router.py | 137 |
3 files changed, 137 insertions, 77 deletions
diff --git a/dashboard_website/dashboard.py b/dashboard_website/dashboard.py index 90bd718..ba29799 100644 --- a/dashboard_website/dashboard.py +++ b/dashboard_website/dashboard.py @@ -3,7 +3,7 @@ # dashboard.py contains web interface to clue DB + router # -from flask import Flask, flash, request, redirect, render_template, send_from_directory, jsonify +from flask import Flask, request, render_template, send_from_directory, jsonify import db import router diff --git a/dashboard_website/datastructs.py b/dashboard_website/datastructs.py index 997d3e7..f150825 100644 --- a/dashboard_website/datastructs.py +++ b/dashboard_website/datastructs.py @@ -2,20 +2,20 @@ import math import time # time since last ping before deactivating/deleting -BIKE_TIMEOUT = 60000 # 3 minutes -BIKE_DELETE = 360000 # time before bike deletes itself +BIKE_TIMEOUT = 60000 # 3 minutes +BIKE_DELETE = 360000 # 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} + json_dict = {"longitude": self.longitude, "latitude": self.latitude} return json_dict - + def setCoords(self, lat, long): self.longitude = long self.latitude = lat @@ -26,19 +26,24 @@ class Point: def __str__(self): return f"{self.longitude},{self.latitude}" - + def __repr__(self): return f"{self.longitude},{self.latitude}" - - def distanceTo(self, pt): # distance between points in miles - lat1 = self.latitude; lon1 = self.longitude; - lat2 = pt.latitude; lon2 = pt.longitude; - R = 3958.8 # Radius of the earth - lat_d = math.radians(lat2-lat1); - lon_d = math.radians(lon2-lon1); - a = math.sin(lat_d/2) * math.sin(lat_d/2) + math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) * math.sin(lon_d/2) * math.sin(lon_d/2) - c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a)); - d = R * c; # Distance in mi + + def distanceTo(self, pt): # distance between points in miles + lat1 = self.latitude + lon1 = self.longitude + lat2 = pt.latitude + lon2 = pt.longitude + R = 3958.8 # Radius of the earth + lat_d = math.radians(lat2 - lat1) + lon_d = math.radians(lon2 - lon1) + a = math.sin(lat_d / 2) * math.sin(lat_d / 2) + math.cos( + math.radians(lat1) + ) * math.cos(math.radians(lat2)) * math.sin(lon_d / 2) * math.sin(lon_d / 2) + c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + d = R * c + # Distance in mi return d @@ -52,6 +57,7 @@ class Clue(Point): self.pool_inbex = pool_index self.required = required + def visit(self): self.status = "VISITED" @@ -68,8 +74,10 @@ class Clue(Point): 'clue_info' : self.info.replace('"', "'"), 'clue_status' : self.status, 'clue_required' : self.required} + return json_dict + class Bike(Point): def __init__(self, lat, long, name, status): self.longitude = long @@ -77,35 +85,34 @@ class Bike(Point): self.name = name self.last_contact = time.time() self.target_name = "N/A" - self.cluster = [] # list of clues this bike team is responsible for - self.status = status # ACTIVE | INACTIVE - + self.cluster = [] # list of clues this bike team is responsible for + self.status = status # ACTIVE | INACTIVE + def setTarget(self, clue_name): self.target_name = clue_name - + def setCluster(self, clue_cluster): self.cluster = clue_cluster self.updateTarget() - + def updateTarget(self): if len(self.cluster) <= 0: self.target_name = "N/A" else: self.target_name = self.cluster[0].name - + def visitTarget(self): self.cluster[0].visit() self.cluster.pop(0) self.updateTarget() while len(self.cluster) > 0 and self.cluster[0].status == "VISITED": - self.cluster.pop(0) # skip next node if it has been somehow visited + self.cluster.pop(0) # skip next node if it has been somehow visited self.updateTarget() - def ping(self): self.status = "ACTIVE" self.last_contact = time.time() - + def disable(self): self.status = "INACTIVE" self.target = "N/A" @@ -120,10 +127,12 @@ class Bike(Point): 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_name} - return json_dict
\ No newline at end of file + 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_name, + } + return json_dict diff --git a/dashboard_website/router.py b/dashboard_website/router.py index e3784d3..e0e0720 100644 --- a/dashboard_website/router.py +++ b/dashboard_website/router.py @@ -1,5 +1,4 @@ import datetime -import time import numpy as np import requests @@ -9,33 +8,37 @@ from datastructs import * host = "http://acetyl.net:5000" # queries acetyl.net:5000, the OSRM engine -endtime = datetime.datetime(2023, 11, 18, hour=18, minute=45) # 11/18/2023 6:35pm +endtime = datetime.datetime(2023, 11, 18, hour=18, minute=45) # 11/18/2023 6:35pm # external facing functions + # gets single leg route between bike and clue # should be HD and GeoJSON def getRouteFullJSON(bike, clue): - bike = bike.toJSON(); clue = clue.toJSON() + bike = bike.toJSON() + clue = clue.toJSON() url = f"{host}/route/v1/bike/{bike['longitude']},{bike['latitude']};{clue['longitude']},{clue['latitude']}?steps=true&overview=full&geometries=geojson" r = requests.get(url) return r.json() def getRouteHDPolyline(bike, clue): - bike = bike.toJSON(); clue = clue.toJSON() + bike = bike.toJSON() + clue = clue.toJSON() url = f"{host}/route/v1/bike/{bike['longitude']},{bike['latitude']};{clue['longitude']},{clue['latitude']}?overview=full&geometries=geojson" r = requests.get(url) - p = r.json()['routes'][0]['geometry']['coordinates'] + p = r.json()["routes"][0]["geometry"]["coordinates"] return p def getRouteFastPolyline(bike, clue): - bike = bike.toJSON(); clue = clue.toJSON() + bike = bike.toJSON() + clue = clue.toJSON() url = f"{host}/route/v1/bike/{bike['longitude']},{bike['latitude']};{clue['longitude']},{clue['latitude']}?geometries=geojson" r = requests.get(url) - p = r.json()['routes'][0]['geometry']['coordinates'] + p = r.json()["routes"][0]["geometry"]["coordinates"] return p @@ -45,8 +48,8 @@ def getZSP(bike, home, clue_cluster): # determines clusters based on current bikes and clues def getClusters(bikes, clues, endpoint): - clusters = [[] for bike in bikes ] - route_geos = [[] for bike in bikes ] + clusters = [[] for bike in bikes] + route_geos = [[] for bike in bikes] times = {} 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"] @@ -55,10 +58,12 @@ def getClusters(bikes, clues, endpoint): active_clues = [clue for clue in clues if clue.status == "UNVISITED"] # select only active bikes # select only unvisited clues - clusters_t, route_geos_t, times_t = cluster_and_optimize(active_clues, active_bikes, endpoint) + clusters_t, route_geos_t, times_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] + clusters[active_indices[i]] = clusters_t[i] bikes[active_indices[i]].setCluster(clusters_t[i]) times[bikes[active_indices[i]].name] = times_t[i] @@ -67,7 +72,9 @@ def getClusters(bikes, clues, endpoint): # utility functions (internal) -def cluster_and_optimize(clues: [Clue], bikes: [Bike], end: 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 @@ -80,8 +87,10 @@ def cluster_and_optimize(clues: [Clue], bikes: [Bike], end: Point, time_diff=0.2 """ # OVERRIDE MAX TIME max_time = datetime.datetime.now() - endtime - max_time = max_time.seconds/3600 - routes = [clues] # one bike = one set of routes. only need to remove the faraway waypoints + max_time = max_time.seconds / 3600 + routes = [ + clues + ] # one bike = one set of routes. only need to remove the faraway waypoints if len(bikes) > 1: # Create a new column with normalized gps coordinates and centroids normalized_points, norm_centroids = __normalize_points(clues, bikes) @@ -95,29 +104,32 @@ def cluster_and_optimize(clues: [Clue], bikes: [Bike], end: Point, time_diff=0.2 routes[label].append(clues[i]) routes = __minimize_route_time_diff(routes, bikes, end, time_diff, n) - + # 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) - # Get the json of the routes route_waypoints = [] geometries = [] times = [] for i, route in enumerate(routes): - 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']) - eta = time.time() + route_json['trips'][0]['duration'] + 90 * len(route) + 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"]) + eta = time.time() + route_json["trips"][0]["duration"] + 90 * len(route) eta_str = datetime.datetime.fromtimestamp(eta).strftime("%I:%M:%S%p") times.append(eta_str) # Use the waypoint_index to reorder each route for i, route in enumerate(routes): route2 = ["" for x in route] - for j,k in enumerate(route_waypoints[i][1:-1]): - route2[ k['waypoint_index']-1 ] = route[j] + for j, k in enumerate(route_waypoints[i][1:-1]): + route2[k["waypoint_index"] - 1] = route[j] routes[i] = route2 return routes, geometries, times @@ -129,9 +141,9 @@ def __clues_to_string(points: [Clue]): :param points: a list of points :return: a string of the list of points """ - string = '' + string = "" for i in points: - string += str(i.longitude) + ',' + str(i.latitude) + ';' + string += str(i.longitude) + "," + str(i.latitude) + ";" return string @@ -145,13 +157,20 @@ def __get_json(coordinate_string, start, end): :return: the json of the route """ coordinates = requests.get( - 'http://acetyl.net:5000/trip/v1/bike/' + start + coordinate_string + end + '?roundtrip=false&source=first&destination=last&geometries=geojson&overview=full') + "http://acetyl.net:5000/trip/v1/bike/" + + start + + coordinate_string + + end + + "?roundtrip=false&source=first&destination=last&geometries=geojson&overview=full" + ) coordinates = coordinates.json() return coordinates -def __get_trip_time(coordinate_string, num_waypoints, start, end, time_per_waypoint=90, seconds=False): +def __get_trip_time( + coordinate_string, num_waypoints, start, end, time_per_waypoint=90, seconds=False +): """ Takes a string of coordinates and returns the trip time in hours :param coordinate_string: a string of coordinates @@ -162,19 +181,26 @@ def __get_trip_time(coordinate_string, num_waypoints, start, end, time_per_waypo :return: the trip time in hours """ coordinates = requests.get( - 'http://acetyl.net:5000/trip/v1/bike/' + start + coordinate_string + end + '?roundtrip=false&source=first&destination=last') + "http://acetyl.net:5000/trip/v1/bike/" + + start + + coordinate_string + + end + + "?roundtrip=false&source=first&destination=last" + ) coordinates = coordinates.json() - travel_time_seconds = int(coordinates['trips'][0]['duration']) + travel_time_seconds = int(coordinates["trips"][0]["duration"]) waypoint_time_seconds = num_waypoints * time_per_waypoint if seconds: - return (travel_time_seconds + waypoint_time_seconds) + return travel_time_seconds + waypoint_time_seconds total_time_hours = (travel_time_seconds + waypoint_time_seconds) / 3600 return total_time_hours -def __minimize_route_time_diff(routes: [Clue], starts: [Point], end: 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 @@ -187,8 +213,14 @@ def __minimize_route_time_diff(routes: [Clue], starts: [Point], end: Point, time times = [] for i, route in enumerate(routes): - times.append(__get_trip_time(__clues_to_string(route), len(route), __clues_to_string([starts[i]]), - __clues_to_string([end])[:-1])) + times.append( + __get_trip_time( + __clues_to_string(route), + len(route), + __clues_to_string([starts[i]]), + __clues_to_string([end])[:-1], + ) + ) # Find the average trip time average_time = np.mean(times) @@ -202,8 +234,9 @@ def __minimize_route_time_diff(routes: [Clue], starts: [Point], end: Point, time # 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]])) + closest_coordinate = __find_closest_coordinate( + routes[sorted_indices[-1]], __mean_center(routes[sorted_indices[0]]) + ) routes[sorted_indices[0]].append(closest_coordinate) routes[sorted_indices[-1]].remove(closest_coordinate) @@ -214,7 +247,9 @@ def __minimize_route_time_diff(routes: [Clue], starts: [Point], end: Point, time return routes -def __remove_longest_waypoints(route_coordinates: [Clue], start: Bike, end: 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 @@ -224,8 +259,12 @@ def __remove_longest_waypoints(route_coordinates: [Clue], start: Bike, end: Poin :return: a list of coordinates """ # Find the trip time for the route - route_time = __get_trip_time(__clues_to_string(route_coordinates), len(route_coordinates), - __clues_to_string([start]), __clues_to_string([end])[:-1]) + route_time = __get_trip_time( + __clues_to_string(route_coordinates), + len(route_coordinates), + __clues_to_string([start]), + __clues_to_string([end])[:-1], + ) # 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: @@ -263,10 +302,18 @@ def __normalize_points(clues: [Clue], bikes: [Bike]): for i in clues: normalized_coordinates.append( - [__min_max_normalize(i.latitude, min_lat, max_lat), __min_max_normalize(i.longitude, min_lon, max_lon)]) + [ + __min_max_normalize(i.latitude, min_lat, max_lat), + __min_max_normalize(i.longitude, min_lon, max_lon), + ] + ) for i in bikes: normalized_centroids.append( - [__min_max_normalize(i.latitude, min_lat, max_lat), __min_max_normalize(i.longitude, min_lon, max_lon)]) + [ + __min_max_normalize(i.latitude, min_lat, max_lat), + __min_max_normalize(i.longitude, min_lon, max_lon), + ] + ) return normalized_coordinates, normalized_centroids @@ -325,8 +372,10 @@ def __mean_center(clues: [Clue]): :param clues: the list of coordinates :return: the mean center of the coordinates """ - return Point(np.mean([coordinate.latitude for coordinate in clues]), - np.mean([coordinate.longitude 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: Clue, coordinate2: Point): @@ -336,5 +385,7 @@ def __distance(coordinate1: Clue, coordinate2: Point): :param coordinate2: the second coordinate :return: the distance between the two coordinates """ - return ((coordinate1.latitude - coordinate2.latitude) ** 2 + ( - coordinate1.longitude - coordinate2.longitude) ** 2) ** 0.5 + return ( + (coordinate1.latitude - coordinate2.latitude) ** 2 + + (coordinate1.longitude - coordinate2.longitude) ** 2 + ) ** 0.5 |
