# stores and manages clue DB # also manages currently available bike teams import csv from threading import Thread import router from datastructs import * # constants CLUE_MIN_DISTANCE = 0.1 # minimum distance between clue and bike to be considered valid visitation # variables homeBase = Point(42.340226, -71.088395) # krentzman, can be changed on dashboard clues = [] bikes = [] clusters = [] routes = {"clusters": [], "cluster_times": {}, "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() currently_updating = False startup = False # # called every time a node is added # 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, routes print("Calculating clusters...") routes = {"clusters": [], "cluster_times": {}, "individual_routes": []} # reset clusters, paths, times = router.getClusters(bikes, clues, homeBase) routes['individual_routes'] = paths.copy() routes['cluster_times'] = times routes['clusters'] = paths for i in range(len(bikes)): if bikes[i].status == "ACTIVE": routes['individual_routes'][i] = router.getRouteHDPolyline(bikes[i], clusters[i][0]) routes_last_changed = time.time() print("Finished calculating clusters/routes.") currently_updating = False def updateRoutes(): # if necessary global currently_updating if not currently_updating: save() currently_updating = True t = Thread(target=updateRoutes_background) t.start() # interface functions def getTime(): return time.time() def reassignIndividualRoute(bike, route_json): global routes_last_changed for i, b in enumerate(bikes): if b.name == bike.name: routes['individual_routes'][i] = route_json['routes'][0]['geometry']['coordinates'] routes_last_changed = time.time() return def getHomeBaseJSON(timestamp): if timestamp < 0 or home_last_changed - timestamp > 0: return homeBase.toJSON() return False def setHomeBase(latitude, longitude): homeBase.setCoords(latitude, longitude) home_last_changed = time.time() def getBikeCluePair(team_name): b = None; c = None for bike in bikes: if bike.name == team_name: b = bike for clue in clues: if clue.name == b.target_name: c = clue break break return b, c def getRoutesJSON(timestamp): if timestamp < 0 or routes_last_changed - timestamp > 0: return routes return False def deleteBike(team_name): for bike in bikes: if bike.name == team_name: # already exists bike.disable() updateRoutes() return 0 # OK return 4 # bike does not exist def addBike(team_name, latitude, longitude): for bike in bikes: if bike.name == team_name: # already exists return 4 # already exists newBike = Bike(latitude, longitude, team_name, "ACTIVE") bikes.append(newBike) updateRoutes() return 0 def pingBike(team_name, latitude, longitude): for bike in bikes: if bike.name == team_name: bike.ping() bike.setCoords(latitude, longitude) return 0 return 4 # team not found def getBikesJSON(): global bikes old_length = len(bikes) bikes = [x for x in bikes if x.checkStatus() >= 0] if old_length != len(bikes): updateRoutes() return [x.toJSON() for x in bikes] def getCluesJSON(timestamp): if timestamp < 0 or clues_last_changed - timestamp > 0: return [x.toJSON() for x in clues] return False def addClue(clue_name, clue_info, longitude, latitude, visited="UNVISITED"): newClue = Clue(latitude, longitude, clue_name, clue_info, visited) clues.append(newClue) clues_last_changed = time.time() def deleteClue(clue_name): for i, clue in enumereate(clues): if clue.name == clue_name: clues.pop(i) clues_last_changed = time.time() updateRoutes() break def visitClue(clue_name): global clues_last_changed for clue in clues: if clue.name == clue_name: if clue.status == "VISITED": return 3 # already visited clue.visit() clues_last_changed = time.time() updateRoutes() return 0 # OK return 2 # no clue found def visitClueTeam(team_name, clue_name): global clues_last_changed b = None for bike in bikes: if bike.name == team_name: b = bike break else: return 4 # team not found c = None for clue in clues: if clue.name == clue_name: c = clue if c.status == "VISITED": return 6 # already visited break # continue else: return 5 # clue not found # if visited clue is the expected one (current target) # no need to recalculate if clue_name == b.target_name: if c.distanceTo(b) < CLUE_MIN_DISTANCE: return 3 # too far away b.visitTarget() clues_last_changed = time.time() return 0 # otherwise c.visit() clues_last_changed = time.time() updateRoutes() # must be updated due to unexpected visitation def load(filename=None): """ Loads all clues from a CSV file :param filename: name of CSV file :return: None """ # if there is no filename, wipe all clues if filename == None: clues.clear() filename = "savefile.csv" # otherwise, load from file with open(filename, newline='') as f: csvreader = csv.reader(f, delimiter=',', quotechar='"') # skip header row next(csvreader) for row in csvreader: try: name = row[0] latitude = float(row[1]) longitude = float(row[2]) info = row[3] status = row[4] clues.append(Clue(latitude, longitude, name, info, status)) except: return 1 clues_last_changed = time.time() updateRoutes() return 0 def loadDirty(filename=None): global clues_last_changed if filename == None: return # otherwise, load from file with open(filename, newline='') as f: csvreader = csv.reader(f, delimiter=',', quotechar='"') # skip header row next(csvreader) for row in csvreader: try: name = row[0] info = row[2] latlong = row[3].split(",") if len(latlong) != 2: continue latitude = float(latlong[0]) longitude = float(latlong[1]) clues.append(Clue(latitude, longitude, name, info, "UNVISITED")) except: return 1 clues_last_changed = time.time() updateRoutes() return 0 def save(): """ Writes all clues to a csv file :return: a CSV file with all clues """ with open("savefile.csv", 'w', newline='') as f: csvwriter = csv.writer(f, delimiter=',', quotechar='"') # add a header row csvwriter.writerow(["name", "latitude", "longitude", "info", "status"]) for clue in clues: csvwriter.writerow([clue.name, clue.latitude, clue.longitude, clue.info, clue.status]) load()