def strip_mouse_press(strip, event): if event.button() == Qt.LeftButton: acknowledge_strip(strip) selection.selectStrip(strip) elif event.button() == Qt.MiddleButton: if event.modifiers() & Qt.ShiftModifier: # STRIP UNLINK REQUEST if strip is selection.strip: if selection.fpl != None and selection.acft != None: # both are linked signals.statusBarMsg.emit( 'Ambiguous action. Use SHIFT+MMB on FPL or radar contact to unlink.' ) elif selection.fpl == None and selection.acft != None: # XPDR link only strip.linkAircraft(None) signals.stripInfoChanged.emit() selection.selectAircraft(selection.acft) elif selection.fpl != None and selection.acft == None: # FPL link only strip.linkFPL(None) signals.stripInfoChanged.emit() selection.selectFPL(selection.fpl) else: # STRIP LINK REQUEST if selection.fpl != None and env.linkedStrip( selection.fpl) == None and strip.linkedFPL() == None: strip.linkFPL(selection.fpl) signals.stripInfoChanged.emit() selection.selectStrip(strip) elif selection.acft != None and env.linkedStrip( selection.acft) == None and strip.linkedAircraft() == None: strip.linkAircraft(selection.acft) if settings.strip_autofill_on_ACFT_link: strip.fillFromXPDR() signals.stripInfoChanged.emit() selection.selectStrip(strip) if strip is selection.strip: acknowledge_strip(strip)
def vertical_assignment(acft): strip = env.linkedStrip(acft) if strip == None: return None else: alt = strip.assignedPressureAlt(env.QNH()) return None if alt == None else alt.ft1013()
def updateContents(self): self.prepareGeometryChange() expand = self.mouse_hovering or self.radar_contact is selection.acft or env.linkedStrip( self.radar_contact) != None self.paint_border = expand self.info_text = infoTextLines(self.radar_contact, not expand) self.rectangle = TextBoxItem.txt_rect_3lines if expand else TextBoxItem.txt_rect_2lines
def auto_print_strip_reason(fpl): ''' Returns reason to print if strip should be auto-printed from FPL; None otherwise ''' if fpl.status() == FPL.CLOSED or fpl.strip_auto_printed \ or settings.auto_print_strips_IFR_only and fpl[FPL.FLIGHT_RULES] != 'IFR' \ or env.airport_data == None or env.linkedStrip(fpl) != None: return None present_time = now() ok_reason = None if settings.auto_print_strips_include_DEP: # check DEP time dep = fpl[FPL.TIME_OF_DEP] if dep != None and fpl[ FPL. ICAO_DEP] == env.airport_data.navpoint.code: # we know: fpl.status() != FPL.CLOSED if dep - settings.auto_print_strips_anticipation <= present_time <= dep: ok_reason = 'departure due ' + rel_datetime_str(dep) if ok_reason == None and settings.auto_print_strips_include_ARR: # check ARR time eta = fpl.ETA() if eta != None and fpl[ FPL.ICAO_ARR] == env.airport_data.navpoint.code and fpl.status( ) == FPL.OPEN: if eta - settings.auto_print_strips_anticipation <= present_time <= eta: ok_reason = 'arrival due ' + rel_datetime_str(eta) return ok_reason
def linkAircraft(self, acft): if self.strip != None and self.strip.linkedAircraft( ) == None and env.linkedStrip(acft) == None: self.strip.linkAircraft(acft) if settings.strip_autofill_on_ACFT_link: self.strip.fillFromXPDR() signals.stripInfoChanged.emit() self.selectAircraft(acft)
def selectFPL(self, select): self.fpl = select self.strip = env.linkedStrip(self.fpl) if self.strip == None: self.acft = None else: self.acft = self.strip.linkedAircraft() signals.selectionChanged.emit()
def aircraftHasDisappeared(self, acft): strip = env.linkedStrip(acft) if strip != None: strip.linkAircraft(None) if selection.acft is acft: if strip == None: # was not linked selection.deselect() else: selection.selectStrip(strip)
def ACFT_pen_colour(radar_contact): if radar_contact.ignored: return settings.colour('ACFT_ignored') strip = env.linkedStrip(radar_contact) if strip != None: # look for strip position colour (rack colour, overrides range colour) rack = strip.lookup(rack_detail) if rack != None and rack in settings.rack_colours: return settings.rack_colours[rack] sq = radar_contact.xpdrCode() if sq != None: # look for a range colour try: return next(rng for rng in settings.XPDR_assignment_ranges if rng.lo <= sq <= rng.hi and rng.col != None).col except StopIteration: pass # no range colour available return settings.colour( 'ACFT_unlinked') if strip == None else settings.colour('ACFT_linked')
def horizontal_path(acft, hdg=True, rte=True, ttf=None, div=False): ''' Returns the anticipated ACFT path, considering headings (if "hdg") and/or routes (if "rte"), until route destination is reached, "ttf" is given and flown, or path goes out of both map and radar ranges. If "div" is True, the returned list is divided into sections flown in one "route division time". NB: Heading assignments override routes if both are considered. ''' speed = acft.groundSpeed() if speed != None: strip = env.linkedStrip(acft) if strip != None: pos = acft.coords() waypoints = None if hdg: assHdg = strip.lookup(assigned_heading_detail) if assHdg != None: wp_dist = 2 * max(settings.map_range, settings.radar_range ) if ttf == None else distance_flown( ttf, speed) waypoints = [pos.moved(assHdg, wp_dist)] if waypoints == None and rte: route = strip.lookup(parsed_route_detail) if route != None: waypoints = [ route.waypoint(i).coordinates for i in range( route.currentLegIndex(pos), route.legCount()) ] if waypoints != None: dist_limit = None if ttf == None else distance_flown( ttf, speed) div_dist = distance_flown(route_division_ttf, speed) if div_dist < min_path_hop_when_not_dividing and dist_limit == None: if div: raise ValueError( 'ACFT speed too low to calculate path and count on divisions with no TTF limit' ) else: # set to fall-back value, shortcutting ACFT speed but resulting div's are flattened anyway (not wanted) div_dist = min_path_hop_when_not_dividing divisions = horizontal_route_divisions(pos, waypoints, div_dist, limit=dist_limit) return divisions if div else flatten(divisions) raise NoPath()
def __init__(self, gui, fpl): QDialog.__init__(self, gui) self.setupUi(self) self.installEventFilter(RadioKeyEventFilter(self)) self.setWindowIcon(QIcon(IconFile.panel_FPLs)) self.fpl = fpl self.updateOnlineStatusBox() self.stripLinked_info.setText( 'yes' if env.linkedStrip(self.fpl) != None else 'no') SharedDetailSheet.__init__(self) self.viewOnlineComments_button.clicked.connect(self.viewOnlineComments) self.openFPL_button.clicked.connect( lambda: self.changeFplOnlineStatus(FPL.OPEN)) self.closeFPL_button.clicked.connect( lambda: self.changeFplOnlineStatus(FPL.CLOSED)) # FUTURE[tabOrder]: remove tab order corrections below? current Qt focus behaviour is currently bad wthout them self.setTabOrder(self.cruiseAlt_edit, self.altAirportPicker_widget) self.setTabOrder(self.altAirportPicker_widget.focusProxy(), self.flightRules_select)
def updateItem(self): self.occupation_indication = 0 if self.physical_location == settings.location_code: try: boxed_strip = env.strips.findStrip(lambda strip: strip.lookup(runway_box_detail) == self.physical_runway_index) self.occupation_indication = 1 except StopIteration: # no strip boxed on this runway boxed_strip = None if settings.monitor_runway_occupation: occ = env.radar.runwayOccupation(self.physical_runway_index) if len(occ) >= 2 or len(occ) == 1 and boxed_strip == None: self.occupation_indication = 2 elif len(occ) == 1: # one traffic on RWY and runway is reserved acft = occ[0] boxed_link = boxed_strip.linkedAircraft() # NB: can be None if not (boxed_link == None and env.linkedStrip(acft) == None or boxed_link is acft): self.occupation_indication = 2 self.setZValue(self.occupation_indication) self.update(self.rect)
def mousePressEvent(self, event): QTableView.mousePressEvent(self, event) try: proxy_index = self.selectedIndexes()[0] except IndexError: return fpl = env.FPLs.FPL(self.model().mapToSource(proxy_index).row()) if event.button() == Qt.MiddleButton: if event.modifiers() & Qt.ShiftModifier: # Trying to unlink if selection.strip != None and selection.strip.linkedFPL() is fpl: selection.strip.linkFPL(None) selection.selectStrip(selection.strip) else: signals.selectionChanged.emit() # revert selection else: # Trying to link if selection.strip != None and selection.strip.linkedFPL() == None and env.linkedStrip(fpl) == None: selection.strip.linkFPL(fpl) selection.selectFPL(fpl) else: signals.selectionChanged.emit() # revert selection else: selection.selectFPL(fpl)
def updateContacts(self): for item in self.layerItems(Layer.AIRCRAFT): item.updateItem() item.setVisible(self.show_GND_modes or not item.radar_contact.xpdrGND() or env.linkedStrip(item.radar_contact) != None)
def strip_sheet_FPL_match(callsign_filter, fpl): return callsign_filter.upper() in some(fpl[FPL.CALLSIGN], '').upper() \ and env.linkedStrip(fpl) == None and fpl.flightIsInTimeWindow(timedelta(hours=12))
def updateButtons(self): fpl = selection.fpl self.revert_button.setEnabled(fpl != None and fpl.existsOnline() and fpl.needsUpload()) self.remove_button.setEnabled(fpl != None and not fpl.existsOnline() and env.linkedStrip(fpl) == None)
def infoTextLines(radar_contact, compact_display): strip = env.linkedStrip(radar_contact) # FL/ALT. & SPEED LINE ass_alt = None if strip == None else strip.lookup(assigned_altitude_detail) if ass_alt != None: try: ass_alt = StdPressureAlt.reformatReading(ass_alt, unit=False) except ValueError: ass_alt = None if settings.SSR_mode_capability in '0A': fl_speed_line = '' else: # we may hope for altitude values from XPDR if radar_contact.xpdrGND(): fl_speed_line = 'GND ' else: xpdr_alt = radar_contact.xpdrAlt() if xpdr_alt == None: fl_speed_line = 'alt? ' else: if settings.radar_tag_interpret_XPDR_FL: alt_str = env.readStdAlt(xpdr_alt, unit=False) else: alt_str = '%03d' % xpdr_alt.FL() vs = radar_contact.verticalSpeed() if vs == None: comp_char = '-' elif abs(vs) < vertical_speed_sensitivity: comp_char = '=' elif vs > 0: comp_char = '↗' else: comp_char = '↘' fl_speed_line = '%s %c ' % (alt_str, comp_char) if ass_alt != None: fl_speed_line += ass_alt fl_speed_line += ' ' if strip != None: fl_speed_line += some(strip.lookup(FPL.WTC, fpl=True), '') speed = radar_contact.groundSpeed() if speed != None: fl_speed_line += '%03d' % speed.kt # XPDR CODE || WAYPOINT/DEST LINE xpdr_code = radar_contact.xpdrCode() emg = False if xpdr_code == None else xpdr_code in XPDR_emergency_codes if emg or strip == None: # Show XPDR code sq_wp_line = '' if xpdr_code == None else '%04o' % xpdr_code if emg: sq_wp_line += ' !!EMG' else: parsed_route = strip.lookup(parsed_route_detail) if parsed_route == None: dest = some(strip.lookup(FPL.ICAO_ARR, fpl=True), '') try: ad = world_navpoint_db.findAirfield(dest) sq_wp_line = '%s %s°' % (ad.code, radar_contact.coords().headingTo( ad.coordinates).read()) except NavpointError: # not an airport sq_wp_line = dest else: # got parsed route leg = parsed_route.currentLegIndex(radar_contact.coords()) if leg == 0 and parsed_route.SID() != None: sq_wp_line = 'SID %s' % parsed_route.SID() elif leg == parsed_route.legCount() - 1 and parsed_route.STAR( ) != None: sq_wp_line = 'STAR %s' % parsed_route.STAR() else: wp = parsed_route.waypoint(leg) sq_wp_line = '%s %s°' % (wp.code, radar_contact.coords().headingTo( wp.coordinates).read()) result_lines = [ sq_wp_line, fl_speed_line ] if settings.radar_tag_FL_at_bottom else [fl_speed_line, sq_wp_line] # CALLSIGN & ACFT TYPE LINE (top line, only if NOT compact display) if not compact_display: line1 = '' cs = radar_contact.xpdrCallsign() if cs == None and strip != None: cs = strip.callsign(fpl=True) if settings.session_manager.session_type == SessionType.TEACHER: dl = env.cpdlc.currentDataLink(radar_contact.identifier) else: dl = None if cs == None else env.cpdlc.currentDataLink(cs) if dl != None: line1 += { ConnectionStatus.OK: '⚡ ', ConnectionStatus.EXPECTING: '[⚡] ', ConnectionStatus.PROBLEM: '!![⚡] ' }[dl.status()] line1 += some(cs, '?') if strip != None and strip.lookup(FPL.COMMENTS) != None: line1 += '*' if radar_contact.xpdrIdent(): line1 += ' !!ident' t = radar_contact.xpdrAcftType() if t == None and strip != None: t = strip.lookup(FPL.ACFT_TYPE, fpl=True) if t != None: line1 += ' %s' % t result_lines.insert(0, line1) return '\n'.join(result_lines)
def scan(self): visible_aircraft = { a.identifier: a for a in settings.session_manager.getAircraft() if a.isRadarVisible() } ## UPDATE AIRCRAFT LIST lost_contacts = [] for got_acft in self.aircraft_list: try: ignore = visible_aircraft.pop(got_acft.identifier) got_acft.saveRadarSnapshot() self.blips_invisible[got_acft.identifier] = 0 except KeyError: count = self.blips_invisible[got_acft.identifier] if count < settings.invisible_blips_before_contact_lost: self.blips_invisible[got_acft.identifier] = count + 1 else: lost_contacts.append(got_acft.identifier) # Remove lost aircraft for acft in pop_all(self.aircraft_list, lambda acft: acft.identifier in lost_contacts): strip = env.linkedStrip(acft) del self.blips_invisible[acft.identifier] self.known_EMG_squawkers.discard(acft.identifier) self.lostContact.emit(acft) if strip != None: signals.controlledContactLost.emit(strip, acft.coords()) # Add newly visible aircraft for new_acft in visible_aircraft.values(): new_acft.saveRadarSnapshot() self.aircraft_list.append(new_acft) self.blips_invisible[new_acft.identifier] = 0 self.newContact.emit(new_acft) ## CHECK FOR NEW EMERGENCIY SQUAWKS for acft in self.aircraft_list: if acft.xpdrCode() in XPDR_emergency_codes: if acft.identifier not in self.known_EMG_squawkers: self.known_EMG_squawkers.add(acft.identifier) self.emergencySquawk.emit(acft) else: self.known_EMG_squawkers.discard(acft.identifier) ## CHECK FOR NEW/LOST RADAR IDENTIFICATIONS if settings.traffic_identification_assistant: found_S_links = [] found_A_links = [] for strip in env.strips.listStrips( lambda s: s.linkedAircraft() == None): mode_S_found = False # Try mode S identification if strip.lookup(FPL.CALLSIGN) != None: scs = strip.lookup(FPL.CALLSIGN).upper() if env.strips.count( lambda s: s.lookup(FPL.CALLSIGN) != None and s. lookup(FPL.CALLSIGN).upper() == scs) == 1: candidates = [ acft for acft in self.aircraft_list if acft.xpdrCallsign() != None and acft.xpdrCallsign().upper() == scs ] if len(candidates) == 1: found_S_links.append((strip, candidates[0])) mode_S_found = True # Try mode A identification if not mode_S_found: ssq = strip.lookup(assigned_SQ_detail) if ssq != None and env.strips.count(lambda s: \ s.lookup(assigned_SQ_detail) == ssq and s.linkedAircraft() == None) == 1: # only one non-linked strip with this SQ candidates = [acft for acft in self.aircraft_list if not any(a is acft for s, a in found_S_links) \ and acft.xpdrCode() == ssq and env.linkedStrip(acft) == None] if len(candidates) == 1: # only one aircraft matching found_A_links.append((strip, candidates[0])) for s, a in pop_all( self.soft_links, lambda sl: not any(sl[0] is s and sl[ 1] is a for s, a in found_S_links + found_A_links)): s.writeDetail(soft_link_detail, None) for s, a, m in [(s, a, True) for s, a in found_S_links ] + [(s, a, False) for s, a in found_A_links]: if not any(sl[0] is s and sl[1] is a for sl in self.soft_links): # new found soft link if strip.lookup( received_from_detail ) != None and settings.strip_autolink_on_ident and ( m or settings.strip_autolink_include_modeC): s.linkAircraft(a) else: # strip not automatically linked; notify of a new identification self.soft_links.append((s, a)) s.writeDetail(soft_link_detail, a) signals.aircraftIdentification.emit(s, a, m) ## UPDATE POSITION/ROUTE WARNINGS conflicts = { acft.identifier: Conflict.NO_CONFLICT for acft in self.aircraft_list } traffic_for_route_checks = [] for strip in env.strips.listStrips( ): # check for position conflicts and build list of traffic to check for routes later acft = strip.linkedAircraft() if acft != None and acft.identifier in conflicts: # controlled traffic with radar contact for other in self.aircraft_list: if other is not acft and position_conflict_test( acft, other ) == Conflict.NEAR_MISS: # positive separation loss detected conflicts[acft.identifier] = conflicts[ other.identifier] = Conflict.NEAR_MISS if not bypass_route_conflict_check(strip): traffic_for_route_checks.append(acft) if settings.route_conflict_warnings: # check for route conflicts while traffic_for_route_checks != []: # progressively emptying the list acft = traffic_for_route_checks.pop() for other in traffic_for_route_checks: c = path_conflict_test(acft, other) conflicts[acft.identifier] = max( conflicts[acft.identifier], c) conflicts[other.identifier] = max( conflicts[other.identifier], c) # now update aircraft conflicts and emit signals if any are new new_near_miss = new_path_conflict = False for contact in self.aircraft_list: new_conflict = conflicts[contact.identifier] if new_conflict > contact.conflict: new_near_miss |= new_conflict == Conflict.NEAR_MISS new_path_conflict |= new_conflict in [ Conflict.DEPENDS_ON_ALT, Conflict.PATH_CONFLICT ] contact.conflict = new_conflict if new_path_conflict: self.pathConflict.emit() if new_near_miss: self.nearMiss.emit() ## UPDATE RUNWAY OCCUPATION for phrwy in self.runway_occupation: new_occ = [] if settings.monitor_runway_occupation: rwy1, rwy2 = env.airport_data.physicalRunway(phrwy) width_metres = env.airport_data.physicalRunwayData(phrwy)[0] thr1 = rwy1.threshold().toRadarCoords() thr2 = rwy2.threshold().toRadarCoords() w = m2NM * width_metres for acft in self.aircraft_list: if acft.considerOnGround(): if acft.coords().toRadarCoords().isBetween( thr1, thr2, w / 2): # ACFT is on RWY new_occ.append(acft) if not any( a is acft for a in self.runway_occupation[phrwy] ): # just entered the RWY: check if alarm must sound try: boxed_link = env.strips.findStrip( lambda strip: strip.lookup( runway_box_detail) == phrwy ).linkedAircraft() except StopIteration: # no strip boxed on this runway if rwy1.inUse() or rwy2.inUse( ): # entering a non-reserved but active RWY self.runwayIncursion.emit(phrwy, acft) else: # RWY is reserved if boxed_link == None and env.linkedStrip( acft ) == None or boxed_link is acft: # entering ACFT is the one cleared to enter, or can be if self.runway_occupation[ phrwy] != []: # some ACFT was/were already on RWY call_guilty = acft if boxed_link == None else self.runway_occupation[ phrwy][0] self.runwayIncursion.emit( phrwy, call_guilty) else: # entering ACFT is known to be different from the one cleared to enter self.runwayIncursion.emit(phrwy, acft) self.runway_occupation[phrwy] = new_occ # Finished aircraft stuff self.last_sweep = now() self.blip.emit()
def updateDisplay(self): if self.radar_contact == None: self.info_area.setEnabled(False) return else: self.info_area.setEnabled(True) # AIRCRAFT BOX # Heading hdg = self.radar_contact.heading() self.aircraftHeading_info.setText('?' if hdg == None else hdg.read() + '°') # Alt./FL alt = self.radar_contact.xpdrAlt() if alt == None: self.aircraftAltitude_info.setText( 'N/A' if settings.SSR_mode_capability in '0A' else '?') else: alt_str = env.readStdAlt(alt, step=None, unit=True) if not alt_str.startswith('FL') and env.QNH( noneSafe=False) == None: alt_str += ' !!QNH' self.aircraftAltitude_info.setText(alt_str) # Ground speed groundSpeed = self.radar_contact.groundSpeed() if groundSpeed == None: self.aircraftGroundSpeed_info.setText('?') else: self.aircraftGroundSpeed_info.setText(str(groundSpeed)) # Indicated airspeed speed ias = self.radar_contact.IAS() if ias == None: self.indicatedAirSpeed_info.setText( '?' if settings.SSR_mode_capability == 'S' else 'N/A') else: s = str(ias) if self.radar_contact.xpdrIAS() == None: s += ' !!estimate' self.indicatedAirSpeed_info.setText(s) # Vertical speed vs = self.radar_contact.verticalSpeed() if vs == None: self.aircraftVerticalSpeed_info.setText( 'N/A' if settings.SSR_mode_capability in '0A' else '?') else: self.aircraftVerticalSpeed_info.setText('%+d ft/min' % vs) # ROUTE BOX coords = self.radar_contact.coords() strip = env.linkedStrip(self.radar_contact) route = None if strip == None else strip.lookup(parsed_route_detail) self._last_known_route = route if route == None: self.route_box.setEnabled(False) else: self.route_box.setEnabled(True) i_leg = route.currentLegIndex(coords) wpdist = coords.distanceTo(route.waypoint(i_leg).coordinates) self.legCount_info.setText('%d of %d' % (i_leg + 1, route.legCount())) self.legSpec_info.setText(route.legStr(i_leg)) self.waypointAt_info.setText(dist_str(wpdist)) try: # TTF if groundSpeed == None: raise ValueError('No ground speed info') self.waypointTTF_info.setText(TTF_str(wpdist, groundSpeed)) except ValueError: self.waypointTTF_info.setText('?') # AIRPORT BOX if env.airport_data != None: airport_dist = coords.distanceTo(env.radarPos()) self.airportBearing_info.setText( coords.headingTo(env.radarPos()).read()) self.airportDistance_info.setText(dist_str(airport_dist)) try: # TTF if groundSpeed == None: raise ValueError('No ground speed info') self.airportTTF_info.setText(TTF_str(airport_dist, groundSpeed)) except ValueError: self.airportTTF_info.setText('?')