Esempio n. 1
0
 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 []
Esempio n. 2
0
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))
Esempio n. 3
0
 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
Esempio n. 4
0
 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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
    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
Esempio n. 8
0
    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)
Esempio n. 9
0
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