summaryrefslogtreecommitdiff
path: root/dashboard_website/router.py
diff options
context:
space:
mode:
authorAnson Bridges <bridges.anson@gmail.com>2024-11-12 20:44:04 -0500
committerAnson Bridges <bridges.anson@gmail.com>2024-11-12 20:44:04 -0500
commit7bfcb40689ed1a9e49e9f6df1777c7b9801706e5 (patch)
tree9b5bd4a825487e110fd06478aa3574617031ec4d /dashboard_website/router.py
parentfeb047071ab7f7a688d2f3925d07d757b4b37d7d (diff)
workington in progress
Diffstat (limited to 'dashboard_website/router.py')
-rw-r--r--dashboard_website/router.py161
1 files changed, 103 insertions, 58 deletions
diff --git a/dashboard_website/router.py b/dashboard_website/router.py
index e0e0720..4840c8d 100644
--- a/dashboard_website/router.py
+++ b/dashboard_website/router.py
@@ -5,10 +5,11 @@ import requests
from sklearn.cluster import KMeans
from datastructs import *
+import db
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(2024, 11, 16, hour=18, minute=45) # 11/18/2023 6:35pm
# external facing functions
@@ -42,38 +43,36 @@ def getRouteFastPolyline(bike, clue):
return p
-def getZSP(bike, home, clue_cluster):
- pass
-
-
# determines clusters based on current bikes and clues
-def getClusters(bikes, clues, endpoint):
+def getClusters(bikes, clues, endpoint, minimal=True):
+ status = 0
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"]
+ active_indices = [i for i in range(len(bikes)) if bikes[i].status != "DISABLED"]
+ active_bikes = [bike for bike in bikes if bike.status != "DISABLED"]
if len(active_bikes) == 0:
- return clusters, route_geos, times
- active_clues = [clue for clue in clues if clue.status == "UNVISITED"]
+ return status, clusters, route_geos, times
+ active_clues = [ clue for clue in clues if (clue.status != "VISITED" and clue.status != "DISABLED") ]
# select only active bikes
# select only unvisited clues
- clusters_t, route_geos_t, times_t = cluster_and_optimize(
- active_clues, active_bikes, endpoint
+ status_c, clusters_t, route_geos_t, times_t = cluster_and_optimize(
+ active_clues, bikes, endpoint, minimal
)
- for i in range(len(active_indices)):
- route_geos[active_indices[i]] = route_geos_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]
+ if status_c != 0: # routing canceled
+ return -1, [], [], []
+ for i in range(len(bikes)):
+ route_geos[i] = route_geos_t[i]
+ clusters[i] = clusters_t[i]
+ times[i] = times_t[i]
# return list of clue clusters corresponding to bikes
- return clusters, route_geos, times
+ return status, clusters, route_geos, times
# utility functions (internal)
def cluster_and_optimize(
- clues: [Clue], bikes: [Bike], end: Point, time_diff=0.25, max_time=24, n=2
+ clues: [Clue], bikes: [Bike], end: Point, minimal : bool, time_diff=0.25, max_time=24
):
"""
Takes a dataframe of gps coordinates, a list of centroids, and an end point and returns a dataframe with a cluster
@@ -85,25 +84,43 @@ def cluster_and_optimize(
:param n: the number of routes to create
:return: a list of lists of clues (ordered by position on route), and a list of json route geojsons
"""
+ status = 0
# 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
- if len(bikes) > 1:
- # Create a new column with normalized gps coordinates and centroids
- normalized_points, norm_centroids = __normalize_points(clues, bikes)
- # Cluster the coordinates
- kmeans = KMeans(n_clusters=len(norm_centroids), init=norm_centroids)
- kmeans.fit(normalized_points)
-
- # Split the clues into clusters based on the cluster labels
- routes = [[] for i in range(len(norm_centroids))]
- for i, label in enumerate(kmeans.labels_):
- routes[label].append(clues[i])
-
- routes = __minimize_route_time_diff(routes, bikes, end, time_diff, n)
+
+ # Create a new column with normalized gps coordinates and centroids
+ active_indices = [i for i in range(len(bikes)) if bikes[i].status != "DISABLED"]
+ active_bikes = [bike for bike in bikes if bike.status != "DISABLED"]
+
+ normalized_points, norm_centroids = __normalize_points(clues, active_bikes)
+ # Cluster the coordinates
+ kmeans = KMeans(n_clusters=len(norm_centroids), init=norm_centroids)
+ kmeans.fit(normalized_points)
+
+ # assign pre-determined clues to bikes
+ already_assigned_clues = []
+ routes = [[] for i in bikes]
+ for clue in clues:
+ if clue.assigned_team != 0 and ((clue.assigned_team-1) in active_indices):
+ routes[clue.assigned_team - 1].append(clue)
+ already_assigned_clues.append(clue)
+
+ # if we are not doing a hard reset, keep the current target clues
+ if minimal:
+ for i, bike in enumerate(bikes):
+ if (bike.status != "DISABLED") and (bike.target_clue != None) and (bike.target_clue not in routes[i]):
+ routes[i].append(bike.target_clue)
+ already_assigned_clues.append(clue)
+
+ # Split the remaining clues into clusters based on the cluster labels
+ for i, label in enumerate(kmeans.labels_):
+ if clues[i].assigned_team == 0 and (clues[i] not in already_assigned_clues):
+ routes[active_indices[label]].append(clues[i])
+
+ routes = __minimize_route_time_diff(routes, bikes, end, minimal, time_diff)
+ if routes == -1:
+ return -1, [], [], []
# Remove waypoints from the longest route until the trip time is less than the max time
for i in range(len(routes)):
@@ -116,7 +133,7 @@ def cluster_and_optimize(
for i, route in enumerate(routes):
route_json = __get_json(
__clues_to_string(route),
- __clues_to_string([bikes[i]]),
+ __clues_to_string([bikes[i].target_clue if (minimal and (bikes[i].target_clue != None)) else bikes[i]]),
__clues_to_string([end])[:-1],
)
geometries.append(route_json["trips"][0]["geometry"]["coordinates"])
@@ -126,13 +143,15 @@ def cluster_and_optimize(
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]):
+ start_index = 0 if (minimal and (bikes[i].target_clue != None)) else 1 # if starting route with first clue, first index of trip must be included
+ for j, k in enumerate(route_waypoints[i][start_index:-1]):
route2[k["waypoint_index"] - 1] = route[j]
routes[i] = route2
- return routes, geometries, times
+ return status, routes, geometries, times
def __clues_to_string(points: [Clue]):
@@ -199,7 +218,7 @@ def __get_trip_time(
def __minimize_route_time_diff(
- routes: [Clue], starts: [Point], end: Point, time_diff, n
+ routes: [Clue], bikes: [Bike], end: Point, minimal: bool, time_diff
):
"""
Takes a list of lists of coordinates, a list of start points, an end point, a time difference, and a number of routes
@@ -210,17 +229,25 @@ def __minimize_route_time_diff(
:param n: the number of routes
:return: a list of lists of coordinates
"""
- times = []
+
+ if db.should_cancel_routing(): return -1
+
+ active_indices = [i for i in range(len(bikes)) if bikes[i].status != "DISABLED"]
+ starts = [ bike.target_clue if (minimal and (bike.target_clue != None)) else bike for bike in bikes ] # all bikes regardless of enabled
+ max_times = [ bike.timeTilDeadline()/3600 for bike in bikes if bike.status != "DISABLED"]
+ times = [ ]
for i, route in enumerate(routes):
+ if bikes[i].status == "DISABLED": continue
times.append(
__get_trip_time(
__clues_to_string(route),
len(route),
__clues_to_string([starts[i]]),
__clues_to_string([end])[:-1],
- )
+ ) * bikes[i].time_modifier
)
+ if db.should_cancel_routing(): return -1
# Find the average trip time
average_time = np.mean(times)
@@ -237,18 +264,18 @@ def __minimize_route_time_diff(
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)
+ 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, starts, end, time_diff, n)
+ return __minimize_route_time_diff(routes, bikes, end, minimal, time_diff)
# If the difference of the longest trip time from average is less than the time difference, return the routes
return routes
def __remove_longest_waypoints(
- route_coordinates: [Clue], start: Bike, end: Point, max_time
+ route_coordinates: [Clue], start: Bike, end: Point, max_time, start_already_set=False
):
"""
Takes a list of coordinates, a start point, an end point, and a maximum time and returns a list of coordinates
@@ -258,6 +285,11 @@ def __remove_longest_waypoints(
:param max_time: the maximum time
:return: a list of coordinates
"""
+ if len(route_coordinates) < 1:
+ return []
+
+ if start.status != "DISABLED" and start.target_clue != None: # already assigned a clue
+ start = start.target_clue
# Find the trip time for the route
route_time = __get_trip_time(
__clues_to_string(route_coordinates),
@@ -269,10 +301,11 @@ 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:
route_mean = __mean_center(route_coordinates)
- furthest_coordinate = __find_farthest_coordinate(route_coordinates, route_mean)
- route_coordinates.remove(furthest_coordinate)
+ farthest_coordinates = __find_farthest_coordinates(route_coordinates, route_mean)
+ for farthest_coordinate in farthest_coordinates:
+ route_coordinates.remove(farthest_coordinate)
- return __remove_longest_waypoints(route_coordinates, start, end, max_time)
+ return __remove_longest_waypoints(route_coordinates, start, end, max_time, start_already_set=True)
return route_coordinates
@@ -348,22 +381,34 @@ def __find_closest_coordinate(clues: [Clue], centroid: Point):
return closest_coordinate
-def __find_farthest_coordinate(clues: [Clue], centroid: Point):
+def __find_farthest_coordinates(clues: [Clue], centroid: Point, n:int=1):
"""
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
:param centroid: the centroid
:return: the clue in the list that is farthest from the centroid
"""
- farthest_coordinate = clues[0]
- farthest_coordinate_distance = __distance(farthest_coordinate, centroid)
-
- for clue in clues:
- if __distance(clue, centroid) > farthest_coordinate_distance:
- farthest_coordinate = clue
- farthest_coordinate_distance = __distance(clue, centroid)
-
- return farthest_coordinate
+
+ farthest_coordinates = []
+
+ for _ in range(n):
+ farthest_coordinate = clues[0]
+ # remove only unrequired clues
+ i = 1
+ while farthest_coordinate.required or (farthest_coordinate in farthest_coordinates):
+ farthest_coordinate = clues[i]
+ i += 1
+
+ farthest_coordinate_distance = __distance(farthest_coordinate, centroid)
+
+ for clue in clues:
+ if __distance(clue, centroid) > farthest_coordinate_distance and (not clue.required) and (clue not in farthest_coordinates):
+ farthest_coordinate = clue
+ farthest_coordinate_distance = __distance(clue, centroid)
+
+ farthest_coordinates.append(farthest_coordinate)
+
+ return farthest_coordinates
def __mean_center(clues: [Clue]):