def new_departure_DEP(self, goal_point): acft_type = choice( self.parkable_aircraft_types if self.parkable_aircraft_types != [] else self.playable_aircraft_types) rwy = rnd_rwy([ rwy for rwy in env.airport_data.allRunways() if rwy.use_for_departures ], lambda rwy: rwy.acceptsAcftType(acft_type)) if rwy == None: return None thr = rwy.threshold() hdg = rwy.orientation() pos = thr.moved(hdg, settings.solo_TWR_range_dist) try: # Check for separation horiz_dist = [ pos.distanceTo(acft.params.position) for acft in self.controlled_traffic if acft.isOutboundGoal() ] if time_to_fly(min(horiz_dist), cruise_speed(acft_type)) < TTF_separation: return None except ValueError: pass # No departures in the sky yet alt = GS_alt(env.elevation(thr), rwy.param_FPA, pos.distanceTo(thr)) ias = restrict_speed_under_ceiling(cruise_speed(acft_type), alt, StdPressureAlt.fromFL(100)) params = SoloParams(Status(Status.AIRBORNE), pos, alt, hdg, ias) params.XPDR_code = env.nextSquawkCodeAssignment(XPDR_range_IFR_DEP) acft = self.mkAiAcft(acft_type, params, (goal_point, None)) acft.instructions.append( Instruction(Instruction.VECTOR_ALT, arg=settings.solo_initial_climb_reading)) return acft
def transitionLevel(self): ''' Returns the lowest FL above the TA. This is NOT the lowest assignable, which takes more vertical separation ''' return StdPressureAlt.fromAMSL(self.transitionAltitude(), self.QNH()).FL() + 1
def altitudeInstruction(self): qnh = env.QNH() alt = self.radar_contact.xpdrAlt() if alt == None: zero_cursor_ft = 0 if self.radar_contact.xpdrGND( ) else env.transitionAltitude() else: zero_cursor_ft = alt.ftAMSL(qnh) return env.readStdAlt(StdPressureAlt.fromAMSL( max(0, zero_cursor_ft + self.diff_alt_measured), qnh), step=5, unit=True)
def assignedPressureAlt(self, qnh): ''' returns the StdPressureAlt of the assigned_altitude_detail if valid returns None if None or invalid ''' assAlt = self.lookup(assigned_altitude_detail) # str reading if assAlt == None: return None try: return StdPressureAlt.fromReading(assAlt, qnh) except ValueError: return None
def spawnNewUncontrolledAircraft(self): rndpos = env.radarPos().moved(Heading(randint(1, 360), True), uniform(10, .8 * settings.radar_range)) rndalt = StdPressureAlt(randint(1, 10) * 1000) if self.airbornePositionFullySeparated(rndpos, rndalt): acft_type = choice(self.uncontrolled_aircraft_types) params = SoloParams(Status(Status.AIRBORNE), rndpos, rndalt, Heading(randint(1, 360), True), cruise_speed(acft_type)) params.XPDR_code = settings.uncontrolled_VFR_XPDR_code new_acft = self.mkAiAcft(acft_type, params, goal=None) if new_acft != None: self.uncontrolled_traffic.append(new_acft)
def new_arrival_APP(self, entry_point): type_choice = self.parkable_aircraft_types if self.parkable_aircraft_types != [] else self.playable_aircraft_types # must be landable too rwy_choice = [ rwy for rwy in env.airport_data.allRunways() if rwy.use_for_arrivals ] if rwy_choice == []: rwy_choice = env.airport_data.allRunways() pop_all( type_choice, lambda t: all(not rwy.acceptsAcftType(t) for rwy in rwy_choice)) if type_choice == []: return None acft_type = choice(type_choice) ils = any(rwy.hasILS() for rwy in rwy_choice) and random() >= settings.solo_ILSvsVisual_balance if entry_point == None: hdg = Heading(randint(1, 360), True) pos = env.radarPos().moved( hdg.opposite(), uniform(.33 * settings.radar_range, .75 * settings.radar_range)) else: pos = entry_point.coordinates hdg = pos.headingTo(env.radarPos()) alt = StdPressureAlt.fromFL( 10 * randint(settings.solo_APP_ceiling_FL_min // 10, settings.solo_APP_ceiling_FL_max // 10)) if not self.airbornePositionFullySeparated(pos, alt): return None ias = restrict_speed_under_ceiling( cruise_speed(acft_type), alt, StdPressureAlt.fromFL(150)) # 5000-ft anticipation params = SoloParams(Status(Status.AIRBORNE), pos, alt, hdg, ias) params.XPDR_code = env.nextSquawkCodeAssignment(XPDR_range_IFR_ARR) return self.mkAiAcft(acft_type, params, ils)
def xpdrData(self): res = {} if self.params.XPDR_mode != '0': res[Xpdr.CODE] = self.params.XPDR_code res[Xpdr.IDENT] = self.params.XPDR_idents if self.params.XPDR_mode not in '0A': res[Xpdr.ALT] = StdPressureAlt( rounded(self.params.altitude.ft1013(), step=(100 if self.params.XPDR_mode == 'C' else 10))) if self.params.XPDR_mode not in '0AC': res[Xpdr.IAS] = self.params.ias res[Xpdr.GND] = self.xpdrGndBit() res[Xpdr.CALLSIGN] = self.identifier res[Xpdr.ACFT] = self.aircraft_type return res
def handoverGuard(self, acft, next_atc): # Bad or untimely handovers if next_atc == 'Ramp': if acft.statusType() != Status.TAXIING: return 'Ramp only accepts taxiing aircraft.' if not acft.isInboundGoal(): return 'This aircraft is outbound!' if not acft.canPark(): return 'Bring aircraft close to parking position before handing over to ramp.' elif next_atc == 'GND': if acft.statusType() != Status.TAXIING: return 'Ground only accepts taxiing aircraft.' elif next_atc == 'TWR': if acft.isInboundGoal(): if not inTWRrange(acft.params): return 'Not in TWR range.' else: if not acft.statusType() != Status.READY: return 'Aircraft has not reported ready for departure.' elif next_atc == 'APP': if not acft.isInboundGoal(): return 'Why hand over to APP?!' elif inTWRrange(acft.params): return 'This aircraft is in TWR range.' elif next_atc == 'DEP': if acft.isInboundGoal(): return 'DEP only controls departures!' elif inTWRrange(acft.params): return 'TWR must keep control of aircraft until they fly out of tower range.' elif next_atc == 'CTR': if acft.isInboundGoal(): return 'This aircraft is inbound your airport.' if settings.solo_role_DEP: point, alt = acft.goal if point != None and acft.params.position.distanceTo( point.coordinates) > exit_point_tolerance: return 'Not close enough to exit point' if acft.params.altitude.diff( StdPressureAlt.fromFL( settings.solo_APP_ceiling_FL_min)) < 0: return 'Not high enough for CTR: reach FL%03d before handing over.' % settings.solo_APP_ceiling_FL_min else: return 'You should not be handing over to the centre directly.' else: print( 'INTERNAL ERROR: Please report unexpected ATC name "%s" in solo mode' % next_atc)
def acftInitParams(self): if self.ground_status_radioButton.isChecked(): status = Status(Status.TAXIING) elif self.ready_status_radioButton.isChecked(): status = Status(Status.READY, arg=self.depRWY_select.currentText()) else: # airborne status radio button must be ticked status = Status(Status.AIRBORNE) pos = self.spawn_coords hdg = self.spawn_hdg if self.airborne_status_radioButton.isChecked(): ias = cruise_speed(self.createAircraftType_edit.getAircraftType()) alt = StdPressureAlt.fromFL(self.airborneFL_edit.value()) else: # on ground ias = Speed(0) alt = env.groundStdPressureAlt(pos) if self.parked_tickBox.isChecked() and self.closest_PKG != None: pkinf = env.airport_data.ground_net.parkingPosInfo(self.closest_PKG) pos = pkinf[0] hdg = pkinf[1] return SoloParams(status, pos, alt, hdg, ias)
def saveChangesAndClose(self): SharedDetailSheet.saveChangesAndClose(self) ## Assigned stuff # Squawk code if self.assignSquawkCode.isChecked(): self.set_detail(assigned_SQ_detail, self.xpdrCode_select.getSQ()) else: self.set_detail(assigned_SQ_detail, None) # Heading if self.assignHeading.isChecked(): self.set_detail(assigned_heading_detail, Heading(self.assignedHeading_edit.value(), False)) else: self.set_detail(assigned_heading_detail, None) # Altitude/FL if self.assignAltitude.isChecked(): reading = self.assignedAltitude_edit.text() try: # try reformating self.set_detail(assigned_altitude_detail, StdPressureAlt.reformatReading(reading)) except ValueError: self.set_detail(assigned_altitude_detail, reading) else: self.set_detail(assigned_altitude_detail, None) # Speed if self.assignSpeed.isChecked(): self.set_detail(assigned_speed_detail, Speed(self.assignedSpeed_edit.value())) else: self.set_detail(assigned_speed_detail, None) # DONE with details if self.strip.linkedFPL() == None: if self.linkFPL_tickBox.isChecked(): if self.FPL_link_on_save == None: signals.newLinkedFPLrequest.emit(self.strip) else: self.strip.linkFPL(self.FPL_link_on_save) else: # a flight plan is already linked if self.pushToFPL_tickBox.isChecked(): self.strip.pushToFPL() self.accept()
def groundStdPressureAlt(self, coords): return StdPressureAlt.fromAMSL(self.elevation(coords), self.QNH())
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 generateAircraftAndStrip(self): start_angle = uniform(0, 360) start_pos = env.radarPos().moved(Heading(start_angle, True), settings.solo_CTR_range_dist) end_pos = env.radarPos().moved( Heading(start_angle + 90 + uniform(1, 179), True), settings.solo_CTR_range_dist) transit_hdg = start_pos.headingTo(end_pos) dep_ad = world_navpoint_db.findClosest(env.radarPos().moved(transit_hdg.opposite(), \ uniform(1.2 * settings.map_range, 5000)), types=[Navpoint.AD]) dest_ad = world_navpoint_db.findClosest(env.radarPos().moved(transit_hdg, \ uniform(1.2 * settings.map_range, 5000)), types=[Navpoint.AD]) if env.pointOnMap(dep_ad.coordinates) or env.pointOnMap( dest_ad.coordinates): return None, None candidate_midpoints = [p for code in settings.solo_CTR_routing_points \ for p in env.navpoints.findAll(code, types=[Navpoint.NDB, Navpoint.VOR, Navpoint.FIX]) \ if start_pos.distanceTo(p.coordinates) < start_pos.distanceTo(end_pos)] midpoint = None if candidate_midpoints == [] else choice( candidate_midpoints) FLd10 = randint(settings.solo_CTR_floor_FL // 10, settings.solo_CTR_ceiling_FL // 10) if settings.solo_CTR_semi_circular_rule == SemiCircRule.E_W and (FLd10 % 2 == 0) != (transit_hdg.magneticAngle() >= 180) \ or settings.solo_CTR_semi_circular_rule == SemiCircRule.N_S and (FLd10 % 2 == 1) != (90 <= transit_hdg.magneticAngle() < 270): FLd10 += 1 if 10 * FLd10 > settings.solo_CTR_ceiling_FL: return None, None p_alt = StdPressureAlt.fromFL(10 * FLd10) if not self.airbornePositionFullySeparated(start_pos, p_alt): return None, None acft_type = choice(self.playable_aircraft_types) hdg = start_pos.headingTo(some(midpoint, dest_ad).coordinates) params = SoloParams(Status(Status.AIRBORNE), start_pos, p_alt, hdg, cruise_speed(acft_type)) params.XPDR_code = env.nextSquawkCodeAssignment(XPDR_range_IFR_transit) new_acft = self.mkAiAcft(acft_type, params, dest_ad) dist_key = lambda atc: env.ATCs.getATC(atc).position.distanceTo( start_pos) received_from = min(env.ATCs.knownATCs(), key=dist_key) strip = Strip() strip.writeDetail(FPL.CALLSIGN, new_acft.identifier) strip.writeDetail(FPL.ACFT_TYPE, new_acft.aircraft_type) strip.writeDetail(FPL.WTC, wake_turb_cat(new_acft.aircraft_type)) strip.writeDetail(FPL.FLIGHT_RULES, 'IFR') strip.writeDetail(FPL.ICAO_DEP, dep_ad.code) strip.writeDetail(FPL.ICAO_ARR, dest_ad.code) strip.writeDetail(FPL.CRUISE_ALT, env.readStdAlt(new_acft.params.altitude)) strip.writeDetail(assigned_altitude_detail, strip.lookup(FPL.CRUISE_ALT)) strip.writeDetail(assigned_SQ_detail, new_acft.params.XPDR_code) strip.writeDetail(received_from_detail, received_from) if midpoint != None: strip.insertRouteWaypoint(midpoint) new_acft.instructions.append( Instruction(Instruction.FOLLOW_ROUTE, arg=strip.lookup(parsed_route_detail).dup())) return new_acft, strip
def stdPressureAlt(self, reading): return StdPressureAlt.fromReading(reading, self.QNH())
def inTWRrange(params): return params.position.distanceTo(env.radarPos()) <= settings.solo_TWR_range_dist \ and params.altitude.diff(StdPressureAlt.fromFL(settings.solo_TWR_ceiling_FL)) < 0
def saveRadarSnapshot(self): prev = self.lastSnapshot() # always exists if prev.time_stamp == self.live_update_time: return # No point saving values again: they were not updated since last snapshot # otherwise create a new snapshot snapshot = RadarSnapshot(self.live_update_time, self.liveCoords(), self.liveGeometricAlt()) snapshot.xpdrData = self.live_XPDR_data.copy() if settings.radar_cheat or self.individual_cheat: # We try to compensate, but cannot always win so None values are possible. # Plus: CODE, IDENT and GND have no useful compensation. if Xpdr.ALT not in snapshot.xpdrData: stdpa = StdPressureAlt.fromAMSL(snapshot.geometric_alt, env.QNH()) snapshot.xpdrData[Xpdr.ALT] = StdPressureAlt(stdpa.ft1013()) if Xpdr.CALLSIGN not in snapshot.xpdrData: snapshot.xpdrData[Xpdr.CALLSIGN] = self.identifier if Xpdr.ACFT not in snapshot.xpdrData: snapshot.xpdrData[Xpdr.ACFT] = self.aircraft_type else: # contact is not cheated if settings.SSR_mode_capability == '0': # no SSR so no XPDR data can be snapshot snapshot.xpdrData.clear() else: # SSR on; check against A/C/S capability if settings.SSR_mode_capability == 'A': # radar does not have the capability to pick up altitude if Xpdr.ALT in snapshot.xpdrData: del snapshot.xpdrData[Xpdr.ALT] if settings.SSR_mode_capability != 'S': # radar does not have mode S interrogation capability for k in (Xpdr.CALLSIGN, Xpdr.ACFT, Xpdr.IAS, Xpdr.GND): if k in snapshot.xpdrData: del snapshot.xpdrData[k] # Inferred values if self.frozen: # copy from previous snapshot snapshot.heading = prev.heading snapshot.groundSpeed = prev.groundSpeed snapshot.verticalSpeed = prev.verticalSpeed else: # compute values from change between snapshots # Search history for best snapshot to use for diff diff_seconds = (snapshot.time_stamp - prev.time_stamp).total_seconds() i = 1 # index of currently selected prev while i < len(self.radar_snapshots) and diff_seconds < snapshot_diff_time: i += 1 prev = self.radar_snapshots[-i] diff_seconds = (snapshot.time_stamp - prev.time_stamp).total_seconds() # Fill snapshot diffs if prev.coords != None and snapshot.coords != None: # ground speed snapshot.groundSpeed = Speed(prev.coords.distanceTo(snapshot.coords) * 3600 / diff_seconds) # heading if snapshot.groundSpeed != None and snapshot.groundSpeed.diff(min_taxiing_speed) > 0: # acft moving across the ground try: snapshot.heading = snapshot.coords.headingFrom(prev.coords) except ValueError: snapshot.heading = prev.heading # stopped: keep prev. hdg else: snapshot.heading = prev.heading # vertical speed prev_alt = prev.xpdrData.get(Xpdr.ALT, None) this_alt = snapshot.xpdrData.get(Xpdr.ALT, None) if prev_alt != None and this_alt != None: snapshot.verticalSpeed = (this_alt.diff(prev_alt)) * 60 / diff_seconds # Append snapshot to history self.radar_snapshots.append(snapshot) if len(self.radar_snapshots) > snapshot_history_size: del self.radar_snapshots[0]
def storeSettings(self): ## CHECK SETTINGS FIRST try: new_ranges = [] for i in range(len(self.range_group_boxes)): if self.range_group_boxes[i].isChecked(): name = self.range_name_edits[i].text() if any(rng.name == name for rng in new_ranges): raise ValueError('Duplicate range name') colour = self.range_col_edits[i].getChoice() new_ranges.append( XpdrAssignmentRange(name, self.range_lo_edits[i].value(), self.range_hi_edits[i].value(), colour)) except ValueError as err: QMessageBox.critical(self, 'Assignment range error', str(err)) return if env.airport_data == None: try: bad = next(p for p in self.routingPoints_edit.text().split() if len(env.navpoints.findAll(code=p)) != 1) QMessageBox.critical( self, 'Invalid entry', 'Unknown navpoint or navpoint not unique: %s' % bad) return except StopIteration: pass # no bad navpoints else: try: alt_reading = StdPressureAlt.reformatReading( self.initialClimb_edit.text()) except ValueError: QMessageBox.critical( self, 'Invalid entry', 'Could not read altitude or level for default initial climb.' ) return ## ALL SETTINGS OK. Save them and accept the dialog. GeneralSettingsDialog.last_tab_used = self.settings_tabs.currentIndex() if env.airport_data != None: for i in range(self.runway_tabs.count()): self.runway_tabs.widget(i).applyToRWY() settings.SSR_mode_capability = '0' if self.capability_noSSR_radioButton.isChecked() \ else 'A' if self.capability_modeA_radioButton.isChecked() \ else 'C' if self.capability_modeC_radioButton.isChecked() else 'S' settings.radar_range = self.radarHorizontalRange_edit.value() settings.radar_signal_floor_level = self.radarFloor_edit.value() settings.radar_sweep_interval = timedelta( seconds=self.radarUpdateInterval_edit.value()) settings.radio_direction_finding = self.radioDirectionFinding_tickBox.isChecked( ) settings.controller_pilot_data_link = self.cpdlc_tickBox.isChecked() settings.auto_print_strips_include_DEP = self.stripAutoPrint_DEP_tickBox.isChecked( ) settings.auto_print_strips_include_ARR = self.stripAutoPrint_ARR_tickBox.isChecked( ) settings.auto_print_strips_IFR_only = self.stripAutoPrint_ifrOnly_tickBox.isChecked( ) settings.auto_print_strips_anticipation = timedelta( minutes=self.stripAutoPrint_leadTime_edit.value()) settings.vertical_separation = self.verticalSeparation_edit.value() settings.conflict_warning_floor_FL = self.conflictWarningFloorFL_edit.value( ) settings.location_radio_name = self.radioName_edit.text() settings.transition_altitude = self.transitionAltitude_edit.value( ) # NOTE useless if a TA is set in apt.dat settings.uncontrolled_VFR_XPDR_code = self.uncontrolledVFRcode_edit.value( ) settings.horizontal_separation = self.horizontalSeparation_edit.value() settings.XPDR_assignment_ranges = new_ranges if env.airport_data == None: # CTR mode settings.solo_CTR_floor_FL = self.spawnCTR_minFL_edit.value() settings.solo_CTR_ceiling_FL = self.spawnCTR_maxFL_edit.value() settings.solo_CTR_range_dist = self.CTRrangeDistance_edit.value() settings.solo_CTR_routing_points = self.routingPoints_edit.text( ).split() settings.solo_CTR_semi_circular_rule = self.selectedSemiCircRule() else: # AD mode settings.solo_APP_ceiling_FL_min = self.spawnAPP_minFL_edit.value( ) // 10 * 10 settings.solo_APP_ceiling_FL_max = ( (self.spawnAPP_maxFL_edit.value() - 1) // 10 + 1) * 10 settings.solo_TWR_range_dist = self.TWRrangeDistance_edit.value() settings.solo_TWR_ceiling_FL = self.TWRrangeCeiling_edit.value() settings.solo_initial_climb_reading = alt_reading settings.ATIS_custom_appendix = self.atisCustomAppendix_edit.toPlainText( ) signals.localSettingsChanged.emit() self.accept()