def calculate_final_results(result: FlightResult, task: Task, tp: FlightPointer, lead_coeff: LeadCoeff, airspace: AirspaceCheck = None, deadline: int = None, print=print): """""" '''final results''' print("100|% complete") if tp.start_done: evaluate_start(result, task, tp) '''ESS Time''' if tp.ess_done: evaluate_ess(result, task) if tp.made_all: evaluate_goal(result, task) if result.result_type != 'goal': print(f"Pilot landed after {result.distance_flown / 1000:.2f}km") result.best_waypoint_achieved = str( result.waypoints_achieved[-1].name ) if result.waypoints_achieved else None if lead_coeff: result.fixed_LC = lead_coeff.summing if task.airspace_check: infringements, notifications, penalty = airspace.get_infringements_result( result.infringements) result.infringements = infringements result.notifications.extend(notifications)
def process_igc(task_id: int, par_id: int, tracklog): from pilot.track import create_igc_filename, igc_parsing_config_from_yaml from calcUtils import epoch_to_date from airspace import AirspaceCheck from igc_lib import Flight from task import Task from pilot.flightresult import FlightResult, save_track pilot = FlightResult.read(par_id, task_id) if pilot.name: task = Task.read(task_id) fullname = create_igc_filename(task.file_path, task.date, pilot.name) tracklog.save(fullname) pilot.track_file = Path(fullname).name else: return None, None """import track""" # track = Track(track_file=fullname, par_id=pilot.par_id) FlightParsingConfig = igc_parsing_config_from_yaml(task.igc_config_file) flight = Flight.create_from_file(fullname, config_class=FlightParsingConfig) """check result""" if not flight: error = f"for {pilot.name} - Track is not a valid track file" return None, error elif not epoch_to_date(flight.date_timestamp) == task.date: error = f"for {pilot.name} - Track has a different date from task date" return None, error else: print( f"pilot {pilot.par_id} associated with track {pilot.track_file} \n" ) """checking track against task""" if task.airspace_check: airspace = AirspaceCheck.from_task(task) else: airspace = None pilot.check_flight(flight, task, airspace_obj=airspace) print(f"track verified with task {task.task_id}\n") '''create map file''' pilot.save_tracklog_map_file(task, flight) """adding track to db""" # pilot.to_db() save_track(pilot, task.id) time = '' data = {'par_id': pilot.par_id, 'track_id': pilot.track_id} if pilot.goal_time: time = sec_to_time(pilot.ss_time) if pilot.result_type == 'goal': data['Result'] = f'Goal {time}' elif pilot.result_type == 'lo': data['Result'] = f"LO {round(pilot.distance / 1000, 2)}" if pilot.track_id: # if there is a track, make the result a link to the map # trackid = data['track_id'] parid = data['par_id'] result = data['Result'] data[ 'Result'] = f'<a href="/map/{parid}-{task.task_id}">{result}</a>' return data, None
def create_tracklog_map_result_file(par_id: int, task_id: int): from pilot import flightresult from task import Task from igc_lib import Flight from airspace import AirspaceCheck task = Task.read(task_id) airspace = None if not task.airspace_check else AirspaceCheck.from_task(task) pilot = flightresult.FlightResult.read(par_id, task_id) file = Path(task.file_path, pilot.track_file) '''load track file''' flight = Flight.create_from_file(file) pilot.check_flight(flight, task, airspace) pilot.save_tracklog_map_file(task, flight)
def read(task_id): task = Task.read(task_id) if task.turnpoints: if not task.distance: task.calculate_task_length() if not task.optimised_turnpoints: task.calculate_optimised_task_length() test = task.date < datetime.today().date() task.get_pilots() airspace = AirspaceCheck.from_task(task) livetrack = LiveTracking(task, airspace, test) livetrack.create_result() return livetrack
def check_livetrack(result: FlightResult, task: Task, airspace: AirspaceCheck = None): """ Checks a Flight object against the task. Args: result: a FlightResult object task: a Task object airspace: a AirspaceCheck object Returns: a list of GNSSFixes of when turnpoints were achieved. """ from flightcheck.flightpointer import FlightPointer from flightcheck import flightcheck from formulas.libs.leadcoeff import LeadCoeff '''initialize''' fixes = result.livetrack '''Turnpoint managing''' tp = FlightPointer(task) tp.pointer = result.waypoints_made + 1 '''leadout coefficient''' if task.formula.formula_departure == 'leadout': lead_coeff = LeadCoeff(task) # print(f"{result.name} - cycle init Lead Coeff: {lead_coeff.summing}, fixed LC: {result.fixed_LC}") if tp.start_done: lead_coeff.best_dist_to_ess = [ lead_coeff.opt_dist_to_ess - result.distance_flown / 1000 ] else: lead_coeff = None '''Airspace check managing''' if task.airspace_check: if task.airspace_check and not airspace: print(f'We should not create airspace here') airspace = AirspaceCheck.from_task(task) real_start_time, already_ess, previous_achieved = result.real_start_time, result.ESS_time, result.waypoints_achieved flightcheck.check_fixes(result, fixes, task, tp, lead_coeff, airspace, livetracking=True, igc_parsing_config=config) calculate_incremental_results(result, task, tp, lead_coeff, airspace, real_start_time, already_ess, previous_achieved)
def verify_and_import_track(result: FlightResult, track, task, print=print): from airspace import AirspaceCheck from pilot.flightresult import save_track if task.airspace_check: airspace = AirspaceCheck.from_task(task) else: airspace = None '''check flight against task''' result.check_flight(track.flight, task, airspace_obj=airspace, print=print) '''create map file''' result.save_tracklog_map_file(task, track.flight) '''save to database''' save_track(result, task.id) if result.notifications: print(str(result.notifications)) print('***************END****************') return result
def read(task_id): task = Task.read(task_id) if task.turnpoints: if not task.distance: task.calculate_task_length() if not task.optimised_turnpoints: task.calculate_optimised_task_length() test = task.date < datetime.today().date() task.get_pilots() airspace = AirspaceCheck.from_task(task) livetrack = LiveTracking(task, airspace, test) for p in livetrack.pilots: create_igc_file(p, task) p.result_type = 'lo' # p.saved_to_db = False p.suspect_landing_fix = None livetrack.create_result() return livetrack
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 flightcheck.flightpointer import FlightPointer from flightcheck.flightcheck import check_fixes, calculate_final_results '''initialize''' if not self.result_type == 'nyp': self.reset() self.result_type = 'lo' if not task.optimised_turnpoints: # this should not happen task.calculate_optimised_task_length() ''' 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 '''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) '''Turnpoint managing''' tp = FlightPointer(task) '''Airspace check managing''' 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) check_fixes(self, flight.fixes, task, tp, lead_coeff, airspace_obj, deadline=deadline, print=print) calculate_final_results(self, task, tp, lead_coeff, airspace_obj, deadline=deadline, print=print)
def check_fixes(result: FlightResult, fixes: list, task: Task, tp: FlightPointer, lead_coeff: LeadCoeff = None, airspace: AirspaceCheck = None, livetracking: bool = False, igc_parsing_config: FlightParsingConfig = None, deadline: int = None, print=print): """""" '''initialize''' total_fixes = len(fixes) 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 distances2go = task.distances_to_go # Total task Opt. Distance, in legs list ''' 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 '''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 if not livetracking: percentage_complete = 0 else: '''get if pilot already started in previous track slices''' already_started = tp.start_done '''get if pilot already made ESS in previous track slices''' already_ESS = any(e.name == 'ESS' for e in result.waypoints_achieved) for i in range(total_fixes - 1): # report percentage progress if not livetracking and int( i / len(fixes) * 100) > percentage_complete: percentage_complete = int(i / len(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 = fixes[i] next_fix = fixes[i + 1] if livetracking: 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 > igc_parsing_config.max_alt_change_rate or not igc_parsing_config.min_alt < alt < igc_parsing_config.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 (abs(launch.altitude - alt) > igc_parsing_config.min_alt_difference and speed > igc_parsing_config.min_gsp_flight): '''pilot launched''' result.first_time = next_fix.rawtime result.live_comment = 'flying' else: '''still on launch''' continue else: '''check if pilot landed''' if speed < igc_parsing_config.min_gsp_flight: if not result.suspect_landing_fix: result.suspect_landing_fix = next_fix # suspect_landing_alt = alt else: time_diff = next_fix.rawtime - result.suspect_landing_fix.rawtime alt_diff = abs(alt - result.suspect_landing_fix.alt) if (time_diff > igc_parsing_config.max_still_seconds and alt_diff < igc_parsing_config.min_alt_difference): '''assuming pilot landed''' result.landing_time = next_fix.rawtime result.landing_altitude = alt result.live_comment = 'landed' break elif result.suspect_landing_fix is not None: result.suspect_landing_fix = None else: alt = next_fix.gnss_alt if alt_source == 'GPS' else next_fix.press_alt + alt_compensation if next_fix.rawtime < result.first_time: '''skip''' continue if result.landing_time and next_fix.rawtime > result.landing_time: '''pilot landed out''' # print(f'fix {i}: landed out - {next_fix.rawtime} - {alt}') break '''max altitude''' if alt > result.max_altitude: result.max_altitude = alt '''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: result.still_flying_at_deadline = True result.stopped_distance = stopped_distance result.stopped_altitude = stopped_altitude result.total_distance = total_distance break '''check if task deadline has passed''' if task.task_deadline < next_fix.rawtime: # Task has ended result.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): result.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: {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): time = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) result.waypoints_achieved.append( create_waypoint_achieved(my_fix, tp, time, alt)) # pilot has started result.real_start_time = time if not livetracking: print( f"Pilot started SS at {sec_to_time(result.real_start_time)}" ) result.best_distance_time = time 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 time = int(round(tp_time_civl(my_fix, next_fix, tp.next), 0)) result.waypoints_achieved.pop() result.waypoints_achieved.append( create_waypoint_achieved(my_fix, tp, time, alt)) # pilot has started again result.real_start_time = time result.best_distance_time = time if not livetracking: print( f"Pilot restarted SS at {sec_to_time(result.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)) result.waypoints_achieved.append( create_waypoint_achieved( my_fix, tp, time, alt)) # pilot has achieved turnpoint if not livetracking: 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)))): result.waypoints_achieved.append( create_waypoint_achieved( next_fix, tp, next_fix.rawtime, alt)) # pilot has achieved goal result.best_distance_time = next_fix.rawtime if not livetracking: print(f"Goal at {sec_to_time(next_fix.rawtime)}") tp.done() 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 > result.distance_flown: '''time of trackpoint with shortest distance to ESS''' result.best_distance_time = next_fix.rawtime '''updating best distance flown''' result.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(result, my_fix, next_fix) '''Airspace Check''' if task.airspace_check and airspace: # map_fix = [next_fix.rawtime, next_fix.lat, next_fix.lon, alt] plot, penalty = airspace.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] result.infringements.append([ next_fix, alt, airspace_name, infringement_type, dist, penalty, separation ]) # print([next_fix, alt, airspace_name, infringement_type, dist, penalty]) result.last_altitude = 0 if 'alt' not in locals() else alt result.last_time = 0 if 'next_fix' not in locals() else next_fix.rawtime if livetracking: result.height = (0 if not result.first_time or result.landing_time or 'next_fix' not in locals() else next_fix.height)
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 process_igc_background(task_id: int, par_id: int, file: Path, user: str): from pilot.track import igc_parsing_config_from_yaml from calcUtils import epoch_to_date from pilot.flightresult import FlightResult, save_track from airspace import AirspaceCheck from igc_lib import Flight from task import Task import json pilot = FlightResult.read(par_id, task_id) task = Task.read(task_id) print = partial(print_to_sse, id=par_id, channel=user) """import track""" # pilot.track = Track(track_file=filename, par_id=pilot.par_id) FlightParsingConfig = igc_parsing_config_from_yaml(task.igc_config_file) flight = Flight.create_from_file(file, config_class=FlightParsingConfig) data = { 'par_id': pilot.par_id, 'track_id': pilot.track_id, 'Result': 'Not Yet Processed' } """check result""" if not flight: print(f"for {pilot.name} - Track is not a valid track file") print(json.dumps(data) + '|result') return None if not flight.valid: print( f'IGC does not meet quality standard set by igc parsing config. Notes:{pilot.flight.notes}' ) print(json.dumps(data) + '|result') return None elif not epoch_to_date(flight.date_timestamp) == task.date: print(f"for {pilot.name} - Track has a different date from task date") print(json.dumps(data) + '|result') return None else: print(f"pilot {pilot.par_id} associated with track {file.name} \n") pilot.track_file = file.name """checking track against task""" if task.airspace_check: airspace = AirspaceCheck.from_task(task) else: airspace = None pilot.check_flight(flight, task, airspace_obj=airspace, print=print) print(f"track verified with task {task.task_id}\n") '''create map file''' pilot.save_tracklog_map_file(task, flight) """adding track to db""" # pilot.to_db() save_track(pilot, task.id) data['track_id'] = pilot.track_id time = '' if pilot.goal_time: time = sec_to_time(pilot.ESS_time - pilot.SSS_time) if pilot.result_type == 'goal': data['Result'] = f'Goal {time}' elif pilot.result_type == 'lo': data['Result'] = f"LO {round(pilot.distance / 1000, 2)}" if pilot.track_id: # if there is a track, make the result a link to the map # trackid = data['track_id'] parid = data['par_id'] result = data['Result'] data[ 'Result'] = f'<a href="/map/{parid}-{task.task_id}">{result}</a>' print(data['Result']) print(json.dumps(data) + '|result') print('***************END****************') return None
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