def get_notes(self): """flight notifications list""" from pilot.notification import Notification if self.flight: self.notifications = [Notification(notification_type='track', comment=i) for i in self.flight.notes] else: return []
def evaluate_start(result: FlightResult, task: Task, tp: FlightPointer): from pilot.notification import Notification max_jump_the_gun = task.formula.max_JTG or 0 # seconds jtg_penalty_per_sec = 0 if max_jump_the_gun == 0 else task.formula.JTG_penalty_per_sec if tp.start_done: ''' start time if race, the first times if multistart, the first time of the last gate pilot made if elapsed time, the time of last fix on start SS Time: the gate time''' result.SSS_time = task.start_time if task.task_type == 'RACE' and task.SS_interval: result.SSS_time += max( 0, (start_number_at_time(task, result.real_start_time) - 1) * task.SS_interval) elif task.task_type == 'ELAPSED TIME': result.SSS_time = result.real_start_time '''manage jump the gun''' if max_jump_the_gun > 0 and result.real_start_time < result.SSS_time: diff = result.SSS_time - result.real_start_time penalty = diff * jtg_penalty_per_sec # check comment = f"Jump the gun: {diff} seconds. Penalty: {penalty} points" result.notifications.append( Notification(notification_type='jtg', flat_penalty=penalty, comment=comment))
def get_infringements_result(self, infringements_list): """ Airspace Warnings and Penalties Managing Creates a list of worst infringement for each airspace in infringements_list Calculates penalty Calculates final penalty and comments """ from pilot.notification import Notification '''element: [next_fix, airspace_name, infringement_type, distance, penalty]''' spaces = list(set([x[2] for x in infringements_list])) penalty = 0 max_pen_fix = None infringements_per_space = [] comments = [] notifications = [] '''check distance and penalty for each space in which we recorded an infringement''' for space in spaces: fixes = [fix for fix in infringements_list if fix[2] == space] pen = max(x[5] for x in fixes) fix = min([x for x in fixes if x[5] == pen], key=lambda x: x[4]) dist = fix[4] rawtime = fix[0].rawtime lat = fix[0].lat lon = fix[0].lon alt = fix[1] separation = fix[6] if pen == 0: ''' create warning comment''' comment = f"[{space}] Warning: {separation}. separation less than {dist} meters" else: '''add fix to infringements''' infringements_per_space.append( dict(rawtime=rawtime, lat=lat, lon=lon, alt=alt, space=space, distance=dist, penalty=pen, separation=separation)) if fix[3] == 'full penalty': comment = f"[{space}]: airspace infringement. penalty {round(pen * 100)}%" else: comment = f"[{space}]: {round(dist)}m from limit. penalty {round(pen * 100)}%" if pen > penalty: penalty = pen max_pen_fix = fix notifications.append( Notification(notification_type='airspace', percentage_penalty=pen, comment=comment)) notifications = sorted(notifications, key=lambda x: x.percentage_penalty, reverse=True) return infringements_per_space, notifications, penalty
def from_dict(d: dict): result = FlightResult() for key, value in d.items(): if key == 'notifications' and value: for n in value: result.notifications.append(Notification.from_dict(n)) elif key == 'waypoints_achieved' and value: for n in value: result.waypoints_achieved.append(WaypointAchieved.from_dict(n)) elif hasattr(result, key): setattr(result, key, value) return result
def get_task_results(task_id: int): from db.tables import FlightResultView as F, TblNotification as N, TblTrackWaypoint as W from pilot.notification import Notification pilots = [] with db_session() as db: results = db.query(F).filter_by(task_id=task_id).all() notifications = db.query(N).filter(N.track_id.in_([p.track_id for p in results])).all() achieved = db.query(W).filter(W.track_id.in_([p.track_id for p in results])).all() for row in results: pilot = FlightResult() row.populate(pilot) for el in [n for n in notifications if n.track_id == pilot.track_id]: n = Notification() el.populate(n) pilot.notifications.append(n) for el in [{k: getattr(w, k) for k in ['trw_id', 'wpt_id', 'name', 'rawtime', 'lat', 'lon', 'altitude']} for w in achieved if w.track_id == pilot.track_id]: pilot.waypoints_achieved.append(WaypointAchieved(**el)) pilots.append(pilot) return pilots
def get_task_results(task_id: int): from db.tables import FlightResultView as F, TblNotification as N, TblTrackWaypoint as W, TblTaskResult as R from pilot.notification import Notification pilots = [] results = R.get_task_results(task_id) track_list = list(filter(None, map(lambda x: x.track_id, results))) notifications = N.from_track_list(track_list) achieved = W.get_dict_list(track_list) for row in results: p = FlightResult.from_dict(row._asdict()) if not row.result_type: p.result_type = 'nyp' for el in [n for n in notifications if n.track_id == p.track_id]: n = Notification() el.populate(n) p.notifications.append(n) if p.result_type in ('lo', 'goal'): wa = list(filter(lambda x: x['track_id'] == p.track_id, achieved)) for el in wa: p.waypoints_achieved.append(WaypointAchieved.from_dict(el)) pilots.append(p) return pilots
def from_fsdb(elem, task): """ Creates Results from FSDB FsParticipant element, which is in xml format. Unfortunately the fsdb format isn't published so much of this is simply an exercise in reverse engineering. """ from pilot.notification import Notification offset = task.time_offset dep = task.formula.formula_departure arr = task.formula.formula_arrival result = FlightResult() result.ID = int(elem.get('id')) if elem.find('FsFlightData') is None and elem.find('FsResult') is None: '''pilot is abs''' print(f"ID {result.ID}: ABS") result.result_type = 'abs' return result elif elem.find('FsFlightData') is None or elem.find( 'FsFlightData').get('tracklog_filename') in [None, '']: print(f"ID {result.ID}: No track") print( f" - distance: {float(elem.find('FsResult').get('distance'))}") if float(elem.find('FsResult').get('distance')) > 0: '''pilot is min dist''' print(f"ID {result.ID}: Min Dist") result.result_type = 'mindist' else: '''pilot is dnf''' print(f"ID {result.ID}: DNF") result.result_type = 'dnf' return result if elem.find('FsFlightData') is not None: result.track_file = elem.find('FsFlightData').get( 'tracklog_filename') d = elem.find('FsFlightData') result.real_start_time = None if not d.get( 'started_ss') else string_to_seconds(d.get('started_ss')) - offset result.last_altitude = int( d.get('last_tracklog_point_alt') if d.get('last_tracklog_point_alt' ) is not None else 0) result.max_altitude = int( d.get('max_alt') if d.get('max_alt') is not None else 0) result.track_file = d.get('tracklog_filename') result.lead_coeff = None if d.get('lc') is None else float(d.get('lc')) if not d.get('finished_ss') == "": result.ESS_altitude = int( d.get('altitude_at_ess') if d.get('altitude_at_ess' ) is not None else 0) if d.get('reachedGoal') == "1": result.goal_time = (None if not d.get('finished_task') else string_to_seconds(d.get('finished_task')) - offset) result.result_type = 'goal' if elem.find('FsResult') is not None: '''reading flight data''' r = elem.find('FsResult') # result['rank'] = int(r.get('rank')) result.score = float(r.get('points')) result.total_distance = float( r.get('distance')) * 1000 # in meters result.distance_flown = float( r.get('real_distance')) * 1000 # in meters # print ("start_ss: {}".format(r.get('started_ss'))) result.SSS_time = None if not r.get( 'started_ss') else string_to_seconds( r.get('started_ss')) - offset if result.SSS_time is not None: result.ESS_time = (None if not r.get('finished_ss') else string_to_seconds(r.get('finished_ss')) - offset) if task.SS_distance is not None and result.ESS_time is not None and result.ESS_time > 0: result.speed = (task.SS_distance / 1000) / ( (result.ESS_time - result.SSS_time) / 3600) else: result.ESS_time = None result.last_altitude = int(r.get('last_altitude_above_goal')) result.distance_score = float(r.get('distance_points')) result.time_score = float(r.get('time_points')) result.penalty = 0 # fsdb score is already decreased by penalties if not r.get('penalty_reason') == "": notification = Notification() if r.get('penalty') != "0": notification.percentage_penalty = float(r.get('penalty')) else: notification.flat_penalty = float(r.get('penalty_points')) notification.comment = r.get('penalty_reason') result.notifications.append(notification) if not r.get('penalty_reason_auto') == "": notification = Notification( notification_type='jtg', flat_penalty=r.get('penalty_points_auto'), comment=r.get('penalty_reason_auto')) result.notifications.append(notification) if dep == 'on': result.departure_score = float(r.get('departure_points')) elif dep == 'leadout': result.departure_score = float(r.get('leading_points')) else: result.departure_score = 0 # not necessary as it it initialized to 0 result.arrival_score = float( r.get('arrival_points')) if arr != 'off' else 0 return result
def check_flight(self, flight, task, airspace_obj=None, deadline=None, print=print): """ Checks a Flight object against the task. Args: :param flight: a Flight object :param task: a Task :param airspace_obj: airspace object to check flight against :param deadline: in multiple start or elapsed time, I need to check again track using Min_flight_time as deadline :param print: function to overide print() function. defaults to print() i.e. no override. Intended for sending progress to front end Returns: a list of GNSSFixes of when turnpoints were achieved. """ from .flightpointer import FlightPointer ''' Altitude Source: ''' alt_source = 'GPS' if task.formula.scoring_altitude is None else task.formula.scoring_altitude alt_compensation = 0 if alt_source == 'GPS' or task.QNH == 1013.25 else task.alt_compensation '''initialize''' if not self.result_type == 'nyp': self.reset() self.result_type = 'lo' tolerance = task.formula.tolerance or 0 min_tol_m = task.formula.min_tolerance or 0 max_jump_the_gun = task.formula.max_JTG or 0 # seconds jtg_penalty_per_sec = 0 if max_jump_the_gun == 0 else task.formula.JTG_penalty_per_sec max_altitude = 0 percentage_complete = 0 if not task.optimised_turnpoints: # this should not happen task.calculate_optimised_task_length() distances2go = task.distances_to_go # Total task Opt. Distance, in legs list '''leadout coefficient''' if task.formula.formula_departure == 'leadout': lead_coeff = LeadCoeff(task) else: lead_coeff = None '''flight origin''' self.first_time = flight.fixes[0].rawtime if not hasattr(flight, 'takeoff_fix') else flight.takeoff_fix.rawtime '''flight end''' self.landing_time = flight.landing_fix.rawtime self.landing_altitude = (flight.landing_fix.gnss_alt if alt_source == 'GPS' else flight.landing_fix.press_alt + alt_compensation) '''Stopped task managing''' if task.stopped_time: if not deadline: '''Using stop_time (stopped_time - score_back_time)''' deadline = task.stop_time goal_altitude = task.goal_altitude or 0 glide_ratio = task.formula.glide_bonus or 0 stopped_distance = 0 stopped_altitude = 0 total_distance = 0 '''Turnpoint managing''' tp = FlightPointer(task) '''Airspace check managing''' airspace_plot = [] infringements_list = [] airspace_penalty = 0 if task.airspace_check: if not airspace_obj and not deadline: print(f'We should not create airspace here') airspace_obj = AirspaceCheck.from_task(task) total_fixes = len(flight.fixes) for i in range(total_fixes - 1): # report percentage progress if int(i / len(flight.fixes) * 100) > percentage_complete: percentage_complete = int(i / len(flight.fixes) * 100) print(f"{percentage_complete}|% complete") '''Get two consecutive trackpoints as needed to use FAI / CIVL rules logic ''' # start_time = tt.time() my_fix = flight.fixes[i] next_fix = flight.fixes[i + 1] alt = next_fix.gnss_alt if alt_source == 'GPS' else next_fix.press_alt + alt_compensation if alt > max_altitude: max_altitude = alt '''pilot flying''' if next_fix.rawtime < self.first_time: continue if self.landing_time and next_fix.rawtime > self.landing_time: '''pilot landed out''' # print(f'fix {i}: landed out - {next_fix.rawtime} - {alt}') break '''handle stopped task Pilots who were at a position between ESS and goal at the task stop time will be scored for their complete flight, including the portion flown after the task stop time. This is to remove any discontinuity between pilots just before goal and pilots who had just reached goal at task stop time. ''' if task.stopped_time and next_fix.rawtime > deadline and not tp.ess_done: self.still_flying_at_deadline = True break '''check if task deadline has passed''' if task.task_deadline < next_fix.rawtime: # Task has ended self.still_flying_at_deadline = True break '''check if pilot has arrived in goal (last turnpoint) so we can stop.''' if tp.made_all: break '''check if start closing time passed and pilot did not start''' if task.start_close_time and task.start_close_time < my_fix.rawtime and not tp.start_done: # start closed break '''check tp type is known''' if tp.next.type not in ('launch', 'speed', 'waypoint', 'endspeed', 'goal'): assert False, f"Unknown turnpoint type: {tp.type}" '''check window is open''' if task.window_open_time > next_fix.rawtime: continue '''launch turnpoint managing''' if tp.type == "launch": if task.check_launch == 'on': # Set radius to check to 200m (in the task def it will be 0) # could set this in the DB or even formula if needed..??? tp.next.radius = 200 # meters if tp.next.in_radius(my_fix, tolerance, min_tol_m): self.waypoints_achieved.append(create_waypoint_achieved(my_fix, tp, my_fix.rawtime, alt)) tp.move_to_next() else: tp.move_to_next() # to do check for restarts for elapsed time tasks and those that allow jump the gun # if started and task.task_type != 'race' or result.jump_the_gun is not None: '''start turnpoint managing''' '''given all n crossings for a turnpoint cylinder, sorted in ascending order by their crossing time, the time when the cylinder was reached is determined. turnpoint[i] = SSS : reachingTime[i] = crossing[n].time turnpoint[i] =? SSS : reachingTime[i] = crossing[0].time We need to check start in 3 cases: - pilot has not started yet - race has multiple starts - task is elapsed time ''' if pilot_can_start(task, tp, my_fix): # print(f'time: {my_fix.rawtime}, start: {task.start_time} | Interval: {task.SS_interval} | my start: {self.real_start_time} | better_start: {pilot_get_better_start(task, my_fix.rawtime, self.SSS_time)} | can start: {pilot_can_start(task, tp, my_fix)} can restart: {pilot_can_restart(task, tp, my_fix, self)} | tp: {tp.name}') if start_made_civl(my_fix, next_fix, tp.next, tolerance, min_tol_m): time = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) self.waypoints_achieved.append( create_waypoint_achieved(my_fix, tp, time, alt)) # pilot has started self.real_start_time = time print(f"Pilot started SS at {sec_to_time(self.real_start_time)}") self.best_distance_time = time tp.move_to_next() elif pilot_can_restart(task, tp, my_fix, self): # print(f'time: {my_fix.rawtime}, start: {task.start_time} | Interval: {task.SS_interval} | my start: {self.real_start_time} | better_start: {pilot_get_better_start(task, my_fix.rawtime, self.SSS_time)} | can start: {pilot_can_start(task, tp, my_fix)} can restart: {pilot_can_restart(task, tp, my_fix, self)} | tp: {tp.name}') if start_made_civl(my_fix, next_fix, tp.last_made, tolerance, min_tol_m): tp.pointer -= 1 time = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) self.waypoints_achieved.pop() self.waypoints_achieved.append( create_waypoint_achieved(my_fix, tp, time, alt)) # pilot has started again self.real_start_time = time self.best_distance_time = time print(f"Pilot restarted SS at {sec_to_time(self.real_start_time)}") if lead_coeff: lead_coeff.reset() tp.move_to_next() if tp.start_done: '''Turnpoint managing''' if (tp.next.shape == 'circle' and tp.next.type in ('endspeed', 'waypoint')): if tp_made_civl(my_fix, next_fix, tp.next, tolerance, min_tol_m): time = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) self.waypoints_achieved.append( create_waypoint_achieved(my_fix, tp, time, alt)) # pilot has achieved turnpoint print(f"Pilot took {tp.name} at {sec_to_time(time)} at {alt}m") tp.move_to_next() if tp.ess_done and tp.type == 'goal': if ((tp.next.shape == 'circle' and tp_made_civl(my_fix, next_fix, tp.next, tolerance, min_tol_m)) or (tp.next.shape == 'line' and (in_goal_sector(task, next_fix)))): self.waypoints_achieved.append( create_waypoint_achieved(next_fix, tp, next_fix.rawtime, alt)) # pilot has achieved goal self.best_distance_time = next_fix.rawtime print(f"Goal at {sec_to_time(next_fix.rawtime)}") break '''update result data Once launched, distance flown should be max result among: - previous value; - optimized dist. to last turnpoint made; - total optimized distance minus opt. distance from next wpt to goal minus dist. to next wpt; ''' if tp.pointer > 0: if tp.start_done and not tp.ess_done: '''optimized distance calculation each fix''' fix_dist_flown = task.opt_dist - get_shortest_path(task, next_fix, tp.pointer) # print(f'time: {next_fix.rawtime} | fix: {tp.name} | Optimized Distance used') else: '''simplified and faster distance calculation''' fix_dist_flown = distance_flown(next_fix, tp.pointer, task.optimised_turnpoints, task.turnpoints[tp.pointer], distances2go) # print(f'time: {next_fix.rawtime} | fix: {tp.name} | Simplified Distance used') if fix_dist_flown > self.distance_flown: '''time of trackpoint with shortest distance to ESS''' self.best_distance_time = next_fix.rawtime '''updating best distance flown''' # self.distance_flown = max(fix_dist_flown, # task.partial_distance[tp.last_made_index]) # old approach self.distance_flown = fix_dist_flown '''stopped task ∀p : p ∈ PilotsLandedBeforeGoal : bestDistance p = max(minimumDistance, taskDistance − min(∀trackp.pointi : shortestDistanceToGoal(trackp.pointi )−(trackp .pointi .altitude−GoalAltitude)*GlideRatio)) ∀p :p ∈ PilotsReachedGoal : bestDistance p = taskDistance ''' if task.stopped_time and glide_ratio and total_distance < task.opt_dist: alt_over_goal = max(0, alt - goal_altitude) if fix_dist_flown + glide_ratio * alt_over_goal > total_distance: '''calculate total distance with glide bonus''' stopped_distance = fix_dist_flown stopped_altitude = alt total_distance = min(fix_dist_flown + glide_ratio * alt_over_goal, task.opt_dist) '''Leading coefficient LC = taskTime(i)*(bestDistToESS(i-1)^2 - bestDistToESS(i)^2 ) i : i ? TrackPoints In SS''' if lead_coeff and tp.start_done and not tp.ess_done: lead_coeff.update(self, my_fix, next_fix) '''Airspace Check''' if task.airspace_check and airspace_obj: # map_fix = [next_fix.rawtime, next_fix.lat, next_fix.lon, alt] plot, penalty = airspace_obj.check_fix(next_fix, alt) if plot: # map_fix.extend(plot) '''Airspace Infringement: check if we already have a worse one''' airspace_name = plot[2] infringement_type = plot[3] dist = plot[4] separation = plot[5] infringements_list.append([next_fix, alt, airspace_name, infringement_type, dist, penalty, separation]) # print([next_fix, alt, airspace_name, infringement_type, dist, penalty]) else: '''''' # map_fix.extend([None, None, None, None, None]) # airspace_plot.append(map_fix) '''final results''' print("100|% complete") self.max_altitude = max_altitude self.last_altitude = 0 if 'alt' not in locals() else alt self.last_time = 0 if 'next_fix' not in locals() else next_fix.rawtime '''manage stopped tasks''' if task.stopped_time and self.still_flying_at_deadline: self.stopped_distance = stopped_distance self.stopped_altitude = stopped_altitude self.total_distance = total_distance if tp.start_done: ''' start time if race, the first times if multistart, the first time of the last gate pilot made if elapsed time, the time of last fix on start SS Time: the gate time''' self.SSS_time = task.start_time if task.task_type == 'RACE' and task.SS_interval: self.SSS_time += max(0, (start_number_at_time(task, self.real_start_time) - 1) * task.SS_interval) elif task.task_type == 'ELAPSED TIME': self.SSS_time = self.real_start_time '''manage jump the gun''' # print(f'wayponts made: {self.waypoints_achieved}') if max_jump_the_gun > 0 and self.real_start_time < self.SSS_time: diff = self.SSS_time - self.real_start_time penalty = diff * jtg_penalty_per_sec # check print(f'jump the gun: {diff} - valid: {diff <= max_jump_the_gun} - penalty: {penalty}') comment = f"Jump the gun: {diff} seconds. Penalty: {penalty} points" self.notifications.append(Notification(notification_type='jtg', flat_penalty=penalty, comment=comment)) '''ESS Time''' if any(e.name == 'ESS' for e in self.waypoints_achieved): # self.ESS_time, ess_altitude = min([e[1] for e in self.waypoints_achieved if e[0] == 'ESS']) self.ESS_time, self.ESS_altitude = min([(x.rawtime, x.altitude) for x in self.waypoints_achieved if x.name == 'ESS'], key=lambda t: t[0]) self.speed = (task.SS_distance / 1000) / (self.ss_time / 3600) '''Distance flown''' ''' ?p:p?PilotsLandingBeforeGoal:bestDistancep = max(minimumDistance, taskDistance-min(?trackp.pointi shortestDistanceToGoal(trackp.pointi))) ?p:p?PilotsReachingGoal:bestDistancep = taskDistance ''' if any(e.name == 'Goal' for e in self.waypoints_achieved): # self.distance_flown = distances2go[0] self.distance_flown = task.opt_dist self.goal_time, self.goal_altitude = min([(x.rawtime, x.altitude) for x in self.waypoints_achieved if x.name == 'Goal'], key=lambda t: t[0]) self.result_type = 'goal' if self.result_type != 'goal': print(f"Pilot landed after {self.distance_flown / 1000:.2f}km") self.best_waypoint_achieved = str(self.waypoints_achieved[-1].name) if self.waypoints_achieved else None if lead_coeff: self.fixed_LC = lead_coeff.summing if task.airspace_check: infringements, notifications, penalty = airspace_obj.get_infringements_result(infringements_list) self.infringements = infringements self.notifications.extend(notifications)
def check_livetrack(pilot, task, airspace_obj=None): """ Checks a Flight object against the task. Args: pilot: a Pilot object task: a Task object airspace_obj: a AirspaceCheck object Returns: a list of GNSSFixes of when turnpoints were achieved. """ from pilot.flightresult import Tp, pilot_can_start, pilot_can_restart, start_number_at_time from route import in_semicircle, start_made_civl, tp_made_civl, distance, \ tp_time_civl, get_shortest_path, distance_flown from airspace import AirspaceCheck from formulas.libs.leadcoeff import LeadCoeff from pilot.notification import Notification '''initialize''' tolerance = task.formula.tolerance or 0 min_tol_m = task.formula.min_tolerance or 0 max_jump_the_gun = task.formula.max_JTG or 0 # seconds jtg_penalty_per_sec = 0 if max_jump_the_gun == 0 else task.formula.JTG_penalty_per_sec # if not task.optimised_turnpoints: # task.calculate_optimised_task_length() distances2go = task.distances_to_go # Total task Opt. Distance, in legs list result = pilot.result fixes = pilot.livetrack '''leadout coefficient''' if task.formula.formula_departure == 'leadout': lead_coeff = LeadCoeff(task) lead_coeff.summing = result.fixed_LC or 0.0 else: lead_coeff = None '''Turnpoint managing''' tp = Tp(task) tp.pointer = result.waypoints_made + 1 '''get if pilot already started in previous track slices''' already_started = tp.start_done restarted = False '''get if pilot already made ESS in previous track slices''' already_ESS = any(e[0] == 'ESS' for e in result.waypoints_achieved) '''Airspace check managing''' infringements_list = [] if task.airspace_check: if task.airspace_check and not airspace_obj: print(f'We should not create airspace here') airspace_obj = AirspaceCheck.from_task(task) alt = 0 suspect_landing_fix = None next_fix = None for i in range(len(fixes) - 1): '''Get two consecutive trackpoints as needed to use FAI / CIVL rules logic ''' # start_time = tt.time() my_fix = fixes[i] next_fix = fixes[i + 1] result.last_time = next_fix.rawtime alt = next_fix.alt '''check coherence''' if next_fix.rawtime - my_fix.rawtime < 1: continue alt_rate = abs(next_fix.alt - my_fix.alt) / (next_fix.rawtime - my_fix.rawtime) if alt_rate > max_alt_rate or not (min_alt < alt < max_alt): continue '''check flying''' speed = next_fix.speed # km/h if not result.first_time: '''not launched yet''' launch = next(x for x in tp.turnpoints if x.type == 'launch') if distance(next_fix, launch) < 400: '''still on launch''' continue if abs(launch.altitude - alt) > min_alt_difference and speed > min_flight_speed: '''pilot launched''' result.first_time = next_fix.rawtime result.live_comment = 'flying' else: '''check if pilot landed''' if speed < min_flight_speed: if not suspect_landing_fix: suspect_landing_fix = next_fix # suspect_landing_alt = alt else: time_diff = next_fix.rawtime - suspect_landing_fix.rawtime alt_diff = abs(alt - suspect_landing_fix.alt) if time_diff > max_still_seconds and alt_diff < min_alt_difference: '''assuming pilot landed''' result.landing_time = next_fix.rawtime result.landing_altitude = alt result.live_comment = 'landed' break elif suspect_landing_fix is not None: suspect_landing_fix = None # if alt > result.max_altitude: # result.max_altitude = alt # '''handle stopped task''' # if task.stopped_time and next_fix.rawtime > deadline: # result.last_altitude = alt # check the rules on this point..which alt to # break '''check if pilot has arrived in goal (last turnpoint) so we can stop.''' if tp.made_all: break '''check if task deadline has passed''' if task.task_deadline < next_fix.rawtime: # Task has ended result.live_comment = 'flying past deadline' break '''check if start closing time passed and pilot did not start''' if task.start_close_time and task.start_close_time < my_fix.rawtime and not tp.start_done: # start closed result.live_comment = 'did not start before start closing time' break if result.landing_time and next_fix.rawtime > result.landing_time: '''pilot already landed''' # this should not happen as landed pilot are filtered break '''check tp type is known''' if tp.next.type not in ('launch', 'speed', 'waypoint', 'endspeed', 'goal'): assert False, f"Unknown turnpoint type: {tp.type}" '''check window is open''' if task.window_open_time > next_fix.rawtime: continue '''start turnpoint managing''' '''given all n crossings for a turnpoint cylinder, sorted in ascending order by their crossing time, the time when the cylinder was reached is determined. turnpoint[i] = SSS : reachingTime[i] = crossing[n].time turnpoint[i] =? SSS : reachingTime[i] = crossing[0].time We need to check start in 3 cases: - pilot has not started yet - race has multiple starts - task is elapsed time ''' if pilot_can_start(task, tp, my_fix): # print(f'time: {my_fix.rawtime}, start: {task.start_time} | Interval: {task.SS_interval} | my start: {result.real_start_time} | better_start: {pilot_get_better_start(task, my_fix.rawtime, result.SSS_time)} | can start: {pilot_can_start(task, tp, my_fix)} can restart: {pilot_can_restart(task, tp, my_fix, result)} | tp: {tp.name}') if start_made_civl(my_fix, next_fix, tp.next, tolerance, min_tol_m): t = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) result.waypoints_achieved.append([tp.name, t, alt]) # pilot has started result.real_start_time = t tp.move_to_next() elif pilot_can_restart(task, tp, my_fix, result): # print(f'time: {my_fix.rawtime}, start: {task.start_time} | Interval: {task.SS_interval} | my start: {result.real_start_time} | better_start: {pilot_get_better_start(task, my_fix.rawtime, result.SSS_time)} | can start: {pilot_can_start(task, tp, my_fix)} can restart: {pilot_can_restart(task, tp, my_fix, result)} | tp: {tp.name}') if start_made_civl(my_fix, next_fix, tp.last_made, tolerance, min_tol_m): tp.pointer -= 1 t = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) result.waypoints_achieved.pop() result.waypoints_achieved.append([tp.name, t, alt ]) # pilot has started again result.real_start_time = t if lead_coeff: lead_coeff.reset() tp.move_to_next() restarted = True if tp.start_done: '''Turnpoint managing''' if (tp.next.shape == 'circle' and tp.next.type in ('endspeed', 'waypoint')): if tp_made_civl(my_fix, next_fix, tp.next, tolerance, min_tol_m): t = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) result.waypoints_achieved.append( [tp.name, t, alt]) # pilot has achieved turnpoint tp.move_to_next() if tp.next.type == 'goal': if ((tp.next.shape == 'circle' and tp_made_civl( my_fix, next_fix, tp.next, tolerance, min_tol_m)) or (tp.next.shape == 'line' and (in_semicircle(task, task.turnpoints, tp.pointer, my_fix) or in_semicircle(task, task.turnpoints, tp.pointer, next_fix)))): result.waypoints_achieved.append( [tp.name, next_fix.rawtime, alt]) # pilot has achieved turnpoint break '''update result data Once launched, distance flown should be max result among: - previous value; - optimized dist. to last turnpoint made; - total optimized distance minus opt. distance from next wpt to goal minus dist. to next wpt; ''' if tp.pointer > 0: if tp.start_done and not tp.ess_done: '''optimized distance calculation each fix''' fix_dist_flown = task.opt_dist - get_shortest_path( task, next_fix, tp.pointer) # print(f'time: {next_fix.rawtime} | fix: {tp.name} | Optimized Distance used') else: '''simplified and faster distance calculation''' fix_dist_flown = distance_flown(next_fix, tp.pointer, task.optimised_turnpoints, task.turnpoints[tp.pointer], distances2go) # print(f'time: {next_fix.rawtime} | fix: {tp.name} | Simplified Distance used') result.distance_flown = max(result.distance_flown, fix_dist_flown) '''Leading coefficient LC = taskTime(i)*(bestDistToESS(i-1)^2 - bestDistToESS(i)^2 ) i : i ? TrackPoints In SS''' if lead_coeff and tp.start_done and not tp.ess_done: lead_coeff.update(result, my_fix, next_fix) '''Airspace Check''' if task.airspace_check and airspace_obj: map_fix = [next_fix.rawtime, next_fix.lat, next_fix.lon, alt] plot, penalty = airspace_obj.check_fix(next_fix, alt) if plot: map_fix.extend(plot) '''Airspace Infringement: check if we already have a worse one''' airspace_name = plot[2] infringement_type = plot[3] dist = plot[4] infringements_list.append([ next_fix, airspace_name, infringement_type, dist, penalty ]) else: map_fix.extend([None, None, None, None, None]) # airspace_plot.append(map_fix) '''final results''' result.last_altitude = alt result.height = 0 if not result.first_time or result.landing_time or not next_fix else next_fix.height # print(f'start indev: {tp.start_index}') # print(f'start done: {tp.start_done}') # print(f'pointer: {tp.pointer}') if tp.start_done: if not already_started or restarted: ''' start time if race, the first times if multistart, the first time of the last gate pilot made if elapsed time, the time of last fix on start SS Time: the gate time''' result.SSS_time = task.start_time if task.task_type == 'RACE' and task.SS_interval: result.SSS_time += max( 0, (start_number_at_time(task, result.real_start_time) - 1) * task.SS_interval) elif task.task_type == 'ELAPSED TIME': result.SSS_time = result.real_start_time '''manage jump the gun''' # print(f'wayponts made: {result.waypoints_achieved}') if max_jump_the_gun > 0: if result.real_start_time < result.SSS_time: diff = result.SSS_time - result.real_start_time penalty = diff * jtg_penalty_per_sec # check print( f'jump the gun: {diff} - valid: {diff <= max_jump_the_gun} - penalty: {penalty}' ) comment = f"Jump the gun: {diff} seconds. Penalty: {penalty} points" result.notifications.append( Notification(notification_type='jtg', flat_penalty=penalty, comment=comment)) '''ESS Time''' if any(e[0] == 'ESS' for e in result.waypoints_achieved) and not already_ESS: # result.ESS_time, ess_altitude = min([e[1] for e in result.waypoints_achieved if e[0] == 'ESS']) result.ESS_time, result.ESS_altitude = min( [(x[1], x[2]) for x in result.waypoints_achieved if x[0] == 'ESS'], key=lambda t: t[0]) result.speed = (task.SS_distance / 1000) / (result.ss_time / 3600) '''Distance flown''' ''' ?p:p?PilotsLandingBeforeGoal:bestDistancep = max(minimumDistance, taskDistance-min(?trackp.pointi shortestDistanceToGoal(trackp.pointi))) ?p:p?PilotsReachingGoal:bestDistancep = taskDistance ''' if any(e[0] == 'Goal' for e in result.waypoints_achieved): result.distance_flown = task.opt_dist result.goal_time, result.goal_altitude = min( [(x[1], x[2]) for x in result.waypoints_achieved if x[0] == 'Goal'], key=lambda t: t[0]) result.result_type = 'goal' result.live_comment = 'Goal!' result.best_waypoint_achieved = str( result.waypoints_achieved[-1] [0]) if result.waypoints_achieved else None if lead_coeff: result.fixed_LC = lead_coeff.summing if task.airspace_check: infringements, notifications, penalty = airspace_obj.get_infringements_result( infringements_list) result.notifications.extend(notifications) result.notifications = clear_notifications(task, result) return result