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 evaluate_ess(result: FlightResult, task: Task): if any(e.name == 'ESS' for e in result.waypoints_achieved): result.ESS_time, result.ESS_altitude = min( [(x.rawtime, x.altitude) for x in result.waypoints_achieved if x.name == 'ESS'], key=lambda t: t[0]) result.speed = (task.SS_distance / 1000) / (result.ss_time / 3600)
def calculate_time_points_reduction(t): from pilot.flightresult import FlightResult p = FlightResult() p.distance_flown = t.opt_dist p.SSS_time = t.max_ss_time p.ESS_time = t.stop_time return pilot_speed(t, p)
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 evaluate_goal(result: FlightResult, task: Task): """ ?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 result.waypoints_achieved): result.distance_flown = task.opt_dist result.goal_time, result.goal_altitude = min( [(x.rawtime, x.altitude) for x in result.waypoints_achieved if x.name == 'Goal'], key=lambda t: t[0]) result.result_type = 'goal'
def read(cls, fp, short_name=None, keep_task_path=False, from_CIVL=False): """ A XML reader to read FSDB files Unfortunately the fsdb format isn't published so much of this is simply an exercise in reverse engineering. Input: - fp: STR: filepath - from_CIVL: BOOL: look for pilot on CIVL database """ """read the fsdb file""" try: tree = ET.parse(fp) root = tree.getroot() except ET.Error: print("FSDB Read Error.") return None pilots = [] tasks = [] """Comp Info""" print("Getting Comp Info...") fs_comp = root.find('FsCompetition') comp = Comp.from_fsdb(fs_comp, short_name) """Formula""" comp.formula = Formula.from_fsdb(fs_comp) comp.formula.comp_class = comp.comp_class """Pilots""" print("Getting Pilots Info...") if from_CIVL: print('*** get from CIVL database') p = root.find('FsCompetition').find('FsParticipants') for pil in p.iter('FsParticipant'): pilot = Participant.from_fsdb(pil, from_CIVL=from_CIVL) # pp(pilot.as_dict()) pilots.append(pilot) comp.participants = pilots """Tasks""" print("Getting Tasks Info...") t = root.find('FsCompetition').find('FsTasks') for tas in t.iter('FsTask'): '''create task obj''' task = Task.from_fsdb(tas, comp.time_offset, keep_task_path) '''check if task was valid''' if task is not None: if not task.task_path: task.create_path() # task.time_offset = int(comp.time_offset) """Task Results""" node = tas.find('FsParticipants') if node is not None: task.pilots = [] print("Getting Results Info...") for res in node.iter('FsParticipant'): '''pilots results''' pilot = FlightResult.from_fsdb(res, task) task.pilots.append(pilot) tasks.append(task) return cls(comp, tasks, fp)
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 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 dump_flight(track, task): # TODO check if file already exists otherwise create and save it from pilot.flightresult import FlightResult from mapUtils import get_bbox lib = task.formula.get_lib() task_result = FlightResult.check_flight(track.flight, task) # check flight against task geojson_file = task_result.to_geojson_result(track, task) bbox = get_bbox(track.flight) return geojson_file, bbox
def create_igc_file(result: FlightResult, task: Task): """ Flymaster IGC initialize format: AXFMSFP Flymaster Live, V1.0, S/N 618839 HFFXA010 HFPLTPILOT:Alessandro Ploner HFGTYGLIDERTYPE: HFGIDGLIDERID: HFDTM100GPSDATUM:WGS-1984 HFCIDCOMPETITIONID:72 HFCCLCOMPETITIONCLASS: HOSITSITE:Meduno - Monte Valinis-IT HFGPS:UBLOXNEO6 HFPRSPRESSALTSENSOR:NA HFRFWFIRMWAREVERSION:202g HFRHWHARDWAREVERSION:1.0R2 HFFTYFRTYPE:FLYMASTER,LIVE HFDTE150719 B1132494613837N01248410EA0006900991 B1132504613834N01248408EA0006900990 """ if result.track_file and Path(task.file_path, result.track_file).is_file(): print(f"File already exists") return """check if directory already exists""" if not Path(task.file_path).is_dir(): Path(task.file_path).mkdir(mode=0o755) '''create filename''' file = create_igc_filename(task.file_path, task.date, result.name, result.ID) result.track_file = file.name '''create IGC header''' name = result.name glider = result.glider ID = result.ID site = task.turnpoints[0].description date = task.date.strftime("%d%m%y") header = f"AXFMSFP Flymaster Live, V1.0\n" header += f"HFFXA010\n" header += f"HFPLTPILOT:{name}\n" header += f"HFGTYGLIDERTYPE:{glider}\n" header += f"HFGIDGLIDERID:\n" header += f"HFDTM100GPSDATUM:WGS-1984\n" header += f"HFCIDCOMPETITIONID:{ID}\n" header += f"HFCCLCOMPETITIONCLASS:\n" header += f"HOSITSITE:{site}\n" header += f"HFGPS:UBLOXNEO6\n" header += f"HFPRSPRESSALTSENSOR:NA\n" header += f"HFRFWFIRMWAREVERSION:202g\n" header += f"HFRHWHARDWAREVERSION:1.0R2\n" header += f"HFFTYFRTYPE:FLYMASTER,LIVE\n" header += f"HFDTE{date}\n" '''create file''' f = open(file, "w+") f.write(header) f.close()
def test_track_flight_check(): test_track = Track.read_file('/app/tests/data/test_igc_2.igc', par_id=1) test_result = FlightResult() test_result.check_flight(flight=test_track.flight, task=test_task) assert int(test_result.distance_flown) == 64360 assert test_result.best_waypoint_achieved == 'Goal' assert len(test_result.waypoints_achieved) == test_result.waypoints_made assert test_result.SSS_time == 41400 assert test_result.ESS_time == 50555 assert test_result.ESS_altitude == 880.0 assert test_result.real_start_time == 41428 assert test_result.flight_time == 12183.0 achieved = test_result.waypoints_achieved[1] assert achieved.name == 'TP01' assert achieved.rawtime == 43947 assert achieved.altitude == 1445.0 assert math.isclose(float(achieved.lat), 45.8145667, abs_tol=0.0000001) # ~0.01 meters assert math.isclose(float(achieved.lon), 9.7707167, abs_tol=0.0000001) # ~0.01 meters
def save_livetrack_result(p: FlightResult, task: Task, airspace: AirspaceCheck = None): from igc_lib import Flight from pilot.flightresult import save_track try: flight = Flight.create_from_file(Path(task.file_path, p.track_file)) if flight.valid: print( f"flight valid. Livetracking LC: {p.fixed_LC} distance: {p.distance_flown} time: {p.ss_time}" ) # test = FlightResult() # test.check_flight(flight, task, airspace) # print(f"Calculated LC: {test.fixed_LC} distance: {test.distance_flown} time: {test.ss_time}") # print(f"Difference %: {(test.fixed_LC - p.fixed_LC) / p.fixed_LC * 100}") save_track(p, task.id) p.save_tracklog_map_file(task, flight) else: print(f"{p.track_file} is not a valid igc. Result not saved.") except: print(f"{p.track_file} Error trying to save result.")
def calculate_incremental_results(result: FlightResult, task: Task, tp, lead_coeff, airspace, real_start_time, already_ess, previous_achieved): """Incremental result update function""" from flightcheck import flightcheck if tp.start_done and not real_start_time == result.real_start_time: '''pilot has started or restarted''' result.notifications = [ n for n in result.notifications if not n.notification_type == 'jtg' ] flightcheck.evaluate_start(result, task, tp) if tp.ess_done and not already_ess: '''pilot made ESS''' flightcheck.evaluate_ess(result, task) if tp.made_all: '''pilot made goal''' flightcheck.evaluate_goal(result, task) if len(result.waypoints_achieved) > len(previous_achieved): 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 # print(f"{result.name} - cycle end Lead Coeff: {lead_coeff.summing}, fixed LC: {result.fixed_LC}") if task.airspace_check: _, notifications, penalty = airspace.get_infringements_result( result.infringements) # result.infringements.extend([el for el in infringements if el not in result.infringements]) result.notifications = [ el for el in result.notifications if not el.notification_type == 'airspace' ] result.notifications.extend(notifications)
def get_unscored_pilots(task_id: int, track_source=None): """ Gets list of registered pilots that still do not have a result Input: task_id INT task database ID Output: list of Pilot obj.""" from pilot.flightresult import FlightResult from db.tables import UnscoredPilotView as U pilot_list = [] with db_session() as db: results = db.query(U).filter_by(task_id=task_id) if track_source == 'xcontest': results = results.filter(U.xcontest_id.isnot(None)) elif track_source == 'flymaster': results = results.filter(U.live_id.isnot(None)) results = results.all() for p in results: pilot = FlightResult() p.populate(pilot) pilot_list.append(pilot) return pilot_list
def save_igc_background(task_id: int, par_id: int, tracklog, user, check_g_record=False): from task import Task from pilot.track import create_igc_filename, validate_G_record from pilot.flightresult import FlightResult pilot = FlightResult.read(par_id, task_id) print = partial(print_to_sse, id=par_id, channel=user) if pilot.name: task = Task.read(task_id) fullname = create_igc_filename(task.file_path, task.date, pilot.name) tracklog.save(fullname) print('|open_modal') print('***************START*******************') if check_g_record: print('Checking G-Record...') validation = validate_G_record(fullname) if validation == 'FAILED': print('G-Record not valid') data = { 'par_id': pilot.par_id, 'track_id': pilot.track_id, 'Result': '' } print(json.dumps(data) + '|g_record_fail') return None, None if validation == 'ERROR': print('Error trying to validate G-Record') return None, None if validation == 'PASSED': print('G-Record is valid') print(f'IGC file saved: {fullname.name}') else: return None, None return fullname
def get_unscored_pilots(task_id: int, track_source=None): """ Gets list of registered pilots that still do not have a result Input: task_id INT task database ID Output: list of Pilot obj.""" from pilot.flightresult import FlightResult from db.tables import UnscoredPilotView as U pilot_list = [] with db_session() as db: # results = db.query(U.par_id, U.comp_id, U.ID, U.name, U.nat, U.sex, U.civl_id, # U.live_id, U.glider, U.glider_cert, U.sponsor, U.xcontest_id, # U.team, U.nat_team).filter_by(task_id=task_id) results = db.query(U).filter_by(task_id=task_id) if track_source == 'xcontest': results = results.filter(U.xcontest_id.isnot(None)) elif track_source == 'flymaster': results = results.filter(U.live_id.isnot(None)) results = results.all() for p in results: # pilot = FlightResult.from_dict(p._asdict()) pilot = FlightResult() p.populate(pilot) pilot_list.append(pilot) return pilot_list
def assign_and_import_tracks(files, task, track_source=None, user=None, check_g_record=False, print=print): """Find pilots to associate with tracks""" from compUtils import get_registration from pilot.track import Track, validate_G_record, igc_parsing_config_from_yaml from functools import partial from frontendUtils import print_to_sse import json import importlib pilot_list = [] task_id = task.id comp_id = task.comp_id """checking if comp requires a regisration. Then we create a list of registered pilots to check against tracks filename. This should be much faster than checking against all pilots in database through a query""" registration = get_registration(comp_id) if registration: """We add tracks for the registered pilots not yet scored""" print( "Comp with registration: files will be checked against registered pilots not yet scored" ) pilot_list = get_unscored_pilots(task_id, track_source) if len(pilot_list) == 0: print(f"Pilot list is empty") return print( f"We have {len(pilot_list)} pilots to find tracks for, and {len(files)} tracks" ) else: print( f"No registration required, we have {len(files)} tracks to associate" ) task_date = task.date track_counter = 0 track_path = task.file_path FlightParsingConfig = igc_parsing_config_from_yaml(task.igc_config_file) # print("found {} tracks \n".format(len(files))) for file in files: mytrack = None filename = file.name print(f'filename {filename}, {type(filename)}') if registration: # print(f"checking {filename} against {len(pilot_list)} pilots...") """check filenames to find pilots""" if track_source: # print(f'Source: {track_source}') ''' Use Live server filename format to get pilot ''' lib = importlib.import_module('.'.join( ['sources', track_source])) pilot = lib.get_pilot_from_list(filename, pilot_list) else: pilot = get_pilot_from_list(filename, pilot_list) if pilot: """found a pilot for the track file. dropping pilot from list and creating track obj""" # print(f"Found a pilot to associate with file. dropping {pilot.name} from non scored list") pilot_list[:] = [ d for d in pilot_list if d.par_id != pilot.par_id ] mytrack = Track.read_file(filename=file, config=FlightParsingConfig, print=print) else: """We add track if we find a pilot in database that has not yet been scored""" mytrack = Track.read_file(filename=file, config=FlightParsingConfig, print=print) if get_pil_track(mytrack.par_id, task_id): """pilot has already been scored""" print( f"Pilot with ID {mytrack.par_id} has already a valid track for task with ID {task_id}" ) continue pilot = FlightResult.read(par_id=mytrack.par_id, task_id=task_id) """check result""" if not mytrack: print( f"Track {filename} is not a valid track file, pilot not found in competition or pilot " f"already has a track") continue elif not mytrack.date == task_date: print(f"track {filename} has a different date from task") continue """pilot is registered and has no valid track yet moving file to correct folder and adding to the list of valid tracks""" track_counter += 1 print(f"Track {track_counter}|counter") mytrack.task_id = task_id filename_and_path = mytrack.copy_track_file(task_path=track_path, pname=pilot.name) # print(f"pilot {mytrack.par_id} associated with track {mytrack.filename}") pilot.track_file = filename_and_path.name print(f"processing {pilot.ID} {pilot.name}:") if user: new_print = partial(print_to_sse, id=mytrack.par_id, channel=user) print('***************START*******************') else: new_print = print if check_g_record: print('Checking G-Record...') validation = validate_G_record(filename_and_path) if validation == 'FAILED': print('G-Record not valid') data = { 'par_id': pilot.par_id, 'track_id': pilot.track_id, 'Result': '' } print(json.dumps(data) + '|g_record_fail') continue if validation == 'ERROR': print('Error trying to validate G-Record') continue if validation == 'PASSED': print('G-Record is valid') verify_and_import_track(pilot, mytrack, task, print=new_print) print("*******************processed all tracks**********************")
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 calculate_min_dist_score(t): from pilot.flightresult import FlightResult p = FlightResult() p.distance_flown = t.formula.min_dist return pilot_distance(t, p)
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)