diff --git a/bluesky/__init__.py b/bluesky/__init__.py index f5b2f43610..0db99ac739 100644 --- a/bluesky/__init__.py +++ b/bluesky/__init__.py @@ -2,6 +2,7 @@ from bluesky import settings from bluesky.core import Signal from bluesky import stack +from bluesky import tools # Constants @@ -35,9 +36,6 @@ def init(mode='sim', pygame=False, discovery=False, cfgfile='', scnfile=''): - pygame: indicate if BlueSky is started with BlueSky_pygame.py - discovery: Enable network discovery ''' - # Initialize global settings first, possibly loading a custom config file - settings.init(cfgfile) - # Is this a server running headless? headless = (mode[-8:] == 'headless') @@ -46,6 +44,12 @@ def init(mode='sim', pygame=False, discovery=False, cfgfile='', scnfile=''): gui_type = 'pygame' if pygame else \ 'none' if headless or mode[:3] == 'sim' else 'qtgl' + # Initialize global settings first, possibly loading a custom config file + settings.init(cfgfile) + + # Initialise tools + tools.init() + # Load navdatabase in all versions of BlueSky # Only the headless server doesn't need this if not headless: diff --git a/bluesky/core/plugin.py b/bluesky/core/plugin.py index 033beb7bfd..2f25c4b6b9 100644 --- a/bluesky/core/plugin.py +++ b/bluesky/core/plugin.py @@ -1,8 +1,9 @@ """ Implementation of BlueSky's plugin system. """ import ast +import glob + from bluesky.stack.stackbase import sender from os import path -from pathlib import Path import sys import imp import bluesky as bs @@ -90,7 +91,7 @@ def load(cls, name): @classmethod def find_plugins(cls, reqtype): ''' Create plugin wrapper objects based on source code of potential plug-in files. ''' - for fname in Path(settings.plugin_path).rglob('*.py'): + for fname in glob.glob('{}/**/*.py'.format(settings.plugin_path), recursive=True): with open(fname, 'rb') as f: source = f.read() try: diff --git a/bluesky/network/client.py b/bluesky/network/client.py index 3736f95649..09b810e105 100644 --- a/bluesky/network/client.py +++ b/bluesky/network/client.py @@ -10,6 +10,7 @@ class Client: + ''' Base class for (GUI) clients of a BlueSky server. ''' def __init__(self, actnode_topics=b''): ctx = zmq.Context.instance() self.event_io = ctx.socket(zmq.DEALER) @@ -38,20 +39,24 @@ def __init__(self, actnode_topics=b''): bluesky.scr = self def start_discovery(self): + ''' Start UDP-based discovery of available BlueSky servers. ''' if not self.discovery: self.discovery = Discovery(self.client_id) self.poller.register(self.discovery.handle, zmq.POLLIN) self.discovery.send_request() def stop_discovery(self): + ''' Stop UDP-based discovery. ''' if self.discovery: self.poller.unregister(self.discovery.handle) self.discovery = None def get_hostid(self): + ''' Return the id of the host that this client is connected to. ''' return self.host_id def sender(self): + ''' Return the id of the sender of the most recent event. ''' return self.sender_id def event(self, name, data, sender_id): @@ -69,15 +74,42 @@ def actnode_changed(self, newact): to implement actual actnode change handling. ''' print('Client active node changed.') - def subscribe(self, streamname, node_id=b''): - ''' Subscribe to a stream. ''' + def subscribe(self, streamname, node_id=b'', actonly=False): + ''' Subscribe to a stream. + + Arguments: + - streamname: The name of the stream to subscribe to + - node_id: The id of the node from which to receive the stream (optional) + - actonly: Set to true if you only want to receive this stream from + the active node. + ''' + if actonly and not node_id and streamname not in self.acttopics: + self.acttopics.append(streamname) + node_id = self.act self.stream_in.setsockopt(zmq.SUBSCRIBE, streamname + node_id) def unsubscribe(self, streamname, node_id=b''): - ''' Unsubscribe from a stream. ''' + ''' Unsubscribe from a stream. + + Arguments: + - streamname: The name of the stream to unsubscribe from. + - node_id: ID of the specific node to unsubscribe from. + This is also used when switching active nodes. + ''' + if not node_id and streamname in self.acttopics: + self.acttopics.remove(streamname) + node_id = self.act self.stream_in.setsockopt(zmq.UNSUBSCRIBE, streamname + node_id) def connect(self, hostname='localhost', event_port=0, stream_port=0, protocol='tcp'): + ''' Connect client to a server. + + Arguments: + - hostname: Network name or ip of the server to connect to + - event_port: Network port to use for event communication + - stream_port: Network port to use for stream communication + - protocol: Network protocol to use + ''' conbase = '{}://{}'.format(protocol, hostname) econ = conbase + (':{}'.format(event_port) if event_port else '') scon = conbase + (':{}'.format(stream_port) if stream_port else '') @@ -160,6 +192,7 @@ def _getroute(self, target): return None def actnode(self, newact=None): + ''' Set the new active node, or return the current active node. ''' if newact: route = self._getroute(newact) if route is None: @@ -178,9 +211,18 @@ def actnode(self, newact=None): return self.act def addnodes(self, count=1): + ''' Tell the server to add 'count' nodes. ''' self.send_event(b'ADDNODES', count) def send_event(self, name, data=None, target=None): + ''' Send an event to one or all simulation node(s). + + Arguments: + - name: Name of the event + - data: Data to send as payload + - target: Destination of this event. Event is sent to all nodes + if * is specified as target. + ''' pydata = msgpack.packb(data, default=encode_ndarray, use_bin_type=True) if not target: self.event_io.send_multipart(self.actroute + [self.act, name, pydata]) diff --git a/bluesky/settings.py b/bluesky/settings.py index 01d8f651e5..1da25e5822 100644 --- a/bluesky/settings.py +++ b/bluesky/settings.py @@ -5,6 +5,7 @@ import shutil import site import inspect +from pathlib import Path def init(cfgfile=''): @@ -30,11 +31,19 @@ def init(cfgfile=''): root_dirs += site.getsitepackages() # search for bluesky shared data directory + found_dir = False for root_dir in root_dirs: dirpath = os.path.join(root_dir, 'share', 'bluesky') if os.path.exists(dirpath): srcdir = dirpath + found_dir = True break + + # if the path does not exist, it's worth trying the project root. This + # would work if the package was cloned from the git and is installed + # with "pip install -e ." + if not found_dir: + srcdir = get_project_root() datadir = os.path.join(rundir, 'data') cachedir = os.path.join(rundir, 'data/cache') @@ -99,6 +108,18 @@ def init(cfgfile=''): exec(compile(open(cfgfile).read(), cfgfile, 'exec'), globals()) + # Use the path specified in cfgfile if available + if 'cache_path' in globals(): + cachedir = globals()['cache_path'] + if 'log_path' in globals(): + outdir = globals()['log_path'] + if 'perf_path_bada' in globals(): + badadir = globals()['perf_path_bada'] + if 'scenario_path' in globals(): + scndir = globals()['scenario_path'] + if 'plugin_path' in globals(): + plgdir = globals()['plugin_path'] + # Update cachedir with python version-specific subfolder cachedir = os.path.join(cachedir, 'py%d' % sys.version_info[0]) globals()['cache_path'] = cachedir @@ -196,3 +217,10 @@ def save(fname=None, changes=None): file_out.write(f'{key} = {value}\n') return True, f'Saved settings to {fname}' + +def get_project_root() -> str: + ''' Return the absolute path of the project root. ''' + + # return root dir relative to this file, make sure you update it if this + # file is moved in the project directory + return str(Path(__file__).absolute().parent.parent) diff --git a/bluesky/simulation/screenio.py b/bluesky/simulation/screenio.py index 7752bd4e81..05ea4734f5 100644 --- a/bluesky/simulation/screenio.py +++ b/bluesky/simulation/screenio.py @@ -27,11 +27,13 @@ def __init__(self): # Screen state defaults self.def_pan = (0.0, 0.0) self.def_zoom = 1.0 + self.route_all = "" + # Screen state overrides per client self.client_pan = dict() self.client_zoom = dict() self.client_ar = dict() - self.route_acid = dict() + self.client_route = dict() # Dicts of custom aircraft and group colors self.custacclr = dict() @@ -58,12 +60,20 @@ def step(self): self.samplecount += 1 def reset(self): + self.client_pan = dict() + self.client_zoom = dict() + self.client_ar = dict() + self.client_route = dict() + self.route_all = '' self.custacclr = dict() self.custgrclr = dict() self.samplecount = 0 self.prevcount = 0 self.prevtime = 0.0 + self.def_pan = (0.0, 0.0) + self.def_zoom = 1.0 + # Communicate reset to gui bs.net.send_event(b'RESET', b'ALL') @@ -164,7 +174,11 @@ def trails(self,sw): def showroute(self, acid): ''' Toggle show route for this aircraft ''' - self.route_acid[stack.sender()] = acid + if not stack.sender(): + self.route_all = acid + self.client_route.clear() + else: + self.client_route[stack.sender()] = acid return True def addnavwpt(self, name, lat, lon): @@ -260,24 +274,42 @@ def send_aircraft_data(self): bs.net.send_stream(b'ACDATA', data) def send_route_data(self): - for sender, acid in self.route_acid.items(): - data = dict() - data['acid'] = acid - idx = bs.traf.id2idx(acid) - if idx >= 0: - route = bs.traf.ap.route[idx] - data['iactwp'] = route.iactwp - - # We also need the corresponding aircraft position - data['aclat'] = bs.traf.lat[idx] - data['aclon'] = bs.traf.lon[idx] - - data['wplat'] = route.wplat - data['wplon'] = route.wplon - - data['wpalt'] = route.wpalt - data['wpspd'] = route.wpspd - - data['wpname'] = route.wpname - - bs.net.send_stream(b'ROUTEDATA' + (sender or b'*'), data) # Send route data to GUI + ''' Send route data to client(s) ''' + # print(self.client_route, self.route_all) + # Case 1: A route is selected by one or more specific clients + if self.client_route: + for sender, acid in self.client_route.items(): + _sendrte(sender, acid) + # Check if there are other senders and also a scenario-selected route + if self.route_all: + remclients = bs.sim.clients.difference(self.client_route.keys()) + #print(bs.sim.clients, remclients) + for sender in remclients: + _sendrte(sender, self.route_all) + # Case 2: only a route selected from scenario file: + # Broadcast the same route to everyone + elif self.route_all: + _sendrte(b'*', self.route_all) + +def _sendrte(sender, acid): + ''' Local shorthand function to send route. ''' + data = dict() + data['acid'] = acid + idx = bs.traf.id2idx(acid) + if idx >= 0: + route = bs.traf.ap.route[idx] + data['iactwp'] = route.iactwp + + # We also need the corresponding aircraft position + data['aclat'] = bs.traf.lat[idx] + data['aclon'] = bs.traf.lon[idx] + + data['wplat'] = route.wplat + data['wplon'] = route.wplon + + data['wpalt'] = route.wpalt + data['wpspd'] = route.wpspd + + data['wpname'] = route.wpname + + bs.net.send_stream(b'ROUTEDATA' + (sender or b'*'), data) # Send route data to GUI diff --git a/bluesky/simulation/simulation.py b/bluesky/simulation/simulation.py index 8c0797a44b..289f9f6b2a 100644 --- a/bluesky/simulation/simulation.py +++ b/bluesky/simulation/simulation.py @@ -50,6 +50,9 @@ def __init__(self): # Flag indicating whether timestep can be varied to ensure realtime op self.rtmode = False + # Keep track of known clients + self.clients = set() + def step(self): ''' Perform a simulation timestep. ''' # Simulation starts as soon as there is traffic, or pending commands @@ -218,6 +221,8 @@ def event(self, eventname, eventdata, sender_rte): event_processed = True elif eventname == b'GETSIMSTATE': + # Add this client to the list of known clients + self.clients.add(sender_rte[-1]) # Send list of stack functions available in this sim to gui at start stackdict = {cmd : val.brief[len(cmd) + 1:] for cmd, val in bs.stack.get_commands().items()} shapes = [shape.raw for shape in areafilter.basic_shapes.values()] diff --git a/bluesky/stack/argparser.py b/bluesky/stack/argparser.py index c854983388..6e0709eb02 100644 --- a/bluesky/stack/argparser.py +++ b/bluesky/stack/argparser.py @@ -20,7 +20,7 @@ # re_getarg = re.compile(r'[\'"]?((?<=[\'"])[^\'"]+|(?= 0 and wpname not in bs.traf.ap.route[refdata.acidx].wpname: - raise ArgumentError(f'{wpname} not found in the route of {bs.traf.id[refdata.acidx]}') - return wpname, argstring - + if refdata.acidx >= 0 and wpname in bs.traf.ap.route[refdata.acidx].wpname or wpname == '*': + return wpname, argstring + raise ArgumentError(f'{wpname} not found in the route of {bs.traf.id[refdata.acidx]}') class WptArg(Parser): ''' Argument parser for waypoints. diff --git a/bluesky/stack/basecmds.py b/bluesky/stack/basecmds.py index a27a726108..13908a0fb1 100644 --- a/bluesky/stack/basecmds.py +++ b/bluesky/stack/basecmds.py @@ -5,7 +5,7 @@ import bluesky as bs from bluesky import settings from bluesky.core import select_implementation, simtime, varexplorer as ve -from bluesky.tools import geo, areafilter, plotter +from bluesky.tools import geo, aero, areafilter, plotter from bluesky.tools.calculator import calculator from bluesky.stack.cmdparser import append_commands @@ -56,44 +56,12 @@ def initbasecmds(): bs.net.addnodes, "Add a simulation instance/node", ], - "ADDWPT": [ - "ADDWPT acid, (wpname/lat,lon/FLYBY/FLYOVER/ TAKEOFF,APT/RWY),[alt,spd,afterwp]", - "acid,wpt,[alt,spd,wpinroute,wpinroute]", - # - # lambda *arg: short-hand for using function output as argument, equivalent with: - # - # def fun(idx, args): - # return bs.traf.ap.route[idx].addwptStack(idx, *args) - # fun(idx,*args) - # - lambda idx, *args: bs.traf.ap.route[idx].addwptStack(idx, *args), - "Add a waypoint to route of aircraft (FMS)", - ], - "AFTER": [ - "acid AFTER afterwp ADDWPT (wpname/lat,lon),[alt,spd]", - "acid,wpinroute,txt,wpt,[alt,spd]", - lambda idx, * \ - args: bs.traf.ap.route[idx].afteraddwptStack(idx, *args), - "After waypoint, add a waypoint to route of aircraft (FMS)", - ], "AIRWAY": [ "AIRWAY wp/airway", "txt", bs.traf.airwaycmd, "Get info on airway or connections of a waypoint", ], - "ALT": [ - "ALT acid, alt, [vspd]", - "acid,alt,[vspd]", - bs.traf.ap.selaltcmd, - "Altitude command (autopilot)", - ], - "AT": [ - "acid AT wpname [DEL] SPD/ALT/DO [spd/alt/command line]", - "acid,wpinroute,[txt,txt,...]", - lambda idx, *args: bs.traf.ap.route[idx].atwptStack(idx, *args), - "Edit, delete or show spd/alt constraints at a waypoint in the route", - ], "ATALT": [ "acid ATALT alt cmd ", "acid,alt,string", @@ -113,7 +81,7 @@ def initbasecmds(): "When a/c reaches given speed, execute a command cmd", ], "BANK": [ - "BANK bankangle[deg]", + "BANK acid bankangle[deg]", "acid,[float]", bs.traf.setbanklim, "Set or show bank limit for this vehicle", @@ -124,14 +92,6 @@ def initbasecmds(): bs.sim.batch, "Start a scenario file as batch simulation", ], - - "BEFORE": [ - "acid BEFORE beforewp ADDWPT (wpname/lat,lon),[alt,spd]", - "acid,wpinroute,txt,wpt,[alt,spd]", - lambda idx, * \ - args: bs.traf.ap.route[idx].beforeaddwptStack(idx, *args), - "Before waypoint, add a waypoint to route of aircraft (FMS)", - ], "BLUESKY": ["BLUESKY", "", singbluesky, "Sing"], "BENCHMARK": [ "BENCHMARK [scenfile,time]", @@ -153,6 +113,14 @@ def initbasecmds(): calculator, "Simple in-line math calculator, evaluates expression", ], + "CASMACHTHR": [ + "CASMACHTHR threshold", + "float", + aero.casmachthr, + """Set a threshold below which speeds should be considered as Mach numbers + in CRE(ATE), ADDWPT, and SPD commands. Set to zero if speeds should + never be considered as Mach number(e.g., when simulating drones).""" + ], "CD": [ "CD [path]", "[word]", @@ -167,18 +135,31 @@ def initbasecmds(): ), "Define a circle-shaped area", ], + "CLRCRECMD": [ + "CLRCRECMD", + "", + bs.traf.clrcrecmd, + "CLRCRECMD will clear CRECMD list of commands a/c creation", + ], "COLOR": [ "COLOR name,color (named color or r,g,b)", "txt,color", bs.scr.color, "Set a custom color for an aircraft or shape", + ], "CRE": [ "CRE acid,type,lat,lon,hdg,alt,spd", - "txt,txt,latlon,hdg,alt,spd", + "txt,txt,latlon,[hdg,alt,spd]", bs.traf.cre, "Create an aircraft", ], + "CRECMD": [ + "CRECMD cmdline (to be added after a/c id )", + "string", + bs.traf.crecmd, + "Add a command for each aircraft to be issued after creation of aircraft", + ], "CRECONFS": [ "CRECONFS id, type, targetid, dpsi, cpa, tlos_hor, dH, tlos_ver, spd", "txt,txt,acid,hdg,float,time,[alt,time,spd]", @@ -197,7 +178,7 @@ def initbasecmds(): bs.navdb.defwpt, "Define a waypoint only for this scenario/run", ], - "DEL": [ + "DEL": [ "DEL acid/ALL/WIND/shape", "acid/txt,...", lambda *a: bs.traf.wind.clear() @@ -209,30 +190,7 @@ def initbasecmds(): else bs.traf.delete(a), "Delete command (aircraft, wind, area)", ], - "DELRTE": [ - "DELRTE acid", - "acid", - lambda idx: bs.traf.ap.route[idx].delrte(idx), - "Delete for this a/c the complete route/dest/orig (FMS)", - ], - "DELWPT": [ - "DELWPT acid,wpname", - "acid,wpinroute", - lambda idx, wpname: bs.traf.ap.route[idx].delwpt(wpname, idx), - "Delete a waypoint from a route (FMS)", - ], - "DEST": [ - "DEST acid, latlon/airport", - "acid,wpt", - lambda idx, *args: bs.traf.ap.setdestorig("DEST", idx, *args), - "Set destination of aircraft, aircraft wil fly to this airport", - ], - "DIRECT": [ - "DIRECT acid wpname", - "acid,txt", - lambda idx, wpname: bs.traf.ap.route[idx].direct(idx, wpname), - "Go direct to specified waypoint in route (FMS)", - ], + "DIST": [ "DIST lat0, lon0, lat1, lon1", "latlon,latlon", @@ -257,12 +215,6 @@ def initbasecmds(): bs.sim.set_dtmult, "Sel multiplication factor for fast-time simulation", ], - "DUMPRTE": [ - "DUMPRTE acid", - "acid", - lambda idx: bs.traf.ap.route[idx].dumpRoute(idx), - "Write route to output/routelog.txt", - ], "ECHO": [ "ECHO txt", "string", @@ -287,12 +239,7 @@ def initbasecmds(): lambda flag, *args: bs.sim.ff(*args) if flag else bs.op(), "Legacy function for TMX compatibility", ], - "GETWIND": [ - "GETWIND lat,lon,[alt]", - "latlon,[alt]", - bs.traf.wind.get, - "Get wind at a specified position (and optionally at altitude)", - ], + "GROUP": [ "GROUP [grname, (areaname OR acid,...) ]", "[txt,acid/txt,...]", @@ -302,12 +249,6 @@ def initbasecmds(): + "Returns list of aircraft in group when only a groupname is passed.\n" + "A group is created when a group with the given name doesn't exist yet.", ], - "HDG": [ - "HDG acid,hdg (deg,True or Magnetic)", - "acid,hdg", - bs.traf.ap.selhdgcmd, - "Heading command (autopilot)", - ], "HOLD": ["HOLD", "", bs.sim.hold, "Pause(hold) simulation"], "IMPLEMENTATION": [ "IMPLEMENTATION [base, implementation]", @@ -333,18 +274,6 @@ def initbasecmds(): lambda name, *coords: areafilter.defineArea(name, "LINE", coords), "Draw a line on the radar screen", ], - "LISTRTE": [ - "LISTRTE acid, [pagenr]", - "acid,[int]", - lambda idx, *args: bs.traf.ap.route[idx].listrte(idx, *args), - "Show list of route in window per page of 5 waypoints", - ], - "LNAV": [ - "LNAV acid,[ON/OFF]", - "acid,[onoff]", - bs.traf.ap.setLNAV, - "LNAV (lateral FMS mode) switch for autopilot", - ], "LSVAR": [ "LSVAR path.to.variable", "[word]", @@ -381,24 +310,12 @@ def initbasecmds(): bs.traf.setnoise, "Turbulence/noise switch", ], - "NOM": [ - "NOM acid", - "acid", - bs.traf.nom, - "Set nominal acceleration for this aircraft (perf model)", - ], "OP": [ "OP", "", bs.sim.op, "Start/Run simulation or continue after hold" ], - "ORIG": [ - "ORIG acid, latlon/airport", - "acid,wpt", - lambda *args: bs.traf.ap.setdestorig("ORIG", *args), - "Set origin airport of aircraft", - ], "PAN": [ "PAN latlon/acid/airport/waypoint/LEFT/RIGHT/ABOVE/DOWN", "pandir/latlon", @@ -444,24 +361,12 @@ def initbasecmds(): bs.sim.realtime, "En-/disable realtime running allowing a variable timestep."], "RESET": ["RESET", "", bs.sim.reset, "Reset simulation"], - "RTA": [ - "RTA acid,wpinroute,RTAtime", - "acid,wpinroute,txt", - lambda idx, *args: bs.traf.ap.route[idx].SetRTA(idx, *args), - "Set required time of arrival (RTA) at waypoint in route", - ], "SEED": [ "SEED value", "int", bs.sim.setseed, "Set seed for all functions using a randomizer (e.g.mcre,noise)", ], - "SPD": [ - "SPD acid,spd (CAS-kts/Mach)", - "acid,spd", - bs.traf.ap.selspdcmd, - "Speed command (autopilot)", - ], "SSD": [ "SSD ALL/CONFLICTS/OFF or SSD acid0, acid1, ...", "txt,[...]", @@ -499,25 +404,7 @@ def initbasecmds(): bs.traf.groups.ungroup, "Remove aircraft from a group", ], - "VNAV": [ - "VNAV acid,[ON/OFF]", - "acid,[onoff]", - bs.traf.ap.setVNAV, - "Switch on/off VNAV mode, the vertical FMS mode (autopilot)", - ], - "VS": [ - "VS acid,vspd (ft/min)", - "acid,vspd", - bs.traf.ap.selvspdcmd, - "Vertical speed command (autopilot)", - ], - "WIND": [ - "WIND lat,lon,alt/*,dir,spd,[alt,dir,spd,alt,...]", - # last 3 args are repeated - "latlon,[alt],float,alt/float,...,", - bs.traf.wind.add, - "Define a wind vector as part of the 2D or 3D wind field", - ], + "ZOOM": [ "ZOOM IN/OUT or factor", "float/txt", @@ -549,15 +436,10 @@ def initbasecmds(): "CLOSE": "QUIT", "DEBUG": "CALC", "DELETE": "DEL", - "DELWP": "DELWPT", - "DELROUTE": "DELRTE", - "DIRECTTO": "DIRECT", - "DIRTO": "DIRECT", "DISP": "SWRAD", "END": "QUIT", "EXIT": "QUIT", "FWD": "FF", - "HEADING": "HDG", "IMPL": "IMPLEMENTATION", "IMPLEMENT": "IMPLEMENTATION", "LINES": "POLYLINE", @@ -574,12 +456,9 @@ def initbasecmds(): "RUN": "OP", "RUNWAYS": "POS", "SAVE": "SAVEIC", - "SPEED": "SPD", "START": "OP", "TRAILS": "TRAIL", - "TURN": "HDG", - "VAR": "MAGVAR", - "WPTYPE":"ADDWPT" + "VAR": "MAGVAR" } append_commands(cmddict, synonyms) diff --git a/bluesky/stack/cmdparser.py b/bluesky/stack/cmdparser.py index 70eca03ae9..063281b516 100644 --- a/bluesky/stack/cmdparser.py +++ b/bluesky/stack/cmdparser.py @@ -1,6 +1,6 @@ ''' Stack Command implementation. ''' import inspect - +import sys, os from bluesky.stack.argparser import Parameter, getnextarg, ArgumentError @@ -159,9 +159,21 @@ def callback(self, function): def helptext(self, subcmd=''): ''' Return complete help text. ''' - msg = f'{self.help}\nUsage:\n{self.brief}' + msg = f'
{self.help}
\nUsage:\n{self.brief}' if self.aliases: msg += ('\nCommand aliases: ' + ','.join(self.aliases)) + if self._callback.__name__ == '': + msg += '\nAnonymous (lambda) function, implemented in ' + else: + msg += f'\nFunction {self._callback.__name__}(), implemented in ' + if hasattr(self._callback, '__code__'): + fname = self._callback.__code__.co_filename + fname_stripped = fname.replace(os.getcwd(), '').lstrip('/') + firstline = self._callback.__code__.co_firstlineno + msg += f'{fname_stripped} on line {firstline}' + else: + msg += f'module {self._callback.__module__}' + return msg def brieftext(self): diff --git a/bluesky/tools/__init__.py b/bluesky/tools/__init__.py index a142390833..91f97b75cb 100644 --- a/bluesky/tools/__init__.py +++ b/bluesky/tools/__init__.py @@ -8,12 +8,13 @@ except ImportError: from . import geo print('Using Python-based geo functions') - print("Reading magnetic variation data") - geo.initdecl_data() else: from . import geo print('Using Python-based geo functions') - print("Reading magnetic variation data") - geo.initdecl_data() from . import cachefile + + +def init(): + print("Reading magnetic variation data") + geo.initdecl_data() diff --git a/bluesky/tools/aero.py b/bluesky/tools/aero.py index 84cb1ecb1c..6368e049ca 100644 --- a/bluesky/tools/aero.py +++ b/bluesky/tools/aero.py @@ -3,6 +3,10 @@ from math import * import numpy as np +from bluesky import settings + + +settings.set_variable_defaults(casmach_threshold=2.0) # International standard atmpshere only up to 72000 ft / 22 km # @@ -27,6 +31,25 @@ beta = -0.0065 # [K/m] ISA temp gradient below tropopause Rearth = 6371000. # m Average earth radius a0 = np.sqrt(gamma*R*T0) # sea level speed of sound ISA +casmach_thr = settings.casmach_threshold # Threshold below which speeds should + # be considered as Mach numbers in casormach* functions + + +def casmachthr(threshold:float=None): + """ CASMACHTHR threshold + + Set a threshold below which speeds should be considered as Mach numbers + in CRE(ATE), ADDWPT, and SPD commands. Set to zero if speeds should + never be considered as Mach number (e.g., when simulating drones). + + Argument: + - threshold: CAS speed threshold [m/s] + """ + if threshold is None: + return True, f'CASMACHTHR: The current CAS/Mach threshold is {casmach_thr} m/s ({casmach_thr / kts} kts' + + globals()['casmach_thr'] = threshold + return True, f'CASMACHTHR: Set CAS/Mach threshold to {threshold}' # @@ -59,7 +82,17 @@ # ------------------------------------------------------------------------------ # Vectorized aero functions # ------------------------------------------------------------------------------ -def vatmos(h): # h in m +def vatmos(h): + """ Calculate atmospheric pressure, density, and temperature for a given altitude. + + Arguments: + - h: Altitude [m] + + Returns: + - p: Pressure [Pa] + - rho: Density [kg / m3] + - T: Temperature [K] + """ # Temp T = vtemp(h) @@ -74,23 +107,55 @@ def vatmos(h): # h in m return p, rho, T -def vtemp(h): # h [m] +def vtemp(h): + """ Calculate atmospheric temperature for a given altitude. + + Arguments: + - h: Altitude [m] + + Returns: + - T: Temperature [K] + """ T = np.maximum(288.15 - 0.0065 * h, Tstrat) return T # Atmos wrappings: -def vpressure(h): # h [m] - p, r, T = vatmos(h) +def vpressure(h): + """ Calculate atmospheric pressure for a given altitude. + + Arguments: + - h: Altitude [m] + + Returns: + - p: Pressure [Pa] + """ + p, _, _ = vatmos(h) return p -def vdensity(h): # air density at given altitude h [m] - p, r, T = vatmos(h) +def vdensity(h): + """ Calculate atmospheric density for a given altitude. + + Arguments: + - h: Altitude [m] + + Returns: + - rho: Density [kg / m3] + """ + _, r, _ = vatmos(h) return r -def vvsound(h): # Speed of sound for given altitude h [m] +def vvsound(h): + """ Calculate the speed of sound for a given altitude. + + Arguments: + - h: Altitude [m] + + Returns: + - a: Speed of sound [m/s] + """ T = vtemp(h) a = np.sqrt(gamma * R * T) return a @@ -98,47 +163,95 @@ def vvsound(h): # Speed of sound for given altitude h [m] # ---------Speed conversions---h in [m]------------------ def vtas2mach(tas, h): - """ True airspeed (tas) to mach number conversion """ + """ True airspeed (tas) to mach number conversion for numpy arrays. + + Arguments: + - tas: True airspeed [m/s] + - h: Altitude [m] + + Returns: + - M: Mach number [-] + """ a = vvsound(h) - M = tas / a - return M + mach = tas / a + return mach -def vmach2tas(M, h): - """ True airspeed (tas) to mach number conversion """ +def vmach2tas(mach, h): + """ True airspeed (tas) to mach number conversion for numpy arrays. + + Arguments: + - mach: Mach number [-] + - h: Altitude [m] + + Returns: + - tas: True airspeed [m/s] + """ a = vvsound(h) - tas = M * a + tas = mach * a return tas def veas2tas(eas, h): - """ Equivalent airspeed to true airspeed """ + """ Equivalent airspeed to true airspeed conversion for numpy arrays. + + Arguments: + - eas: Equivalent airspeed [m/s] + - h: Altitude [m] + + Returns: + - tas: True airspeed [m/s] + """ rho = vdensity(h) tas = eas * np.sqrt(rho0 / rho) return tas def vtas2eas(tas, h): - """ True airspeed to equivent airspeed """ + """ True airspeed to equivent airspeed conversion for numpy arrays. + + Arguments: + - tas: True airspeed [m/s] + - h: Altitude [m] + + Returns: + - eas: Equivalent airspeed [m/s] + """ rho = vdensity(h) - eas = tas*np.sqrt(rho / rho0) + eas = tas * np.sqrt(rho / rho0) return eas def vcas2tas(cas, h): - """ cas2tas conversion both m/s """ - p, rho, T = vatmos(h) - qdyn = p0*((1.+rho0*cas*cas/(7.*p0))**3.5-1.) - tas = np.sqrt(7.*p/rho*((1.+qdyn/p)**(2./7.)-1.)) + """ Calibrated to true airspeed conversion for numpy arrays. + + Arguments: + - cas: Calibrated airspeed [m/s] + - h: Altitude [m] + + Returns: + - tas: True airspeed [m/s] + """ + p, rho, _ = vatmos(h) + qdyn = p0 * ((1.0 + rho0 * cas * cas / (7.0 * p0)) ** 3.5 - 1.0) + tas = np.sqrt(7.0 * p / rho * ((1.0 + qdyn / p) ** (2.0 / 7.0) - 1.0)) # cope with negative speed - tas = np.where(cas<0, -1*tas, tas) + tas = np.where(cas < 0, -1 * tas, tas) return tas def vtas2cas(tas, h): - """ tas2cas conversion both m/s """ - p, rho, T = vatmos(h) + """ True to calibrated airspeed conversion for numpy arrays. + + Arguments: + - tas: True airspeed [m/s] + - h: Altitude [m] + + Returns: + cas: Calibrated airspeed [m/s] + """ + p, rho, _ = vatmos(h) qdyn = p*((1.+rho*tas*tas/(7.*p))**3.5-1.) cas = np.sqrt(7.*p0/rho0*((qdyn/p0+1.)**(2./7.)-1.)) @@ -147,43 +260,87 @@ def vtas2cas(tas, h): return cas -def vmach2cas(M, h): - """ Mach to CAS conversion """ - tas = vmach2tas(M, h) +def vmach2cas(mach, h): + """ Mach to calibrated airspeed conversion for numpy arrays. + + Arguments: + - mach: Mach number [-] + - h: Altitude [m] + + Returns: + - cas: Calibrated airspeed [m/s] + """ + tas = vmach2tas(mach, h) cas = vtas2cas(tas, h) return cas def vcas2mach(cas, h): - """ CAS to Mach conversion """ + """ Calibrated airspeed to Mach conversion for numpy arrays. + + Arguments: + - cas: Calibrated airspeed [m/s] + - h: Altitude [m] + + Returns: + - mach: Mach number [-] + """ tas = vcas2tas(cas, h) M = vtas2mach(tas, h) return M def vcasormach(spd, h): - ismach = np.logical_and(spd > 0.1, spd < 2.0) + """ Interpret input speed as either CAS or a Mach number, and return TAS, CAS, and Mach. + + Arguments: + - spd: Airspeed. Interpreted as Mach number [-] when its value is below the + CAS/Mach threshold. Otherwise interpreted as CAS [m/s]. + - h: Altitude [m] + + Returns: + - tas: True airspeed [m/s] + - cas: Calibrated airspeed [m/s] + - mach: Mach number [-] + """ + ismach = np.logical_and(spd > 0.1, spd < casmach_thr) tas = np.where(ismach, vmach2tas(spd, h), vcas2tas(spd, h)) cas = np.where(ismach, vtas2cas(tas, h), spd) - m = np.where(ismach, spd, vtas2mach(tas, h)) - return tas, cas, m + mach = np.where(ismach, spd, vtas2mach(tas, h)) + return tas, cas, mach + def vcasormach2tas(spd, h): - tas = np.where(np.abs(spd) < 2.0, vmach2tas(spd, h), vcas2tas(spd, h)) - return tas + """ Interpret input speed as either CAS or a Mach number, and return TAS. + + Arguments: + - spd: Airspeed. Interpreted as Mach number [-] when its value is below the + CAS/Mach threshold. Otherwise interpreted as CAS [m/s]. + - h: Altitude [m] + + Returns: + - tas: True airspeed [m/s] + """ + ismach = np.logical_and(spd > 0.1, spd < casmach_thr) + return np.where(ismach, vmach2tas(spd, h), vcas2tas(spd, h)) + +def crossoveralt(cas, mach): + """ Calculate crossover altitude for given CAS and Mach number. -def crossoveralt(vcas, mach): - ''' Calculate crossover altitude for given CAS and Mach number. - Calculates the altitude where the given CAS and Mach values correspond to the same true airspeed. (BADA User Manual 3.12, p. 12) - Returns: altitude in meters. - ''' + Arguments: + - cas: Calibrated airspeed [m/s] + - mach: Mach number [-] + + Returns: + - Altitude [m]. + """ # Delta: pressure ratio at the transition altitude - delta = (((1.0 + 0.5 * (gamma - 1.0) * (vcas / a0) ** 2) ** + delta = (((1.0 + 0.5 * (gamma - 1.0) * (cas / a0) ** 2) ** (gamma / (gamma - 1.0)) - 1.0) / ((1.0 + 0.5 * (gamma - 1.0) * mach ** 2) ** (gamma / (gamma - 1.0)) - 1.0)) @@ -387,7 +544,7 @@ def cas2mach(cas, h): return M def casormach(spd,h): - if 0.1 < spd < 2.0: + if 0.1 < spd < casmach_thr: # Interpret spd as Mach number tas = mach2tas(spd, h) cas = mach2cas(spd, h) @@ -400,7 +557,7 @@ def casormach(spd,h): return tas, cas, m def casormach2tas(spd,h): - if 0.1 < spd < 2.0: + if 0.1 < spd < casmach_thr: # Interpret spd as Mach number tas = mach2tas(spd, h) else: diff --git a/bluesky/tools/areafilter.py b/bluesky/tools/areafilter.py index db958d8114..49e0508f5c 100644 --- a/bluesky/tools/areafilter.py +++ b/bluesky/tools/areafilter.py @@ -1,7 +1,7 @@ """Area filter module""" -from matplotlib.path import Path from weakref import WeakValueDictionary import numpy as np +from matplotlib.path import Path from rtree import index import bluesky as bs from bluesky.tools.geo import kwikdist @@ -40,6 +40,9 @@ def defineArea(areaname, areatype, coordinates, top=1e9, bottom=-1e9): # Pass the shape on to the screen object bs.scr.objappend(areatype, areaname, coordinates) + return True, f'Created {areatype} {areaname}' + + def checkInside(areaname, lat, lon, alt): """ Check if points with coordinates lat, lon, alt are inside area with name 'areaname'. Returns an array of booleans. True == Inside""" @@ -57,11 +60,12 @@ def deleteArea(areaname): def reset(): """ Clear all data. """ basic_shapes.clear() + Shape.reset() def get_intersecting(lat0, lon0, lat1, lon1): ''' Return all shapes that intersect with a specified rectangular area. - + Arguments: - lat0/1, lon0/1: Coordinates of the top-left and bottom-right corner of the intersection area. @@ -72,7 +76,7 @@ def get_intersecting(lat0, lon0, lat1, lon1): def get_knearest(lat0, lon0, lat1, lon1, k=1): ''' Return the k nearest shapes to a specified rectangular area. - + Arguments: - lat0/1, lon0/1: Coordinates of the top-left and bottom-right corner of the relevant area. @@ -83,6 +87,9 @@ def get_knearest(lat0, lon0, lat1, lon1, k=1): class Shape: + ''' + Base class of BlueSky shapes + ''' # Global counter to keep track of used shape ids max_area_id = 0 @@ -93,6 +100,13 @@ class Shape: # RTree of all areas for efficient geospatial searching areatree = index.Index() + @classmethod + def reset(cls): + ''' Reset shape data when simulation is reset. ''' + # Weak dicts and areatree should be cleared automatically + # Reset max area id + cls.max_area_id = 0 + def __init__(self, name, coordinates, top=1e9, bottom=-1e9): self.raw = dict(name=name, shape=self.kind(), coordinates=coordinates) self.name = name @@ -102,7 +116,7 @@ def __init__(self, name, coordinates, top=1e9, bottom=-1e9): lat = coordinates[::2] lon = coordinates[1::2] self.bbox = [min(lat), min(lon), max(lat), max(lon)] - + # Global weak reference and tree storage self.area_id = Shape.max_area_id Shape.max_area_id += 1 @@ -110,12 +124,18 @@ def __init__(self, name, coordinates, top=1e9, bottom=-1e9): Shape.areas_by_name[self.name] = self Shape.areatree.insert(self.area_id, self.bbox) - def __delete__(self): + def __del__(self): # Objects are removed automatically from the weak-value dicts, # but need to be manually removed from the rtree Shape.areatree.delete(self.area_id, self.bbox) def checkInside(self, lat, lon, alt): + ''' Returns True (or boolean array) if coordinate lat, lon, alt lies + within this shape. + + Reimplement this function in the derived shape classes for this to + work. + ''' return False def _str_vrange(self): @@ -139,6 +159,7 @@ def kind(cls): class Line(Shape): + ''' A line shape ''' def __init__(self, name, coordinates): super().__init__(name, coordinates) @@ -149,6 +170,7 @@ def __str__(self): class Box(Shape): + ''' A box shape ''' def __init__(self, name, coordinates, top=1e9, bottom=-1e9): super().__init__(name, coordinates, top, bottom) # Sort the order of the corner points @@ -163,8 +185,8 @@ def checkInside(self, lat, lon, alt): ((self.bottom <= alt) & (alt <= self.top)) - class Circle(Shape): + ''' A circle shape ''' def __init__(self, name, coordinates, top=1e9, bottom=-1e9): super().__init__(name, coordinates, top, bottom) self.clat = coordinates[0] @@ -183,6 +205,7 @@ def __str__(self): class Poly(Shape): + ''' A polygon shape ''' def __init__(self, name, coordinates, top=1e9, bottom=-1e9): super().__init__(name, coordinates, top, bottom) self.border = Path(np.reshape(coordinates, (len(coordinates) // 2, 2))) diff --git a/bluesky/tools/geo.py b/bluesky/tools/geo.py index 5cac4b12a3..d795904f4e 100644 --- a/bluesky/tools/geo.py +++ b/bluesky/tools/geo.py @@ -1,7 +1,12 @@ """ This module defines a set of standard geographic functions and constants for easy use in BlueSky. """ +from os import path import numpy as np from math import * + +from bluesky import settings + + # Constants nm = 1852. # m 1 nautical mile @@ -89,7 +94,6 @@ def qdrdist(latd1, lond1, latd2, lond2): lat2 = np.radians(latd2) lon2 = np.radians(lond2) - #root = sin1 * sin1 + coslat1 * coslat2 * sin2 * sin2 #d = 2.0 * r * np.arctan2(np.sqrt(root) , np.sqrt(1.0 - root)) # d =2.*r*np.arcsin(np.sqrt(sin1*sin1 + coslat1*coslat2*sin2*sin2)) @@ -100,15 +104,15 @@ def qdrdist(latd1, lond1, latd2, lond2): # Bearing from Ref. http://www.movable-type.co.uk/scripts/latlong.html - sin1 = np.sin(0.5 * (lat2 - lat1)) - sin2 = np.sin(0.5 * (lon2 - lon1)) + # sin1 = np.sin(0.5 * (lat2 - lat1)) + # sin2 = np.sin(0.5 * (lon2 - lon1)) coslat1 = np.cos(lat1) coslat2 = np.cos(lat2) - qdr = np.degrees(np.arctan2(np.sin(lon2 - lon1) * coslat2, - coslat1 * np.sin(lat2) - np.sin(lat1) * coslat2 * np.cos(lon2 - lon1))) + coslat1 * np.sin(lat2) - + np.sin(lat1) * coslat2 * np.cos(lon2 - lon1))) return qdr, d/nm @@ -169,7 +173,7 @@ def qdrdist_matrix(lat1, lon1, lat2, lon2): def latlondist(latd1, lond1, latd2, lond2): - """ Calculates distance using haversine formulae and avaerage r from wgs'84 + """ Calculates only distance using haversine notation of the same formulae and average r from wgs'84 Input: two lat/lon positions in degrees Out: @@ -284,10 +288,10 @@ def qdrpos(latd1, lond1, qdr, dist): # Calculate new position lat2 = np.arcsin(np.sin(lat1)*np.cos(dist/R) + - np.cos(lat1)*np.sin(dist/R)*np.cos(np.radians(qdr))) + np.cos(lat1)*np.sin(dist/R)*np.cos(np.radians(qdr))) lon2 = lon1 + np.arctan2(np.sin(np.radians(qdr))*np.sin(dist/R)*np.cos(lat1), - np.cos(dist/R) - np.sin(lat1)*np.sin(lat2)) + np.cos(dist/R) - np.sin(lat1)*np.sin(lat2)) return np.degrees(lat2), np.degrees(lon2) @@ -389,11 +393,10 @@ def kwikpos(latd1, lond1, qdr, dist): return latd2,lond2 def magdec(latd, lond): - global decl_read, decl_lat_lon """ Gives magnetic declination (also called magnetic variation) at given position, interpolated from an external data table. The interpolation is - done using an object of scipy.interpolate.RectSphereBivariateSpline + done using an object of scipy.interpolate.RectSphereBivariateSpline interpo_dec, which is generated by the function init_interpo_dec() defined in the same module (geo.py). The interpo_dec object rvaluates the magnetic declination at any latitude and longitude (latd, lond). @@ -403,9 +406,9 @@ def magdec(latd, lond): init_interpo_dec() will be called. The arguments of interpo_dec.ev() are 1-D arrays of latitudes and longitudes in radians, with latitude ranging from 0 to pi and longitude - ranging from 0 to 2pi. - In: - latd, lond [deg] Position at which the magnetic declination is + ranging from 0 to 2pi. + In: + latd, lond [deg] Position at which the magnetic declination is evaluated (floats) Out: d_hdg [deg] Magnetic declination, the angle of difference @@ -414,19 +417,22 @@ def magdec(latd, lond): (10 deg), then a compass at that location pointing north (magnetic) would actually align 10 deg W of true North. True North would be 10 deg E relative to - the magnetic North direction given by the compass. + the magnetic North direction given by the compass. Declination varies with location and slowly changes in time. Referenced from https://www.ngdc.noaa.gov/geomag/calculators/help/igrfgridHelp.html - In short, magnetic heading = true heading - d_hdg, (Reminder MTV : M = T - V) + In short, magnetic heading = true heading - d_hdg, + (Reminder MTV : M = T - V) or, true heading = magnetic heading + d_hdg. Created by : Yaofu Zhou Modified by J.M. Hoekstra - Reason: Segmentation fault caused by Scipy's BiVariateSpline interpolation for some data on some machines, so it was - changed to linear interpolation. Difference in methods has been inspected: it is way less than the inaccuracy - of the actual data. Axes were regularly spaced at one degree. The direct manual linear interpolation also 6 x times faster. - TODO: Relocate datafile (make part of navdata?) and move reading of datafile to init phase + Reason: Segmentation fault caused by Scipy's BiVariateSpline interpolation + for some data on some machines, so it was changed to linear interpolation. + Difference in methods has been inspected: it is way less than the inaccuracy + of the actual data. Axes were regularly spaced at one degree. The direct + manual linear interpolation also 6 x times faster. """ + global decl_read, decl_lat_lon if not decl_read: initdecl_data() decl_read = True @@ -487,9 +493,9 @@ def initdecl_data(): # lat : 89 ... -90 # Lon: -180 ... 179 global decl_read, decl_lat_lon - - dec_table = np.genfromtxt("bluesky/tools/geo_declination_data.csv",\ + dec_table = np.genfromtxt(path.join(settings.navdata_path, 'geo_declination_data.csv'), comments='#',delimiter=",") + decl = dec_table[:,4] # <----lon ----> @@ -513,5 +519,6 @@ def initdecl_data(): # Command MAGVAR to get magnetic variation at position lat,lon def magdeccmd(latdeg,londeg): + ''' MAGVAR Get magnetic variation at position lat/lon. ''' return True,"Magnetic variation at "+str(latdeg)+","+str(londeg) + \ " = "+str(magdec(latdeg,londeg))+" deg" diff --git a/bluesky/traffic/activewpdata.py b/bluesky/traffic/activewpdata.py index dce024ff4f..2209b7dde6 100644 --- a/bluesky/traffic/activewpdata.py +++ b/bluesky/traffic/activewpdata.py @@ -10,6 +10,11 @@ def __init__(self): with self.settrafarrays(): self.lat = np.array([]) # [deg] Active WP latitude self.lon = np.array([]) # [deg] Active WP longitude + self.nextturnlat= np.array([]) # [deg] Next turn WP latitude + self.nextturnlon= np.array([]) # [deg] Next turn WP longitude + self.nextturnspd= np.array([]) # [m/s] Next turn WP speed + self.nextturnrad= np.array([]) # [m] Next turn WP turn radius + self.nextturnidx= np.array([]) # [-] Next turn WP index self.nextaltco = np.array([]) # [m] Altitude to arrive at after distance xtoalt self.xtoalt = np.array([]) # [m] Distance to next altitude constraint self.nextspd = np.array([]) # [CAS[m/s]/Mach] save speed from next wp for next leg @@ -18,21 +23,29 @@ def __init__(self): self.vs = np.array([]) # [m/s] Active vertical speed to use self.turndist = np.array([]) # [m] Distance when to turn to next waypoint self.flyby = np.array([]) # Flyby switch, when False, flyover (turndist=0.0) - self.flyturn = np.array([]) # Flyturn switch, when False, when Fkase, use flyby/flyover + self.flyturn = np.array([]) # Flyturn switch, customised turn parameters; when False, use flyby/flyover self.turnrad = np.array([]) # Flyturn turn radius (<0 => not specified) self.turnspd = np.array([]) # [m/s, CAS] Flyturn turn speed for next turn (<=0 => not specified) self.oldturnspd = np.array([]) # [TAS, m/s] Flyturn turn speed for previous turn (<=0 => not specified) self.turnfromlastwp = np.array([]) # Currently in flyturn-mode from last waypoint (old turn, beginning of leg) self.turntonextwp = np.array([]) # Currently in flyturn-mode to next waypoint (new flyturn mode, end of leg) - self.torta = np.array([]) # [s] NExt req Time of Arrival (RTA) (-999. = None) - self.xtorta = np.array([]) # [m] distance ot next RTA + self.torta = np.array([]) # [s] Next req Time of Arrival (RTA) (-999. = None) + self.xtorta = np.array([]) # [m] distance to next RTA self.next_qdr = np.array([]) # [deg] track angle of next leg - self.swlastwp = np.array([],dtype=np.bool) # switch indicsting this is the last waypoint + self.swlastwp = np.array([],dtype=np.bool) # switch indicating this is the last waypoint + self.curlegdir = np.array([]) # [deg] direction to active waypoint upon activation + self.curleglen = np.array([]) # [deg] direction to active waypoint upon activation def create(self, n=1): super().create(n) # LNAV route navigation - self.lat[-n:] = 89.99 # [deg]Active WP latitude + self.lat[-n:] = 0. # [deg]Active WP latitude + self.lon[-n:] = 0. # [deg]Active WP longitude + self.nextturnlat[-n:]= 0 # [deg] Next turn WP latitude + self.nextturnlon[-n:]= 0 # [deg] Next turn WP longitude + self.nextturnspd[-n:]= -999. # [m/s] Next turn WP speed + self.nextturnrad[-n:]= -999. # [m] Next turn WP radius + self.nextturnidx[-n:]= -999. # [-] Next turn WP index self.nextspd[-n:] = -999. # [CAS[m/s]/Mach]Next leg speed from current WP self.spd[-n:] = -999. # [CAS[m/s]/Mach]Active WP speed self.spdcon[-n:] = -999. # [CAS[m/s]/Mach]Active WP speed constraint @@ -47,9 +60,10 @@ def create(self, n=1): self.torta[-n:] = -999.0 # [s] Req Time of Arrival (RTA) for next wp (-999. = None) self.xtorta[-n:] = 0.0 # Distance to next RTA self.next_qdr[-n:] = -999.0 # [deg] bearing next leg - #self.curlegdir[-n:] = self.swlastwp[-n:] = False # Switch indicating active waypoint is last waypoint - + self.curlegdir[-n:] = -999.0 # [deg] direction to active waypoint upon activation + self.curleglen[-n:] = -999.0 # [nm] distance to active waypoint upon activation + def Reached(self, qdr, dist, flyby, flyturn, turnradnm,swlastwp): # Calculate distance before waypoint where to start the turn # Note: this is a vectorized function, called with numpy traffic arrays @@ -62,7 +76,7 @@ def Reached(self, qdr, dist, flyby, flyturn, turnradnm,swlastwp): # First calculate turn distance next_qdr = np.where(self.next_qdr < -900., qdr, self.next_qdr) turntas = np.where(bs.traf.actwp.turnspd<0.0,bs.traf.tas,bs.traf.actwp.turnspd) - flybyturndist,turnrad = self.calcturn(turntas,bs.traf.bank,qdr,next_qdr,turnradnm) + flybyturndist,turnrad = self.calcturn(turntas,bs.traf.ap.bankdef,qdr,next_qdr,turnradnm) # Turb dist iz ero for flyover, calculated distance for others self.turndist = np.logical_or(flyby,flyturn)*flybyturndist diff --git a/bluesky/traffic/asas/mvp.py b/bluesky/traffic/asas/mvp.py index 5ab0cc849e..b113b42ef6 100644 --- a/bluesky/traffic/asas/mvp.py +++ b/bluesky/traffic/asas/mvp.py @@ -5,6 +5,7 @@ class MVP(ConflictResolution): + ''' Conflict resolution using the Modified Voltage Potential Method. ''' def __init__(self): super().__init__() # [-] switch to limit resolution to the horizontal direction @@ -16,21 +17,6 @@ def __init__(self): # [-] switch to limit resolution to the vertical direction self.swresovert = False - mvp_stackfuns = { - "RMETHH": [ - "RMETHH [method]", - "[txt]", - self.setresometh, - "Set resolution method to be used horizontally", - ], - "RMETHV": [ - "RMETHV [method]", - "[txt]", - self.setresometv, - "Set resolution method to be used vertically", - ]} - stack.append_commands(mvp_stackfuns) - def setprio(self, flag=None, priocode=''): '''Set the prio switch and the type of prio ''' if flag is None: @@ -49,6 +35,7 @@ def setprio(self, flag=None, priocode=''): return False, "Priority code Not Understood. Available Options: " + str(options) return super().setprio(flag, priocode) + @stack.command(name="RMETHH") def setresometh(self, value=''): """ Processes the RMETHH command. Sets swresovert = False""" # Acceptable arguments for this command @@ -83,7 +70,7 @@ def setresometh(self, value=''): self.swresohdg = True self.swresovert = False - + @stack.command(name='RMETHV') def setresometv(self, value=''): """ Processes the RMETHV command. Sets swresohoriz = False.""" # Acceptable arguments for this command @@ -94,15 +81,15 @@ def setresometv(self, value=''): ("ON" if self.swresovert else "OFF") if value not in options: return False, "RMETV Not Understood" + "\nRMETHV [ON / V/S / OFF / NONE]" - else: - if value == "ON" or value == "V/S": - self.swresovert = True - self.swresohoriz = False - self.swresospd = False - self.swresohdg = False - elif value == "OFF" or value == "OF" or value == "NONE": - # Do NOT swtich off self.swresohoriz if value == OFF - self.swresovert = False + + if value == "ON" or value == "V/S": + self.swresovert = True + self.swresohoriz = False + self.swresospd = False + self.swresohdg = False + elif value == "OFF" or value == "OF" or value == "NONE": + # Do NOT swtich off self.swresohoriz if value == OFF + self.swresovert = False def applyprio(self, dv_mvp, dv1, dv2, vs1, vs2): ''' Apply the desired priority setting to the resolution ''' @@ -280,29 +267,32 @@ def resolve(self, conf, ownship, intruder): def MVP(self, ownship, intruder, conf, qdr, dist, tcpa, tLOS, idx1, idx2): """Modified Voltage Potential (MVP) resolution method""" # Preliminary calculations------------------------------------------------- - + # Determine largest RPZ and HPZ of the conflict pair, use lookahead of ownship + rpz_m = np.max(conf.rpz[[idx1, idx2]] * self.resofach) + hpz_m = np.max(conf.hpz[[idx1, idx2]] * self.resofacv) + dtlook = conf.dtlookahead[idx1] # Convert qdr from degrees to radians qdr = np.radians(qdr) # Relative position vector between id1 and id2 - drel = np.array([np.sin(qdr)*dist, \ - np.cos(qdr)*dist, \ - intruder.alt[idx2]-ownship.alt[idx1]]) + drel = np.array([np.sin(qdr) * dist, \ + np.cos(qdr) * dist, \ + intruder.alt[idx2] - ownship.alt[idx1]]) # Write velocities as vectors and find relative velocity vector v1 = np.array([ownship.gseast[idx1], ownship.gsnorth[idx1], ownship.vs[idx1]]) v2 = np.array([intruder.gseast[idx2], intruder.gsnorth[idx2], intruder.vs[idx2]]) - vrel = np.array(v2-v1) + vrel = v2 - v1 # Horizontal resolution---------------------------------------------------- # Find horizontal distance at the tcpa (min horizontal distance) dcpa = drel + vrel*tcpa - dabsH = np.sqrt(dcpa[0]*dcpa[0]+dcpa[1]*dcpa[1]) + dabsH = np.sqrt(dcpa[0] * dcpa[0] + dcpa[1] * dcpa[1]) # Compute horizontal intrusion - iH = (conf.rpz * self.resofach) - dabsH + iH = rpz_m - dabsH # Exception handlers for head-on conflicts # This is done to prevent division by zero in the next step @@ -313,12 +303,12 @@ def MVP(self, ownship, intruder, conf, qdr, dist, tcpa, tLOS, idx1, idx2): # If intruder is outside the ownship PZ, then apply extra factor # to make sure that resolution does not graze IPZ - if (conf.rpz * self.resofach) < dist and dabsH < dist: + if rpz_m < dist and dabsH < dist: # Compute the resolution velocity vector in horizontal direction. # abs(tcpa) because it bcomes negative during intrusion. - erratum=np.cos(np.arcsin((conf.rpz * self.resofach)/dist)-np.arcsin(dabsH/dist)) - dv1 = (((conf.rpz * self.resofach)/erratum - dabsH)*dcpa[0])/(abs(tcpa)*dabsH) - dv2 = (((conf.rpz * self.resofach)/erratum - dabsH)*dcpa[1])/(abs(tcpa)*dabsH) + erratum = np.cos(np.arcsin(rpz_m / dist)-np.arcsin(dabsH / dist)) + dv1 = ((rpz_m / erratum - dabsH) * dcpa[0]) / (abs(tcpa) * dabsH) + dv2 = ((rpz_m / erratum - dabsH) * dcpa[1]) / (abs(tcpa) * dabsH) else: dv1 = (iH * dcpa[0]) / (abs(tcpa) * dabsH) dv2 = (iH * dcpa[1]) / (abs(tcpa) * dabsH) @@ -327,35 +317,35 @@ def MVP(self, ownship, intruder, conf, qdr, dist, tcpa, tLOS, idx1, idx2): # Compute the vertical intrusion # Amount of vertical intrusion dependent on vertical relative velocity - iV = (conf.hpz * self.resofacv) if abs(vrel[2])>0.0 else (conf.hpz * self.resofacv)-abs(drel[2]) + iV = hpz_m if abs(vrel[2]) > 0.0 else hpz_m - abs(drel[2]) # Get the time to solve the conflict vertically - tsolveV - tsolV = abs(drel[2]/vrel[2]) if abs(vrel[2])>0.0 else tLOS + tsolV = abs(drel[2] / vrel[2]) if abs(vrel[2]) > 0.0 else tLOS # If the time to solve the conflict vertically is longer than the look-ahead time, # because the the relative vertical speed is very small, then solve the intrusion # within tinconf - if tsolV>conf.dtlookahead: + if tsolV > dtlook: tsolV = tLOS - iV = (conf.hpz * self.resofacv) + iV = hpz_m # Compute the resolution velocity vector in the vertical direction # The direction of the vertical resolution is such that the aircraft with # higher climb/decent rate reduces their climb/decent rate - dv3 = np.where(abs(vrel[2])>0.0, (iV/tsolV)*(-vrel[2]/abs(vrel[2])), (iV/tsolV)) + dv3 = np.where(abs(vrel[2]) > 0.0, (iV / tsolV) * (-vrel[2] / abs(vrel[2])), (iV / tsolV)) # It is necessary to cap dv3 to prevent that a vertical conflict # is solved in 1 timestep, leading to a vertical separation that is too # high (high vs assumed in traf). If vertical dynamics are included to # aircraft model in traffic.py, the below three lines should be deleted. - # mindv3 = -400*fpm# ~ 2.016 [m/s] - # maxdv3 = 400*fpm - # dv3 = np.maximum(mindv3,np.minimum(maxdv3,dv3)) + # mindv3 = -400*fpm# ~ 2.016 [m/s] + # maxdv3 = 400*fpm + # dv3 = np.maximum(mindv3,np.minimum(maxdv3,dv3)) # Combine resolutions------------------------------------------------------ # combine the dv components - dv = np.array([dv1,dv2,dv3]) + dv = np.array([dv1, dv2, dv3]) return dv, tsolV diff --git a/bluesky/traffic/asas/resolution.py b/bluesky/traffic/asas/resolution.py index 6763128265..104ab17eec 100644 --- a/bluesky/traffic/asas/resolution.py +++ b/bluesky/traffic/asas/resolution.py @@ -12,10 +12,6 @@ class ConflictResolution(Entity, replaceable=True): ''' Base class for Conflict Resolution implementations. ''' - # ConflictResolution on/off switch. Set to True whenever another - # implementation than the base implementation (ConflictResolution) is selected. - do_cr = False - def __init__(self): super().__init__() # [-] switch to activate priority rules for conflict resolution @@ -43,6 +39,16 @@ def __init__(self): self.alt = np.array([]) # alt provided by the ASAS [m] self.vs = np.array([]) # vspeed provided by the ASAS [m/s] + def reset(self): + super().reset() + self.swprio = False + self.priocode = '' + self.resopairs.clear() + self.resofach = bs.settings.asas_marh + self.resofacv = bs.settings.asas_marv + self.resodhrelative = True + self.resorrelative = True + # By default all channels are controlled by self.active, # but they can be overloaded with separate variables or functions in a # derived ASAS Conflict Resolution class (@property decorator takes away @@ -93,7 +99,8 @@ def resolve(self, conf, ownship, intruder): def update(self, conf, ownship, intruder): ''' Perform an update step of the Conflict Resolution implementation. ''' - if ConflictResolution.do_cr: + if ConflictResolution.selected() is not ConflictResolution: + # Only perform CR when an actual method is selected if conf.confpairs: self.trk, self.tas, self.vs, self.alt = self.resolve(conf, ownship, intruder) self.resumenav(conf, ownship, intruder) @@ -111,6 +118,17 @@ def resumenav(self, conf, ownship, intruder): delpairs = set() changeactive = dict() + # smallest relative angle between vectors of heading a and b + def anglediff(a, b): + d = a - b + if d > 180: + return anglediff(a, b + 360) + elif d < -180: + return anglediff(a + 360, b) + else: + return d + + # Look at all conflicts, also the ones that are solved but CPA is yet to come for conflict in self.resopairs: idx1, idx2 = bs.traf.id2idx(conflict) @@ -134,20 +152,21 @@ def resumenav(self, conf, ownship, intruder): # Check if conflict is past CPA past_cpa = np.dot(dist, vrel) > 0.0 + rpz = np.max(conf.rpz[[idx1, idx2]]) # hor_los: # Aircraft should continue to resolve until there is no horizontal # LOS. This is particularly relevant when vertical resolutions # are used. hdist = np.linalg.norm(dist) - hor_los = hdist < conf.rpz + hor_los = hdist < rpz # Bouncing conflicts: # If two aircraft are getting in and out of conflict continously, # then they it is a bouncing conflict. ASAS should stay active until # the bouncing stops. is_bouncing = \ - abs(ownship.trk[idx1] - intruder.trk[idx2]) < 30.0 and \ - hdist < conf.rpz * self.resofach + abs(anglediff(ownship.trk[idx1], intruder.trk[idx2])) < 30.0 and \ + hdist < rpz * self.resofach # Start recovery for ownship if intruder is deleted, or if past CPA # and not in horizontal LOS or a bouncing conflict @@ -232,10 +251,10 @@ def setresofacv(self, factor: float = None): ''' Set resolution factor vertical (to maneuver only a fraction of a resolution vector). ''' if factor is None: return True, f'RFACV [FACTOR]\nCurrent vertical resolution factor is: {self.resofacv}' - else: - self.resofacv = factor - self.resodhrelative = True # Size of resolution zone dh, vertically, set relative to CD zone - return True, f'Vertical resolution factor set to {self.resofacv}' + self.resofacv = factor + # Size of resolution zone dh, vertically, set relative to CD zone + self.resodhrelative = True + return True, f'Vertical resolution factor set to {self.resofacv}' @command(name='RSZONER', aliases=('RESOZONER',)) def setresozoner(self, zoner : float = None): @@ -244,13 +263,14 @@ def setresozoner(self, zoner : float = None): ''' if not bs.traf.cd.global_rpz: self.resorrelative = True - return False, f'RSZONER [radiusnm]\nCan only set resolution factor when simulation contains aircraft with different RPZ,\nUse RFACH instead.' + return False, 'RSZONER [radiusnm]\nCan only set resolution factor when simulation contains aircraft with different RPZ,\nUse RFACH instead.' if zoner is None: return True, f'RSZONER [radiusnm]\nCurrent horizontal resolution factor is: {self.resofach}, resulting in radius: {self.resofach*bs.traf.cd.rpz_def/nm} nm' - else: - self.resofach = zoner / bs.traf.cd.rpz_def * nm - self.resorrelative = False # Size of resolution zone r, vertically, no longer relative to CD zone - return True, f'Horizontal resolution factor updated to {self.resofach}, resulting in radius: {zoner} nm' + + self.resofach = zoner / bs.traf.cd.rpz_def * nm + # Size of resolution zone r, vertically, no longer relative to CD zone + self.resorrelative = False + return True, f'Horizontal resolution factor updated to {self.resofach}, resulting in radius: {zoner} nm' @command(name='RSZONEDH', aliases=('RESOZONEDH',)) def setresozonedh(self, zonedh : float = None): @@ -260,13 +280,14 @@ def setresozonedh(self, zonedh : float = None): ''' if not bs.traf.cd.global_hpz: self.resodhrelative = True - return False, f'RSZONEH [zonedhft]\nCan only set resolution factor when simulation contains aircraft with different HPZ,\nUse RFACV instead.' + return False, 'RSZONEH [zonedhft]\nCan only set resolution factor when simulation contains aircraft with different HPZ,\nUse RFACV instead.' if zonedh is None: return True, f'RSZONEDH [zonedhft]\nCurrent vertical resolution factor is: {self.resofacv}, resulting in height: {self.resofacv*bs.traf.cd.hpz_def/ft} ft' - else: - self.resofacv = zonedh / bs.traf.cd.hpz_def * ft - self.resodhrelative = False # Size of resolution zone dh, vertically, no longer relative to CD zone - return True, f'Vertical resolution factor updated to {self.resofacv}, resulting in height: {zonedh} ft' + + self.resofacv = zonedh / bs.traf.cd.hpz_def * ft + # Size of resolution zone dh, vertically, no longer relative to CD zone + self.resodhrelative = False + return True, f'Vertical resolution factor updated to {self.resofacv}, resulting in height: {zonedh} ft' @staticmethod @command(name='RESO') @@ -283,7 +304,6 @@ def setmethod(name : 'txt' = ''): f'\nAvailable CR methods: {", ".join(names)}' # Check if the requested method exists if name == 'OFF': - ConflictResolution.do_cr = False ConflictResolution.select() return True, 'Conflict Resolution turned off.' method = methods.get(name, None) @@ -293,5 +313,4 @@ def setmethod(name : 'txt' = ''): # Select the requested method method.select() - ConflictResolution.do_cr = True return True, f'Selected {method.__name__} as CR method.' diff --git a/bluesky/traffic/autopilot.py b/bluesky/traffic/autopilot.py index 8e8ecdc184..109a270614 100644 --- a/bluesky/traffic/autopilot.py +++ b/bluesky/traffic/autopilot.py @@ -18,11 +18,24 @@ # In python <3.3 collections.abc doesn't exist from collections import Collection -bs.settings.set_variable_defaults( fms_dt=10.5 ) +import bluesky as bs +from bluesky import stack +from bluesky.tools import geo +from bluesky.tools.misc import degto180 +from bluesky.tools.position import txt2pos +from bluesky.tools.aero import ft, nm, fpm, vcasormach2tas, vcas2tas, tas2cas, cas2tas, g0 +from bluesky.core import Entity, timed_function +from .route import Route +#debug +from inspect import stack as callstack -class Autopilot( Entity, replaceable=True ): - def __init__( self ): +bs.settings.set_variable_defaults(fms_dt=10.5) + + +class Autopilot(Entity, replaceable=True): + ''' BlueSky Autopilot implementation. ''' + def __init__(self): super().__init__() # Standard self.steepness for descent @@ -39,18 +52,35 @@ def __init__( self ): self.vs = np.array( [] ) # VNAV variables - self.dist2vs = np.array( [] ) # distance from coming waypoint to TOD - self.swvnavvs = np.array( [] ) # whether to use given VS or not - self.vnavvs = np.array( [] ) # vertical speed in VNAV + self.swtoc = np.array([]) # ToC switch to switch on VNAV Top of Climb logic (default value True) + self.swtod = np.array([]) # ToD switch to switch on VNAV Top of Descent logic (default value True) + + self.dist2vs = np.array([]) # distance from coming waypoint to TOD + self.dist2accel = np.array([]) # Distance to go to acceleration(decelaration) for turn next waypoint [nm] + + self.swvnavvs = np.array([]) # whether to use given VS or not + self.vnavvs = np.array([]) # vertical speed in VNAV + # LNAV variables - self.qdr2wp = np.array( [] ) # Direction to waypoint from the last time passing was checked - # to avoid 180 turns due to updated qdr shortly before passing wp + self.qdr2wp = np.array([]) # Direction to waypoint from the last time passing was checked + # to avoid 180 turns due to updated qdr shortly before passing wp + self.dist2wp = np.array([]) # [m] Distance to active waypoint + self.qdrturn = np.array([]) # qdr to next turn] + self.dist2turn = np.array([]) # Distance to next turn [m] + self.inturn = np.array([]) # If we're in a turn maneuver or not # Traffic navigation information self.orig = [] # Four letter code of origin airport self.dest = [] # Four letter code of destination airport + # Default values + self.bankdef = np.array([]) # nominal bank angle, [radians] + self.vsdef = np.array([]) # [m/s]default vertical speed of autopilot + + # Currently used roll/bank angle [rad] + self.turnphi = np.array([]) # [rad] bank angle setting of autopilot + # Route objects self.route = [] @@ -63,23 +93,39 @@ def create( self, n=1 ): self.alt[-n:] = bs.traf.alt[-n:] # LNAV variables - self.qdr2wp[-n:] = -999 # Direction to waypoint from the last time passing was checked + self.qdr2wp[-n:] = -999. # Direction to waypoint from the last time passing was checked + self.dist2wp[-n:] = -999. # Distance to go to next waypoint [nm] + # to avoid 180 turns due to updated qdr shortly before passing wp # VNAV Variables self.dist2vs[-n:] = -999. + self.dist2accel[-n:] = -999. # Distance to go to acceleration(decelaration) for turn next waypoint [nm] + + # Traffic performance data + #(temporarily default values) + self.vsdef[-n:] = 1500. * fpm # default vertical speed of autopilot + self.bankdef[-n:] = np.radians(25.) # Route objects for ridx, acid in enumerate( bs.traf.id[-n:] ): self.route[ridx - n] = Route( acid ) - # no longer timed @timed_function(name='fms', dt=bs.settings.fms_dt, manual=True) - def update_fms( self, qdr, dist ): - # Check which aircraft i have reached their active waypoint - # Shift waypoints for aircraft i where necessary - # Reached function return list of indices where reached logic is True - for i in bs.traf.actwp.Reached( qdr, dist, bs.traf.actwp.flyby, - bs.traf.actwp.flyturn, bs.traf.actwp.turnrad, bs.traf.actwp.swlastwp ): + # Default ToC/ToD logic on + self.swtoc[-n:] = True + self.swtod[-n:] = True + + #no longer timed @timed_function(name='fms', dt=bs.settings.fms_dt, manual=True) + def update_fms(self, qdr, dist): + """ + Waypoint switching function: + - Check which aircraft i have reached their active waypoint + - Reached function return list of indices where reached logic is True + - Shift waypoint (last,next etc.) data for aircraft i where necessary + - Compute VNAV profile for this new leg + """ + for i in bs.traf.actwp.Reached(qdr, dist, bs.traf.actwp.flyby, + bs.traf.actwp.flyturn,bs.traf.actwp.turnrad,bs.traf.actwp.swlastwp): # Save current wp speed for use on next leg when we pass this waypoint # VNAV speeds are always FROM-speeds, so we accelerate/decellerate at the waypoint @@ -90,8 +136,6 @@ def update_fms( self, qdr, dist ): bs.traf.actwp.spd[i] = bs.traf.actwp.nextspd[i] bs.traf.actwp.spdcon[i] = bs.traf.actwp.nextspd[i] - - # Execute stack commands for the still active waypoint, which we pass self.route[i].runactwpstack() @@ -103,20 +147,25 @@ def update_fms( self, qdr, dist ): turnspd = bs.traf.tas[i] if bs.traf.actwp.turnrad[i] > 0.: - bs.traf.aphi[i] = atan( turnspd * turnspd / ( bs.traf.actwp.turnrad[i] * nm * g0 ) ) # [rad] + self.turnphi[i] = atan(turnspd*turnspd/(bs.traf.actwp.turnrad[i]*nm*g0)) # [rad] else: - bs.traf.aphi[i] = 0.0 # [rad] or leave untouched??? + self.turnphi[i] = 0.0 # [rad] or leave untouched??? else: - bs.traf.aphi[i] = 0.0 # [rad] or leave untouched??? + self.turnphi[i] = 0.0 #[rad] or leave untouched??? # Get next wp, if there still is one if not bs.traf.actwp.swlastwp[i]: - lat, lon, alt, bs.traf.actwp.nextspd[i], bs.traf.actwp.xtoalt[i], toalt, \ + lat, lon, alt, bs.traf.actwp.nextspd[i], \ + bs.traf.actwp.xtoalt[i], toalt, \ bs.traf.actwp.xtorta[i], bs.traf.actwp.torta[i], \ - lnavon, flyby, flyturn, turnrad, turnspd, \ - bs.traf.actwp.next_qdr[i], bs.traf.actwp.swlastwp[i] = \ - self.route[i].getnextwp() # note: xtoalt,toalt in [m] + lnavon, flyby, flyturn, turnrad, turnspd,\ + bs.traf.actwp.next_qdr[i], bs.traf.actwp.swlastwp[i] = \ + self.route[i].getnextwp() # [m] note: xtoalt,nextaltco are in meters + + bs.traf.actwp.nextturnlat[i], bs.traf.actwp.nextturnlon[i], \ + bs.traf.actwp.nextturnspd[i], bs.traf.actwp.nextturnrad[i], \ + bs.traf.actwp.nextturnidx[i] = self.route[i].getnextturnwp() # Prevent trying to activate the next waypoint when it was already the last waypoint else: @@ -141,12 +190,25 @@ def update_fms( self, qdr, dist ): # 1.0 in case of fly by, else fly over bs.traf.actwp.flyby[i] = int( flyby ) + # Update qdr and turndist for this new waypoint for ComputeVNAV + qdr[i], distnmi = geo.qdrdist(bs.traf.lat[i], bs.traf.lon[i], + bs.traf.actwp.lat[i], bs.traf.actwp.lon[i]) + + #dist[i] = distnmi * nm + self.dist2wp[i] = distnmi*nm + + bs.traf.actwp.curlegdir[i] = qdr[i] + bs.traf.actwp.curleglen[i] = self.dist2wp[i] + # User has entered an altitude for this waypoint - if alt >= -0.01: + if alt >= -0.01: # positive alt on this waypoint means altitude constraint bs.traf.actwp.nextaltco[i] = alt # [m] + bs.traf.actwp.xtoalt[i] = 0.0 + else: + bs.traf.actwp.nextaltco[i] = toalt # [m] - if not bs.traf.swlnav[i]: - bs.traf.actwp.spd[i] = -999. + #if not bs.traf.swlnav[i]: + # bs.traf.actwp.spd[i] = -997. # VNAV spd mode: use speed of this waypoint as commanded speed # while passing waypoint and save next speed for passing next wp @@ -154,12 +216,6 @@ def update_fms( self, qdr, dist ): if bs.traf.swvnavspd[i] and bs.traf.actwp.spd[i] >= 0.0: bs.traf.selspd[i] = bs.traf.actwp.spd[i] - # Update qdr and turndist for this new waypoint for ComputeVNAV - qdr[i], distnmi = geo.qdrdist( bs.traf.lat[i], bs.traf.lon[i], - bs.traf.actwp.lat[i], bs.traf.actwp.lon[i] ) - - dist[i] = distnmi * nm - # Update turndist so ComputeVNAV works, is there a next leg direction or not? if bs.traf.actwp.next_qdr[i] < -900.: local_next_qdr = qdr[i] @@ -185,8 +241,8 @@ def update_fms( self, qdr, dist ): # Calculate turn dist (and radius which we do not use) now for scalar variable [i] bs.traf.actwp.turndist[i], dummy = \ - bs.traf.actwp.calcturn( bs.traf.tas[i], bs.traf.bank[i], - qdr[i], local_next_qdr, turnrad ) # update turn distance for VNAV + bs.traf.actwp.calcturn(bs.traf.tas[i], self.bankdef[i], + qdr[i], local_next_qdr,turnrad) # update turn distance for VNAV # Reduce turn dist for reduced turnspd if bs.traf.actwp.flyturn[i] and bs.traf.actwp.turnrad[i] < 0.0 and bs.traf.actwp.turnspd[i] >= 0.: @@ -197,6 +253,8 @@ def update_fms( self, qdr, dist ): self.ComputeVNAV( i, toalt, bs.traf.actwp.xtoalt[i], bs.traf.actwp.torta[i], bs.traf.actwp.xtorta[i] ) + + # End of per waypoint i switching loop # Update qdr2wp with up-to-date qdr, now that we have checked passing wp self.qdr2wp = qdr % 360. @@ -209,9 +267,9 @@ def update_fms( self, qdr, dist ): if bs.traf.ap.route[iac].wprta[iwp] > -99.: # For all a/c flying to an RTA waypoint, recalculate speed more often - dist2go4rta = geo.kwikdist( bs.traf.lat[iac], bs.traf.lon[iac], \ - bs.traf.actwp.lat[iac], bs.traf.actwp.lon[iac] ) * nm \ - +bs.traf.ap.route[iac].wpxtorta[iwp] # last term zero for active wp rta + dist2go4rta = geo.kwikdist(bs.traf.lat[iac],bs.traf.lon[iac], + bs.traf.actwp.lat[iac],bs.traf.actwp.lon[iac])*nm \ + + bs.traf.ap.route[iac].wpxtorta[iwp] # last term zero for active wp rta # Set bs.traf.actwp.spd to rta speed, if necessary self.setspeedforRTA( iac, bs.traf.actwp.torta[iac], dist2go4rta ) @@ -223,53 +281,57 @@ def update_fms( self, qdr, dist ): def update( self ): # FMS LNAV mode: # qdr[deg],distinnm[nm] - qdr, distinnm = geo.qdrdist( bs.traf.lat, bs.traf.lon, - bs.traf.actwp.lat, bs.traf.actwp.lon ) # [deg][nm]) - self.qdr2wp = qdr - dist2wp = distinnm * nm # Conversion to meters + qdr, distinnm = geo.qdrdist(bs.traf.lat, bs.traf.lon, + bs.traf.actwp.lat, bs.traf.actwp.lon) # [deg][nm]) + + self.qdr2wp = qdr + self.dist2wp = distinnm*nm # Conversion to meters # FMS route update and possibly waypoint shift. Note: qdr, dist2wp will be updated accordingly in case of wp switch - self.update_fms( qdr, dist2wp ) # Updates self.qdr2wp when necessary + self.update_fms(qdr, self.dist2wp) # Updates self.qdr2wp when necessary #================= Continuous FMS guidance ======================== - # Waypoint switching in the loop above was scalar (per a/c with index i) - # Code below is vectorized, with arrays for all aircraft + # Note that the code below is vectorized, with traffic arrays, so for all aircraft + # ComputeVNAV and inside waypoint loop of update_fms, it was scalar (per a/c with index i) - # Do VNAV start of descent check - # dy = (bs.traf.actwp.lat - bs.traf.lat) #[deg lat = 60 nm] - # dx = (bs.traf.actwp.lon - bs.traf.lon) * bs.traf.coslat #[corrected deg lon = 60 nm] - # dist2wp = 60. * nm * np.sqrt(dx * dx + dy * dy) # [m] + # VNAV guidance logic (using the variables prepared by ComputeVNAV when activating waypoint) + # + # Central question is: + # - Can we please we start to descend or to climb? + # + # Well, when Top of Descent (ToD) switch is on, descend as late as possible, + # But when Top of Climb switch is on or off, climb as soon as possible, only difference is steepness used in ComputeVNAV + # to calculate bs.traf.actwp.vs + # Only use this logic if there is a valid next altitude constraint (nextaltco) - # VNAV logic: descend as late as possible, climb as soon as possible - startdescent = ( dist2wp < self.dist2vs ) + ( bs.traf.actwp.nextaltco > bs.traf.alt ) + startdescorclimb = (bs.traf.actwp.nextaltco>=-0.1) * \ + np.logical_or(self.swtod * (bs.traf.alt>bs.traf.actwp.nextaltco) * (self.dist2wp < self.dist2vs), + bs.traf.alt0: - # debug print("using current speed constraint") - # debug else: - # debug print("no speed given") + bs.traf.selspd = np.where(inoldturn*(bs.traf.actwp.oldturnspd>0.)*bs.traf.swvnavspd*bs.traf.swvnav*bs.traf.swlnav, + bs.traf.actwp.oldturnspd,bs.traf.selspd) + + self.inturn = np.logical_or(useturnspd,inoldturn) + + #debug if inoldturn[0]: + #debug print("inoldturn bs.traf.trk =",bs.traf.trk[0],"qdr =",qdr) + #debug elif usenextspdcon[0]: + #debug print("usenextspdcon") + #debug elif useturnspd[0]: + #debug print("useturnspd") + #debug elif bs.traf.actwp.spdcon>0: + #debug print("using current speed constraint") + #debug else: + #debug print("no speed given") # Below crossover altitude: CAS=const, above crossover altitude: Mach = const self.tas = vcasormach2tas( bs.traf.selspd, bs.traf.alt ) - return + def ComputeVNAV(self, idx, toalt, xtoalt, torta, xtorta): + """ + This function to do VNAV (and RTA) calculations is only called only once per leg. + If: + - switching to next waypoint + - when VNAV is activated + - when a DIRECT is given + + It prepares the profile of this leg using the the current altitude and the next altitude constraint (nextaltco). + The distance to the next altitude constraint is given by xtoalt [m] after active waypoint. + + Options are (classic VNAV logic, swtoc and swtod True): + - no altitude constraint in the future, do nothing + - Top of CLimb logic (swtoc=True): if next altitude constrain is baove us, climb as soon as possible with default steepness + - Top of Descent Logic (swtod =True) Use ToD logic: descend as late aspossible, based on + steepness. Prepare a ToD somewhere on the leg if necessary based on distance to next altitude constraint. + This is done by calculating distance to next waypoint where descent should start + + Alternative logic (e.g. for UAVs or GA): + - swtoc=False and next alt co is above us, climb with the angle/steepness needed to arrive at the altitude at + the waypoint with the altitude constraint (xtoalt m after active waypoint) + - swtod=False and next altco is below us, descend with the angle/steepness needed to arrive at at the altitude at + the waypoint with the altitude constraint (xtoalt m after active waypoint) + + Output if this function: + self.dist2vs = distance 2 next waypoint where climb/descent needs to activated + bs.traf.actwp.vs = V/S to be used during climb/descent part, so when dist2wp11.99: -# print("bs.traf.tas.shape =",bs.traf.tas.shape) -# print("bs.traf.gs.shape =", bs.traf.gs.shape) -# print("bs.traf.actwp.vs.shape =", bs.traf.actwp.vs.shape) - - bs.traf.actwp.vs[idx] = -self.steepness * ( bs.traf.gs[idx] + - ( bs.traf.gs[idx] < 0.2 * bs.traf.tas[idx] ) * bs.traf.tas[idx] ) - + # We are higher but swtod = False, so there is no ToD descent logic, simply aim at next altco + steepness = (bs.traf.alt[idx]-bs.traf.actwp.nextaltco[idx])/(max(0.01,self.dist2wp[idx]+xtoalt)) + bs.traf.actwp.vs[idx] = -abs(steepness) * (bs.traf.gs[idx] + + (bs.traf.gs[idx] < 0.2 * bs.traf.tas[idx]) * bs.traf.tas[ + idx]) # VNAV climb mode: climb as soon as possible (T/C logic) elif bs.traf.alt[idx] < toalt - 10. * ft: + # Stop potential current descent (e.g. due to not making it to previous altco) + # then stop immediately, as in: do not make it worse. + if bs.traf.vs[idx] < -0.0001: + self.vnavvs[idx] = 0.0 + self.alt = bs.traf.alt[idx] + if bs.traf.swvnav[idx]: + bs.traf.selalt[idx] = bs.traf.alt[idx] # Altitude we want to climb to: next alt constraint in our route (could be further down the route) - bs.traf.actwp.nextaltco[idx] = toalt # [m] - bs.traf.actwp.xtoalt[idx] = xtoalt # [m] distance to next alt constraint measured from next waypoint - self.alt[idx] = bs.traf.actwp.nextaltco[idx] # dial in altitude of next waypoint as calculated - self.dist2vs[idx] = 99999.*nm # [m] Forces immediate climb as current distance to next wp will be less - - # Flat earth distance to next wp - dy = ( bs.traf.actwp.lat[idx] - bs.traf.lat[idx] ) - dx = ( bs.traf.actwp.lon[idx] - bs.traf.lon[idx] ) * bs.traf.coslat[idx] - legdist = 60. * nm * np.sqrt( dx * dx + dy * dy ) # [m] - t2go = max( 0.1, legdist + xtoalt ) / max( 0.01, bs.traf.gs[idx] ) - bs.traf.actwp.vs[idx] = np.maximum( self.steepness * bs.traf.gs[idx], \ - ( bs.traf.actwp.nextaltco[idx] - bs.traf.alt[idx] ) / t2go ) # [m/s] + bs.traf.actwp.nextaltco[idx] = toalt # [m] + bs.traf.actwp.xtoalt[idx] = xtoalt # [m] distance to next alt constraint measured from next waypoint + self.alt[idx] = bs.traf.actwp.nextaltco[idx] # dial in altitude of next waypoint as calculated + self.dist2vs[idx] = 99999. #[m] Forces immediate climb as current distance to next wp will be less + + + t2go = max(0.1, self.dist2wp[idx]+xtoalt) / max(0.01, bs.traf.gs[idx]) + if self.swtoc[idx]: + steepness = self.steepness # default steepness + else: + steepness = (bs.traf.alt[idx] - bs.traf.actwp.nextaltco[idx]) / (max(0.01, self.dist2wp[idx] + xtoalt)) + + bs.traf.actwp.vs[idx] = np.maximum(steepness*bs.traf.gs[idx], + (bs.traf.actwp.nextaltco[idx] - bs.traf.alt[idx]) / t2go) # [m/s] # Level leg: never start V/S else: self.dist2vs[idx] = -999. # [m] - return def setspeedforRTA( self, idx, torta, xtorta ): @@ -479,14 +601,14 @@ def setspeedforRTA( self, idx, torta, xtorta ): if torta < -90. : # -999 signals there is no RTA defined in remainder of route return False - deltime = torta - bs.sim.simt # Remaining time to next RTA [s] in simtime - if deltime > 0: # Still possible? - trafax = abs( bs.traf.perf.acceleration()[idx] ) - gsrta = calcvrta( bs.traf.gs[idx], xtorta, deltime, trafax ) + deltime = torta-bs.sim.simt # Remaining time to next RTA [s] in simtime + if deltime>0: # Still possible? + gsrta = calcvrta(bs.traf.gs[idx], xtorta, + deltime, bs.traf.perf.axmax[idx]) # Subtract tail wind speed vector - tailwind = ( bs.traf.windnorth[idx] * bs.traf.gsnorth[idx] + bs.traf.windeast[idx] * bs.traf.gseast[idx] ) / \ - bs.traf.gs[idx] * bs.traf.gs[idx] + tailwind = (bs.traf.windnorth[idx]*bs.traf.gsnorth[idx] + bs.traf.windeast[idx]*bs.traf.gseast[idx]) / \ + bs.traf.gs[idx] # Convert to CAS rtacas = tas2cas( gsrta - tailwind, bs.traf.alt[idx] ) @@ -500,11 +622,13 @@ def setspeedforRTA( self, idx, torta, xtorta ): else: return False - - def selaltcmd( self, idx, alt, vspd=None ): - """ Select altitude command: ALT acid, alt, [vspd] """ - bs.traf.selalt[idx] = alt - bs.traf.swvnav[idx] = False + @stack.command(name='ALT') + def selaltcmd(self, idx: 'acid', alt: 'alt', vspd: 'vspd'=None): + """ ALT acid, alt, [vspd] + + Select autopilot altitude command.""" + bs.traf.selalt[idx] = alt + bs.traf.swvnav[idx] = False # Check for optional VS argument if vspd: @@ -519,18 +643,24 @@ def selaltcmd( self, idx, alt, vspd=None ): bs.traf.selvs[idx[oppositevs]] = 0. - def selvspdcmd( self, idx, vspd ): - """ Vertical speed autopilot command: VS acid vspd """ - bs.traf.selvs[idx] = vspd # [fpm] + @stack.command(name='VS') + def selvspdcmd(self, idx: 'acid', vspd:'vspd'): + """ VS acid,vspd (ft/min) + + Vertical speed command (autopilot) """ + bs.traf.selvs[idx] = vspd #[fpm] # bs.traf.vs[idx] = vspd bs.traf.swvnav[idx] = False - def selhdgcmd( self, idx, hdg ): # HDG command - """ Select heading command: HDG acid, hdg """ - if not isinstance( idx, Collection ): - idx = np.array( [idx] ) - if not isinstance( hdg, Collection ): - hdg = np.array( [hdg] ) + @stack.command(name='HDG', aliases=("HEADING", "TURN")) + def selhdgcmd(self, idx: 'acid', hdg: 'hdg'): # HDG command + """ HDG acid,hdg (deg,True or Magnetic) + + Autopilot select heading command. """ + if not isinstance(idx, Collection): + idx = np.array([idx]) + if not isinstance(hdg, Collection): + hdg = np.array([hdg]) # If there is wind, compute the corresponding track angle if bs.traf.wind.winddim > 0: ab50 = bs.traf.alt[idx] > 50.0 * ft @@ -552,8 +682,11 @@ def selhdgcmd( self, idx, hdg ): # HDG command # Everything went ok! return True - def selspdcmd( self, idx, casmach ): # SPD command - """ Select speed command: SPD acid, casmach (= CASkts/Mach) """ + @stack.command(name='SPD', aliases=("SPEED",)) + def selspdcmd(self, idx: 'acid', casmach: 'spd'): # SPD command + """ SPD acid, casmach (= CASkts/Mach) + + Select autopilot speed. """ # Depending on or position relative to crossover altitude, # we will maintain CAS or Mach when altitude changes # We will convert values when needed @@ -563,86 +696,94 @@ def selspdcmd( self, idx, casmach ): # SPD command bs.traf.swvnavspd[idx] = False return True - def setdestorig( self, cmd, idx, *args ): - if len( args ) == 0: - if cmd == 'DEST': - return True, 'DEST ' + bs.traf.id[idx] + ': ' + self.dest[idx] - return True, 'ORIG ' + bs.traf.id[idx] + ': ' + self.orig[idx] - - route = self.route[idx] - name = args[0] - apidx = bs.navdb.getaptidx( name ) + @stack.command(name='DEST') + def setdest(self, acidx: 'acid', wpname:'wpt' = None): + ''' DEST acid, latlon/airport + Set destination of aircraft, aircraft wil fly to this airport. ''' + if wpname is None: + return True, 'DEST ' + bs.traf.id[acidx] + ': ' + self.dest[acidx] + route = self.route[acidx] + apidx = bs.navdb.getaptidx(wpname) if apidx < 0: - if cmd == "DEST" and bs.traf.ap.route[idx].nwp > 0: - reflat = bs.traf.ap.route[idx].wplat[-1] - reflon = bs.traf.ap.route[idx].wplon[-1] + if bs.traf.ap.route[acidx].nwp > 0: + reflat = bs.traf.ap.route[acidx].wplat[-1] + reflon = bs.traf.ap.route[acidx].wplon[-1] else: - reflat = bs.traf.lat[idx] - reflon = bs.traf.lon[idx] + reflat = bs.traf.lat[acidx] + reflon = bs.traf.lon[acidx] - success, posobj = txt2pos( name, reflat, reflon ) + success, posobj = txt2pos(wpname, reflat, reflon) if success: lat = posobj.lat lon = posobj.lon else: - return False, ( cmd + ": Position " + name + " not found." ) + return False, "DEST: Position " + wpname + " not found." else: lat = bs.navdb.aptlat[apidx] lon = bs.navdb.aptlon[apidx] + self.dest[acidx] = wpname + iwp = route.addwpt(acidx, self.dest[acidx], route.dest, + lat, lon, 0.0, bs.traf.cas[acidx]) + # If only waypoint: activate + if (iwp == 0) or (self.orig[acidx] != "" and route.nwp == 2): + bs.traf.actwp.lat[acidx] = route.wplat[iwp] + bs.traf.actwp.lon[acidx] = route.wplon[iwp] + bs.traf.actwp.nextaltco[acidx] = route.wpalt[iwp] + bs.traf.actwp.spd[acidx] = route.wpspd[iwp] + + bs.traf.swlnav[acidx] = True + bs.traf.swvnav[acidx] = True + route.iactwp = iwp + route.direct(acidx, route.wpname[iwp]) + + # If not found, say so + elif iwp < 0: + return False, ('DEST position'+self.dest[acidx] + " not found.") + + @stack.command(name='ORIG') + def setorig(self, acidx: 'acid', wpname: 'wpt' = None): + ''' ORIG acid, latlon/airport + + Set origin of aircraft. ''' + if wpname is None: + return True, 'ORIG ' + bs.traf.id[acidx] + ': ' + self.orig[acidx] + route = self.route[acidx] + apidx = bs.navdb.getaptidx(wpname) + if apidx < 0: + if bs.traf.ap.route[acidx].nwp > 0: + reflat = bs.traf.ap.route[acidx].wplat[-1] + reflon = bs.traf.ap.route[acidx].wplon[-1] + else: + reflat = bs.traf.lat[acidx] + reflon = bs.traf.lon[acidx] - if cmd == "DEST": - self.dest[idx] = name - iwp = route.addwpt( idx, self.dest[idx], route.dest, - lat, lon, 0.0, bs.traf.cas[idx] ) - # If only waypoint: activate - if ( iwp == 0 ) or ( self.orig[idx] != "" and route.nwp == 2 ): - bs.traf.actwp.lat[idx] = route.wplat[iwp] - bs.traf.actwp.lon[idx] = route.wplon[iwp] - bs.traf.actwp.nextaltco[idx] = route.wpalt[iwp] - bs.traf.actwp.spd[idx] = route.wpspd[iwp] - - bs.traf.swlnav[idx] = True - bs.traf.swvnav[idx] = True - route.iactwp = iwp - route.direct( idx, route.wpname[iwp] ) - - # If not found, say so - elif iwp < 0: - return False, ( 'DEST' + self.dest[idx] + " not found." ) + success, posobj = txt2pos(wpname, reflat, reflon) + if success: + lat = posobj.lat + lon = posobj.lon + else: + return False, ("ORIG: Position " + wpname + " not found.") - # Origin: bookkeeping only for now, store in route as origin else: - self.orig[idx] = name - apidx = bs.navdb.getaptidx( name ) - - if apidx < 0: - - if cmd == "ORIG" and bs.traf.ap.route[idx].nwp > 0: - reflat = bs.traf.ap.route[idx].wplat[0] - reflon = bs.traf.ap.route[idx].wplon[0] - else: - reflat = bs.traf.lat[idx] - reflon = bs.traf.lon[idx] - - success, posobj = txt2pos( name, reflat, reflon ) - if success: - lat = posobj.lat - lon = posobj.lon - else: - return False, ( cmd + ": Orig " + name + " not found." ) - - - iwp = route.addwpt( idx, self.orig[idx], route.orig, - lat, lon, 0.0, bs.traf.cas[idx] ) - if iwp < 0: - return False, ( self.orig[idx] + " not found." ) + lat = bs.navdb.aptlat[apidx] + lon = bs.navdb.aptlon[apidx] - def setLNAV( self, idx, flag=None ): - """ Set LNAV on or off for specific or for all aircraft """ - if not isinstance( idx, Collection ): + # Origin: bookkeeping only for now, store in route as origin + self.orig[acidx] = wpname + iwp = route.addwpt(acidx, self.orig[acidx], route.orig, + lat, lon, 0.0, bs.traf.cas[acidx]) + if iwp < 0: + return False, (self.orig[acidx] + " not found.") + + @stack.command(name='LNAV') + def setLNAV(self, idx: 'acid', flag: 'bool'=None): + """ LNAV acid,[ON/OFF] + + LNAV (lateral FMS mode) switch for autopilot """ + if not isinstance(idx, Collection): if idx is None: # All aircraft are targeted bs.traf.swlnav = np.array( bs.traf.ntraf * [flag] ) @@ -665,19 +806,22 @@ def setLNAV( self, idx, flag=None ): route.direct( i, route.wpname[route.findact( i )] ) else: bs.traf.swlnav[i] = False - if flag == None: - return True, '\n'.join( output ) - - def setVNAV( self, idx, flag=None ): - """ Set VNAV on or off for specific or for all aircraft """ - if not isinstance( idx, Collection ): + if flag is None: + return True, '\n'.join(output) + + @stack.command(name='VNAV') + def setVNAV(self, idx: 'acid', flag: 'bool'=None): + """ VNAV acid,[ON/OFF] + + Switch on/off VNAV mode, the vertical FMS mode (autopilot) """ + if not isinstance(idx, Collection): if idx is None: # All aircraft are targeted bs.traf.swvnav = np.array( bs.traf.ntraf * [flag] ) bs.traf.swvnavspd = np.array( bs.traf.ntraf * [flag] ) else: # Prepare for the loop - idx = np.array( [idx] ) + idx = np.array([idx]) # Set VNAV for all aircraft in idx array output = [] @@ -772,6 +916,7 @@ def calcvrta( v0, dx, deltime, trafax ): # Normal case is one solution else: vtarg = vlst[0] + return vtarg def distaccel( v0, v1, axabs ): @@ -780,4 +925,4 @@ def distaccel( v0, v1, axabs ): accel/decel is detemremind by sign of v1-v0 axabs is acceleration/deceleration of which absolute value will be used solve for x: x = vo*t + 1/2*a*t*t v = v0 + a*t """ - return 0.5 * np.abs( v1 * v1 - v0 * v0 ) / np.maximum( .001, np.abs( axabs ) ) + return 0.5*np.abs(v1*v1-v0*v0)/np.maximum(.001,np.abs(axabs)) diff --git a/bluesky/traffic/performance/bada/coeff_bada.py b/bluesky/traffic/performance/bada/coeff_bada.py index 73652c01c1..a1bc2c6037 100644 --- a/bluesky/traffic/performance/bada/coeff_bada.py +++ b/bluesky/traffic/performance/bada/coeff_bada.py @@ -105,8 +105,8 @@ def init(bada_path=''): releasefile = path.join(path.normpath(bada_path), 'ReleaseSummary') if path.isfile(releasefile): global release_date, bada_version - re_reldate = re.compile('Summary Date:\s+(.+(?0.) or (bs.traf.selvs.any()<0.): # thrust = f(selvs) - T = ((bs.traf.selvs!=0)*(((bs.traf.aporasas.vs*self.mass*g0)/ \ - (self.ESF*np.maximum(bs.traf.eps,bs.traf.tas)*cpred)) \ - + self.D)) + ((bs.traf.selvs==0)*T) + T_vs = ((bs.traf.selvs!=0) * \ + (((bs.traf.aporasas.vs * np.sign(delalt)*self.mass*g0)/ \ + (self.ESF*np.maximum(bs.traf.eps,bs.traf.tas)*cpred)) \ + + self.D)) + ((bs.traf.selvs==0)*T) + # limit minimum thrust in descent to idle thrust + T = np.where(descent, np.maximum(T_vs, T), T) self.thrust = T @@ -550,7 +553,12 @@ def update(self, dt): self.pf_flag = np.where ((bs.traf.alt <0.5)*(self.post_flight), False, self.pf_flag) - return + # define acceleration: aircraft taxiing and taking off use ground acceleration, + # landing aircraft use ground deceleration, others use standard acceleration + # --> BADA uses the same value for ground acceleration as for deceleration + self.axmax = ((self.phase == PHASE['IC']) + (self.phase == PHASE['CR']) + (self.phase == PHASE['AP']) + (self.phase == PHASE['LD'])) * 0.5 \ + + ((self.phase == PHASE['TO']) + (self.phase == PHASE['GD'])*(1-self.post_flight)) * self.gr_acc \ + + (self.phase == PHASE['GD']) * self.post_flight * self.gr_acc def limits(self, intent_v, intent_vs, intent_h, ax): """FLIGHT ENVELPOE""" @@ -595,31 +603,6 @@ def limits(self, intent_v, intent_vs, intent_h, ax): return allowed_tas, allowed_vs, allowed_alt - def acceleration(self): - # define acceleration: aircraft taxiing and taking off use ground acceleration, - # landing aircraft use ground deceleration, others use standard acceleration - # --> BADA uses the same value for ground acceleration as for deceleration - - - ax = ((self.phase==PHASE['IC']) + (self.phase==PHASE['CR']) + (self.phase==PHASE['AP']) + (self.phase==PHASE['LD'])) * 0.5 \ - + ((self.phase==PHASE['TO']) + (self.phase==PHASE['GD'])*(1-self.post_flight)) * self.gr_acc \ - + (self.phase==PHASE['GD']) * self.post_flight * self.gr_acc - - return ax - - - #------------------------------------------------------------------------------ - #DEBUGGING - - #record data - # self.log.write(self.dt, str(bs.traf.alt[0]), str(bs.traf.tas[0]), str(self.D[0]), str(self.T[0]), str(self.fuelflow[0]), str(bs.traf.vs[0]), str(cd[0])) - # self.log.save() - - # print self.id, self.phase, self.alt/ft, self.tas/kts, self.cas/kts, self.M, \ - # self.thrust, self.D, self.fuelflow, cl, cd, self.vs/fpm, self.ESF,self.atrans, maxthr, \ - # self.vmto/kts, self.vmic/kts ,self.vmcr/kts, self.vmap/kts, self.vmld/kts, \ - # CD0f, kf, self.hmaxact - def show_performance(self, acid): # PERF acid command bs.scr.echo("Flight phase: %s" % self.phase[acid]) diff --git a/bluesky/traffic/performance/legacy/perfbs.py b/bluesky/traffic/performance/legacy/perfbs.py index 1bfeda26c8..5ca2d1ba82 100644 --- a/bluesky/traffic/performance/legacy/perfbs.py +++ b/bluesky/traffic/performance/legacy/perfbs.py @@ -198,7 +198,7 @@ def update(self, dt): # allocate aircraft to their flight phase self.phase, self.bank = phases(bs.traf.alt, bs.traf.gs, delalt, bs.traf.cas, self.vmto, self.vmic, self.vmap, self.vmcr, self.vmld, - bs.traf.bank, bs.traf.bphase, bs.traf.swhdgsel,swbada) + bs.traf.ap.bankdef, bs.traf.bphase, bs.traf.swhdgsel,swbada) # AERODYNAMICS # compute CL: CL = 2*m*g/(VTAS^2*rho*S) @@ -317,7 +317,11 @@ def update(self, dt): # otherwise taxiing will be impossible afterwards self.pf_flag = np.where ((bs.traf.alt <0.5)*(self.post_flight), False, self.pf_flag) - return + # define acceleration: aircraft taxiing and taking off use ground acceleration, + # landing aircraft use ground deceleration, others use standard acceleration + self.axmax = ((self.phase == PHASE['IC']) + (self.phase == PHASE['CR']) + (self.phase == PHASE['AP']) + (self.phase == PHASE['LD'])) * 0.5 \ + + ((self.phase == PHASE['TO']) + (self.phase == PHASE['GD'])*(1-self.post_flight)) * self.gr_acc \ + + (self.phase == PHASE['GD']) * self.post_flight * self.gr_dec def limits(self, intent_v, intent_vs, intent_h, ax): """Flight envelope""" # Connect this with function limits in performance.py @@ -359,17 +363,6 @@ def limits(self, intent_v, intent_vs, intent_h, ax): return allowed_tas, allowed_vs, allowed_alt - def acceleration(self): - # define acceleration: aircraft taxiing and taking off use ground acceleration, - # landing aircraft use ground deceleration, others use standard acceleration - - ax = ((self.phase==PHASE['IC']) + (self.phase==PHASE['CR']) + (self.phase==PHASE['AP']) + (self.phase==PHASE['LD'])) * 0.5 \ - + ((self.phase==PHASE['TO']) + (self.phase==PHASE['GD'])*(1-self.post_flight)) * self.gr_acc \ - + (self.phase==PHASE['GD']) * self.post_flight * self.gr_dec - - return ax - - def engchange(self, idx, engid=None): """change of engines - for jet aircraft only!""" if not engid: diff --git a/bluesky/traffic/performance/openap/coeff.py b/bluesky/traffic/performance/openap/coeff.py index 14262c184e..e1fd7e7a7f 100644 --- a/bluesky/traffic/performance/openap/coeff.py +++ b/bluesky/traffic/performance/openap/coeff.py @@ -3,9 +3,14 @@ import json import numpy as np import pandas as pd +import bluesky as bs from bluesky import settings +from bluesky.settings import get_project_root -settings.set_variable_defaults(perf_path_openap="data/performance/OpenAP") + +settings.set_variable_defaults( + perf_path_openap=os.path.join(get_project_root(), "data", "performance", "OpenAP") +) LIFT_FIXWING = 1 # fixwing aircraft LIFT_ROTOR = 2 # rotor aircraft @@ -36,7 +41,12 @@ def __init__(self): else: dataline = line.strip("\n") acmod, synomod = dataline.split("=") - self.synodict[acmod.strip().upper()] = synomod.strip().upper() + acmod = acmod.strip().upper() + synomod = synomod.strip().upper() + + if acmod == synomod: + continue + self.synodict[acmod] = synomod self.acs_fixwing = self._load_all_fixwing_flavor() self.engines_fixwing = pd.read_csv(fixwing_engine_db, encoding="utf-8") @@ -94,10 +104,10 @@ def _load_all_fixwing_envelop(self): All unit in SI""" limits_fixwing = {} for mdl, ac in self.acs_fixwing.items(): - fenv = fixwing_envelops_dir + mdl.lower() + ".csv" + fenv = fixwing_envelops_dir + mdl.lower() + ".txt" if os.path.exists(fenv): - df = pd.read_csv(fenv, index_col="param") + df = pd.read_fwf(fenv).set_index("variable") limits_fixwing[mdl] = {} limits_fixwing[mdl]["vminto"] = df.loc["to_v_lof"]["min"] limits_fixwing[mdl]["vmaxto"] = df.loc["to_v_lof"]["max"] @@ -132,17 +142,17 @@ def _load_all_fixwing_envelop(self): limits_fixwing[mdl]["axmax"] = df.loc["to_acc_tof"]["max"] limits_fixwing[mdl]["vsmax"] = max( - df.loc["ic_vz_avg"]["max"], - df.loc["cl_vz_avg_pre_cas"]["max"], - df.loc["cl_vz_avg_cas_const"]["max"], - df.loc["cl_vz_avg_mach_const"]["max"], + df.loc["ic_vs_avg"]["max"], + df.loc["cl_vs_avg_pre_cas"]["max"], + df.loc["cl_vs_avg_cas_const"]["max"], + df.loc["cl_vs_avg_mach_const"]["max"], ) limits_fixwing[mdl]["vsmin"] = min( - df.loc["ic_vz_avg"]["min"], - df.loc["de_vz_avg_after_cas"]["min"], - df.loc["de_vz_avg_cas_const"]["min"], - df.loc["de_vz_avg_mach_const"]["min"], + df.loc["ic_vs_avg"]["min"], + df.loc["de_vs_avg_after_cas"]["min"], + df.loc["de_vs_avg_cas_const"]["min"], + df.loc["de_vs_avg_mach_const"]["min"], ) # create envolop based on synonym @@ -153,13 +163,23 @@ def _load_all_fixwing_envelop(self): return limits_fixwing def _load_all_rotor_envelop(self): - """ load rotor aircraft envelop, all unit in SI""" + """load rotor aircraft envelop, all unit in SI""" limits_rotor = {} for mdl, ac in self.acs_rotor.items(): limits_rotor[mdl] = {} - limits_rotor[mdl]["vmin"] = ac["envelop"]["v_min"] - limits_rotor[mdl]["vmax"] = ac["envelop"]["v_max"] - limits_rotor[mdl]["vsmin"] = ac["envelop"]["vs_min"] - limits_rotor[mdl]["vsmax"] = ac["envelop"]["vs_max"] - limits_rotor[mdl]["hmax"] = ac["envelop"]["h_max"] + + limits_rotor[mdl]["vmin"] = ac["envelop"].get("v_min", -20) + limits_rotor[mdl]["vmax"] = ac["envelop"].get("v_max", 20) + limits_rotor[mdl]["vsmin"] = ac["envelop"].get("vs_min", -5) + limits_rotor[mdl]["vsmax"] = ac["envelop"].get("vs_max", 5) + limits_rotor[mdl]["hmax"] = ac["envelop"].get("h_max", 2500) + + params = ["v_min", "v_max", "vs_min", "vs_max", "h_max"] + if set(params) <= set(ac["envelop"].keys()): + pass + else: + warn = f"Warning: Some performance parameters for {mdl} are not found, default values used." + print(warn) + bs.scr.echo(warn) + return limits_rotor diff --git a/bluesky/traffic/performance/openap/perfoap.py b/bluesky/traffic/performance/openap/perfoap.py index 4b3800bdc5..c1d3b5c41c 100644 --- a/bluesky/traffic/performance/openap/perfoap.py +++ b/bluesky/traffic/performance/openap/perfoap.py @@ -1,3 +1,5 @@ +import warnings +from bluesky.stack.stackbase import stack import numpy as np import bluesky as bs from bluesky.tools import aero @@ -25,20 +27,15 @@ def __init__(self): self.coeff = coeff.Coefficient() with self.settrafarrays(): - self.actypes = np.array([], dtype=str) - self.phase = np.array([]) self.lifttype = np.array([]) # lift type, fixwing [1] or rotor [2] - self.mass = np.array([]) # mass of aircraft self.engnum = np.array([], dtype=int) # number of engines self.engthrmax = np.array([]) # static engine thrust self.engbpr = np.array([]) # engine bypass ratio - self.thrust = np.array([]) # thrust ratio at current alt spd self.max_thrust = np.array([]) # thrust ratio at current alt spd self.ff_coeff_a = np.array([]) # icao fuel flows coefficient a self.ff_coeff_b = np.array([]) # icao fuel flows coefficient b self.ff_coeff_c = np.array([]) # icao fuel flows coefficient c self.engpower = np.array([]) # engine power, rotor ac - self.cd0 = np.array([]) # zero drag coefficient self.cd0_clean = np.array([]) # Cd0, clean configuration self.k_clean = np.array([]) # k, clean configuration self.cd0_to = np.array([]) # Cd0, takeoff configuration @@ -57,7 +54,6 @@ def __init__(self): self.vsmin = np.array([]) self.vsmax = np.array([]) self.hmax = np.array([]) - self.axmax = np.array([]) self.vminto = np.array([]) self.hcross = np.array([]) self.mmo = np.array([]) @@ -72,8 +68,10 @@ def create(self, n=1): if (actype not in self.coeff.actypes_rotor) and ( actype not in self.coeff.dragpolar_fixwing ): - if actype in self.coeff.synodict: - # print(actype,"replaced by",self.coeff.synodict[actype]) + if actype in self.coeff.synodict.keys(): + # warn = f"Warning: {actype} replaced by {self.coeff.synodict[actype]}" + # print(warn) + # bs.scr.echo(warn) actype = self.coeff.synodict[actype] # initialize aircraft / engine performance parameters @@ -90,6 +88,9 @@ def create(self, n=1): else: # convert to known aircraft type if actype not in self.coeff.actypes_fixwing: + # warn = f"Warning: {actype} replaced by B744" + # print(warn) + # bs.scr.echo(warn) actype = "B744" # populate fuel flow model @@ -170,11 +171,16 @@ def create(self, n=1): "delta_cd_gear" ] - # append update actypes, after removing unkown types - self.actypes[-n:] = [actype] * n + # append update actypes, after removing unknown types + self.actype[-n:] = [actype] * n + + # Update envelope speed limits + mask = np.zeros_like(self.actype, dtype=bool) + mask[-n:] = True + self.vmin[-n:], self.vmax[-n:] = self._construct_v_limits(mask) def update(self, dt): - """ Periodic update function for performance calculations. """ + """Periodic update function for performance calculations.""" # update phase, infer from spd, roc, alt lenph1 = len(self.phase) self.phase = ph.get( @@ -182,7 +188,7 @@ def update(self, dt): ) # update speed limits, based on phase change - self.vmin, self.vmax = self._construct_v_limits(self.actypes, self.phase) + self.vmin, self.vmax = self._construct_v_limits() idx_fixwing = np.where(self.lifttype == coeff.LIFT_FIXWING)[0] @@ -244,6 +250,9 @@ def update(self, dt): + self.ff_coeff_c[idx_fixwing] ) + # ----- update max acceleration ---- + self.axmax = self.calc_axmax() + # TODO: implement thrust computation for rotor aircraft # idx_rotor = np.where(self.lifttype==coeff.LIFT_ROTOR)[0] # self.thrust[idx_rotor] = 0 @@ -279,7 +288,6 @@ def limits(self, intent_v_tas, intent_vs, intent_h, ax): allow_h = np.where(intent_h > self.hmax, self.hmax, intent_h) intent_v_cas = aero.vtas2cas(intent_v_tas, allow_h) - allow_v_cas = np.where((intent_v_cas < self.vmin), self.vmin, intent_v_cas) allow_v_cas = np.where(intent_v_cas > self.vmax, self.vmax, allow_v_cas) allow_v_tas = aero.vcas2tas(allow_v_cas, allow_h) @@ -338,21 +346,21 @@ def currentlimits(self, id=None): else: return vtasmin, vtasmax, self.vsmin, self.vsmax - def _construct_v_limits(self, actypes, phases): + def _construct_v_limits(self, mask=True): """Compute speed limist base on aircraft model and flight phases Args: - actypes (String or 1D-array): aircraft type / model - phases (int or 1D-array): aircraft flight phases + mask: Indices (boolean) for aircraft to construct speed limits for. + When no indices are passed, all aircraft are updated. Returns: 2D-array: vmin, vmax """ - n = len(actypes) + n = len(self.actype) vmin = np.zeros(n) vmax = np.zeros(n) - ifw = np.where(self.lifttype == coeff.LIFT_FIXWING)[0] + ifw = np.where(np.logical_and(self.lifttype == coeff.LIFT_FIXWING, mask))[0] vminfw = np.zeros(len(ifw)) vmaxfw = np.zeros(len(ifw)) @@ -360,25 +368,25 @@ def _construct_v_limits(self, actypes, phases): # obtain flight envelope for speed, roc, and alt, based on flight phase # --- minimum speed --- - vminfw = np.where(phases[ifw] == ph.NA, 0, vminfw) - vminfw = np.where(phases[ifw] == ph.IC, self.vminic[ifw], vminfw) + vminfw = np.where(self.phase[ifw] == ph.NA, 0, vminfw) + vminfw = np.where(self.phase[ifw] == ph.IC, self.vminic[ifw], vminfw) vminfw = np.where( - (phases[ifw] >= ph.CL) | (phases[ifw] <= ph.DE), self.vminer[ifw], vminfw + (self.phase[ifw] >= ph.CL) | (self.phase[ifw] <= ph.DE), self.vminer[ifw], vminfw ) - vminfw = np.where(phases[ifw] == ph.AP, self.vminap[ifw], vminfw) - vminfw = np.where(phases[ifw] == ph.GD, 0, vminfw) + vminfw = np.where(self.phase[ifw] == ph.AP, self.vminap[ifw], vminfw) + vminfw = np.where(self.phase[ifw] == ph.GD, 0, vminfw) # --- maximum speed --- - vmaxfw = np.where(phases[ifw] == ph.NA, self.vmaxer[ifw], vmaxfw) - vmaxfw = np.where(phases[ifw] == ph.IC, self.vmaxic[ifw], vmaxfw) + vmaxfw = np.where(self.phase[ifw] == ph.NA, self.vmaxer[ifw], vmaxfw) + vmaxfw = np.where(self.phase[ifw] == ph.IC, self.vmaxic[ifw], vmaxfw) vmaxfw = np.where( - (phases[ifw] >= ph.CL) | (phases[ifw] <= ph.DE), self.vmaxer[ifw], vmaxfw + (self.phase[ifw] >= ph.CL) | (self.phase[ifw] <= ph.DE), self.vmaxer[ifw], vmaxfw ) - vmaxfw = np.where(phases[ifw] == ph.AP, self.vmaxap[ifw], vmaxfw) - vmaxfw = np.where(phases[ifw] == ph.GD, self.vmaxic[ifw], vmaxfw) + vmaxfw = np.where(self.phase[ifw] == ph.AP, self.vmaxap[ifw], vmaxfw) + vmaxfw = np.where(self.phase[ifw] == ph.GD, self.vmaxic[ifw], vmaxfw) # rotor - ir = np.where(self.lifttype == coeff.LIFT_ROTOR)[0] + ir = np.where(np.logical_and(self.lifttype == coeff.LIFT_ROTOR, mask))[0] vminr = self.vmin[ir] vmaxr = self.vmax[ir] @@ -387,23 +395,30 @@ def _construct_v_limits(self, actypes, phases): vmin[ir] = vminr vmax[ir] = vmaxr - return vmin, vmax + if isinstance(mask, bool): + return vmin, vmax + return vmin[mask], vmax[mask] - def acceleration(self): + def calc_axmax(self): # accelerations depending on phase and wing type - acc_fixwing_ground = 2 - acc_rotor = 3.5 + axmax_fixwing_ground = 2 + axmax_rotor = 3.5 + + axmax = np.zeros(bs.traf.ntraf) - accs = np.zeros(bs.traf.ntraf) - accs = (self.max_thrust - self.drag) / self.mass + # fix-wing, in flight + axmax = (self.max_thrust - self.drag) / self.mass - accs[self.phase == ph.GD] = acc_fixwing_ground + # fix-wing, on ground + axmax[self.phase == ph.GD] = axmax_fixwing_ground - accs[self.lifttype == coeff.LIFT_ROTOR] = acc_rotor + # drones + axmax[self.lifttype == coeff.LIFT_ROTOR] = axmax_rotor - accs[accs < 0.5] = 0.5 # minumum acceleration + # global minumum acceleration + axmax[axmax < 0.5] = 0.5 - return accs + return axmax def show_performance(self, acid): return ( diff --git a/bluesky/traffic/performance/openap/thrust.py b/bluesky/traffic/performance/openap/thrust.py index b580a89997..e0cd8a0aa1 100644 --- a/bluesky/traffic/performance/openap/thrust.py +++ b/bluesky/traffic/performance/openap/thrust.py @@ -28,7 +28,7 @@ def compute_max_thr_ratio(phase, bpr, v, h, vs, thr0): # ---- thrust ratio for descent ---- # considering 15% of inflight model thrust - ratio_idle = 0.15 * ratio_inflight + ratio_idle = 0.07 * ratio_inflight # thrust ratio array # LD and GN assume ZERO thrust diff --git a/bluesky/traffic/performance/perfbase.py b/bluesky/traffic/performance/perfbase.py index dbe138995e..79147dfaea 100644 --- a/bluesky/traffic/performance/perfbase.py +++ b/bluesky/traffic/performance/perfbase.py @@ -1,6 +1,6 @@ -''' This module provides PerfBase, the base class for aircraft +""" This module provides PerfBase, the base class for aircraft performance implementations. -''' +""" import numpy as np from bluesky import settings from bluesky.core import Entity, timed_function @@ -11,7 +11,8 @@ class PerfBase(Entity, replaceable=True): - """ Base class for BlueSky aircraft performance implementations. """ + """Base class for BlueSky aircraft performance implementations.""" + def __init__(self): super().__init__() with self.settrafarrays(): @@ -33,58 +34,73 @@ def __init__(self): # Performance limits per aircraft self.vmin = np.array([]) self.vmax = np.array([]) + self.axmax = np.array([]) # Max/min acceleration [m/s2] + def create(self, n): + super().create(n=n) + self.axmax[-n:] = 2.0 # Default acceleration limit is 2 m/s2 @timed_function(name="performance", dt=settings.performance_dt, manual=True) def update(self, dt=settings.performance_dt): - """implement this method """ + """implement this method""" pass def limits(self, intent_v, intent_vs, intent_h, ax): - """implement this method """ + """implement this method""" return intent_v, intent_vs, intent_h def currentlimits(self): - """implement this method """ + """implement this method""" # Get current kinematic performance envelop of all aircraft pass - def acceleration(self): - ''' Default aircraft acceleration is 2 m/s2. ''' - return 2.0 - - @command(name='ENG') - def engchange(self, acid : 'acid', engine_id : 'txt' = ''): - """ Specify a different engine type for aircraft 'acid' """ - return False, 'The currently selected performance model doesn\'t support engine changes.' - - @command(name='PERFSTATS', aliases=('PERFINFO', 'PERFDATA')) - def show_performance(self, acid : 'acid'): - """ Show aircraft perfromance parameters for aircraft 'acid' """ - return False, 'The currently selected performance model doesn\'t provide this function.' - + @command(name="ENG") + def engchange(self, acid: "acid", engine_id: "txt" = ""): + """Specify a different engine type for aircraft 'acid'""" + return ( + False, + "The currently selected performance model doesn't support engine changes.", + ) + + @command(name="PERFSTATS", aliases=("PERFINFO", "PERFDATA")) + def show_performance(self, acid: "acid"): + """Show aircraft perfromance parameters for aircraft 'acid'""" + return ( + False, + "The currently selected performance model doesn't provide this function.", + ) @staticmethod - @command(name='PERF') - def setmethod(name: 'txt' = ''): - ''' Select a Performance implementation. ''' + @command(name="PERF") + def setmethod(name: "txt" = ""): + """Select a Performance implementation.""" # Get a dict of all registered Performance models methods = PerfBase.derived() - names = ['OFF' if n == 'PERFBASE' else n for n in methods] + names = ["OFF" if n == "PERFBASE" else n for n in methods] if not name: - curname = 'OFF' if PerfBase.selected() is PerfBase else PerfBase.selected().__name__ - return True, f'Current Performance model: {curname}' + \ - f'\nAvailable performance models: {", ".join(names)}' + curname = ( + "OFF" + if PerfBase.selected() is PerfBase + else PerfBase.selected().__name__ + ) + return ( + True, + f"Current Performance model: {curname}" + + f'\nAvailable performance models: {", ".join(names)}', + ) # Check if the requested method exists - if name == 'OFF': + if name == "OFF": PerfBase.select() - return True, 'Performance model turned off.' + return True, "Performance model turned off." method = methods.get(name, None) if method is None: - return False, f'{name} doesn\'t exist.\n' + \ - f'Available performance models: {", ".join(names)}' + return ( + False, + f"{name} doesn't exist.\n" + + f'Available performance models: {", ".join(names)}', + ) # Select the requested method method.select() - return True, f'Selected {method.__name__} as performance model.' + return True, f"Selected {method.__name__} as performance model." diff --git a/bluesky/traffic/route.py b/bluesky/traffic/route.py index 36f0916cde..0ef96f1f86 100644 --- a/bluesky/traffic/route.py +++ b/bluesky/traffic/route.py @@ -1,5 +1,6 @@ """ Route implementation for the BlueSky FMS.""" from os import path +from weakref import WeakValueDictionary from numpy import * import bluesky as bs from bluesky.tools import geo @@ -33,16 +34,21 @@ class Route(Replaceable): calcwp = 4 # Calculated waypoint (T/C, T/D, A/C) runway = 5 # Runway: Copy name and positions + # Aircraft route objects + _routes = WeakValueDictionary() + def __init__(self, acid): + # Add self to dictionary of all aircraft routes + Route._routes[acid] = self # Aircraft id (callsign) of the aircraft to which this route belongs self.acid = acid self.nwp = 0 - # Waypoint data - self.wpname = [] - self.wptype = [] - self.wplat = [] - self.wplon = [] + # Waypoint data + self.wpname = [] # List of waypoint names for this flight plan + self.wptype = [] # List of waypoint types + self.wplat = [] # List of waypoint latitudes + self.wplon = [] # List of waypoint longitudes self.wpalt = [] # [m] negative value means not specified self.wpspd = [] # [m/s] negative value means not specified self.wprta = [] # [m/s] negative value means not specified @@ -97,10 +103,50 @@ def get_available_name(data, name_, len_=2): appi += 1 name_ = name_[:-len_]+fmt_.format(appi) return name_ + + @stack.command(name = 'ADDWPTMODE', annotations = 'acid, [wpt,alt]') + @staticmethod + def addwptMode(acidx, mode = None, value = None): + '''Changes the mode of the ADDWPT command to add waypoints of type 'mode'. + Available modes: FLYBY, FLYOVER, FLYTURN. Also used to specify + TURNSPEED or TURNRADIUS.''' + # Get aircraft route + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + # First, we want to check what 'mode' is, and then call addwptStack + # accordingly. + if mode in ['FLYBY', 'FLYOVER', 'FLYTURN']: + # We're just changing addwpt mode, call the appropriate function. + Route.addwptStack(acidx, mode) + return True + + elif mode in ['TURNSPEED', 'TURNSPD', 'TURNRADIUS', 'TURNRAD']: + # We're changing the turn speed or radius + Route.addwptStack(acidx, mode, value) + return True + + elif mode == None: + # Just echo the current wptmode + if acrte.swflyby == True and acrte.swflyturn == False: + bs.scr.echo('Current ADDWPT mode is FLYBY.') + return True - def addwptStack(self, idx, *args): # args: all arguments of addwpt - """ADDWPT acid, (wpname/lat,lon),[alt],[spd],[afterwp],[beforewp]""" + elif acrte.swflyby == False and acrte.swflyturn == False: + bs.scr.echo('Current ADDWPT mode is FLYOVER.') + return True + else: + bs.scr.echo('Current ADDWPT mode is FLYTURN.') + return True + + @stack.command(name='ADDWPT', annotations='acid,wpt,[alt,spd,wpinroute,wpinroute]', aliases=("WPTYPE",)) + @staticmethod + def addwptStack(acidx, *args): # args: all arguments of addwpt + """ADDWPT acid, (wpname/lat,lon),[alt],[spd],[afterwp],[beforewp]""" + # First get the appropriate ac route + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + #debug print ("addwptStack:",args) #print("active = ",self.wpname[self.iactwp]) #print(args) @@ -110,20 +156,18 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt swwpmode = args[0].replace('-', '') if swwpmode == "FLYBY": - self.swflyby = True - self.swflyturn = False + acrte.swflyby = True + acrte.swflyturn = False return True elif swwpmode == "FLYOVER": - - self.swflyby = False - self.swflyturn = False + acrte.swflyby = False + acrte.swflyturn = False return True elif swwpmode == "FLYTURN": - - self.swflyby = False - self.swflyturn = True + acrte.swflyby = False + acrte.swflyturn = True return True elif len(args) == 2: @@ -133,7 +177,7 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt if swwpmode == "TURNRAD" or swwpmode == "TURNRADIUS": try: - self.turnrad = float(args[1])/ft # arg was originally parsed as wpalt + acrte.turnrad = float(args[1])/ft # arg was originally parsed as wpalt except: return False,"Error in processing value of turn radius" return True @@ -141,7 +185,7 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt elif swwpmode == "TURNSPD" or swwpmode == "TURNSPEED": try: - self.turnspd = args[1]*kts/ft # [m/s] Arg was wpalt Keep it as IAS/CAS orig in kts, now in m/s + acrte.turnspd = args[1]*kts/ft # [m/s] Arg was wpalt Keep it as IAS/CAS orig in kts, now in m/s except: return False, "Error in processing value of turn speed" @@ -152,18 +196,18 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt # Choose reference position ot look up VOR and waypoints # First waypoint: own position - if self.nwp == 0: - reflat = bs.traf.lat[idx] - reflon = bs.traf.lon[idx] + if acrte.nwp == 0: + reflat = bs.traf.lat[acidx] + reflon = bs.traf.lon[acidx] # Or last waypoint before destination else: - if self.wptype[-1] != Route.dest or self.nwp == 1: - reflat = self.wplat[-1] - reflon = self.wplon[-1] + if acrte.wptype[-1] != Route.dest or acrte.nwp == 1: + reflat = acrte.wplat[-1] + reflon = acrte.wplon[-1] else: - reflat = self.wplat[-2] - reflon = self.wplon[-2] + reflat = acrte.wplat[-2] + reflon = acrte.wplon[-2] # Default altitude, speed and afterwp alt = -999. @@ -190,7 +234,7 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt wptype = Route.runway else: # treat as lat/lon - name = bs.traf.id[idx] + name = acid wptype = Route.wplatlon if len(args) > 1 and args[1]: @@ -214,8 +258,8 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt # Look up runway in route rwyrteidx = -1 i = 0 - while i0: + while i0: # print (self.wpname[i]) rwyrteidx = i i += 1 @@ -227,19 +271,19 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt # print ("rwyrteidx =",rwyrteidx) # We find a runway in the route, so use it if rwyrteidx>0: - rwylat = self.wplat[rwyrteidx] - rwylon = self.wplon[rwyrteidx] + rwylat = acrte.wplat[rwyrteidx] + rwylon = acrte.wplon[rwyrteidx] aptidx = bs.navdb.getapinear(rwylat,rwylon) aptname = bs.navdb.aptname[aptidx] - rwyname = self.wpname[rwyrteidx].split("/")[1] + rwyname = acrte.wpname[rwyrteidx].split("/")[1] rwyid = rwyname.replace("RWY","").replace("RW","") rwyhdg = bs.navdb.rwythresholds[aptname][rwyid][2] else: - rwylat = bs.traf.lat[idx] - rwylon = bs.traf.lon[idx] - rwyhdg = bs.traf.trk[idx] + rwylat = bs.traf.lat[acidx] + rwylon = bs.traf.lon[acidx] + rwyhdg = bs.traf.trk[acidx] elif args[1].count("/") > 0 or len(args) > 2 and args[2]: # we need apt,rwy # Take care of both EHAM/RW06 as well as EHAM,RWY18L (so /&, and RW/RWY) @@ -267,8 +311,8 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt if success: rwylat,rwylon = posobj.lat,posobj.lon else: - rwylat = bs.traf.lat[idx] - rwylon = bs.traf.lon[idx] + rwylat = bs.traf.lat[acidx] + rwylon = bs.traf.lon[acidx] else: return False,"Use ADDWPT TAKEOFF,AIRPORTID,RWYNAME" @@ -280,258 +324,333 @@ def addwptStack(self, idx, *args): # args: all arguments of addwpt # Add after the runwy in the route if rwyrteidx > 0: - afterwp = self.wpname[rwyrteidx] + afterwp = acrte.wpname[rwyrteidx] - elif self.wptype and self.wptype[0] == Route.orig: - afterwp = self.wpname[0] + elif acrte.wptype and acrte.wptype[0] == Route.orig: + afterwp = acrte.wpname[0] else: # Assume we're called before other waypoints are added afterwp = "" - name = "T/O-" + bs.traf.id[idx] # Use lat/lon naming convention + name = "T/O-" + acid # Use lat/lon naming convention # Add waypoint - wpidx = self.addwpt(idx, name, wptype, lat, lon, alt, spd, afterwp, beforewp) + wpidx = acrte.addwpt(acidx, name, wptype, lat, lon, alt, spd, afterwp, beforewp) # Recalculate flight plan - self.calcfp() + acrte.calcfp() # Check for success by checking inserted location in flight plan >= 0 if wpidx < 0: return False, "Waypoint " + name + " not added." # check for presence of orig/dest - norig = int(bs.traf.ap.orig[idx] != "") # 1 if orig is present in route - ndest = int(bs.traf.ap.dest[idx] != "") # 1 if dest is present in route + norig = int(bs.traf.ap.orig[acidx] != "") # 1 if orig is present in route + ndest = int(bs.traf.ap.dest[acidx] != "") # 1 if dest is present in route # Check whether this is first 'real' waypoint (not orig & dest), # And if so, make active - if self.nwp - norig - ndest == 1: # first waypoint: make active - self.direct(idx, self.wpname[norig]) # 0 if no orig + if acrte.nwp - norig - ndest == 1: # first waypoint: make active + acrte.direct(acidx, acrte.wpname[norig]) # 0 if no orig #print("direct ",self.wpname[norig]) - bs.traf.swlnav[idx] = True + bs.traf.swlnav[acidx] = True - if afterwp and self.wpname.count(afterwp) == 0: - return True, "Waypoint " + afterwp + " not found" + \ + if afterwp and acrte.wpname.count(afterwp) == 0: + print(afterwp, acrte.wpname) + return True, "Waypoint " + afterwp + " not found\n" + \ "waypoint added at end of route" else: return True + @stack.command + def addwaypoints(acidx: 'acid', *args): + # Args come in this order: lat, lon, alt, spd, TURNSPD/TURNRAD/FLYBY, turnspeed or turnrad value + # If turn is '0', then ignore turnspeed + if len(args)%6 !=0: + bs.scr.echo('You missed a waypoint value, arguement number must be a multiple of 6.') + return - def afteraddwptStack(self, idx, *args): # args: all arguments of addwpt + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) - # AFTER acid, wpinroute ADDWPT (wpname/lat,lon),[alt],[spd]" - if len(args) < 3: - return False, "AFTER needs more arguments" + args = reshape(args, (int(len(args)/6), 6)) - # Change order of arguments - arglst = [args[2], None, None, args[0]] # postxt,,,afterwp + for wpdata in args: + # Get needed values + lat = float(wpdata[0]) # deg + lon = float(wpdata[1]) # deg + if wpdata[2]: + alt = txt2alt(wpdata[2]) # comes in feet, convert + else: + alt = -999 + if wpdata[3]: + spd = txt2spd(wpdata[3]) + else: + spd = -999 + + # Do flyby or flyturn processing + if wpdata[4] in ['TURNSPD', 'TURNSPEED']: + acrte.turnspd = txt2spd(wpdata[5]) + acrte.swflyby = False + acrte.swflyturn = True + elif wpdata[4] in ['TURNRAD', 'TURNRADIUS']: + acrte.turnrad = float(wpdata[5]) + acrte.swflyby = False + acrte.swflyturn = True + else: + # Either it's a flyby, or a typo. + acrte.swflyby = True + acrte.swflyturn = False - # Add alt when given - if len(args) > 3: - arglst[1] = args[3] # alt + name = acid + wptype = Route.wplatlon - # Add speed when given - if len(args) > 4: - arglst[2] = args[4] # spd + wpidx = acrte.addwpt_simple(acidx, name, wptype, lat, lon, alt, spd) - result = self.addwptStack(idx, *arglst) # args: all arguments of addwpt + # Calculate flight plan + acrte.calcfp() - return result + # Check for success by checking inserted location in flight plan >= 0 + if wpidx < 0: + return False, "Waypoint " + name + " not added." - def atwptStack(self, idx, *args): # args: all arguments of addwpt - #print("args=",args) + def addwpt_simple(self, iac, name, wptype, lat, lon, alt=-999., spd=-999.): + """Adds waypoint in the most simple way possible""" + # For safety + self.nwp = len(self.wplat) - # AT acid, wpinroute [DEL] ALT/SPD spd/alt" - # args = wpname,SPD/ALT, spd/alt(string) + name = name.upper().strip() - if len(args) < 1: - return False, "AT needs at least an aicraft id and a waypoint name" + wplat = lat + wplon = lon - else: - name = args[0] - if name in self.wpname: - wpidx = self.wpname.index(name) + # Check if name already exists, if so add integer 01, 02, 03 etc. + newname = Route.get_available_name( + self.wpname, name, 3) + + self.addwpt_data( + False, self.nwp, newname, wplat, wplon, wptype, alt, spd) + + idx = self.nwp + self.nwp += 1 + + #update qdr and "last waypoint switch" in traffic + if idx>=0: + bs.traf.actwp.next_qdr[iac] = self.getnextqdr() + bs.traf.actwp.swlastwp[iac] = (self.iactwp==self.nwp-1) + + # Update autopilot settings + if 0 <= self.iactwp < self.nwp: + self.direct(iac, self.wpname[self.iactwp]) + + return idx + + @stack.command + @staticmethod + def before(acidx : 'acid', beforewp: 'wpinroute', addwpt, waypoint, alt: 'alt' = None, spd: 'spd' = None): + ''' BEFORE acid, wpinroute ADDWPT acid, (wpname/lat,lon),[alt],[spd] + + Before waypoint, add a waypoint to route of aircraft (FMS). + ''' + return Route.addwptStack(acidx, waypoint, alt, spd, None, beforewp) + + @stack.command + @staticmethod + def after(acidx: 'acid', afterwp: 'wpinroute', addwpt, waypoint, alt:'alt' = None, spd: 'spd' = None): + ''' AFTER acid, wpinroute ADDWPT (wpname/lat,lon),[alt],[spd] - if len(args) == 1 or \ - (len(args) == 2 and not args[1].count("/") == 1): - # Only show Altitude and/or speed set in route at this waypoint: - # KL204 AT LOPIK => acid AT wpinroute: show alt & spd constraints at this waypoint - # KL204 AT LOPIK SPD => acid AT wpinroute SPD: show spd constraint at this waypoint - # KL204 AT LOPIK ALT => acid AT wpinroute ALT: show alt constraint at this waypoint + After waypoint, add a waypoint to route of aircraft (FMS). + ''' + return Route.addwptStack(acidx, waypoint, alt, spd, afterwp) - txt = name + " : " + @stack.command + @staticmethod + def at(acidx: 'acid', atwp : 'wpinroute', *args): + ''' AT acid, wpinroute [DEL] ALT/SPD/DO alt/spd/stack command''' + # args = wpname,SPD/ALT, spd/alt(string) + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + if atwp in acrte.wpname: + wpidx = acrte.wpname.index(atwp) + + if not args or \ + (len(args) == 1 and not args[0].count("/") == 1): + # Only show Altitude and/or speed set in route at this waypoint: + # KL204 AT LOPIK => acid AT wpinroute: show alt & spd constraints at this waypoint + # KL204 AT LOPIK SPD => acid AT wpinroute SPD: show spd constraint at this waypoint + # KL204 AT LOPIK ALT => acid AT wpinroute ALT: show alt constraint at this waypoint + txt = atwp + " : " + + # Select what to show + if len(args) == 0: + swalt = True + swspd = True + swat = True + else: + swalt = args[0].upper() == "ALT" + swspd = args[0].upper() in ("SPD","SPEED") + swat = args[0].upper() in ("DO", "STACK") - # Select what to show - if len(args)==1: + # To be safe show both when we do not know what + if not (swalt or swspd or swat): swalt = True swspd = True swat = True - else: - swalt = args[1].upper()=="ALT" - swspd = args[1].upper() in ("SPD","SPEED") - swat = args[1].upper() in ("DO", "STACK") - # To be safe show both when we do not know what - if not (swalt or swspd or swat): - swalt = True - swspd = True - swat = True + # Show altitude + if swalt: + if acrte.wpalt[wpidx] < 0: + txt += "-----" - # Show altitude - if swalt: - if self.wpalt[wpidx] < 0: - txt += "-----" + elif acrte.wpalt[wpidx] > 4500 * ft: + fl = int(round((acrte.wpalt[wpidx] / (100. * ft)))) + txt += "FL" + str(fl) - elif self.wpalt[wpidx] > 4500 * ft: - fl = int(round((self.wpalt[wpidx] / (100. * ft)))) - txt += "FL" + str(fl) + else: + txt += str(int(round(acrte.wpalt[wpidx] / ft))) - else: - txt += str(int(round(self.wpalt[wpidx] / ft))) + if swspd: + txt += "/" - if swspd: - txt += "/" + # Show speed + if swspd: + if acrte.wpspd[wpidx] < 0: + txt += "---" + else: + txt += str(int(round(acrte.wpspd[wpidx] / kts))) - # Show speed - if swspd: - if self.wpspd[wpidx] < 0: - txt += "---" - else: - txt += str(int(round(self.wpspd[wpidx] / kts))) + # Type + if swalt and swspd: + if acrte.wptype[wpidx] == Route.orig: + txt += "[orig]" + elif acrte.wptype[wpidx] == Route.dest: + txt += "[dest]" - # Type - if swalt and swspd: - if self.wptype[wpidx] == Route.orig: - txt += "[orig]" - elif self.wptype[wpidx] == Route.dest: - txt += "[dest]" + # Show also stacked commands for when passing this waypoint + if swat: + if len(acrte.wpstack[wpidx])>0: + txt = txt+"\nStack:\n" + for stackedtxt in acrte.wpstack[wpidx]: + txt = txt + stackedtxt + "\n" - # Show also stacked commands for when passing this waypoint - if swat: - if len(self.wpstack[wpidx])>0: - txt = txt+"\nStack:\n" - for stackedtxt in self.wpstack[wpidx]: - txt = txt + stackedtxt + "\n" + return True, txt - return True, txt + elif args[0].count("/")==1: + # Set both alt & speed at this waypoint + # KL204 AT LOPIK FL090/250 => acid AT wpinroute alt/spd + success = True - elif args[1].count("/")==1: - # Set both alt & speed at this waypoint - # KL204 AT LOPIK FL090/250 => acid AT wpinroute alt/spd - success = True + # Use parse from stack.py to interpret alt & speed + alttxt, spdtxt = args[0].split('/') - # Use parse from stack.py to interpret alt & speed - alttxt, spdtxt = args[1].split('/') + # Edit waypoint altitude constraint + if alttxt.count('-') > 1: # "----" = delete + acrte.wpalt[wpidx] = -999. + else: + try: + acrte.wpalt[wpidx] = txt2alt(alttxt) + except ValueError as e: + success = False - # Edit waypoint altitude constraint - if alttxt.count('-') > 1: # "----" = delete - self.wpalt[wpidx] = -999. - else: - try: - self.wpalt[wpidx] = txt2alt(alttxt) - except ValueError as e: - success = False + # Edit waypoint speed constraint + if spdtxt.count('-') > 1: # "----" = delete + acrte.wpspd[wpidx] = -999. + else: + try: + acrte.wpalt[wpidx] = txt2spd(spdtxt) + except ValueError as e: + success = False - # Edit waypoint speed constraint - if spdtxt.count('-') > 1: # "----" = delete - self.wpspd[wpidx] = -999. - else: - try: - self.wpalt[wpidx] = txt2spd(spdtxt) - except ValueError as e: - success = False + if not success: + return False,"Could not parse "+args[0]+" as alt / spd" - if not success: - return False,"Could not parse "+args[1]+" as alt / spd" + # If success: update flight plan and guidance + acrte.calcfp() + acrte.direct(acidx, acrte.wpname[acrte.iactwp]) - # If success: update flight plan and guidance - self.calcfp() - self.direct(idx, self.wpname[self.iactwp]) + #acid AT wpinroute ALT/SPD alt/spd + elif len(args)>=2: + # KL204 AT LOPIK ALT FL090 => set altitude to be reached at this waypoint in route + # KL204 AT LOPIK SPD 250 => Set speed at twhich is set at this waypoint + # KL204 AT LOPIK DO PAN LOPIK => When passing stack command after DO + # KL204 AT LOPIK STACK PAN LOPIK => AT...STACK synonym for AT...DO + # KL204 AT LOPIK DO ALT FL240 => => stack "KL204 ALT FL240" => use acid from beginning if omitted as first argument - #acid AT wpinroute ALT/SPD alt/spd - elif len(args)>=3: - # KL204 AT LOPIK ALT FL090 => set altitude to be reached at this waypoint in route - # KL204 AT LOPIK SPD 250 => Set speed at twhich is set at this waypoint - # KL204 AT LOPIK DO PAN LOPIK => When passing stack command after DO - # KL204 AT LOPIK STACK PAN LOPIK => AT...STACK synonym for AT...DO - # KL204 AT LOPIK DO ALT FL240 => => stack "KL204 ALT FL240" => use acid from beginning if omitted as first argument + swalt = args[0].upper()=="ALT" + swspd = args[0].upper() in ("SPD","SPEED") + swat = args[0].upper() in ("DO","STACK") - swalt = args[1].upper()=="ALT" - swspd = args[1].upper() in ("SPD","SPEED") - swat = args[1].upper() in ("DO","STACK") + # Use parse from stack.py to interpret alt & speed - # Use parse from stack.py to interpret alt & speed + # Edit waypoint altitude constraint + if swalt: + try: + acrte.wpalt[wpidx] = txt2alt(args[1]) + except ValueError as e: + return False, e.args[0] - # Edit waypoint altitude constraint - if swalt: + # Edit waypoint speed constraint + elif swspd: + try: + acrte.wpspd[wpidx] = txt2spd(args[1]) + except ValueError as e: + return False, e.args[0] + + # add stack command: args[1] is DO or STACK, args[2:] contains a command + elif swat: + # Check if first argument is missing aircraft id, if so, use this acid + + # IF command starts with aircraft id, it is not missing + cmd = args[1].upper() + if not(cmd in bs.traf.id): + # Look up arg types try: - self.wpalt[wpidx] = txt2alt(args[2]) - except ValueError as e: - return False, e.args[0] + cmdobj = Command.cmddict.get(cmd) - # Edit waypoint speed constraint - elif swspd: - try: - self.wpspd[wpidx] = txt2spd(args[2]) - except ValueError as e: - return False, e.args[0] - - # add stack command: args[1] is DO or STACK, args[2:] contains a command - elif swat: - # Check if first argument is missing aircraft id, if so, use this acid - - # IF command starts with aircraft id, it is not missing - cmd = args[2].upper() - if not(cmd in bs.traf.id): - # Look up arg types - try: - cmdobj = Command.cmddict.get(cmd) - - # Command found, check arguments - argtypes = cmdobj.annotations - - if argtypes[0]=="acid" and not (args[3].upper() in bs.traf.id): - # missing acid, so add ownship acid - self.wpstack[wpidx].append(self.acid+" "+" ".join(args[2:])) - else: - # This command does not need an acid or it is already first argument - self.wpstack[wpidx].append(" ".join(args[2:])) - except: - return False, "Stacked command "+cmd+"unknown" - else: - # Command line starts with an aircraft id at the beginning of the command line, stack it - self.wpstack[wpidx].append(" ".join(args[2:])) + # Command found, check arguments + argtypes = cmdobj.annotations - # Delete a constraint (or both) at this waypoint - elif args[1]=="DEL" or args[1]=="DELETE" or args[1]=="CLR" or args[1]=="CLEAR" : - swalt = args[2].upper()=="ALT" - swspd = args[2].upper() in ("SPD","SPEED") - swboth = args[2].upper()=="BOTH" - swall = args[2].upper()=="ALL" + if argtypes[0]=="acid" and not (args[2].upper() in bs.traf.id): + # missing acid, so add ownship acid + acrte.wpstack[wpidx].append(acid+" "+" ".join(args[1:])) + else: + # This command does not need an acid or it is already first argument + acrte.wpstack[wpidx].append(" ".join(args[1:])) + except: + return False, "Stacked command "+cmd+"unknown" + else: + # Command line starts with an aircraft id at the beginning of the command line, stack it + acrte.wpstack[wpidx].append(" ".join(args[1:])) - if swspd or swboth or swall: - self.wpspd[wpidx] = -999. + # Delete a constraint (or both) at this waypoint + elif args[0]=="DEL" or args[0]=="DELETE" or args[0]=="CLR" or args[0]=="CLEAR" : + swalt = args[1].upper()=="ALT" + swspd = args[1].upper() in ("SPD","SPEED") + swboth = args[1].upper()=="BOTH" + swall = args[1].upper()=="ALL" - if swalt or swboth or swall: - self.wpalt[wpidx] = -999. + if swspd or swboth or swall: + acrte.wpspd[wpidx] = -999. - if swall: - self.wpstack[wpidx]=[] + if swalt or swboth or swall: + acrte.wpalt[wpidx] = -999. - else: - return False,"No "+args[1]+" at ",name + if swall: + acrte.wpstack[wpidx]=[] + else: + return False,"No "+args[0]+" at ",atwp - # If success: update flight plan and guidance - self.calcfp() - self.direct(idx, self.wpname[self.iactwp]) - # Waypoint not found in route - else: - return False, name + " not found in route " + bs.traf.id[idx] + # If success: update flight plan and guidance + acrte.calcfp() + acrte.direct(acidx, acrte.wpname[acrte.iactwp]) + + # Waypoint not found in route + else: + return False, atwp + " not found in route " + acid return True @@ -729,155 +848,151 @@ def addwpt(self, iac, name, wptype, lat, lon, alt=-999., spd=-999., afterwp="", return idx - def beforeaddwptStack(self, idx, *args): # args: all arguments of addwpt - # BEFORE acid, wpinroute ADDWPT acid, (wpname/lat,lon),[alt],[spd]" - if len(args) < 3: - return False, "BEFORE needs more arguments" - - # Change order of arguments - arglst = [args[2], None, None, None, args[0]] # postxt,,,,beforewp - - # Add alt when given - if len(args) > 3: - arglst[1] = args[3] # alt - - # Add speed when given - if len(args) > 4: - arglst[2] = args[4] # spd - - result = self.addwptStack(idx, *arglst) # args: all arguments of addwpt - - return result + @stack.command(aliases=("DIRECTTO", "DIRTO")) + @staticmethod + def direct(acidx: 'acid', wpname: 'wpinroute'): + """DIRECT acid wpname + + Go direct to specified waypoint in route (FMS)""" + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + wpidx = acrte.wpname.index(wpname) - def direct(self, idx, wpnam): - #print("Hello from direct") - """Set active point to a waypoint by name""" - name = wpnam.upper().strip() - if name != "" and self.wpname.count(name) > 0: - wpidx = self.wpname.index(name) + acrte.iactwp = wpidx + bs.traf.actwp.lat[acidx] = acrte.wplat[wpidx] + bs.traf.actwp.lon[acidx] = acrte.wplon[wpidx] + bs.traf.actwp.flyby[acidx] = acrte.wpflyby[wpidx] + bs.traf.actwp.flyturn[acidx] = acrte.wpflyturn[wpidx] + bs.traf.actwp.turnrad[acidx] = acrte.wpturnrad[wpidx] + bs.traf.actwp.turnspd[acidx] = acrte.wpturnspd[wpidx] - self.iactwp = wpidx + bs.traf.actwp.nextturnlat[acidx], bs.traf.actwp.nextturnlon[acidx], \ + bs.traf.actwp.nextturnspd[acidx], bs.traf.actwp.nextturnrad[acidx], \ + bs.traf.actwp.nextturnidx[acidx] = acrte.getnextturnwp() - bs.traf.actwp.lat[idx] = self.wplat[wpidx] - bs.traf.actwp.lon[idx] = self.wplon[wpidx] - bs.traf.actwp.flyby[idx] = self.wpflyby[wpidx] - bs.traf.actwp.flyturn[idx] = self.wpflyturn[wpidx] - bs.traf.actwp.turnrad[idx] = self.wpturnrad[wpidx] - bs.traf.actwp.turnspd[idx] = self.wpturnspd[wpidx] + # Determine next turn waypoint data - # Do calculation for VNAV - self.calcfp() - bs.traf.actwp.xtoalt[idx] = self.wpxtoalt[wpidx] - bs.traf.actwp.nextaltco[idx] = self.wptoalt[wpidx] + # Do calculation for VNAV + acrte.calcfp() - bs.traf.actwp.torta[idx] = self.wptorta[wpidx] # available for active RTA-guidance - bs.traf.actwp.xtorta[idx] = self.wpxtorta[wpidx] # available for active RTA-guidance + bs.traf.actwp.xtoalt[acidx] = acrte.wpxtoalt[wpidx] + bs.traf.actwp.nextaltco[acidx] = acrte.wptoalt[wpidx] - #VNAV calculations like V/S and speed for RTA - bs.traf.ap.ComputeVNAV(idx, self.wptoalt[wpidx], self.wpxtoalt[wpidx],\ - self.wptorta[wpidx],self.wpxtorta[wpidx]) + bs.traf.actwp.torta[acidx] = acrte.wptorta[wpidx] # available for active RTA-guidance + bs.traf.actwp.xtorta[acidx] = acrte.wpxtorta[wpidx] # available for active RTA-guidance - # If there is a speed specified, process it - if self.wpspd[wpidx]>0.: - # Set target speed for autopilot + #VNAV calculations like V/S and speed for RTA + bs.traf.ap.ComputeVNAV(acidx, acrte.wptoalt[wpidx], acrte.wpxtoalt[wpidx],\ + acrte.wptorta[wpidx],acrte.wpxtorta[wpidx]) - if self.wpalt[wpidx] < 0.0: - alt = bs.traf.alt[idx] - else: - alt = self.wpalt[wpidx] + # If there is a speed specified, process it + if acrte.wpspd[wpidx]>0.: + # Set target speed for autopilot - # Check for valid Mach or CAS - if self.wpspd[wpidx] <2.0: - cas = mach2cas(self.wpspd[wpidx], alt) - else: - cas = self.wpspd[wpidx] - - # Save it for next leg - bs.traf.actwp.nextspd[idx] = cas - - # No speed specified for next leg + if acrte.wpalt[wpidx] < 0.0: + alt = bs.traf.alt[acidx] else: - bs.traf.actwp.nextspd[idx] = -999. + alt = acrte.wpalt[wpidx] + # Check for valid Mach or CAS + if acrte.wpspd[wpidx] <2.0: + cas = mach2cas(acrte.wpspd[wpidx], alt) + else: + cas = acrte.wpspd[wpidx] - qdr, dist = geo.qdrdist(bs.traf.lat[idx], bs.traf.lon[idx], - bs.traf.actwp.lat[idx], bs.traf.actwp.lon[idx]) + # Save it for next leg + bs.traf.actwp.nextspd[acidx] = cas - if self.wpflyturn[wpidx] or self.wpturnrad[wpidx]<0.: - turnrad = self.wpturnrad[wpidx] - else: - turnrad = bs.traf.tas[idx]*bs.traf.tas[idx]/tan(radians(25.)) / g0 / nm # [nm]default bank angle 25 deg + # No speed specified for next leg + else: + bs.traf.actwp.nextspd[acidx] = -999. - bs.traf.actwp.turndist[idx] = (bs.traf.actwp.flyby[idx] > 0.5) * \ - turnrad*abs(tan(0.5*radians(max(5., abs(degto180(qdr - - self.wpdirfrom[self.iactwp])))))) # [nm] + qdr,dist = geo.qdrdist(bs.traf.lat[acidx], bs.traf.lon[acidx], + bs.traf.actwp.lat[acidx], bs.traf.actwp.lon[acidx]) + # Save leg length & direction in actwp data + bs.traf.actwp.curlegdir[acidx] = qdr #[deg] + bs.traf.actwp.curleglen[acidx] = dist*nm #[m] - bs.traf.swlnav[idx] = True - return True + if acrte.wpflyturn[wpidx] or acrte.wpturnrad[wpidx]<0.: + turnrad = acrte.wpturnrad[wpidx] else: - return False, "Waypoint " + wpnam + " not found" + turnrad = bs.traf.tas[acidx]*bs.traf.tas[acidx]/tan(radians(25.)) / g0 / nm # [nm]default bank angle 25 deg - def SetRTA(self, idx, name, txt): # all arguments of setRTA - """SetRTA acid, wpname, time: add RTA to waypoint record""" - timeinsec = txt2tim(txt) - #print(timeinsec) - if name in self.wpname: - wpidx = self.wpname.index(name) - self.wprta[wpidx] = timeinsec - #print("Ik heb",self.wprta[wpidx],"op",self.wpname[wpidx],"gezet!") + bs.traf.actwp.turndist[acidx] = (bs.traf.actwp.flyby[acidx] > 0.5) * \ + turnrad*abs(tan(0.5*radians(max(5., abs(degto180(qdr - + acrte.wpdirfrom[acrte.iactwp])))))) # [nm] - # Recompute route and update actwp because of RTA addition - self.direct(idx, self.wpname[self.iactwp]) + bs.traf.swlnav[acidx] = True return True - def listrte(self, idx, ipage=0): - """LISTRTE command: output route to screen""" - if self.nwp <= 0: - return False, "Aircraft has no route." + @stack.command(name='RTA') + @staticmethod + def SetRTA(acidx: 'acid', wpname: 'wpinroute', time: 'time'): # all arguments of setRTA + """ RTA acid, wpname, time + + Add RTA to waypoint record""" + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + wpidx = acrte.wpname.index(wpname) + acrte.wprta[wpidx] = time + + # Recompute route and update actwp because of RTA addition + acrte.direct(acidx, acrte.wpname[acrte.iactwp]) + + return True - if idx<0: - return False, "Aircraft id not found." + @stack.command + @staticmethod + def listrte(acidx: 'acid', ipage=0): + """ LISTRTE acid, [pagenr] + + Show list of route in window per page of 5 waypoints/""" + # First get the appropriate ac route + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + if acrte.nwp <= 0: + return False, "Aircraft has no route." for i in range(ipage * 7, ipage * 7 + 7): - if 0 <= i < self.nwp: + if 0 <= i < acrte.nwp: # Name - if i == self.iactwp: - txt = "*" + self.wpname[i] + " : " + if i == acrte.iactwp: + txt = "*" + acrte.wpname[i] + " : " else: - txt = " " + self.wpname[i] + " : " + txt = " " + acrte.wpname[i] + " : " # Altitude - if self.wpalt[i] < 0: + if acrte.wpalt[i] < 0: txt += "-----/" - elif self.wpalt[i] > 4500 * ft: - fl = int(round((self.wpalt[i] / (100. * ft)))) + elif acrte.wpalt[i] > 4500 * ft: + fl = int(round((acrte.wpalt[i] / (100. * ft)))) txt += "FL" + str(fl) + "/" else: - txt += str(int(round(self.wpalt[i] / ft))) + "/" + txt += str(int(round(acrte.wpalt[i] / ft))) + "/" # Speed - if self.wpspd[i] < 0.: + if acrte.wpspd[i] < 0.: txt += "---" - elif self.wpspd[i] > 2.0: - txt += str(int(round(self.wpspd[i] / kts))) + elif acrte.wpspd[i] > 2.0: + txt += str(int(round(acrte.wpspd[i] / kts))) else: - txt += "M" + str(self.wpspd[i]) + txt += "M" + str(acrte.wpspd[i]) # Type: orig, dest, C = flyby, | = flyover, U = flyturn - if self.wptype[i] == Route.orig: + if acrte.wptype[i] == Route.orig: txt += "[orig]" - elif self.wptype[i] == Route.dest: + elif acrte.wptype[i] == Route.dest: txt += "[dest]" - elif self.wpflyturn[i]: + elif acrte.wpflyturn[i]: txt += "[U]" - elif self.wpflyby[i]: + elif acrte.wpflyby[i]: txt += "[C]" else: # FLYOVER txt += "[|]" @@ -887,9 +1002,27 @@ def listrte(self, idx, ipage=0): bs.scr.echo(txt) # Add command for next page to screen command line - npages = int((self.nwp + 6) / 7) + npages = int((acrte.nwp + 6) / 7) if ipage + 1 < npages: - bs.scr.cmdline("LISTRTE " + bs.traf.id[idx] + "," + str(ipage + 1)) + bs.scr.cmdline("LISTRTE " + acid + "," + str(ipage + 1)) + + def getnextturnwp(self): + """Give the next turn waypoint data.""" + # Starting point + wpidx = self.iactwp + # Find next turn waypoint index + turnidx_all = where(self.wpflyturn)[0] + argwhere_arr = argwhere(turnidx_all>=wpidx) + if argwhere_arr.size == 0: + # No turn waypoints, return default values + return [0., 0., -999., -999., -999.] + + trnidx = turnidx_all[argwhere(turnidx_all>=wpidx)[0]][0] + + + + # Return the next turn waypoint info + return [self.wplat[trnidx], self.wplon[trnidx], self.wpturnspd[trnidx], self.wpturnrad[trnidx], trnidx] def getnextwp(self): """Go to next waypoint and return data""" @@ -910,13 +1043,9 @@ def getnextwp(self): # Change RW06,RWY18C,RWY24001 to resp. 06,18C,24 if "RWY" in name: rwykey = name[8:10] - if not name[10].isdigit(): - rwykey = rwykey+name[10] # also if it is only RW else: rwykey = name[7:9] - if not name[9].isdigit(): - rwykey = rwykey+name[9] wphdg = bs.navdb.rwythresholds[name[:4]][rwykey][2] @@ -982,61 +1111,69 @@ def runactwpstack(self): # stack.stack("ECHO "+self.acid+" AT "+self.wpname[self.iactwp]+" command issued:"+cmdline) return - def delrte(self,iac=None): - """Delete complete route""" + @stack.command(aliases=("DELROUTE",)) + @staticmethod + def delrte(acidx: 'acid' = None): + """ DELRTE acid + Delete for this a/c the complete route/dest/orig (FMS).""" + if acidx is None: + if bs.traf.ntraf == 0: + return False, 'No aircraft in simulation' + if bs.traf.ntraf > 1: + return False, 'Specify callsign of aircraft to delete route of' + acidx = 0 # Simple re-initialize this route as empty - self.__init__(bs.traf.id[iac]) + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + acrte.__init__(acid) # Also disable LNAV,VNAV if route is deleted - if self.nwp == 0 and (iac or iac == 0): - bs.traf.swlnav[iac] = False - bs.traf.swvnav[iac] = False - bs.traf.swvnavspd[iac] = False + bs.traf.swlnav[acidx] = False + bs.traf.swvnav[acidx] = False + bs.traf.swvnavspd[acidx] = False return True - def delwpt(self,delwpname,iac=None): - """Delete waypoint""" - + @stack.command(aliases=("DELWP",)) + @staticmethod + def delwpt(acidx: 'acid', wpname: 'wpinroute'): + """DELWPT acid,wpname + + Delete a waypoint from a route (FMS). """ # Delete complete route? - if delwpname =="*": - return self.delrte(iac) + if wpname == "*": + return Route.delrte(acidx) # Look up waypoint - idx = -1 - i = len(self.wpname) - while idx == -1 and i > 0: - i -= 1 - if self.wpname[i].upper() == delwpname.upper(): - idx = i - + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) + try: + wpidx = acrte.wpname.index(wpname.upper()) + except ValueError: + return False, "Waypoint " + wpname + " not found" # check if active way point is the one being deleted and that it is not the last wpt. # If active wpt is deleted then change path of aircraft - if self.iactwp == idx and not idx == self.nwp - 1: - self.direct(iac, self.wpname[idx + 1]) - - # Delete waypoint - if idx == -1: - return False, "Waypoint " + delwpname + " not found" - - self.nwp =self.nwp - 1 - del self.wpname[idx] - del self.wplat[idx] - del self.wplon[idx] - del self.wpalt[idx] - del self.wpspd[idx] - del self.wprta[idx] - del self.wptype[idx] - if self.iactwp > idx: - self.iactwp = max(0, self.iactwp - 1) - - self.iactwp = min(self.iactwp, self.nwp - 1) + if acrte.iactwp == wpidx and not wpidx == acrte.nwp - 1: + acrte.direct(acidx, acrte.wpname[wpidx + 1]) + + acrte.nwp =acrte.nwp - 1 + del acrte.wpname[wpidx] + del acrte.wplat[wpidx] + del acrte.wplon[wpidx] + del acrte.wpalt[wpidx] + del acrte.wpspd[wpidx] + del acrte.wprta[wpidx] + del acrte.wptype[wpidx] + if acrte.iactwp > wpidx: + acrte.iactwp = max(0, acrte.iactwp - 1) + + acrte.iactwp = min(acrte.iactwp, acrte.nwp - 1) # If no waypoints left, make sure to disable LNAV/VNAV - if self.nwp==0 and (iac or iac==0): - bs.traf.swlnav[iac] = False - bs.traf.swvnav[iac] = False - bs.traf.swvnavspd[iac] = False + if acrte.nwp==0 and (acidx or acidx==0): + bs.traf.swlnav[acidx] = False + bs.traf.swvnav[acidx] = False + bs.traf.swvnavspd[acidx] = False return True @@ -1325,7 +1462,7 @@ def findact(self,i): # we only turn to the first waypoint if we can reach the required # heading before reaching the waypoint - time_turn = max(0.01,bs.traf.tas[i])*radians(delhdg)/(g0*tan(bs.traf.bank[i])) + time_turn = max(0.01,bs.traf.tas[i])*radians(delhdg)/(g0*tan(bs.traf.ap.bankdef[i])) time_straight= sqrt(dist2[iwpnear])*60.*nm/max(0.01,bs.traf.tas[i]) if time_turn > time_straight: @@ -1333,8 +1470,15 @@ def findact(self,i): return iwpnear - def dumpRoute(self, idx): - acid = bs.traf.id[idx] + @stack.command + @staticmethod + def dumprte(acidx: 'acid'): + """ DUMPRTE acid + + Write route to output/routelog.txt. + """ + acid = bs.traf.id[acidx] + acrte = Route._routes.get(acid) # Open file in append mode, write header with open(path.join(bs.settings.log_path, 'routelog.txt'), "a") as f: f.write("\nRoute "+acid+":\n") @@ -1342,11 +1486,11 @@ def dumpRoute(self, idx): f.write("type: 0=latlon 1=navdb 2=orig 3=dest 4=calwp\n") # write flight plan VNAV data (Lateral is visible on screen) - for j in range(self.nwp): - f.write( str(( j, self.wpname[j], self.wptype[j], - round(self.wplat[j], 4), round(self.wplon[j], 4), - int(0.5+self.wpalt[j]/ft), int(0.5+self.wpspd[j]/kts), - int(0.5+self.wptoalt[j]/ft), round(self.wpxtoalt[j]/nm, 3) + for j in range(acrte.nwp): + f.write( str(( j, acrte.wpname[j], acrte.wptype[j], + round(acrte.wplat[j], 4), round(acrte.wplon[j], 4), + int(0.5+acrte.wpalt[j]/ft), int(0.5+acrte.wpspd[j]/kts), + int(0.5+acrte.wptoalt[j]/ft), round(acrte.wpxtoalt[j]/nm, 3) )) + "\n") # End of data diff --git a/bluesky/traffic/traffic.py b/bluesky/traffic/traffic.py index 275889d7c2..8837b04a4d 100644 --- a/bluesky/traffic/traffic.py +++ b/bluesky/traffic/traffic.py @@ -81,6 +81,9 @@ def __init__(self): self.turbulence = Turbulence() self.translvl = 5000.*ft # [m] Default transition level + # Default commands issued for an aircraft after creation + self.crecmdlist = [] + with self.settrafarrays(): # Aircraft Info self.id = [] # identifier (string) @@ -103,6 +106,9 @@ def __init__(self): self.M = np.array([]) # mach number self.vs = np.array([]) # vertical speed [m/s] + # Acceleration + self.ax = np.array([]) # [m/s2] current longitudinal acceleration + # Atmosphere self.p = np.array([]) # air pressure [N/m2] self.rho = np.array([]) # air density [kg/m3] @@ -138,10 +144,6 @@ def __init__(self): self.groups = TrafficGroups() # Traffic autopilot data - self.apvsdef = np.array([]) # [m/s]default vertical speed of autopilot - self.aphi = np.array([]) # [rad] bank angle setting of autopilot - self.ax = np.array([]) # [m/s2] absolute value of longitudinal accelleration - self.bank = np.array([]) # nominal bank angle, [radians] self.swhdgsel = np.array([], dtype=np.bool) # determines whether aircraft is turning # Traffic autothrottle settings @@ -228,7 +230,7 @@ def cre(self, acid, actype="B744", aclat=52., aclon=4., achdg=None, acalt=0, acs aclon[aclon > 180.0] -= 360.0 aclon[aclon < -180.0] += 360.0 - achdg = refdata.hdg if achdg is None else achdg + achdg = (refdata.hdg or 0.0) if achdg is None else achdg # Aircraft Info self.id[-n:] = acid @@ -265,13 +267,6 @@ def cre(self, acid, actype="B744", aclat=52., aclon=4., achdg=None, acalt=0, acs self.windnorth[-n:] = 0.0 self.windeast[-n:] = 0.0 - # Traffic performance data - #(temporarily default values) - self.apvsdef[-n:] = 1500. * fpm # default vertical speed of autopilot - self.aphi[-n:] = 0. # bank angle output of autopilot (optional) - self.ax[-n:] = kts # absolute value of longitudinal accelleration - self.bank[-n:] = np.radians(25.) - # Traffic autopilot settings self.selspd[-n:] = self.cas[-n:] self.aptas[-n:] = self.tas[-n:] @@ -300,6 +295,13 @@ def cre(self, acid, actype="B744", aclat=52., aclon=4., achdg=None, acalt=0, acs # So insert a dummy command to record the line savecmd("---",line) + # Check for crecmdlist: contains commands to be issued for this a/c + # If any are there, then stack them for all aircraft + for j in range(self.ntraf - n, self.ntraf): + for cmdtxt in self.crecmdlist: + bs.stack.stack(self.id[j]+" "+cmdtxt) + + def creconfs(self, acid, actype, targetidx, dpsi, dcpa, tlosh, dH=None, tlosv=None, spd=None): ''' Create an aircraft in conflict with target aircraft. @@ -435,17 +437,15 @@ def update_asas(self): def update_airspeed(self): # Compute horizontal acceleration delta_spd = self.aporasas.tas - self.tas - ax = self.perf.acceleration() - need_ax = np.abs(delta_spd) > np.abs(bs.sim.simdt * ax) - self.ax = need_ax * np.sign(delta_spd) * ax + need_ax = np.abs(delta_spd) > np.abs(bs.sim.simdt * self.perf.axmax) + self.ax = need_ax * np.sign(delta_spd) * self.perf.axmax # Update velocities self.tas = np.where(need_ax, self.tas + self.ax * bs.sim.simdt, self.aporasas.tas) self.cas = vtas2cas(self.tas, self.alt) self.M = vtas2mach(self.tas, self.alt) # Turning - - turnrate = np.degrees(g0 * np.tan(np.where(self.aphi>self.eps,self.aphi,self.bank) \ + turnrate = np.degrees(g0 * np.tan(np.where(self.ap.turnphi>self.eps,self.ap.turnphi,self.ap.bankdef) \ / np.maximum(self.tas, self.eps))) delhdg = (self.aporasas.hdg - self.hdg + 180) % 360 - 180 # [deg] self.swhdgsel = np.abs(delhdg) > np.abs(bs.sim.simdt * turnrate) @@ -556,11 +556,6 @@ def move(self, idx, lat, lon, alt=None, hdg=None, casmach=None, vspd=None): self.vs[idx] = vspd self.swvnav[idx] = False - - def nom(self, idx): - """ Reset acceleration back to nominal (1 kt/s^2): NOM acid """ - self.ax[idx] = kts #[m/s2] - def poscommand(self, idxorwp):# Show info on aircraft(int) or waypoint or airport (str) """POS command: Show info or an aircraft, airport, waypoint or navaid""" # Aircraft index @@ -774,9 +769,9 @@ def settrans(self, alt=-999.): def setbanklim(self, idx, bankangle=None): ''' Set bank limit for given aircraft. ''' if bankangle: - self.bank[idx] = np.radians(bankangle) # [rad] + self.ap.bankdef[idx] = np.radians(bankangle) # [rad] return True - return True, f"Banklimit of {self.id[idx]} is {int(np.degrees(self.bank[idx]))} deg" + return True, f"Banklimit of {self.id[idx]} is {int(np.degrees(self.ap.bankdef[idx]))} deg" def setthrottle(self,idx,throttle=""): """Set throttle to given value or AUTO, meaning autothrottle on (default)""" @@ -817,3 +812,35 @@ def setthrottle(self,idx,throttle=""): if self.swats[idx]: return True,"ATS of "+self.id[idx]+" is ON" return True, "ATS of " + self.id[idx] + " is OFF. THR is "+str(self.thr[idx]) + + def crecmd(self,cmdline): + """CRECMD command: list of commands to be issued for each aircraft after creation + This commands adds a command to the list of default commands. + """ + # Help text need or info on current list? + if cmdline=="" or cmdline=="?": + if len(self.crecmdlist)>0: + allcmds = "" + for i,txt in enumerate(self.crecmdlist): + if i==0: + allcmds = "[acid] "+txt + else: + allcmds +="; [acid] "+txt + return True,"CRECMD list: "+allcmds + else: + return True,"CRECMD will add a/c specific commands to an aircraft after creation" + # Command to be added to list + else: + self.crecmdlist.append(cmdline) + return True, "" + + def clrcrecmd(self): + """CRECMD command: list of commands to be issued for each aircraft after creation + This commands adds a command to the list of default commands. + """ + ncrecmd = len(self.crecmdlist) + if ncrecmd==0: + return True,"CLRCRECMD deletes all commands on clears command" + else: + self.crecmdlist = [] + return True,str("All",ncrecmd,"crecmd commands deleted.") diff --git a/bluesky/traffic/windsim.py b/bluesky/traffic/windsim.py index d6f8f5c311..79c3c65bf1 100644 --- a/bluesky/traffic/windsim.py +++ b/bluesky/traffic/windsim.py @@ -3,27 +3,40 @@ from bluesky.tools.aero import kts from bluesky.core import Entity +from bluesky.stack import command from .windfield import Windfield class WindSim(Entity, Windfield, replaceable=True): - def add(self, *arg): - - lat = arg[0] - lon = arg[1] - winddata = arg[2:] - + @command(name='WIND') + def add(self, lat: 'lat', lon: 'lon', *winddata: 'alt/float'): + """ Define a wind vector as part of the 2D or 3D wind field. + + Arguments: + - lat/lon: Horizonal position to define wind vector(s) + - winddata: + - If the wind at this location is independent of altitude + winddata has two elements: + - direction [degrees] + - speed (magnitude) [knots] + - If the wind varies with altitude winddata has three elements: + - altitude [ft] + - direction [degrees] + - speed (magnitude) [knots] + In this case, repeating combinations of alt/dir/spd can be provided + to specify wind at multiple altitudes. + """ ndata = len(winddata) # No altitude or just one: same wind for all altitudes at this position - if ndata == 3 or (ndata == 4 and winddata[0] is None): # only one point, ignore altitude - if winddata[1] is None or winddata[2] is None: + if ndata == 2 or (ndata == 3 and winddata[0] is None): # only one point, ignore altitude + if winddata[-2] is None or winddata[-1] is None: return False, "Wind direction and speed needed." - self.addpoint(lat,lon,winddata[1],winddata[2]*kts) + self.addpoint(lat,lon,winddata[-2],winddata[-1]*kts) # More than one altitude is given - elif ndata > 3: + elif ndata >= 3: windarr = array(winddata) dirarr = windarr[1::3] spdarr = windarr[2::3] * kts @@ -39,8 +52,14 @@ def add(self, *arg): return True - def get(self, lat, lon, alt=None): - """ Get wind vector at gioven position (and optioanlly altitude)""" + @command(name='GETWIND') + def get(self, lat: 'lat', lon: 'lon', alt: 'alt'=None): + """ Get wind at a specified position (and optionally at altitude) + + Arguments: + - lat, lon: Horizontal position where wind should be determined [deg] + - alt: Altitude at which wind should be determined [ft] + """ vn,ve = self.getdata(lat,lon,alt) wdir = (degrees(arctan2(ve,vn)) + 180.) % 360. diff --git a/bluesky/ui/qtgl/console.py b/bluesky/ui/qtgl/console.py index 4061294abc..2eb72634d2 100644 --- a/bluesky/ui/qtgl/console.py +++ b/bluesky/ui/qtgl/console.py @@ -1,12 +1,15 @@ """ Console interface for the QTGL implementation.""" from PyQt5.QtCore import Qt +from PyQt5.Qt import QDesktopServices, QUrl, QApplication from PyQt5.QtWidgets import QWidget, QTextEdit import bluesky as bs +from bluesky.tools import cachefile from bluesky.tools.misc import cmdsplit from bluesky.core.signal import Signal from . import autocomplete + cmdline_stacked = Signal('cmdline_stacked') @@ -31,10 +34,14 @@ def get_args(): return Console._instance.args -def append_cmdline(text): +def process_cmdline(cmdlines): assert Console._instance is not None, 'No console created yet: can only change' + \ ' command line after main window is created.' - Console._instance.append_cmdline(text) + lines = cmdlines.split('\n') + if lines: + Console._instance.append_cmdline(lines[-1]) + for cmd in lines[:-1]: + Console._instance.stack(cmd) class Console(QWidget): @@ -44,7 +51,11 @@ class Console(QWidget): def __init__(self, parent=None): super().__init__(parent) - self.command_history = [] + with cachefile.openfile('console_history.p') as cache: + try: + self.command_history = cache.load() + except: + self.command_history = [] self.cmd = '' self.args = [] self.history_pos = 0 @@ -59,6 +70,14 @@ def __init__(self, parent=None): "already exists! Cannot have more than one console." Console._instance = self + # Connect function to save command history on quit + QApplication.instance().aboutToQuit.connect(self.close) + + def close(self): + ''' Save command history when BlueSky closes. ''' + with cachefile.openfile('console_history.p') as cache: + cache.dump(self.command_history) + def on_simevent_received(self, eventname, eventdata, sender_id): ''' Processing of events from simulation nodes. ''' if eventname == b'CMDLINE': @@ -66,7 +85,8 @@ def on_simevent_received(self, eventname, eventdata, sender_id): def actnodedataChanged(self, nodeid, nodedata, changed_elems): if 'ECHOTEXT' in changed_elems: - self.stackText.setPlainText(nodedata.echo_text) + # self.stackText.setPlainText(nodedata.echo_text) + self.stackText.setHtml(nodedata.echo_text.replace('\n', '
') + '
') self.stackText.verticalScrollBar().setValue( self.stackText.verticalScrollBar().maximum()) @@ -84,7 +104,8 @@ def stack(self, text): def echo(self, text): actdata = bs.net.get_nodedata() actdata.echo(text) - self.stackText.append(text) + # self.stackText.append(text) + self.stackText.insertHtml(text.replace('\n', '
') + '
') self.stackText.verticalScrollBar().setValue( self.stackText.verticalScrollBar().maximum()) @@ -235,3 +256,14 @@ def __init__(self, parent=None): super().__init__(parent) Console.stackText = self self.setFocusPolicy(Qt.NoFocus) + + def mousePressEvent(self, e): + self.anchor = self.anchorAt(e.pos()) + if self.anchor: + QApplication.setOverrideCursor(Qt.PointingHandCursor) + + def mouseReleaseEvent(self, e): + if self.anchor: + QDesktopServices.openUrl(QUrl(self.anchor)) + QApplication.setOverrideCursor(Qt.ArrowCursor) + self.anchor = None diff --git a/bluesky/ui/qtgl/guiclient.py b/bluesky/ui/qtgl/guiclient.py index 943b2c8713..2029ff9a88 100644 --- a/bluesky/ui/qtgl/guiclient.py +++ b/bluesky/ui/qtgl/guiclient.py @@ -10,7 +10,7 @@ from bluesky.tools.aero import ft # Globals -UPDATE_ALL = ['SHAPE', 'TRAILS', 'CUSTWPT', 'PANZOOM', 'ECHOTEXT'] +UPDATE_ALL = ['SHAPE', 'TRAILS', 'CUSTWPT', 'PANZOOM', 'ECHOTEXT', 'ROUTEDATA'] ACTNODE_TOPICS = [b'ACDATA', b'PLOT*', b'ROUTEDATA*'] @@ -170,6 +170,10 @@ def clear_scen_data(self): self.custwplat = np.array([], dtype=np.float32) self.custwplon = np.array([], dtype=np.float32) + self.naircraft = 0 + self.acdata = ACDataEvent() + self.routedata = RouteDataEvent() + # Filteralt settings self.filteralt = False diff --git a/bluesky/ui/qtgl/radarwidget.py b/bluesky/ui/qtgl/radarwidget.py index f6db701672..096fdf8895 100644 --- a/bluesky/ui/qtgl/radarwidget.py +++ b/bluesky/ui/qtgl/radarwidget.py @@ -326,10 +326,8 @@ def event(self, event): actdata = bs.net.get_nodedata() tostack, tocmdline = radarclick(console.get_cmdline(), lat, lon, actdata.acdata, actdata.routedata) - if '\n' not in tocmdline: - console.append_cmdline(tocmdline) - if tostack: - bs.stack.stack(tostack) + + console.process_cmdline((tostack + '\n' + tocmdline) if tostack else tocmdline) elif event.type() == QEvent.MouseMove: self.mousedragged = True diff --git a/bluesky/ui/qtgl/tiledtexture.py b/bluesky/ui/qtgl/tiledtexture.py index 15e5fb106a..6a779f3491 100644 --- a/bluesky/ui/qtgl/tiledtexture.py +++ b/bluesky/ui/qtgl/tiledtexture.py @@ -1,4 +1,8 @@ ''' Tile texture manager for BlueSky Qt/OpenGL gui. ''' +import traceback +import math +from os import makedirs, path +import weakref from collections import OrderedDict from os import makedirs, path from urllib.error import URLError @@ -6,7 +10,8 @@ import math import weakref -from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal +from PyQt5.Qt import Qt +from PyQt5.QtCore import QObject, QRunnable, QThreadPool, pyqtSignal, pyqtSlot from PyQt5.QtGui import QImage from bluesky.core import Signal @@ -73,7 +78,8 @@ def __init__( self, source, zoom, tilex, tiley, idxx, idxy ): fout.write( data ) break except URLError as e: - print( e, f'({url.format(zoom=zoom, x=tilex, y=tiley)})' ) + print(f'Error loading {url.format(zoom=zoom, x=tilex, y=tiley)}:') + print(traceback.format_exc()) class TileLoader( QRunnable ): @@ -111,17 +117,32 @@ def __call__( cls, *args, **kwargs ): class TiledTexture( glh.Texture, metaclass=TiledTextureMeta ): ''' Tiled texture implementation for the BlueSky GL gui. ''' - def __init__( self, glsurface, tilesource='opentopomap' ): - super().__init__( target=glh.Texture.Target2DArray ) + class SlotHolder(QObject): + ''' Wrapper class for Qt slot, which can only be owned by a + QObject-derived parent. We need slots to allow signal receiver + to be executed in the receiving (main) thread. ''' + def __init__(self, callback): + super().__init__() + self.cb = callback + + @pyqtSlot(Tile) + def slot(self, *args, **kwargs): + self.cb(*args, **kwargs) + + def __init__(self, glsurface, tilesource='opentopomap'): + super().__init__(target=glh.Texture.Target2DArray) self.threadpool = QThreadPool() tileinfo = bs.settings.tile_sources.get( tilesource ) if not tileinfo: - raise KeyError( f'Tile source {tilesource} not found!' ) - max_dl = tileinfo.get( 'max_download_workers', bs.settings.max_download_workers ) - self.maxzoom = tileinfo.get( 'max_tile_zoom', bs.settings.max_tile_zoom ) - self.threadpool.setMaxThreadCount( min( bs.settings.max_download_workers, max_dl ) ) + raise KeyError(f'Tile source {tilesource} not found!') + max_dl = tileinfo.get('max_download_workers', bs.settings.max_download_workers) + self.maxzoom = tileinfo.get('max_tile_zoom', bs.settings.max_tile_zoom) + self.threadpool.setMaxThreadCount(min(bs.settings.max_download_workers, max_dl)) + self.tileslot = TiledTexture.SlotHolder(self.load_tile) self.tilesource = tilesource - self.tilesize = ( 256, 256 ) + self.tilesize = (256, 256) + self.curtileext = (0, 0, 0, 0) + self.curtilezoom = 1 self.curtiles = OrderedDict() self.fullscreen = False self.offsetscale = np.array( [0, 0, 1], dtype=np.float32 ) @@ -133,8 +154,7 @@ def __init__( self, glsurface, tilesource='opentopomap' ): bs.net.actnodedata_changed.connect( self.actdata_changed ) Signal( 'panzoom' ).connect( self.on_panzoom_changed ) - - def add_bounding_box( self, lat0, lon0, lat1, lon1 ): + def add_bounding_box(self, lat0, lon0, lat1, lon1): ''' Add the bounding box of a textured shape. These bounding boxes are used to determine if tiles need to be @@ -219,25 +239,26 @@ def on_panzoom_changed( self, finished=False ): # Estimated width in longitude of one tile est_tilewidth = abs( viewport[3] - viewport[1] ) / ntiles_hor - zoom = tilezoom( est_tilewidth, self.maxzoom ) + self.curtilezoom = tilezoom(est_tilewidth, self.maxzoom) # With the tile zoom level get the required number of tiles # Top-left and bottom-right tiles: - x0, y0 = latlon2tilenum( *viewport[:2], zoom ) - x1, y1 = latlon2tilenum( *viewport[2:], zoom ) - nx = abs( x1 - x0 ) + 1 - ny = abs( y1 - y0 ) + 1 + x0, y0 = latlon2tilenum(*viewport[:2], self.curtilezoom) + x1, y1 = latlon2tilenum(*viewport[2:], self.curtilezoom) + nx = abs(x1 - x0) + 1 + ny = abs(y1 - y0) + 1 + self.curtileext = (x0, y0, x1, y1) # Calculate the offset of the top-left tile w.r.t. the screen top-left corner - tile0_topleft = np.array( tilenum2latlon( x0, y0, zoom ) ) - tile0_bottomright = np.array( tilenum2latlon( x0 + 1, y0 + 1, zoom ) ) - tilesize_latlon0 = np.abs( tile0_bottomright - tile0_topleft ) + tile0_topleft = np.array(tilenum2latlon(x0, y0, self.curtilezoom)) + tile0_bottomright = np.array(tilenum2latlon(x0 + 1, y0 + 1, self.curtilezoom)) + tilesize_latlon0 = np.abs(tile0_bottomright - tile0_topleft) offset_latlon0 = viewport[:2] - tile0_topleft tex_y0, tex_x0 = np.abs( offset_latlon0 / tilesize_latlon0 ) # Calculate the offset of the bottom-right tile w.r.t. the screen bottom right corner - tile1_topleft = np.array( tilenum2latlon( x1, y1, zoom ) ) - tile1_bottomright = np.array( tilenum2latlon( x1 + 1, y1 + 1, zoom ) ) - tilesize_latlon1 = np.abs( tile1_bottomright - tile1_topleft ) + tile1_topleft = np.array(tilenum2latlon(x1, y1, self.curtilezoom)) + tile1_bottomright = np.array(tilenum2latlon(x1 + 1, y1 + 1, self.curtilezoom)) + tilesize_latlon1 = np.abs(tile1_bottomright - tile1_topleft) offset_latlon1 = viewport[2:] - tile1_topleft tex_y1, tex_x1 = np.abs( offset_latlon1 / tilesize_latlon1 ) + [ny - 1, nx - 1] # Store global offset and scale for shader uniform @@ -245,32 +266,33 @@ def on_panzoom_changed( self, finished=False ): [tex_x0, tex_y0, tex_x1 - tex_x0, tex_y1 - tex_y0], dtype=np.float32 ) # Determine required tiles index_tex = [] + curtiles = OrderedDict() curtiles_difzoom = OrderedDict() for j, y in enumerate( range( y0, y1 + 1 ) ): for i, x in enumerate( range( x0, x1 + 1 ) ): # Find tile index if already loaded - idx = self.curtiles.pop( ( x, y, zoom ), None ) + idx = self.curtiles.pop((x, y, self.curtilezoom), None) if idx is not None: # correct zoom, so dx,dy=0, zoomfac=1 - index_tex.extend( ( 0, 0, 1, idx ) ) - curtiles[( x, y, zoom )] = idx + index_tex.extend((0, 0, 1, idx)) + curtiles[(x, y, self.curtilezoom)] = idx else: if finished: # Tile not loaded yet, fetch in the background - task = TileLoader( self.tilesource, zoom, x, y, i, j ) - task.signals.finished.connect( self.load_tile ) - self.threadpool.start( task ) + task = TileLoader(self.tilesource, self.curtilezoom, x, y, i, j) + task.signals.finished.connect(self.tileslot.slot, Qt.QueuedConnection) + self.threadpool.start(task) # In the mean time, check if more zoomed-out tiles are loaded that can be used - for z in range( zoom - 1, max( 2, zoom - 5 ), -1 ): - zx, zy, dx, dy = zoomout_tilenum( x, y, z - zoom ) - idx = self.curtiles.pop( ( zx, zy, z ), None ) + for z in range(self.curtilezoom - 1, max(2, self.curtilezoom - 5), -1): + zx, zy, dx, dy = zoomout_tilenum(x, y, z - self.curtilezoom) + idx = self.curtiles.pop((zx, zy, z), None) if idx is not None: - curtiles_difzoom[( zx, zy, z )] = idx - zoomfac = int( 2 ** ( zoom - z ) ) - dxi = int( round( dx * zoomfac ) ) - dyi = int( round( dy * zoomfac ) ) + curtiles_difzoom[(zx, zy, z)] = idx + zoomfac = int(2 ** (self.curtilezoom - z)) + dxi = int(round(dx * zoomfac)) + dyi = int(round(dy * zoomfac)) # offset zoom, so possible offset dxi, dyi index_tex.extend( ( dxi, dyi, zoomfac, idx ) ) break @@ -281,7 +303,8 @@ def on_panzoom_changed( self, finished=False ): curtiles.update( curtiles_difzoom ) curtiles.update( self.curtiles ) self.curtiles = curtiles - data = np.array( index_tex, dtype=np.int32 ) + + data = np.array(index_tex, dtype=np.int32) self.glsurface.makeCurrent() self.indextexture.bind( 2 ) glh.gl.glTexSubImage2D_alt( glh.Texture.Target2D, 0, 0, 0, nx, ny, @@ -294,24 +317,46 @@ def load_tile( self, tile ): This function is called on callback from the asynchronous image load function. ''' - layer = len( self.curtiles ) - if layer >= bs.settings.tile_array_size: - # we're exceeding the size of the GL texture array. Replace the least-recent tile - _, layer = self.curtiles.popitem() + # First check whether tile is still relevant + # If there's a lot of panning/zooming, sometimes tiles are loaded that + # become obsolete before they are even downloaded. + if not ((self.curtilezoom == tile.zoom) and + (self.curtileext[0] <= tile.tilex <= self.curtileext[2]) and + (self.curtileext[1] <= tile.tiley <= self.curtileext[3])): + return - self.curtiles[( tile.tilex, tile.tiley, tile.zoom )] = layer - # Update the ordering of the tile dict: the new tile should be on top - self.curtiles.move_to_end( ( tile.tilex, tile.tiley, tile.zoom ), last=False ) - idxdata = np.array( [0, 0, 1, layer], dtype=np.int32 ) + # Make sure our GL context is current self.glsurface.makeCurrent() - self.indextexture.bind( 2 ) - glh.gl.glTexSubImage2D_alt( glh.Texture.Target2D, 0, tile.idxx, tile.idxy, + + # Check if tile is already loaded. This can happen here with multiple + # pans/zooms shortly after each other + layer = self.curtiles.get((tile.tilex, tile.tiley, tile.zoom), None) + if layer is None: + # This tile is not loaded yet. Select layer to upload it to + layer = len(self.curtiles) + if layer >= bs.settings.tile_array_size: + # we're exceeding the size of the GL texture array. + # Replace the least-recent tile + _, layer = self.curtiles.popitem() + + self.curtiles[(tile.tilex, tile.tiley, tile.zoom)] = layer + # Upload tile to texture array + super().bind(4) + self.setLayerData(layer, tile.image) + self.release() + + # Update the ordering of the tile dict: the new tile should be on top + self.curtiles.move_to_end( + (tile.tilex, tile.tiley, tile.zoom), last=False) + + # Update index texture + idxdata = np.array([0, 0, 1, layer], dtype=np.int32) + self.indextexture.bind(2) + glh.gl.glTexSubImage2D_alt(glh.Texture.Target2D, 0, tile.idxx, tile.idxy, 1, 1, glh.Texture.RGBA_Integer, - glh.Texture.Int32, idxdata.tobytes() ) - super().bind( 4 ) - self.setLayerData( layer, tile.image ) + glh.Texture.Int32, idxdata.tobytes()) + self.indextexture.release() - self.release() def latlon2tilenum( lat_deg, lon_deg, zoom ): diff --git a/bluesky/ui/radarclick.py b/bluesky/ui/radarclick.py index 462260cde5..94577ed113 100644 --- a/bluesky/ui/radarclick.py +++ b/bluesky/ui/radarclick.py @@ -186,6 +186,7 @@ def radarclick(cmdline, lat, lon, acdata=None, route=None): # Is it the last argument? (then we will insert ENTER as well) if curarg + 1 >= totargs: tostack = cmdline + todisplay - todisplay = todisplay + '\n' + # todisplay = todisplay + '\n' + todisplay = '' return tostack, todisplay diff --git a/check.py b/check.py index f818e725ac..46975e15db 100644 --- a/check.py +++ b/check.py @@ -130,7 +130,7 @@ def initializeGL( self ): print( "Checking bluesky modules" ) try: import bluesky - bluesky.init() + bluesky.init(mode='client') from bluesky.ui import * from bluesky.stack import * from bluesky.simulation import * diff --git a/data/graphics/mainwindow.ui b/data/graphics/mainwindow.ui index f645eb10df..6c9b1bf5ee 100644 --- a/data/graphics/mainwindow.ui +++ b/data/graphics/mainwindow.ui @@ -901,6 +901,15 @@ + + + + 0 + 240 + 0 + + + @@ -930,6 +939,15 @@ + + + + 0 + 240 + 0 + + + @@ -959,6 +977,15 @@ + + + + 0 + 240 + 0 + + + @@ -981,7 +1008,7 @@ true - Qt::NoTextInteraction + Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse @@ -1026,6 +1053,15 @@ + + + + 0 + 255 + 0 + + + @@ -1046,6 +1082,15 @@ + + + + 0 + 255 + 0 + + + @@ -1066,6 +1111,15 @@ + + + + 0 + 255 + 0 + + + diff --git a/bluesky/tools/geo_declination_data.csv b/data/navdata/geo_declination_data.csv similarity index 100% rename from bluesky/tools/geo_declination_data.csv rename to data/navdata/geo_declination_data.csv diff --git a/data/performance/OpenAP/fixwing/dragpolar.csv b/data/performance/OpenAP/fixwing/dragpolar.csv index bc3672cdc6..8f1f2cdcc2 100644 --- a/data/performance/OpenAP/fixwing/dragpolar.csv +++ b/data/performance/OpenAP/fixwing/dragpolar.csv @@ -1,22 +1,26 @@ mdl,cd0_clean,k_clean,e_clean,cd0_to,k_to,e_to,cd0_ld,k_ld,e_ld,delta_cd_gear -A319,0.019,0.039,0.793,0.021,0.037,0.845,0.025,0.035,0.897,0.017 -A320,0.018,0.039,0.798,0.020,0.036,0.850,0.024,0.034,0.902,0.017 -A321,0.026,0.043,0.746,0.028,0.040,0.798,0.034,0.036,0.876,0.019 -A332,0.029,0.044,0.728,0.030,0.041,0.780,0.035,0.037,0.858,0.014 -A333,0.030,0.044,0.719,0.032,0.041,0.771,0.036,0.037,0.849,0.014 -A359,0.031,0.046,0.725,0.032,0.043,0.777,0.037,0.039,0.855,0.013 -A388,0.028,0.054,0.781,0.030,0.051,0.833,0.033,0.048,0.885,0.012 -B734,0.034,0.049,0.705,0.036,0.046,0.757,0.038,0.043,0.809,0.021 -B737,0.029,0.046,0.736,0.030,0.043,0.788,0.035,0.039,0.866,0.016 -B738,0.023,0.044,0.775,0.024,0.041,0.827,0.029,0.037,0.905,0.017 -B739,0.024,0.044,0.769,0.025,0.041,0.821,0.030,0.038,0.899,0.018 -B744,0.028,0.052,0.774,0.030,0.049,0.826,0.034,0.046,0.878,0.015 -B748,0.027,0.049,0.771,0.029,0.046,0.823,0.032,0.043,0.875,0.015 -B772,0.034,0.051,0.723,0.036,0.047,0.775,0.041,0.043,0.853,0.014 -B77W,0.037,0.048,0.687,0.039,0.045,0.739,0.044,0.041,0.817,0.016 -B788,0.027,0.045,0.748,0.029,0.042,0.800,0.031,0.039,0.852,0.013 -B789,0.029,0.045,0.737,0.030,0.042,0.789,0.033,0.040,0.841,0.014 -E75L,0.019,0.043,0.803,0.020,0.040,0.855,0.025,0.037,0.933,0.017 -E190,0.019,0.044,0.813,0.020,0.041,0.865,0.025,0.038,0.943,0.016 -E195,0.028,0.048,0.752,0.029,0.045,0.804,0.034,0.041,0.882,0.017 +A319,0.020,0.039,0.783,0.021,0.037,0.835,0.026,0.035,0.887,0.017 +A320,0.018,0.039,0.799,0.019,0.036,0.851,0.023,0.034,0.903,0.017 +A20N,0.017,0.038,0.807,0.018,0.036,0.859,0.022,0.034,0.911,0.017 +A321,0.020,0.041,0.785,0.022,0.038,0.837,0.028,0.035,0.915,0.019 +A332,0.022,0.041,0.774,0.023,0.038,0.826,0.028,0.035,0.904,0.014 +A333,0.022,0.041,0.773,0.023,0.038,0.825,0.028,0.035,0.903,0.014 +A343,0.019,0.040,0.799,0.020,0.037,0.851,0.025,0.034,0.929,0.016 +A359,0.022,0.043,0.783,0.023,0.040,0.835,0.028,0.037,0.913,0.013 +A388,0.016,0.050,0.855,0.017,0.047,0.907,0.020,0.044,0.959,0.012 +B734,0.020,0.044,0.793,0.021,0.041,0.845,0.024,0.039,0.897,0.021 +B737,0.022,0.043,0.780,0.023,0.041,0.832,0.028,0.037,0.910,0.016 +B738,0.019,0.042,0.799,0.020,0.040,0.851,0.025,0.036,0.929,0.017 +B38M,0.020,0.042,0.797,0.021,0.040,0.849,0.025,0.036,0.927,0.018 +B739,0.020,0.042,0.797,0.021,0.040,0.849,0.025,0.036,0.927,0.018 +B744,0.021,0.049,0.816,0.022,0.046,0.868,0.027,0.044,0.920,0.015 +B748,0.021,0.047,0.806,0.023,0.044,0.858,0.026,0.041,0.910,0.015 +B752,0.021,0.049,0.813,0.022,0.046,0.865,0.029,0.043,0.943,0.016 +B772,0.024,0.047,0.783,0.025,0.044,0.835,0.031,0.040,0.913,0.014 +B77W,0.024,0.043,0.765,0.026,0.041,0.817,0.031,0.037,0.895,0.016 +B788,0.017,0.041,0.819,0.018,0.038,0.871,0.021,0.036,0.923,0.013 +B789,0.022,0.042,0.783,0.023,0.040,0.835,0.026,0.037,0.887,0.014 +E75L,0.018,0.042,0.809,0.019,0.040,0.861,0.024,0.036,0.939,0.017 +E190,0.018,0.044,0.817,0.019,0.041,0.869,0.024,0.038,0.947,0.016 +E195,0.021,0.045,0.795,0.022,0.042,0.847,0.027,0.039,0.925,0.017 F16,0.0175,0.109,0.908,0.018,0.109,0.908,0.018,0.109,0.908,0.000 diff --git a/data/performance/OpenAP/fixwing/wrap/a319.csv b/data/performance/OpenAP/fixwing/wrap/a319.csv deleted file mode 100644 index 6a31d7296e..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/a319.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,82.7,72.4,93.0,norm,82.7|7.1 -TO,to_d_tof,1.53,1.05,2.40,gamma,11.88|0.23|0.12 -TO,to_acc_tof,1.84,1.38,2.29,norm,1.84|0.28 -IC,ic_va_avg,80,72,87,norm,80|5 -IC,ic_vz_avg,12.25,9.36,15.15,norm,12.26|1.76 -CL,cl_d_range,207,157,350,gamma,5|138|16 -CL,cl_v_cas_const,148,138,166,gamma,11|121|2 -CL,cl_v_mach_const,0.77,0.73,0.81,norm,0.77|0.02 -CL,cl_h_cas_const,5.0,3.0,7.1,norm,5.1|1.1 -CL,cl_h_mach_const,10.0,7.6,11.1,beta,8.9|3.1|4.0|7.6 -CL,cl_vz_avg_pre_cas,11.54,8.92,14.17,norm,11.55|1.60 -CL,cl_vz_avg_cas_const,8.67,6.02,11.33,norm,8.67|1.61 -CL,cl_vz_avg_mach_const,5.67,3.24,8.12,norm,5.68|1.48 -CR,cr_d_range,288,172,3904,gamma,1|164|651 -CR,cr_v_cas_mean,126,118,140,gamma,18|99|1 -CR,cr_v_cas_max,128,121,148,gamma,3|116|4 -CR,cr_v_mach_mean,0.77,0.74,0.80,norm,0.77|0.02 -CR,cr_v_mach_max,0.79,0.75,0.82,norm,0.79|0.02 -CR,cr_h_mean,11.5,10.1,12.0,beta,6.2|2.0|8.1|4.1 -CR,cr_h_max,11.6,10.3,12.0,beta,6.1|2.0|8.4|3.8 -DE,de_d_range,235,175,492,gamma,3|162|34 -DE,de_v_mach_const,0.76,0.70,0.80,beta,7.49|4.28|0.62|0.22 -DE,de_v_cas_const,143,134,163,gamma,6|123|3 -DE,de_h_mach_const,9.2,6.9,10.7,beta,5.9|3.2|4.1|7.6 -DE,de_h_cas_const,5.2,2.7,8.2,beta,3.7|4.3|0.8|10.0 -DE,de_vz_avg_mach_const,-5.82,-12.06,-2.41,beta,4.07|2.36|-17.36|16.65 -DE,de_vz_avg_cas_const,-9.60,-14.30,-4.88,norm,-9.59|2.87 -DE,de_vz_avg_after_cas,-5.94,-7.76,-4.10,norm,-5.93|1.11 -FA,fa_va_avg,67,63,76,gamma,7|56|1 -FA,fa_vz_avg,-3.42,-4.20,-2.64,norm,-3.42|0.47 -LD,ld_v_app,66.2,60.6,71.9,norm,66.2|3.9 -LD,ld_d_brk,1.40,0.70,4.16,gamma,2.76|0.24|0.66 -LD,ld_acc_brk,-0.79,-1.92,-0.35,beta,22.41|3.49|-7.36|7.34 diff --git a/data/performance/OpenAP/fixwing/wrap/a319.txt b/data/performance/OpenAP/fixwing/wrap/a319.txt new file mode 100644 index 0000000000..7f5962b02c --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/a319.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 82.7 72.5 92.9 norm 82.71|7.10 +to_d_tof takeoff Takeoff distance 1.51 1.06 2.41 gamma 8.56|0.43|0.14 +to_acc_tof takeoff Mean takeoff accelaration 1.84 1.38 2.29 norm 1.84|0.28 +ic_va_avg initial_climb Mean airspeed 80 73 87 norm 80.26|4.86 +ic_vs_avg initial_climb Mean vertical rate 12.27 9.52 15.04 norm 12.28|1.68 +cl_d_range climb Climb range 206 157 348 gamma 5.16|138.89|16.28 +cl_v_cas_const climb Constant CAS 149 140 159 norm 150.02|6.06 +cl_v_mach_const climb Constant Mach 0.77 0.74 0.8 beta 16.21|6.30|0.62|0.20 +cl_h_cas_const climb Constant CAS crossover altitude 3.2 2.1 6 gamma 5.22|0.99|0.53 +cl_h_mach_const climb Constant Mach crossover altitude 8.9 8 9.8 norm 8.93|0.55 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 11.1 8.41 13.8 norm 11.11|1.64 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 10.15 7.63 12.67 norm 10.15|1.53 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.07 3.99 8.16 norm 6.08|1.27 +cr_d_range cruise Cruise range 299 182 4019 gamma 1.19|173.76|669.46 +cr_v_cas_mean cruise Mean cruise CAS 125 119 139 gamma 4.75|114.21|2.92 +cr_v_cas_max cruise Maximum cruise CAS 128 121 148 gamma 3.54|116.92|4.46 +cr_v_mach_mean cruise Mean cruise Mach 0.77 0.74 0.8 norm 0.77|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.79 0.75 0.82 norm 0.79|0.02 +cr_h_init cruise Initial cruise altitude 11.54 10.27 11.97 beta 7.48|2.22|8.11|4.08 +cr_h_mean cruise Mean cruise altitude 11.54 10.21 11.97 beta 7.00|2.13|8.10|4.09 +cr_h_max cruise Maximum cruise altitude 11.64 10.3 12.03 beta 6.12|1.89|8.42|3.79 +de_d_range descent Descent range 233 176 473 gamma 3.15|163.83|32.50 +de_v_mach_const descent Constant Mach 0.76 0.72 0.8 norm 0.76|0.03 +de_v_cas_const descent Constant CAS 145 136 164 gamma 6.49|126.84|3.35 +de_h_mach_const descent Constant Mach crossover altitude 9.2 7.7 10.7 norm 9.18|0.92 +de_h_cas_const descent Constant CAS crossover altitude 5.3 2.6 8.1 norm 5.34|1.66 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.87 -12.13 -2.41 beta 3.27|2.09|-16.27|15.37 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.84 -14.22 -5.45 norm -9.83|2.66 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -5.88 -7.69 -4.07 norm -5.88|1.10 +fa_va_avg final_approach Mean airspeed 67 63 76 gamma 6.43|57.71|1.84 +fa_vs_avg final_approach Mean vertical rate -3.42 -4.19 -2.65 norm -3.42|0.47 +fa_agl final_approach Approach angle 3.15 2.25 4.05 norm 3.15|0.55 +ld_v_app landing Touchdown speed 66.2 61 71.5 norm 66.23|3.65 +ld_d_brk landing Braking distance 1.38 0.71 4.18 gamma 2.62|0.28|0.68 +ld_acc_brk landing Mean braking acceleration -0.87 -1.79 -0.35 beta 5.62|2.87|-2.90|2.86 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/a320.csv b/data/performance/OpenAP/fixwing/wrap/a320.csv deleted file mode 100644 index eb3f3fb014..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/a320.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,85.3,74.5,96.1,norm,85.3|7.5 -TO,to_d_tof,1.65,1.06,2.25,norm,1.65|0.36 -TO,to_acc_tof,1.93,1.49,2.37,norm,1.93|0.27 -IC,ic_va_avg,83,76,90,norm,83|4 -IC,ic_vz_avg,12.58,9.13,16.06,norm,12.59|2.11 -CL,cl_d_range,236,171,440,gamma,4|149|24 -CL,cl_v_cas_const,155,141,169,norm,155|8 -CL,cl_v_mach_const,0.78,0.70,0.81,beta,38.80|3.94|0.09|0.74 -CL,cl_h_cas_const,5.0,2.8,7.3,norm,5.0|1.1 -CL,cl_h_mach_const,9.6,7.2,11.0,beta,9.4|3.7|3.4|8.2 -CL,cl_vz_avg_pre_cas,10.32,7.72,12.93,norm,10.33|1.58 -CL,cl_vz_avg_cas_const,7.18,4.83,9.54,norm,7.18|1.43 -CL,cl_vz_avg_mach_const,5.07,3.07,7.07,norm,5.07|1.22 -CR,cr_d_range,909,443,4269,gamma,1|391|524 -CR,cr_v_cas_mean,134,124,145,norm,134|6 -CR,cr_v_cas_max,138,127,156,gamma,17|103|2 -CR,cr_v_mach_mean,0.79,0.75,0.81,beta,28.61|5.59|0.53|0.29 -CR,cr_v_mach_max,0.80,0.77,0.83,norm,0.80|0.02 -CR,cr_h_mean,10.9,9.9,11.9,norm,10.9|0.6 -CR,cr_h_max,11.0,10.1,11.9,norm,11.0|0.6 -DE,de_d_range,235,180,468,gamma,3|168|31 -DE,de_v_mach_const,0.77,0.71,0.81,beta,8.96|4.09|0.61|0.23 -DE,de_v_cas_const,146,136,167,gamma,8|122|3 -DE,de_h_mach_const,9.5,7.3,10.8,beta,7.0|3.1|3.9|7.7 -DE,de_h_cas_const,5.7,2.9,8.4,beta,3.1|3.0|1.1|8.9 -DE,de_vz_avg_mach_const,-5.86,-13.45,-2.12,beta,5.70|2.62|-22.78|22.78 -DE,de_vz_avg_cas_const,-9.78,-14.70,-4.84,norm,-9.77|3.00 -DE,de_vz_avg_after_cas,-6.09,-7.92,-4.26,norm,-6.09|1.11 -FA,fa_va_avg,72,67,77,norm,72|3 -FA,fa_vz_avg,-3.55,-4.19,-2.90,norm,-3.55|0.39 -LD,ld_v_app,69.3,62.7,76.0,norm,69.4|4.6 -LD,ld_d_brk,1.10,0.62,3.20,gamma,2.49|0.33|0.52 -LD,ld_acc_brk,-1.22,-1.98,-0.47,norm,-1.22|0.46 diff --git a/data/performance/OpenAP/fixwing/wrap/a320.txt b/data/performance/OpenAP/fixwing/wrap/a320.txt new file mode 100644 index 0000000000..3d818154ae --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/a320.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 85.3 74.5 96 norm 85.29|7.47 +to_d_tof takeoff Takeoff distance 1.65 1.06 2.24 norm 1.65|0.36 +to_acc_tof takeoff Mean takeoff accelaration 1.93 1.5 2.37 norm 1.93|0.27 +ic_va_avg initial_climb Mean airspeed 83 76 89 norm 83.31|4.64 +ic_vs_avg initial_climb Mean vertical rate 12.59 9.15 16.04 norm 12.59|2.09 +cl_d_range climb Climb range 257 141 374 norm 258.08|45.16 +cl_v_cas_const climb Constant CAS 151 140 161 norm 151.04|6.27 +cl_v_mach_const climb Constant Mach 0.78 0.73 0.8 beta 12.39|3.68|0.60|0.22 +cl_h_cas_const climb Constant CAS crossover altitude 3.7 1.9 5.4 norm 3.66|1.07 +cl_h_mach_const climb Constant Mach crossover altitude 8.8 7.9 9.8 norm 8.80|0.58 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 10.25 7.63 12.87 norm 10.25|1.59 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.43 6.28 10.6 norm 8.44|1.31 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 5.28 3.6 6.97 norm 5.29|1.03 +cr_d_range cruise Cruise range 856 487 4352 gamma 1.71|453.95|569.12 +cr_v_cas_mean cruise Mean cruise CAS 133 124 143 norm 133.62|5.82 +cr_v_cas_max cruise Maximum cruise CAS 135 128 154 gamma 4.89|121.28|3.70 +cr_v_mach_mean cruise Mean cruise Mach 0.78 0.75 0.8 beta 17.82|5.05|0.62|0.20 +cr_v_mach_max cruise Maximum cruise Mach 0.8 0.77 0.83 norm 0.80|0.02 +cr_h_init cruise Initial cruise altitude 10.82 9.79 11.85 norm 10.82|0.63 +cr_h_mean cruise Mean cruise altitude 10.92 10 11.84 norm 10.92|0.56 +cr_h_max cruise Maximum cruise altitude 11.06 10.2 11.92 norm 11.06|0.52 +de_d_range descent Descent range 234 180 457 gamma 3.15|169.28|30.17 +de_v_mach_const descent Constant Mach 0.77 0.73 0.81 norm 0.77|0.02 +de_v_cas_const descent Constant CAS 144 135 163 gamma 7.43|124.57|3.15 +de_h_mach_const descent Constant Mach crossover altitude 9.6 7.9 10.7 beta 4.91|2.91|6.03|5.37 +de_h_cas_const descent Constant CAS crossover altitude 5.7 3 8.5 norm 5.75|1.66 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.76 -13.45 -2.26 beta 3.52|1.95|-19.00|18.22 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -10.03 -14.68 -5.35 norm -10.02|2.84 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.08 -7.88 -4.27 norm -6.08|1.10 +fa_va_avg final_approach Mean airspeed 72 67 77 norm 72.43|3.49 +fa_vs_avg final_approach Mean vertical rate -3.55 -4.18 -2.91 norm -3.55|0.39 +fa_agl final_approach Approach angle 3.06 2.4 3.73 norm 3.07|0.41 +ld_v_app landing Touchdown speed 69.4 62.7 76 norm 69.37|4.61 +ld_d_brk landing Braking distance 1.08 0.63 3.21 gamma 2.36|0.35|0.54 +ld_acc_brk landing Mean braking acceleration -1.22 -1.97 -0.47 norm -1.22|0.46 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/a321.csv b/data/performance/OpenAP/fixwing/wrap/a321.csv deleted file mode 100644 index 6c96375d5a..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/a321.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,90.8,80.0,101.6,norm,90.8|7.5 -TO,to_d_tof,1.85,1.16,2.54,norm,1.85|0.42 -TO,to_acc_tof,1.95,1.48,2.42,norm,1.95|0.29 -IC,ic_va_avg,86,78,94,norm,86|5 -IC,ic_vz_avg,13.21,9.31,17.13,norm,13.22|2.38 -CL,cl_d_range,236,164,439,gamma,5|136|23 -CL,cl_v_cas_const,158,145,171,norm,158|7 -CL,cl_v_mach_const,0.78,0.72,0.81,beta,15.34|6.21|0.57|0.29 -CL,cl_h_cas_const,5.3,3.5,7.1,norm,5.3|0.9 -CL,cl_h_mach_const,9.2,7.1,10.5,beta,10.3|4.5|3.6|7.7 -CL,cl_vz_avg_pre_cas,9.49,7.16,11.82,norm,9.49|1.42 -CL,cl_vz_avg_cas_const,6.73,4.29,9.18,norm,6.74|1.49 -CL,cl_vz_avg_mach_const,4.89,2.81,6.99,norm,4.90|1.27 -CR,cr_d_range,601,194,5334,gamma,1|161|790 -CR,cr_v_cas_mean,136,127,153,gamma,7|117|2 -CR,cr_v_cas_max,140,130,164,gamma,5|121|4 -CR,cr_v_mach_mean,0.78,0.75,0.80,norm,0.78|0.02 -CR,cr_v_mach_max,0.79,0.77,0.84,gamma,14.56|0.71|0.01 -CR,cr_h_mean,10.4,9.2,11.5,norm,10.4|0.7 -CR,cr_h_max,10.5,9.5,11.5,norm,10.5|0.6 -DE,de_d_range,242,168,464,gamma,4|143|26 -DE,de_v_mach_const,0.77,0.71,0.80,beta,8.91|3.49|0.60|0.22 -DE,de_v_cas_const,149,136,163,norm,149|8 -DE,de_h_mach_const,9.1,7.1,10.4,beta,5.2|3.2|5.1|6.2 -DE,de_h_cas_const,6.2,3.3,8.4,beta,3.3|2.7|1.3|8.4 -DE,de_vz_avg_mach_const,-5.57,-11.75,-2.02,beta,4.71|2.68|-17.89|17.89 -DE,de_vz_avg_cas_const,-9.15,-13.70,-4.57,norm,-9.14|2.77 -DE,de_vz_avg_after_cas,-6.03,-7.81,-4.24,norm,-6.02|1.09 -FA,fa_va_avg,75,70,80,norm,75|3 -FA,fa_vz_avg,-3.69,-4.37,-2.99,norm,-3.68|0.42 -LD,ld_v_app,72.9,66.9,79.0,norm,72.9|4.2 -LD,ld_d_brk,1.44,0.73,4.12,gamma,2.87|0.26|0.63 -LD,ld_acc_brk,-1.06,-2.11,-0.49,beta,9.63|3.80|-4.21|4.17 diff --git a/data/performance/OpenAP/fixwing/wrap/a321.txt b/data/performance/OpenAP/fixwing/wrap/a321.txt new file mode 100644 index 0000000000..9ec6e22b01 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/a321.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 90.8 80.1 101.5 norm 90.80|7.45 +to_d_tof takeoff Takeoff distance 1.85 1.18 2.52 norm 1.85|0.41 +to_acc_tof takeoff Mean takeoff accelaration 1.95 1.49 2.42 norm 1.95|0.28 +ic_va_avg initial_climb Mean airspeed 86 79 94 norm 86.60|5.22 +ic_vs_avg initial_climb Mean vertical rate 13.21 9.34 17.11 norm 13.22|2.36 +cl_d_range climb Climb range 230 164 426 gamma 4.93|140.03|22.96 +cl_v_cas_const climb Constant CAS 155 144 166 norm 155.67|6.67 +cl_v_mach_const climb Constant Mach 0.77 0.74 0.81 norm 0.77|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 3.7 2.1 5.4 norm 3.75|0.98 +cl_h_mach_const climb Constant Mach crossover altitude 8.4 7.5 9.3 norm 8.41|0.56 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 9.38 7.03 11.73 norm 9.38|1.43 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.17 5.91 10.44 norm 8.17|1.38 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 5.19 3.33 7.05 norm 5.19|1.13 +cr_d_range cruise Cruise range 610 196 5338 gamma 1.56|162.63|789.69 +cr_v_cas_mean cruise Mean cruise CAS 138 127 149 norm 138.28|6.66 +cr_v_cas_max cruise Maximum cruise CAS 140 130 162 gamma 5.67|121.09|4.11 +cr_v_mach_mean cruise Mean cruise Mach 0.78 0.75 0.8 norm 0.78|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.79 0.77 0.84 gamma 13.42|0.72|0.01 +cr_h_init cruise Initial cruise altitude 10.34 9.23 11.46 norm 10.35|0.68 +cr_h_mean cruise Mean cruise altitude 10.41 9.36 11.46 norm 10.41|0.64 +cr_h_max cruise Maximum cruise altitude 10.56 9.6 11.52 norm 10.56|0.58 +de_d_range descent Descent range 236 174 463 gamma 3.63|159.06|29.37 +de_v_mach_const descent Constant Mach 0.77 0.74 0.8 norm 0.77|0.02 +de_v_cas_const descent Constant CAS 150 138 163 norm 150.75|7.62 +de_h_mach_const descent Constant Mach crossover altitude 9 7.8 10.3 norm 9.05|0.76 +de_h_cas_const descent Constant CAS crossover altitude 6.2 3.3 8.5 beta 2.88|2.40|1.57|8.08 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.52 -11.86 -2.17 beta 4.06|2.31|-17.23|16.72 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.41 -13.64 -5.15 norm -9.40|2.58 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.01 -7.78 -4.24 norm -6.01|1.08 +fa_va_avg final_approach Mean airspeed 75 70 80 norm 75.41|3.61 +fa_vs_avg final_approach Mean vertical rate -3.69 -4.36 -3.01 norm -3.68|0.41 +fa_agl final_approach Approach angle 3.01 2.36 3.66 norm 3.01|0.39 +ld_v_app landing Touchdown speed 72.9 66.9 78.9 norm 72.94|4.17 +ld_d_brk landing Braking distance 1.55 0.64 3.94 beta 1.50|2.41|0.34|4.65 +ld_acc_brk landing Mean braking acceleration -1.21 -1.98 -0.45 norm -1.21|0.47 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/a332.csv b/data/performance/OpenAP/fixwing/wrap/a332.csv deleted file mode 100644 index 8be4da388a..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/a332.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,89.9,77.8,102.0,norm,89.9|8.4 -TO,to_d_tof,1.98,1.16,2.80,norm,1.98|0.50 -TO,to_acc_tof,1.75,1.32,2.19,norm,1.75|0.26 -IC,ic_va_avg,86,78,94,norm,86|5 -IC,ic_vz_avg,12.48,8.38,16.60,norm,12.49|2.50 -CL,cl_d_range,306,159,453,norm,306|57 -CL,cl_v_cas_const,155,145,166,norm,155|6 -CL,cl_v_mach_const,0.80,0.75,0.86,norm,0.80|0.03 -CL,cl_h_cas_const,5.2,3.2,7.2,norm,5.2|1.0 -CL,cl_h_mach_const,10.1,8.2,12.0,norm,10.1|1.0 -CL,cl_vz_avg_pre_cas,9.72,7.19,12.26,norm,9.73|1.54 -CL,cl_vz_avg_cas_const,6.80,4.19,9.42,norm,6.81|1.59 -CL,cl_vz_avg_mach_const,4.15,2.47,7.39,gamma,9.22|0.05|0.50 -CR,cr_d_range,224,241,10006,beta,0|2|216|10638 -CR,cr_v_cas_mean,130,122,144,gamma,11|109|2 -CR,cr_v_cas_max,136,126,163,gamma,4|118|5 -CR,cr_v_mach_mean,0.82,0.78,0.84,beta,23.04|6.20|0.64|0.22 -CR,cr_v_mach_max,0.84,0.80,0.87,norm,0.84|0.02 -CR,cr_h_mean,12.3,10.3,12.4,beta,4.8|1.2|8.1|4.4 -CR,cr_h_max,12.5,10.7,12.5,beta,5.4|1.0|8.1|4.4 -DE,de_d_range,285,206,517,gamma,5|176|26 -DE,de_v_mach_const,0.80,0.76,0.85,norm,0.80|0.03 -DE,de_v_cas_const,152,138,166,norm,152|8 -DE,de_h_mach_const,10.3,7.7,11.4,beta,9.5|3.0|2.3|9.8 -DE,de_h_cas_const,6.2,3.6,8.9,norm,6.2|1.6 -DE,de_vz_avg_mach_const,-6.18,-12.67,-2.48,beta,6.29|3.17|-21.29|21.29 -DE,de_vz_avg_cas_const,-9.15,-13.51,-4.78,norm,-9.14|2.65 -DE,de_vz_avg_after_cas,-5.81,-7.58,-4.04,norm,-5.81|1.08 -FA,fa_va_avg,72,67,78,norm,72|3 -FA,fa_vz_avg,-3.62,-4.25,-2.98,norm,-3.62|0.39 -LD,ld_v_app,70.7,65.4,75.9,norm,70.7|3.6 -LD,ld_d_brk,1.55,0.86,3.66,gamma,3.68|0.32|0.46 -LD,ld_acc_brk,-1.18,-1.85,-0.50,norm,-1.18|0.41 diff --git a/data/performance/OpenAP/fixwing/wrap/a332.txt b/data/performance/OpenAP/fixwing/wrap/a332.txt new file mode 100644 index 0000000000..90cd4bba69 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/a332.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 89.9 77.9 101.9 norm 89.89|8.34 +to_d_tof takeoff Takeoff distance 1.97 1.16 2.79 norm 1.98|0.49 +to_acc_tof takeoff Mean takeoff accelaration 1.75 1.32 2.18 norm 1.75|0.26 +ic_va_avg initial_climb Mean airspeed 86 78 94 norm 86.44|5.43 +ic_vs_avg initial_climb Mean vertical rate 12.48 8.41 16.57 norm 12.49|2.48 +cl_d_range climb Climb range 297 152 442 norm 297.55|56.26 +cl_v_cas_const climb Constant CAS 152 143 162 norm 152.98|6.05 +cl_v_mach_const climb Constant Mach 0.81 0.76 0.84 beta 11.67|3.95|0.63|0.22 +cl_h_cas_const climb Constant CAS crossover altitude 3.6 2.3 6 gamma 8.62|0.62|0.39 +cl_h_mach_const climb Constant Mach crossover altitude 9.2 8.3 10.2 norm 9.25|0.60 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 9.53 7.04 12.02 norm 9.53|1.51 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 7.98 5.51 10.47 norm 7.99|1.51 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 5.23 3.03 7.45 norm 5.24|1.34 +cr_d_range cruise Cruise range 1023 229 11012 beta 1.11|2.41|187.36|12081.93 +cr_v_cas_mean cruise Mean cruise CAS 129 123 143 gamma 6.40|116.17|2.49 +cr_v_cas_max cruise Maximum cruise CAS 135 126 160 gamma 3.86|120.04|5.41 +cr_v_mach_mean cruise Mean cruise Mach 0.81 0.79 0.84 norm 0.81|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.84 0.81 0.87 norm 0.84|0.02 +cr_h_init cruise Initial cruise altitude 11.33 9.99 12.68 norm 11.34|0.82 +cr_h_mean cruise Mean cruise altitude 11.67 10.71 12.63 norm 11.67|0.58 +cr_h_max cruise Maximum cruise altitude 11.95 11.12 12.79 norm 11.96|0.51 +de_d_range descent Descent range 283 207 505 gamma 4.98|180.10|25.87 +de_v_mach_const descent Constant Mach 0.81 0.76 0.84 beta 10.12|3.59|0.65|0.20 +de_v_cas_const descent Constant CAS 152 136 162 beta 4.92|2.86|119.57|49.23 +de_h_mach_const descent Constant Mach crossover altitude 10.2 8.4 11.4 beta 4.18|2.78|6.90|5.17 +de_h_cas_const descent Constant CAS crossover altitude 6.3 3.6 9 norm 6.29|1.63 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -6.28 -12.53 -2.85 beta 4.74|2.60|-18.78|17.84 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.39 -13.44 -5.31 norm -9.38|2.47 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -5.82 -7.56 -4.07 norm -5.82|1.06 +fa_va_avg final_approach Mean airspeed 72 67 78 norm 72.83|3.65 +fa_vs_avg final_approach Mean vertical rate -3.62 -4.24 -2.99 norm -3.62|0.38 +fa_agl final_approach Approach angle 2.98 2.44 3.53 norm 2.99|0.33 +ld_v_app landing Touchdown speed 70.7 65.5 75.9 norm 70.67|3.61 +ld_d_brk landing Braking distance 1.54 0.87 3.67 gamma 3.53|0.35|0.47 +ld_acc_brk landing Mean braking acceleration -1.18 -1.85 -0.5 norm -1.18|0.41 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/a333.csv b/data/performance/OpenAP/fixwing/wrap/a333.csv deleted file mode 100644 index e4da49dcc4..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/a333.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,89.9,77.7,102.2,norm,90.0|8.5 -TO,to_d_tof,1.89,1.26,2.98,gamma,12.87|0.14|0.15 -TO,to_acc_tof,1.72,1.31,2.14,norm,1.73|0.25 -IC,ic_va_avg,87,79,94,norm,87|5 -IC,ic_vz_avg,12.29,8.41,16.18,norm,12.30|2.36 -CL,cl_d_range,311,158,463,norm,311|59 -CL,cl_v_cas_const,157,146,168,norm,157|6 -CL,cl_v_mach_const,0.79,0.73,0.86,norm,0.79|0.04 -CL,cl_h_cas_const,5.0,2.8,7.3,norm,5.0|1.1 -CL,cl_h_mach_const,9.8,7.8,11.7,norm,9.8|1.0 -CL,cl_vz_avg_pre_cas,9.17,6.79,11.57,norm,9.18|1.45 -CL,cl_vz_avg_cas_const,6.19,4.12,9.55,gamma,15.98|-0.05|0.42 -CL,cl_vz_avg_mach_const,4.28,2.71,7.66,gamma,7.00|0.81|0.58 -CR,cr_d_range,560,571,8414,beta,0|1|553|8450 -CR,cr_v_cas_mean,134,123,144,norm,134|6 -CR,cr_v_cas_max,140,128,162,beta,2|6|121|75 -CR,cr_v_mach_mean,0.81,0.79,0.84,norm,0.81|0.01 -CR,cr_v_mach_max,0.84,0.81,0.87,gamma,25.03|0.74|0.00 -CR,cr_h_mean,11.5,10.5,12.5,norm,11.5|0.6 -CR,cr_h_max,12.4,10.6,12.5,beta,5.1|1.2|8.5|4.0 -DE,de_d_range,286,214,522,gamma,4|191|28 -DE,de_v_mach_const,0.80,0.74,0.86,norm,0.80|0.03 -DE,de_v_cas_const,151,137,166,norm,152|8 -DE,de_h_mach_const,9.7,7.9,11.6,norm,9.7|1.1 -DE,de_h_cas_const,6.3,3.2,9.1,beta,4.1|3.6|0.6|10.5 -DE,de_vz_avg_mach_const,-6.32,-12.18,-2.52,beta,5.21|3.17|-18.57|18.57 -DE,de_vz_avg_cas_const,-8.72,-13.03,-4.39,norm,-8.71|2.63 -DE,de_vz_avg_after_cas,-5.74,-7.45,-4.03,norm,-5.74|1.04 -FA,fa_va_avg,73,69,78,norm,73|3 -FA,fa_vz_avg,-3.65,-4.25,-3.04,norm,-3.65|0.37 -LD,ld_v_app,71.5,65.5,77.6,norm,71.5|4.2 -LD,ld_d_brk,1.51,0.81,3.76,gamma,3.53|0.26|0.49 -LD,ld_acc_brk,-1.20,-1.87,-0.53,norm,-1.20|0.41 diff --git a/data/performance/OpenAP/fixwing/wrap/a333.txt b/data/performance/OpenAP/fixwing/wrap/a333.txt new file mode 100644 index 0000000000..41f77f6256 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/a333.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 89.9 77.8 102.1 norm 89.97|8.44 +to_d_tof takeoff Takeoff distance 1.86 1.27 3.01 gamma 8.89|0.45|0.18 +to_acc_tof takeoff Mean takeoff accelaration 1.72 1.32 2.13 norm 1.73|0.25 +ic_va_avg initial_climb Mean airspeed 87 79 94 norm 87.04|4.98 +ic_vs_avg initial_climb Mean vertical rate 12.28 8.47 16.11 norm 12.29|2.32 +cl_d_range climb Climb range 287 174 456 beta 2.84|4.07|153.70|358.08 +cl_v_cas_const climb Constant CAS 153 142 165 norm 153.59|7.03 +cl_v_mach_const climb Constant Mach 0.8 0.75 0.84 norm 0.80|0.03 +cl_h_cas_const climb Constant CAS crossover altitude 3.1 1.9 6.2 gamma 4.75|0.84|0.61 +cl_h_mach_const climb Constant Mach crossover altitude 9.1 8 10.2 norm 9.08|0.66 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 8.82 6.46 11.19 norm 8.82|1.44 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 7.65 5.24 10.08 norm 7.66|1.47 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 4.56 3.14 7.9 gamma 5.77|1.63|0.61 +cr_d_range cruise Cruise range 789 604 8911 beta 1.02|1.72|576.76|8731.39 +cr_v_cas_mean cruise Mean cruise CAS 131 124 144 gamma 6.44|117.90|2.41 +cr_v_cas_max cruise Maximum cruise CAS 137 128 161 beta 2.59|7.64|123.03|77.33 +cr_v_mach_mean cruise Mean cruise Mach 0.81 0.79 0.83 norm 0.81|0.01 +cr_v_mach_max cruise Maximum cruise Mach 0.83 0.81 0.87 gamma 17.42|0.76|0.00 +cr_h_init cruise Initial cruise altitude 11.1 9.74 12.46 norm 11.10|0.83 +cr_h_mean cruise Mean cruise altitude 11.49 10.54 12.45 norm 11.50|0.58 +cr_h_max cruise Maximum cruise altitude 12.53 10.63 12.48 beta 4.06|0.99|8.87|3.66 +de_d_range descent Descent range 284 214 508 gamma 4.31|193.23|27.46 +de_v_mach_const descent Constant Mach 0.81 0.77 0.84 norm 0.81|0.02 +de_v_cas_const descent Constant CAS 150 137 162 norm 150.13|7.55 +de_h_mach_const descent Constant Mach crossover altitude 10.2 8.5 11.3 beta 4.10|2.55|6.93|4.96 +de_h_cas_const descent Constant CAS crossover altitude 6.3 3.4 9.1 norm 6.28|1.73 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -6.17 -12.5 -2.68 beta 4.37|2.47|-18.33|17.48 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.03 -13.07 -4.96 norm -9.02|2.46 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -5.74 -7.41 -4.06 norm -5.74|1.02 +fa_va_avg final_approach Mean airspeed 73 69 78 norm 73.84|3.33 +fa_vs_avg final_approach Mean vertical rate -3.74 -4.22 -2.97 gamma 16.90|-5.22|0.09 +fa_agl final_approach Approach angle 2.97 2.44 3.49 norm 2.97|0.32 +ld_v_app landing Touchdown speed 71.5 65.5 77.6 norm 71.55|4.18 +ld_d_brk landing Braking distance 1.48 0.81 3.79 gamma 3.19|0.33|0.53 +ld_acc_brk landing Mean braking acceleration -1.2 -1.86 -0.53 norm -1.20|0.40 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/a343.csv b/data/performance/OpenAP/fixwing/wrap/a343.csv deleted file mode 100644 index 24816f4177..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/a343.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,87.0,76.0,98.2,norm,87.1|7.7 -TO,to_d_tof,2.21,1.43,3.78,gamma,8.13|0.40|0.25 -TO,to_acc_tof,1.40,1.01,1.80,norm,1.40|0.24 -IC,ic_va_avg,84,76,93,beta,3|5|69|36 -IC,ic_vz_avg,6.58,4.79,10.89,gamma,5.53|2.93|0.81 -CL,cl_d_range,307,173,443,norm,308|52 -CL,cl_v_cas_const,159,146,168,beta,5|2|131|41 -CL,cl_v_mach_const,0.78,0.75,0.82,norm,0.78|0.02 -CL,cl_h_cas_const,5.0,3.0,6.9,norm,5.0|1.0 -CL,cl_h_mach_const,9.5,7.9,11.1,norm,9.5|0.8 -CL,cl_vz_avg_pre_cas,7.13,5.54,10.68,gamma,6.44|3.72|0.63 -CL,cl_vz_avg_cas_const,5.85,3.99,8.94,gamma,14.54|0.46|0.40 -CL,cl_vz_avg_mach_const,4.05,2.03,6.09,norm,4.06|1.23 -CR,cr_d_range,5438,-3430,14332,norm,5450|3447 -CR,cr_v_cas_mean,138,128,149,norm,138|6 -CR,cr_v_cas_max,149,133,166,norm,149|9 -CR,cr_v_mach_mean,0.81,0.79,0.83,norm,0.81|0.01 -CR,cr_v_mach_max,0.83,0.81,0.88,gamma,11.68|0.77|0.01 -CR,cr_h_mean,11.0,10.0,12.0,norm,11.0|0.6 -CR,cr_h_max,11.5,10.6,12.4,norm,11.5|0.6 -DE,de_d_range,284,202,501,gamma,6|166|23 -DE,de_v_mach_const,0.80,0.75,0.84,norm,0.80|0.03 -DE,de_v_cas_const,152,139,166,norm,152|8 -DE,de_h_mach_const,9.6,8.0,11.2,norm,9.6|1.0 -DE,de_h_cas_const,5.9,3.1,8.6,norm,5.9|1.7 -DE,de_vz_avg_mach_const,-5.78,-11.97,-2.68,beta,6.21|2.77|-20.26|19.42 -DE,de_vz_avg_cas_const,-8.98,-13.47,-4.46,norm,-8.97|2.74 -DE,de_vz_avg_after_cas,-5.51,-7.22,-3.80,norm,-5.51|1.04 -FA,fa_va_avg,74,69,79,norm,74|3 -FA,fa_vz_avg,-3.64,-4.32,-2.96,norm,-3.64|0.41 -LD,ld_v_app,72.2,67.5,77.0,norm,72.3|3.3 -LD,ld_d_brk,1.68,0.91,3.97,gamma,3.87|0.28|0.49 -LD,ld_acc_brk,-1.06,-1.82,-0.47,beta,5.15|3.83|-2.62|2.62 diff --git a/data/performance/OpenAP/fixwing/wrap/a343.txt b/data/performance/OpenAP/fixwing/wrap/a343.txt new file mode 100644 index 0000000000..42ebe3c3d0 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/a343.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 87 76 98.2 norm 87.08|7.70 +to_d_tof takeoff Takeoff distance 2.16 1.46 3.82 gamma 5.71|0.71|0.31 +to_acc_tof takeoff Mean takeoff accelaration 1.4 1.01 1.8 norm 1.40|0.24 +ic_va_avg initial_climb Mean airspeed 84 76 93 beta 3.76|4.87|69.49|35.32 +ic_vs_avg initial_climb Mean vertical rate 6.44 4.82 11.01 gamma 4.18|3.43|0.95 +cl_d_range climb Climb range 293 154 431 norm 293.32|53.81 +cl_v_cas_const climb Constant CAS 155 144 169 beta 6.81|9.57|129.33|64.98 +cl_v_mach_const climb Constant Mach 0.78 0.75 0.81 norm 0.78|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 3.6 1.7 5.5 norm 3.61|1.16 +cl_h_mach_const climb Constant Mach crossover altitude 8.5 7.3 9.8 beta 5.08|5.89|6.16|5.14 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 6.54 5.09 10.41 gamma 4.51|3.79|0.78 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 6.59 4.87 10.05 gamma 8.32|2.54|0.55 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 4.08 2.65 7.17 gamma 6.86|0.95|0.53 +cr_d_range cruise Cruise range 6021 -1865 13929 norm 6032.29|3066.01 +cr_v_cas_mean cruise Mean cruise CAS 138 128 148 norm 138.63|6.17 +cr_v_cas_max cruise Maximum cruise CAS 149 132 165 norm 149.24|9.97 +cr_v_mach_mean cruise Mean cruise Mach 0.81 0.79 0.83 norm 0.81|0.01 +cr_v_mach_max cruise Maximum cruise Mach 0.83 0.81 0.88 gamma 11.93|0.77|0.01 +cr_h_init cruise Initial cruise altitude 10.01 8.92 12.09 gamma 9.56|7.32|0.32 +cr_h_mean cruise Mean cruise altitude 10.86 10.12 12.1 gamma 15.04|8.66|0.16 +cr_h_max cruise Maximum cruise altitude 11.7 10.61 12.35 beta 6.39|3.32|9.14|3.66 +de_d_range descent Descent range 281 203 489 gamma 5.92|169.03|22.84 +de_v_mach_const descent Constant Mach 0.81 0.76 0.84 beta 7.14|4.09|0.69|0.17 +de_v_cas_const descent Constant CAS 154 138 165 beta 3.94|2.85|125.92|46.48 +de_h_mach_const descent Constant Mach crossover altitude 9.9 8.3 11.1 beta 3.65|2.90|7.05|4.83 +de_h_cas_const descent Constant CAS crossover altitude 5.8 3 8.8 beta 2.75|2.94|1.39|9.27 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.84 -12.15 -2.68 beta 4.78|2.43|-18.58|17.56 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.27 -13.56 -4.97 norm -9.26|2.61 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -5.5 -7.22 -3.78 norm -5.50|1.04 +fa_va_avg final_approach Mean airspeed 74 69 79 norm 74.30|3.48 +fa_vs_avg final_approach Mean vertical rate -3.64 -4.31 -2.97 norm -3.64|0.41 +fa_agl final_approach Approach angle 3.04 2.47 3.61 norm 3.04|0.35 +ld_v_app landing Touchdown speed 72.2 67.5 77 norm 72.26|3.30 +ld_d_brk landing Braking distance 1.65 0.92 3.99 gamma 3.45|0.37|0.52 +ld_acc_brk landing Mean braking acceleration -1.01 -1.83 -0.49 beta 4.33|2.73|-2.58|2.38 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/a388.csv b/data/performance/OpenAP/fixwing/wrap/a388.csv deleted file mode 100644 index 7d34682021..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/a388.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,89.9,75.4,104.5,norm,89.9|10.1 -TO,to_d_tof,2.56,1.34,3.79,norm,2.56|0.74 -TO,to_acc_tof,1.35,1.03,1.67,norm,1.35|0.19 -IC,ic_va_avg,88,79,96,norm,88|5 -IC,ic_vz_avg,5.72,4.37,8.91,gamma,5.67|2.95|0.59 -CL,cl_d_range,298,213,506,gamma,6|171|21 -CL,cl_v_cas_const,166,157,174,norm,166|5 -CL,cl_v_mach_const,0.83,0.79,0.87,norm,0.83|0.02 -CL,cl_h_cas_const,5.0,2.8,7.2,norm,5.0|1.1 -CL,cl_h_mach_const,9.9,8.4,11.5,norm,9.9|0.8 -CL,cl_vz_avg_pre_cas,8.45,6.58,10.32,norm,8.45|1.14 -CL,cl_vz_avg_cas_const,6.52,3.92,9.19,beta,6.41|6.73|0.68|12.03 -CL,cl_vz_avg_mach_const,4.88,2.58,7.19,norm,4.88|1.40 -CR,cr_d_range,4111,906,17803,gamma,3|227|1863 -CR,cr_v_cas_mean,136,129,145,beta,4|7|124|35 -CR,cr_v_cas_max,143,135,167,gamma,3|128|5 -CR,cr_v_mach_mean,0.84,0.82,0.86,norm,0.84|0.01 -CR,cr_v_mach_max,0.87,0.85,0.91,gamma,28.56|0.78|0.00 -CR,cr_h_mean,11.8,10.9,12.3,beta,13.1|5.6|8.7|4.2 -CR,cr_h_max,12.2,11.4,12.7,beta,8.0|4.3|10.1|3.0 -DE,de_d_range,313,235,538,gamma,5|204|25 -DE,de_v_mach_const,0.83,0.79,0.87,norm,0.83|0.03 -DE,de_v_cas_const,156,143,170,norm,156|8 -DE,de_h_mach_const,10.3,7.7,11.5,beta,7.8|2.9|3.3|9.0 -DE,de_h_cas_const,6.5,3.8,9.3,norm,6.5|1.7 -DE,de_vz_avg_mach_const,-6.45,-11.67,-2.79,beta,4.14|2.91|-16.15|15.58 -DE,de_vz_avg_cas_const,-8.17,-11.73,-4.60,norm,-8.16|2.17 -DE,de_vz_avg_after_cas,-5.49,-6.97,-4.00,norm,-5.48|0.90 -FA,fa_va_avg,73,68,77,norm,73|3 -FA,fa_vz_avg,-3.70,-4.14,-2.92,gamma,11.94|-4.88|0.11 -LD,ld_v_app,70.0,61.8,78.3,norm,70.0|5.7 -LD,ld_d_brk,2.26,0.73,3.80,norm,2.26|0.93 -LD,ld_acc_brk,-1.01,-1.51,-0.52,norm,-1.01|0.30 diff --git a/data/performance/OpenAP/fixwing/wrap/a388.txt b/data/performance/OpenAP/fixwing/wrap/a388.txt new file mode 100644 index 0000000000..dc7b8222ee --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/a388.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 89.9 75.4 104.4 norm 89.93|10.07 +to_d_tof takeoff Takeoff distance 2.56 1.35 3.78 norm 2.56|0.74 +to_acc_tof takeoff Mean takeoff accelaration 1.35 1.04 1.66 norm 1.35|0.19 +ic_va_avg initial_climb Mean airspeed 88 80 96 norm 88.15|5.64 +ic_vs_avg initial_climb Mean vertical rate 5.65 4.4 8.94 gamma 4.76|3.22|0.65 +cl_d_range climb Climb range 296 200 446 beta 3.23|5.18|179.46|335.24 +cl_v_cas_const climb Constant CAS 163 155 170 norm 163.39|4.51 +cl_v_mach_const climb Constant Mach 0.84 0.8 0.86 beta 12.23|5.32|0.72|0.17 +cl_h_cas_const climb Constant CAS crossover altitude 3.3 1.3 5.3 norm 3.29|1.24 +cl_h_mach_const climb Constant Mach crossover altitude 8.9 8.2 9.7 norm 8.94|0.47 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 7.85 5.95 9.75 norm 7.85|1.16 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 7.51 5.2 9.82 norm 7.51|1.40 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 5.56 3.23 7.91 norm 5.57|1.42 +cr_d_range cruise Cruise range 4348 892 20565 gamma 2.81|246.73|2274.81 +cr_v_cas_mean cruise Mean cruise CAS 136 130 145 beta 3.32|5.27|126.00|29.75 +cr_v_cas_max cruise Maximum cruise CAS 145 134 164 beta 2.02|3.21|130.38|46.65 +cr_v_mach_mean cruise Mean cruise Mach 0.84 0.82 0.86 norm 0.84|0.01 +cr_v_mach_max cruise Maximum cruise Mach 0.87 0.85 0.9 gamma 16.14|0.80|0.00 +cr_h_init cruise Initial cruise altitude 11.55 9.3 12.23 beta 3.82|1.66|7.49|5.01 +cr_h_mean cruise Mean cruise altitude 11.73 10.87 12.28 beta 7.22|3.92|9.59|3.14 +cr_h_max cruise Maximum cruise altitude 12.06 11.52 12.6 norm 12.06|0.33 +de_d_range descent Descent range 310 238 528 gamma 4.73|213.47|25.87 +de_v_mach_const descent Constant Mach 0.83 0.8 0.87 norm 0.83|0.02 +de_v_cas_const descent Constant CAS 154 142 167 norm 154.84|7.74 +de_h_mach_const descent Constant Mach crossover altitude 10.1 8.6 11.5 norm 10.06|0.88 +de_h_cas_const descent Constant CAS crossover altitude 6.6 3.9 9.4 norm 6.64|1.69 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -6.06 -11.9 -2.97 beta 3.43|2.08|-15.98|14.36 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -8.36 -11.74 -4.97 norm -8.36|2.06 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -5.48 -6.93 -4.02 norm -5.48|0.88 +fa_va_avg final_approach Mean airspeed 73 68 77 norm 73.28|3.02 +fa_vs_avg final_approach Mean vertical rate -3.71 -4.13 -2.92 gamma 9.49|-4.74|0.12 +fa_agl final_approach Approach angle 2.9 2.42 3.38 norm 2.90|0.29 +ld_v_app landing Touchdown speed 70 62.1 78 norm 70.00|5.52 +ld_d_brk landing Braking distance 2.26 0.73 3.8 norm 2.26|0.93 +ld_acc_brk landing Mean braking acceleration -1.01 -1.51 -0.52 norm -1.01|0.30 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b737.csv b/data/performance/OpenAP/fixwing/wrap/b737.csv deleted file mode 100644 index 54c08443ee..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b737.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,83.6,72.3,94.9,norm,83.6|7.9 -TO,to_d_tof,1.55,1.00,2.56,gamma,10.44|0.14|0.15 -TO,to_acc_tof,1.77,1.30,2.23,norm,1.77|0.28 -IC,ic_va_avg,81,70,91,norm,81|7 -IC,ic_vz_avg,11.86,7.69,16.06,norm,11.87|2.54 -CL,cl_d_range,202,154,346,gamma,5|136|16 -CL,cl_v_cas_const,151,137,165,norm,151|8 -CL,cl_v_mach_const,0.78,0.73,0.81,beta,39.32|5.26|0.28|0.56 -CL,cl_h_cas_const,5.3,3.2,7.4,norm,5.3|1.1 -CL,cl_h_mach_const,10.3,7.8,11.6,beta,7.4|3.3|4.7|7.5 -CL,cl_vz_avg_pre_cas,12.32,9.29,15.36,norm,12.33|1.84 -CL,cl_vz_avg_cas_const,9.89,6.32,13.47,norm,9.89|2.18 -CL,cl_vz_avg_mach_const,5.82,3.28,8.38,norm,5.83|1.55 -CR,cr_d_range,491,175,4489,gamma,1|150|675 -CR,cr_v_cas_mean,125,114,135,norm,125|6 -CR,cr_v_cas_max,129,115,150,gamma,24|78|2 -CR,cr_v_mach_mean,0.77,0.75,0.80,norm,0.77|0.02 -CR,cr_v_mach_max,0.80,0.76,0.84,norm,0.80|0.02 -CR,cr_h_mean,11.7,10.7,12.8,norm,11.7|0.6 -CR,cr_h_max,12.5,10.7,12.5,beta,5.3|1.0|8.1|4.4 -DE,de_d_range,249,173,586,gamma,2|157|46 -DE,de_v_mach_const,0.78,0.69,0.81,beta,28.18|3.95|0.18|0.66 -DE,de_v_cas_const,145,131,160,norm,145|8 -DE,de_h_mach_const,9.4,7.6,11.2,norm,9.4|1.1 -DE,de_h_cas_const,5.7,3.0,8.5,norm,5.7|1.7 -DE,de_vz_avg_mach_const,-5.45,-12.87,-2.28,beta,3.94|1.99|-18.99|18.09 -DE,de_vz_avg_cas_const,-8.95,-14.05,-3.84,norm,-8.94|3.10 -DE,de_vz_avg_after_cas,-5.86,-7.94,-3.77,norm,-5.86|1.27 -FA,fa_va_avg,70,65,80,gamma,7|58|1 -FA,fa_vz_avg,-3.58,-4.46,-2.69,norm,-3.58|0.54 -LD,ld_v_app,68.8,62.9,74.7,norm,68.8|4.1 -LD,ld_d_brk,2.07,0.64,4.38,beta,1.43|1.69|0.27|4.71 -LD,ld_acc_brk,-0.82,-1.91,-0.31,beta,6.14|2.65|-3.36|3.34 diff --git a/data/performance/OpenAP/fixwing/wrap/b737.txt b/data/performance/OpenAP/fixwing/wrap/b737.txt new file mode 100644 index 0000000000..bff99052d6 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b737.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 83.6 72.3 94.8 norm 83.58|7.82 +to_d_tof takeoff Takeoff distance 1.5 1.03 2.58 gamma 5.97|0.52|0.20 +to_acc_tof takeoff Mean takeoff accelaration 1.77 1.31 2.23 norm 1.77|0.28 +ic_va_avg initial_climb Mean airspeed 81 71 91 norm 81.31|6.79 +ic_vs_avg initial_climb Mean vertical rate 11.93 8.05 15.83 norm 11.94|2.37 +cl_d_range climb Climb range 204 155 348 gamma 5.17|136.24|16.48 +cl_v_cas_const climb Constant CAS 151 139 164 norm 151.95|7.82 +cl_v_mach_const climb Constant Mach 0.78 0.72 0.81 beta 33.60|3.10|0.26|0.56 +cl_h_cas_const climb Constant CAS crossover altitude 3.4 2.2 6.1 gamma 6.82|0.72|0.46 +cl_h_mach_const climb Constant Mach crossover altitude 8.9 7.9 9.8 norm 8.85|0.59 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 11.4 8.01 14.82 norm 11.41|2.07 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 11.71 8.49 14.93 norm 11.71|1.96 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.74 4.4 9.08 norm 6.74|1.42 +cr_d_range cruise Cruise range 453 175 4929 gamma 1.38|156.17|773.74 +cr_v_cas_mean cruise Mean cruise CAS 122 115 136 gamma 11.06|103.01|1.94 +cr_v_cas_max cruise Maximum cruise CAS 126 118 150 gamma 3.76|111.86|5.16 +cr_v_mach_mean cruise Mean cruise Mach 0.78 0.74 0.8 beta 14.90|4.49|0.63|0.19 +cr_v_mach_max cruise Maximum cruise Mach 0.8 0.76 0.83 norm 0.80|0.02 +cr_h_init cruise Initial cruise altitude 12.52 10.36 12.47 beta 5.29|0.98|7.50|5.02 +cr_h_mean cruise Mean cruise altitude 11.74 10.73 12.75 norm 11.74|0.61 +cr_h_max cruise Maximum cruise altitude 11.9 11.03 12.78 norm 11.90|0.53 +de_d_range descent Descent range 244 173 566 gamma 2.89|159.35|44.87 +de_v_mach_const descent Constant Mach 0.78 0.7 0.81 beta 22.01|2.63|0.28|0.54 +de_v_cas_const descent Constant CAS 146 133 159 norm 146.60|7.70 +de_h_mach_const descent Constant Mach crossover altitude 9.6 8.1 11.2 norm 9.63|0.95 +de_h_cas_const descent Constant CAS crossover altitude 5.7 3 8.5 norm 5.73|1.69 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.63 -13.13 -2.29 beta 2.76|1.69|-17.10|16.02 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.39 -14.21 -4.55 norm -9.38|2.94 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -5.85 -7.84 -3.86 norm -5.85|1.21 +fa_va_avg final_approach Mean airspeed 70 65 80 gamma 7.33|58.48|1.90 +fa_vs_avg final_approach Mean vertical rate -3.58 -4.45 -2.7 norm -3.58|0.53 +fa_agl final_approach Approach angle 3.08 2.28 3.87 norm 3.08|0.48 +ld_v_app landing Touchdown speed 68.8 62.9 74.6 norm 68.78|4.05 +ld_d_brk landing Braking distance 1.91 0.66 4.39 beta 1.32|1.63|0.35|4.63 +ld_acc_brk landing Mean braking acceleration -0.83 -1.9 -0.31 beta 5.10|2.45|-3.08|3.04 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b738.csv b/data/performance/OpenAP/fixwing/wrap/b738.csv deleted file mode 100644 index 4577a74c28..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b738.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,86.5,75.0,98.1,norm,86.5|8.0 -TO,to_d_tof,1.66,1.12,2.63,gamma,11.91|0.19|0.13 -TO,to_acc_tof,1.82,1.36,2.28,norm,1.82|0.28 -IC,ic_va_avg,87,80,93,norm,87|4 -IC,ic_vz_avg,12.27,8.25,16.30,norm,12.28|2.44 -CL,cl_d_range,218,170,359,gamma,5|152|16 -CL,cl_v_cas_const,150,141,167,gamma,9|129|2 -CL,cl_v_mach_const,0.78,0.74,0.81,norm,0.78|0.02 -CL,cl_h_cas_const,5.2,3.1,7.3,norm,5.2|1.1 -CL,cl_h_mach_const,10.0,8.5,11.5,norm,10.0|0.8 -CL,cl_vz_avg_pre_cas,11.36,8.85,13.88,norm,11.37|1.53 -CL,cl_vz_avg_cas_const,8.68,6.25,11.13,norm,8.69|1.48 -CL,cl_vz_avg_mach_const,5.33,3.26,7.40,norm,5.33|1.26 -CR,cr_d_range,937,489,4767,gamma,1|444|613 -CR,cr_v_cas_mean,128,121,142,gamma,7|113|2 -CR,cr_v_cas_max,133,125,153,gamma,5|116|3 -CR,cr_v_mach_mean,0.78,0.75,0.80,beta,13.35|5.86|0.66|0.16 -CR,cr_v_mach_max,0.80,0.77,0.83,norm,0.80|0.02 -CR,cr_h_mean,11.2,10.2,12.2,norm,11.2|0.6 -CR,cr_h_max,11.4,10.5,12.3,norm,11.4|0.5 -DE,de_d_range,244,177,487,gamma,3|158|31 -DE,de_v_mach_const,0.77,0.70,0.81,beta,8.42|3.02|0.58|0.25 -DE,de_v_cas_const,147,132,162,norm,147|9 -DE,de_h_mach_const,9.8,7.6,11.1,beta,5.1|2.7|5.1|6.6 -DE,de_h_cas_const,5.9,3.2,8.5,norm,5.9|1.6 -DE,de_vz_avg_mach_const,-5.69,-12.60,-1.99,beta,4.67|2.52|-19.44|19.44 -DE,de_vz_avg_cas_const,-9.66,-14.36,-4.93,norm,-9.65|2.87 -DE,de_vz_avg_after_cas,-6.20,-7.93,-4.46,norm,-6.19|1.05 -FA,fa_va_avg,77,71,82,norm,77|3 -FA,fa_vz_avg,-3.84,-4.57,-3.11,norm,-3.84|0.44 -LD,ld_v_app,75.4,69.2,81.6,norm,75.4|4.3 -LD,ld_d_brk,1.33,0.66,3.55,gamma,3.31|0.17|0.50 -LD,ld_acc_brk,-1.36,-2.14,-0.57,norm,-1.36|0.48 diff --git a/data/performance/OpenAP/fixwing/wrap/b738.txt b/data/performance/OpenAP/fixwing/wrap/b738.txt new file mode 100644 index 0000000000..5194fe2aaf --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b738.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 86.5 75 98 norm 86.54|7.99 +to_d_tof takeoff Takeoff distance 1.64 1.13 2.63 gamma 9.42|0.37|0.15 +to_acc_tof takeoff Mean takeoff accelaration 1.82 1.37 2.28 norm 1.82|0.28 +ic_va_avg initial_climb Mean airspeed 87 80 93 norm 87.24|4.53 +ic_vs_avg initial_climb Mean vertical rate 12.27 8.29 16.26 norm 12.28|2.42 +cl_d_range climb Climb range 216 168 350 gamma 5.40|149.68|15.22 +cl_v_cas_const climb Constant CAS 151 140 161 norm 151.22|6.33 +cl_v_mach_const climb Constant Mach 0.77 0.75 0.8 norm 0.77|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 3.6 1.8 5.4 norm 3.64|1.09 +cl_h_mach_const climb Constant Mach crossover altitude 8.9 7.9 9.8 norm 8.88|0.57 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 10.61 7.83 13.41 norm 10.62|1.70 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 10.24 7.7 12.8 norm 10.25|1.55 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.2 4.31 8.11 norm 6.21|1.15 +cr_d_range cruise Cruise range 929 502 4953 gamma 1.71|463.22|654.96 +cr_v_cas_mean cruise Mean cruise CAS 130 121 139 norm 130.27|5.50 +cr_v_cas_max cruise Maximum cruise CAS 132 125 151 gamma 5.83|116.60|3.39 +cr_v_mach_mean cruise Mean cruise Mach 0.78 0.75 0.8 norm 0.78|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.8 0.77 0.83 norm 0.80|0.02 +cr_h_init cruise Initial cruise altitude 11.13 10.16 12.1 norm 11.13|0.59 +cr_h_mean cruise Mean cruise altitude 11.23 10.35 12.12 norm 11.24|0.54 +cr_h_max cruise Maximum cruise altitude 11.38 10.57 12.19 norm 11.38|0.49 +de_d_range descent Descent range 241 179 472 gamma 3.59|163.64|30.00 +de_v_mach_const descent Constant Mach 0.77 0.73 0.81 norm 0.77|0.03 +de_v_cas_const descent Constant CAS 145 132 159 norm 145.77|8.27 +de_h_mach_const descent Constant Mach crossover altitude 9.7 8.3 11.1 norm 9.68|0.86 +de_h_cas_const descent Constant CAS crossover altitude 5.9 3.2 8.7 norm 5.95|1.65 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.8 -12.73 -2.11 beta 3.43|2.10|-17.58|17.11 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.95 -14.39 -5.48 norm -9.94|2.71 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.23 -7.91 -4.53 norm -6.22|1.03 +fa_va_avg final_approach Mean airspeed 77 72 82 norm 77.35|3.71 +fa_vs_avg final_approach Mean vertical rate -3.84 -4.56 -3.12 norm -3.84|0.44 +fa_agl final_approach Approach angle 3.02 2.33 3.72 norm 3.03|0.42 +ld_v_app landing Touchdown speed 75.4 69.5 81.3 norm 75.37|4.11 +ld_d_brk landing Braking distance 1.25 0.68 3.6 gamma 2.60|0.33|0.57 +ld_acc_brk landing Mean braking acceleration -1.36 -2.14 -0.57 norm -1.36|0.48 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b739.csv b/data/performance/OpenAP/fixwing/wrap/b739.csv deleted file mode 100644 index 0df74ec749..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b739.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,91.3,77.5,105.1,norm,91.3|9.6 -TO,to_d_tof,1.83,1.18,2.96,gamma,12.03|0.09|0.16 -TO,to_acc_tof,1.86,1.41,2.33,norm,1.87|0.28 -IC,ic_va_avg,91,84,99,norm,91|5 -IC,ic_vz_avg,11.64,7.87,17.70,gamma,16.92|-0.00|0.73 -CL,cl_d_range,243,137,350,norm,243|41 -CL,cl_v_cas_const,159,145,171,beta,4|3|132|46 -CL,cl_v_mach_const,0.79,0.75,0.81,beta,16.78|4.27|0.58|0.25 -CL,cl_h_cas_const,5.5,3.8,7.3,norm,5.5|0.9 -CL,cl_h_mach_const,9.4,8.1,10.8,norm,9.4|0.7 -CL,cl_vz_avg_pre_cas,10.50,8.30,12.71,norm,10.51|1.34 -CL,cl_vz_avg_cas_const,7.38,5.13,9.65,norm,7.39|1.38 -CL,cl_vz_avg_mach_const,4.95,2.82,7.08,norm,4.95|1.29 -CR,cr_d_range,866,189,4416,beta,1|2|159|4736 -CR,cr_v_cas_mean,138,128,149,norm,138|6 -CR,cr_v_cas_max,145,131,159,norm,145|8 -CR,cr_v_mach_mean,0.79,0.75,0.81,beta,13.95|4.86|0.66|0.17 -CR,cr_v_mach_max,0.80,0.77,0.84,gamma,19.02|0.71|0.00 -CR,cr_h_mean,10.5,9.6,11.4,norm,10.5|0.6 -CR,cr_h_max,10.8,9.7,11.6,beta,7.2|5.1|8.1|4.4 -DE,de_d_range,252,174,508,gamma,4|150|31 -DE,de_v_mach_const,0.78,0.71,0.81,beta,9.50|2.96|0.57|0.25 -DE,de_v_cas_const,148,136,160,norm,148|7 -DE,de_h_mach_const,9.2,7.8,10.6,norm,9.2|0.9 -DE,de_h_cas_const,6.4,4.2,8.7,norm,6.4|1.4 -DE,de_vz_avg_mach_const,-5.08,-11.06,-1.64,beta,3.90|2.38|-15.90|15.99 -DE,de_vz_avg_cas_const,-8.69,-13.41,-3.95,norm,-8.68|2.88 -DE,de_vz_avg_after_cas,-6.01,-7.69,-4.32,norm,-6.00|1.03 -FA,fa_va_avg,78,72,83,norm,78|3 -FA,fa_vz_avg,-3.92,-4.73,-3.11,norm,-3.92|0.49 -LD,ld_v_app,76.8,70.4,83.3,norm,76.9|4.5 -LD,ld_d_brk,1.45,0.70,4.23,gamma,2.97|0.18|0.65 -LD,ld_acc_brk,-1.25,-2.19,-0.53,beta,4.23|3.14|-3.02|2.95 diff --git a/data/performance/OpenAP/fixwing/wrap/b739.txt b/data/performance/OpenAP/fixwing/wrap/b739.txt new file mode 100644 index 0000000000..3595a91a21 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b739.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 91.3 77.6 105 norm 91.32|9.53 +to_d_tof takeoff Takeoff distance 1.91 1.14 2.9 beta 4.56|6.31|0.42|3.74 +to_acc_tof takeoff Mean takeoff accelaration 1.87 1.41 2.32 norm 1.87|0.28 +ic_va_avg initial_climb Mean airspeed 91 84 99 norm 91.92|5.26 +ic_vs_avg initial_climb Mean vertical rate 12.36 7.85 16.9 norm 12.37|2.75 +cl_d_range climb Climb range 239 142 337 norm 239.93|37.90 +cl_v_cas_const climb Constant CAS 154 143 166 norm 154.87|6.96 +cl_v_mach_const climb Constant Mach 0.78 0.75 0.81 beta 10.40|4.40|0.66|0.16 +cl_h_cas_const climb Constant CAS crossover altitude 3.4 2.3 5.9 gamma 6.04|1.09|0.46 +cl_h_mach_const climb Constant Mach crossover altitude 8.6 7.7 9.5 norm 8.60|0.56 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 10.18 7.64 12.72 norm 10.18|1.55 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.88 6.66 11.12 norm 8.89|1.36 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 5.55 3.67 7.43 norm 5.55|1.14 +cr_d_range cruise Cruise range 338 193 4458 beta 1.05|2.54|180.63|4861.59 +cr_v_cas_mean cruise Mean cruise CAS 138 127 149 norm 138.40|6.60 +cr_v_cas_max cruise Maximum cruise CAS 145 131 159 norm 145.21|8.55 +cr_v_mach_mean cruise Mean cruise Mach 0.78 0.76 0.81 norm 0.78|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.81 0.78 0.84 norm 0.81|0.02 +cr_h_init cruise Initial cruise altitude 10.43 9.43 11.43 norm 10.43|0.61 +cr_h_mean cruise Mean cruise altitude 10.52 9.63 11.41 norm 10.52|0.54 +cr_h_max cruise Maximum cruise altitude 10.74 9.73 11.61 beta 6.51|5.15|8.41|4.08 +de_d_range descent Descent range 245 180 505 gamma 3.29|165.75|34.68 +de_v_mach_const descent Constant Mach 0.78 0.73 0.8 beta 5.13|2.83|0.68|0.14 +de_v_cas_const descent Constant CAS 148 138 159 norm 148.73|6.45 +de_h_mach_const descent Constant Mach crossover altitude 9.4 8.2 10.6 norm 9.36|0.73 +de_h_cas_const descent Constant CAS crossover altitude 6.5 4.2 8.8 norm 6.46|1.39 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -4.78 -11.29 -1.79 beta 3.42|1.92|-15.82|15.27 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -8.96 -13.87 -4.17 beta 4.39|4.26|-18.26|18.26 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.02 -7.69 -4.35 norm -6.02|1.02 +fa_va_avg final_approach Mean airspeed 78 72 83 norm 78.30|3.70 +fa_vs_avg final_approach Mean vertical rate -3.92 -4.72 -3.13 norm -3.92|0.48 +fa_agl final_approach Approach angle 3.03 2.43 3.64 norm 3.03|0.37 +ld_v_app landing Touchdown speed 76.8 70.5 83.2 norm 76.86|4.40 +ld_d_brk landing Braking distance 1.45 0.7 4.22 gamma 2.95|0.19|0.65 +ld_acc_brk landing Mean braking acceleration -1.33 -2.12 -0.54 norm -1.33|0.48 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b744.csv b/data/performance/OpenAP/fixwing/wrap/b744.csv deleted file mode 100644 index 459caead57..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b744.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,92.4,79.3,105.5,norm,92.4|9.1 -TO,to_d_tof,2.29,1.27,3.31,norm,2.29|0.62 -TO,to_acc_tof,1.67,1.21,2.13,norm,1.67|0.28 -IC,ic_va_avg,91,82,101,norm,91|6 -IC,ic_vz_avg,9.32,6.32,14.11,gamma,17.23|-0.00|0.57 -CL,cl_d_range,231,179,392,gamma,4|160|18 -CL,cl_v_cas_const,171,159,182,norm,171|6 -CL,cl_v_mach_const,0.84,0.81,0.88,norm,0.84|0.02 -CL,cl_h_cas_const,5.6,3.9,7.4,norm,5.6|0.9 -CL,cl_h_mach_const,9.7,8.0,11.4,norm,9.7|0.9 -CL,cl_vz_avg_pre_cas,9.07,7.31,13.09,gamma,6.25|5.31|0.72 -CL,cl_vz_avg_cas_const,8.18,5.07,11.30,norm,8.18|1.90 -CL,cl_vz_avg_mach_const,5.39,2.71,8.10,norm,5.40|1.64 -CR,cr_d_range,5230,-1657,12137,norm,5239|2677 -CR,cr_v_cas_mean,147,135,158,norm,147|7 -CR,cr_v_cas_max,157,140,175,norm,157|10 -CR,cr_v_mach_mean,0.84,0.82,0.87,norm,0.84|0.01 -CR,cr_v_mach_max,0.87,0.84,0.93,gamma,22.25|0.75|0.01 -CR,cr_h_mean,10.8,9.8,11.9,norm,10.8|0.6 -CR,cr_h_max,11.3,10.1,12.3,beta,8.6|7.1|8.2|5.5 -DE,de_d_range,267,193,523,gamma,3|172|32 -DE,de_v_mach_const,0.81,0.75,0.88,norm,0.81|0.04 -DE,de_v_cas_const,153,139,166,norm,153|8 -DE,de_h_mach_const,9.7,7.9,11.4,norm,9.7|1.1 -DE,de_h_cas_const,6.5,4.0,9.1,norm,6.5|1.5 -DE,de_vz_avg_mach_const,-5.45,-11.96,-2.19,beta,4.64|2.38|-18.39|17.87 -DE,de_vz_avg_cas_const,-8.72,-13.61,-3.81,norm,-8.71|2.98 -DE,de_vz_avg_after_cas,-6.25,-8.03,-4.47,norm,-6.25|1.08 -FA,fa_va_avg,79,72,85,norm,79|4 -FA,fa_vz_avg,-3.89,-4.70,-3.09,norm,-3.89|0.49 -LD,ld_v_app,77.9,71.0,85.0,norm,78.0|4.9 -LD,ld_d_brk,1.67,0.92,4.04,gamma,3.56|0.34|0.52 -LD,ld_acc_brk,-1.20,-1.97,-0.59,beta,6.26|4.55|-2.95|2.92 diff --git a/data/performance/OpenAP/fixwing/wrap/b744.txt b/data/performance/OpenAP/fixwing/wrap/b744.txt new file mode 100644 index 0000000000..434eea931e --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b744.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 92.4 79.3 105.4 norm 92.39|9.06 +to_d_tof takeoff Takeoff distance 2.29 1.27 3.31 norm 2.29|0.62 +to_acc_tof takeoff Mean takeoff accelaration 1.67 1.21 2.13 norm 1.67|0.28 +ic_va_avg initial_climb Mean airspeed 91 82 101 beta 4.95|5.64|70.82|44.93 +ic_vs_avg initial_climb Mean vertical rate 9.24 6.76 13.74 gamma 11.16|2.72|0.64 +cl_d_range climb Climb range 223 175 374 gamma 4.44|160.55|18.26 +cl_v_cas_const climb Constant CAS 168 157 180 norm 168.81|7.06 +cl_v_mach_const climb Constant Mach 0.84 0.81 0.87 norm 0.84|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 3.9 2.2 5.6 norm 3.89|1.05 +cl_h_mach_const climb Constant Mach crossover altitude 8.3 7.6 9.7 gamma 10.78|6.39|0.20 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 8.66 6 11.33 norm 8.67|1.62 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.77 6.65 13.11 gamma 7.91|3.87|0.71 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.72 4.07 9.39 norm 6.73|1.61 +cr_d_range cruise Cruise range 5580 -1544 12725 norm 5590.66|2769.86 +cr_v_cas_mean cruise Mean cruise CAS 146 134 158 norm 146.64|7.16 +cr_v_cas_max cruise Maximum cruise CAS 157 139 174 norm 157.08|10.62 +cr_v_mach_mean cruise Mean cruise Mach 0.84 0.82 0.87 norm 0.84|0.01 +cr_v_mach_max cruise Maximum cruise Mach 0.87 0.84 0.92 gamma 16.48|0.77|0.01 +cr_h_init cruise Initial cruise altitude 10.28 8.87 11.7 norm 10.28|0.86 +cr_h_mean cruise Mean cruise altitude 10.81 9.77 11.84 norm 10.81|0.63 +cr_h_max cruise Maximum cruise altitude 11.29 10.17 12.4 beta 6.02|5.99|8.84|4.88 +de_d_range descent Descent range 262 195 510 gamma 3.60|178.39|32.24 +de_v_mach_const descent Constant Mach 0.83 0.77 0.87 beta 5.70|2.75|0.68|0.21 +de_v_cas_const descent Constant CAS 152 139 164 norm 152.44|7.62 +de_h_mach_const descent Constant Mach crossover altitude 10 8.7 11.3 norm 10.00|0.79 +de_h_cas_const descent Constant CAS crossover altitude 6.6 4 9.2 norm 6.61|1.57 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.43 -12.37 -2.2 beta 3.86|2.07|-17.94|17.19 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.17 -13.85 -4.48 norm -9.16|2.85 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.29 -8.02 -4.55 norm -6.29|1.05 +fa_va_avg final_approach Mean airspeed 79 72 85 norm 79.03|4.54 +fa_vs_avg final_approach Mean vertical rate -4.03 -4.6 -3.03 gamma 13.46|-5.66|0.13 +fa_agl final_approach Approach angle 2.98 2.33 3.64 norm 2.98|0.40 +ld_v_app landing Touchdown speed 77.9 71 84.9 norm 77.96|4.85 +ld_d_brk landing Braking distance 1.64 0.93 4.06 gamma 3.26|0.41|0.55 +ld_acc_brk landing Mean braking acceleration -1.18 -1.98 -0.6 beta 5.71|3.74|-2.92|2.76 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b752.csv b/data/performance/OpenAP/fixwing/wrap/b752.csv deleted file mode 100644 index cda52b6b0b..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b752.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,90.5,76.7,104.4,norm,90.5|9.6 -TO,to_d_tof,1.66,0.99,2.70,beta,4.52|8.88|0.37|4.19 -TO,to_acc_tof,1.91,1.45,2.37,norm,1.91|0.28 -IC,ic_va_avg,88,78,98,norm,88|7 -IC,ic_vz_avg,12.93,8.25,17.63,norm,12.94|2.85 -CL,cl_d_range,228,169,389,gamma,5|146|18 -CL,cl_v_cas_const,158,145,171,norm,158|7 -CL,cl_v_mach_const,0.79,0.75,0.84,norm,0.79|0.03 -CL,cl_h_cas_const,5.3,3.6,7.1,norm,5.3|0.9 -CL,cl_h_mach_const,9.9,7.8,11.1,beta,16.6|5.2|2.5|9.4 -CL,cl_vz_avg_pre_cas,10.80,8.34,13.27,norm,10.80|1.50 -CL,cl_vz_avg_cas_const,8.14,5.50,10.79,norm,8.15|1.61 -CL,cl_vz_avg_mach_const,5.63,2.96,8.31,norm,5.63|1.63 -CR,cr_d_range,1023,497,6775,beta,1|2|470|7451 -CR,cr_v_cas_mean,133,123,144,norm,133|6 -CR,cr_v_cas_max,139,127,159,gamma,19|98|2 -CR,cr_v_mach_mean,0.79,0.77,0.82,norm,0.79|0.02 -CR,cr_v_mach_max,0.82,0.78,0.86,norm,0.82|0.02 -CR,cr_h_mean,11.2,10.1,12.1,beta,9.5|6.1|8.1|5.0 -CR,cr_h_max,11.5,10.4,12.3,beta,10.8|5.6|8.1|5.0 -DE,de_d_range,255,173,508,gamma,4|144|30 -DE,de_v_mach_const,0.78,0.73,0.83,norm,0.78|0.03 -DE,de_v_cas_const,151,137,165,norm,151|8 -DE,de_h_mach_const,9.2,7.6,10.9,norm,9.2|1.0 -DE,de_h_cas_const,6.3,3.8,8.7,norm,6.3|1.5 -DE,de_vz_avg_mach_const,-6.23,-12.48,-2.54,beta,4.51|2.66|-18.41|17.94 -DE,de_vz_avg_cas_const,-9.23,-14.31,-4.12,norm,-9.21|3.10 -DE,de_vz_avg_after_cas,-6.13,-7.96,-4.29,norm,-6.12|1.11 -FA,fa_va_avg,69,62,76,norm,69|4 -FA,fa_vz_avg,-3.41,-4.21,-2.61,norm,-3.41|0.49 -LD,ld_v_app,66.7,60.2,73.3,norm,66.7|4.5 -LD,ld_d_brk,1.22,0.68,3.42,gamma,2.67|0.33|0.53 -LD,ld_acc_brk,-0.99,-1.88,-0.47,beta,7.89|3.62|-3.37|3.28 diff --git a/data/performance/OpenAP/fixwing/wrap/b752.txt b/data/performance/OpenAP/fixwing/wrap/b752.txt new file mode 100644 index 0000000000..897be851c6 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b752.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 90.5 76.8 104.3 norm 90.55|9.57 +to_d_tof takeoff Takeoff distance 1.7 0.98 2.66 beta 3.89|5.55|0.41|3.34 +to_acc_tof takeoff Mean takeoff accelaration 1.91 1.46 2.37 norm 1.91|0.28 +ic_va_avg initial_climb Mean airspeed 88 78 98 norm 88.51|7.17 +ic_vs_avg initial_climb Mean vertical rate 12.99 8.6 17.4 norm 13.00|2.68 +cl_d_range climb Climb range 220 167 384 gamma 4.69|149.01|19.46 +cl_v_cas_const climb Constant CAS 155 144 166 norm 155.59|6.70 +cl_v_mach_const climb Constant Mach 0.79 0.75 0.82 norm 0.79|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 4 2.3 5.7 norm 4.00|1.02 +cl_h_mach_const climb Constant Mach crossover altitude 8.7 8 9.5 norm 8.75|0.48 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 10.29 7.8 12.8 norm 10.30|1.52 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.95 6.41 11.51 norm 8.96|1.55 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.23 3.8 8.67 norm 6.24|1.48 +cr_d_range cruise Cruise range 497 512 6087 beta 0.99|1.86|497.24|5936.62 +cr_v_cas_mean cruise Mean cruise CAS 131 123 145 gamma 11.69|110.95|1.94 +cr_v_cas_max cruise Maximum cruise CAS 138 128 159 gamma 6.92|116.87|3.65 +cr_v_mach_mean cruise Mean cruise Mach 0.79 0.77 0.82 norm 0.79|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.81 0.79 0.86 gamma 9.66|0.75|0.01 +cr_h_init cruise Initial cruise altitude 11 9.96 12.05 norm 11.00|0.63 +cr_h_mean cruise Mean cruise altitude 11.17 10.28 12.07 norm 11.17|0.54 +cr_h_max cruise Maximum cruise altitude 11.64 10.39 12.22 beta 5.67|2.52|8.87|3.67 +de_d_range descent Descent range 247 178 504 gamma 3.53|162.02|33.57 +de_v_mach_const descent Constant Mach 0.79 0.75 0.82 norm 0.79|0.02 +de_v_cas_const descent Constant CAS 151 137 165 norm 151.53|8.29 +de_h_mach_const descent Constant Mach crossover altitude 9.4 8 10.8 norm 9.40|0.84 +de_h_cas_const descent Constant CAS crossover altitude 6.3 3.9 8.8 norm 6.35|1.50 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -6.8 -12.65 -2.52 beta 2.78|2.22|-15.86|15.25 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.56 -14.46 -4.64 norm -9.55|2.99 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.16 -7.9 -4.41 norm -6.15|1.06 +fa_va_avg final_approach Mean airspeed 69 62 76 norm 69.77|4.86 +fa_vs_avg final_approach Mean vertical rate -3.41 -4.2 -2.62 norm -3.41|0.48 +fa_agl final_approach Approach angle 3.09 2.2 3.98 norm 3.09|0.54 +ld_v_app landing Touchdown speed 66.7 60.2 73.2 norm 66.71|4.50 +ld_d_brk landing Braking distance 1.21 0.68 3.43 gamma 2.56|0.35|0.55 +ld_acc_brk landing Mean braking acceleration -1.03 -1.85 -0.46 beta 4.58|3.08|-2.63|2.53 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b763.csv b/data/performance/OpenAP/fixwing/wrap/b763.csv deleted file mode 100644 index f7bc84d14f..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b763.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,92.4,79.5,105.3,norm,92.4|9.0 -TO,to_d_tof,1.82,1.03,2.62,norm,1.82|0.48 -TO,to_acc_tof,1.97,1.46,2.49,norm,1.97|0.31 -IC,ic_va_avg,88,80,96,norm,88|5 -IC,ic_vz_avg,14.28,9.41,19.17,norm,14.29|2.97 -CL,cl_d_range,211,165,354,gamma,4|149|17 -CL,cl_v_cas_const,163,149,174,beta,7|4|126|57 -CL,cl_v_mach_const,0.80,0.75,0.84,norm,0.80|0.03 -CL,cl_h_cas_const,5.7,4.1,7.3,norm,5.7|0.8 -CL,cl_h_mach_const,9.6,7.8,11.4,norm,9.6|0.9 -CL,cl_vz_avg_pre_cas,11.02,8.27,13.78,norm,11.02|1.68 -CL,cl_vz_avg_cas_const,8.78,5.15,12.42,norm,8.79|2.21 -CL,cl_vz_avg_mach_const,5.48,2.25,8.72,norm,5.49|1.97 -CR,cr_d_range,1325,540,9818,beta,1|4|496|13389 -CR,cr_v_cas_mean,138,127,149,norm,138|6 -CR,cr_v_cas_max,143,132,166,gamma,6|120|4 -CR,cr_v_mach_mean,0.80,0.77,0.83,norm,0.80|0.02 -CR,cr_v_mach_max,0.82,0.79,0.88,gamma,10.79|0.75|0.01 -CR,cr_h_mean,10.9,9.9,11.8,norm,10.9|0.6 -CR,cr_h_max,11.2,10.1,12.1,beta,7.9|6.1|8.4|4.8 -DE,de_d_range,248,177,497,gamma,3|158|31 -DE,de_v_mach_const,0.79,0.73,0.84,norm,0.79|0.03 -DE,de_v_cas_const,152,137,167,norm,152|9 -DE,de_h_mach_const,9.4,7.7,11.0,norm,9.4|1.0 -DE,de_h_cas_const,6.6,4.2,8.9,norm,6.6|1.4 -DE,de_vz_avg_mach_const,-6.52,-12.96,-2.15,beta,3.30|2.40|-17.28|17.28 -DE,de_vz_avg_cas_const,-9.66,-14.85,-4.44,norm,-9.65|3.17 -DE,de_vz_avg_after_cas,-6.22,-7.96,-4.46,norm,-6.21|1.06 -FA,fa_va_avg,74,68,80,norm,74|4 -FA,fa_vz_avg,-3.73,-4.44,-2.70,gamma,27.03|-6.39|0.10 -LD,ld_v_app,72.1,65.3,79.0,norm,72.1|4.8 -LD,ld_d_brk,1.58,0.64,3.71,beta,1.78|3.06|0.28|4.72 -LD,ld_acc_brk,-1.09,-1.95,-0.52,beta,5.75|3.52|-2.97|2.87 diff --git a/data/performance/OpenAP/fixwing/wrap/b763.txt b/data/performance/OpenAP/fixwing/wrap/b763.txt new file mode 100644 index 0000000000..4623667f40 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b763.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 92.4 79.6 105.3 norm 92.43|8.92 +to_d_tof takeoff Takeoff distance 1.82 1.03 2.61 norm 1.82|0.48 +to_acc_tof takeoff Mean takeoff accelaration 1.97 1.46 2.49 norm 1.97|0.31 +ic_va_avg initial_climb Mean airspeed 88 80 96 beta 4.65|5.42|71.86|36.69 +ic_vs_avg initial_climb Mean vertical rate 14.28 9.45 19.12 norm 14.29|2.94 +cl_d_range climb Climb range 202 160 338 gamma 4.37|146.83|16.52 +cl_v_cas_const climb Constant CAS 159 146 171 beta 5.42|4.92|131.86|52.14 +cl_v_mach_const climb Constant Mach 0.79 0.76 0.83 norm 0.79|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 4 2.6 5.5 norm 4.05|0.90 +cl_h_mach_const climb Constant Mach crossover altitude 8.3 7.6 9.8 gamma 8.58|6.61|0.23 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 10.16 7.27 13.07 norm 10.17|1.76 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 10.26 7.06 13.48 norm 10.27|1.95 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.95 3.76 10.16 norm 6.96|1.95 +cr_d_range cruise Cruise range 1201 562 9544 beta 1.06|1.84|526.21|9525.03 +cr_v_cas_mean cruise Mean cruise CAS 137 127 148 norm 137.87|6.52 +cr_v_cas_max cruise Maximum cruise CAS 142 132 165 gamma 5.96|121.86|4.14 +cr_v_mach_mean cruise Mean cruise Mach 0.8 0.77 0.83 norm 0.80|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.82 0.79 0.87 gamma 9.85|0.75|0.01 +cr_h_init cruise Initial cruise altitude 10.65 9.11 11.75 beta 4.71|3.26|7.61|4.88 +cr_h_mean cruise Mean cruise altitude 10.81 9.9 11.73 norm 10.82|0.55 +cr_h_max cruise Maximum cruise altitude 11.17 10.3 12.04 norm 11.17|0.53 +de_d_range descent Descent range 244 178 489 gamma 3.61|161.96|31.72 +de_v_mach_const descent Constant Mach 0.79 0.76 0.83 norm 0.79|0.02 +de_v_cas_const descent Constant CAS 151 139 164 norm 151.80|7.70 +de_h_mach_const descent Constant Mach crossover altitude 9.5 8.3 10.8 norm 9.55|0.77 +de_h_cas_const descent Constant CAS crossover altitude 6.7 4.4 9 norm 6.70|1.42 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -6.29 -13.2 -2.33 beta 2.75|1.91|-16.88|16.10 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.98 -14.99 -4.94 norm -9.97|3.06 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.29 -7.99 -4.58 norm -6.29|1.04 +fa_va_avg final_approach Mean airspeed 74 68 80 norm 74.38|4.27 +fa_vs_avg final_approach Mean vertical rate -3.76 -4.37 -2.75 gamma 15.83|-5.60|0.12 +fa_agl final_approach Approach angle 2.98 2.22 3.74 norm 2.98|0.46 +ld_v_app landing Touchdown speed 72.1 65.3 78.9 norm 72.10|4.70 +ld_d_brk landing Braking distance 1.51 0.66 3.71 beta 1.65|2.94|0.35|4.63 +ld_acc_brk landing Mean braking acceleration -1.1 -1.94 -0.52 beta 4.70|3.14|-2.76|2.62 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b77w.csv b/data/performance/OpenAP/fixwing/wrap/b77w.csv deleted file mode 100644 index eeae8262d1..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b77w.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,96.4,83.9,109.0,norm,96.5|8.7 -TO,to_d_tof,2.21,1.38,3.04,norm,2.21|0.50 -TO,to_acc_tof,1.89,1.45,2.33,norm,1.89|0.27 -IC,ic_va_avg,98,88,106,beta,3|3|80|33 -IC,ic_vz_avg,13.02,9.69,16.36,norm,13.03|2.03 -CL,cl_d_range,261,154,369,norm,262|41 -CL,cl_v_cas_const,166,158,173,norm,166|4 -CL,cl_v_mach_const,0.83,0.78,0.88,norm,0.83|0.03 -CL,cl_h_cas_const,5.7,3.4,8.0,norm,5.7|1.2 -CL,cl_h_mach_const,9.5,7.5,11.5,norm,9.5|1.0 -CL,cl_vz_avg_pre_cas,9.86,8.18,14.19,gamma,4.91|6.56|0.84 -CL,cl_vz_avg_cas_const,7.22,3.11,11.35,norm,7.23|2.51 -CL,cl_vz_avg_mach_const,4.48,1.69,10.26,beta,1.86|3.03|0.59|13.14 -CR,cr_d_range,2831,675,14048,beta,1|1|594|14118 -CR,cr_v_cas_mean,149,140,158,norm,149|5 -CR,cr_v_cas_max,160,144,176,norm,160|9 -CR,cr_v_mach_mean,0.84,0.82,0.86,norm,0.84|0.01 -CR,cr_v_mach_max,0.86,0.84,0.91,gamma,4.92|0.82|0.01 -CR,cr_h_mean,10.4,9.8,11.5,gamma,13.1|8.7|0.1 -CR,cr_h_max,11.1,10.2,11.7,beta,5.5|3.7|9.2|3.0 -DE,de_d_range,260,197,488,gamma,3|181|29 -DE,de_v_mach_const,0.82,0.76,0.87,norm,0.82|0.03 -DE,de_v_cas_const,156,142,171,norm,156|8 -DE,de_h_mach_const,9.5,8.0,11.0,norm,9.5|0.9 -DE,de_h_cas_const,6.6,4.1,9.1,norm,6.6|1.5 -DE,de_vz_avg_mach_const,-6.94,-11.78,-2.08,norm,-6.93|2.95 -DE,de_vz_avg_cas_const,-8.68,-13.22,-4.12,norm,-8.67|2.77 -DE,de_vz_avg_after_cas,-6.27,-7.92,-4.62,norm,-6.27|1.00 -FA,fa_va_avg,78,73,83,norm,78|3 -FA,fa_vz_avg,-4.03,-4.49,-3.27,gamma,15.93|-5.43|0.09 -LD,ld_v_app,76.9,70.8,83.1,norm,76.9|4.3 -LD,ld_d_brk,1.49,0.84,3.52,gamma,3.57|0.34|0.45 -LD,ld_acc_brk,-1.33,-2.00,-0.66,norm,-1.33|0.41 diff --git a/data/performance/OpenAP/fixwing/wrap/b77w.txt b/data/performance/OpenAP/fixwing/wrap/b77w.txt new file mode 100644 index 0000000000..86855e285b --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b77w.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 96.4 83.9 109 norm 96.46|8.74 +to_d_tof takeoff Takeoff distance 2.21 1.38 3.04 norm 2.21|0.50 +to_acc_tof takeoff Mean takeoff accelaration 1.89 1.45 2.33 norm 1.89|0.27 +ic_va_avg initial_climb Mean airspeed 98 88 106 beta 3.65|3.27|80.15|33.27 +ic_vs_avg initial_climb Mean vertical rate 13.02 9.69 16.36 norm 13.03|2.03 +cl_d_range climb Climb range 214 173 354 gamma 4.08|161.44|17.33 +cl_v_cas_const climb Constant CAS 164 158 170 norm 164.33|3.45 +cl_v_mach_const climb Constant Mach 0.83 0.8 0.86 norm 0.83|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 3.8 1.8 5.9 norm 3.84|1.25 +cl_h_mach_const climb Constant Mach crossover altitude 8.8 8.1 9.5 norm 8.80|0.42 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 8.99 6.8 11.19 norm 8.99|1.33 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.55 6.41 12.95 gamma 7.87|3.61|0.72 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 5.76 3.31 10.81 gamma 7.72|0.15|0.83 +cr_d_range cruise Cruise range 4843 758 14177 beta 1.32|1.74|606.13|14106.67 +cr_v_cas_mean cruise Mean cruise CAS 148 139 158 norm 148.69|5.75 +cr_v_cas_max cruise Maximum cruise CAS 159 142 175 norm 159.16|10.07 +cr_v_mach_mean cruise Mean cruise Mach 0.84 0.82 0.86 norm 0.84|0.01 +cr_v_mach_max cruise Maximum cruise Mach 0.86 0.84 0.91 gamma 5.16|0.82|0.01 +cr_h_init cruise Initial cruise altitude 9.63 8.74 11.52 gamma 7.37|7.62|0.32 +cr_h_mean cruise Mean cruise altitude 10.35 9.75 11.4 gamma 12.87|8.67|0.14 +cr_h_max cruise Maximum cruise altitude 11 10.31 11.69 norm 11.00|0.42 +de_d_range descent Descent range 257 197 476 gamma 3.62|182.39|28.42 +de_v_mach_const descent Constant Mach 0.82 0.79 0.86 norm 0.82|0.02 +de_v_cas_const descent Constant CAS 156 140 167 beta 4.64|3.00|124.43|50.43 +de_h_mach_const descent Constant Mach crossover altitude 9.7 8.6 10.8 norm 9.72|0.67 +de_h_cas_const descent Constant CAS crossover altitude 6.7 4.2 9.2 norm 6.68|1.51 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -7.04 -12.03 -2.02 norm -7.02|3.04 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.05 -13.36 -4.72 norm -9.04|2.63 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.3 -7.92 -4.67 norm -6.30|0.99 +fa_va_avg final_approach Mean airspeed 78 73 83 norm 78.67|3.38 +fa_vs_avg final_approach Mean vertical rate -4.03 -4.49 -3.27 gamma 15.93|-5.43|0.09 +fa_agl final_approach Approach angle 2.95 2.44 3.46 norm 2.95|0.31 +ld_v_app landing Touchdown speed 76.9 70.8 83.1 norm 76.94|4.25 +ld_d_brk landing Braking distance 1.49 0.84 3.52 gamma 3.57|0.34|0.45 +ld_acc_brk landing Mean braking acceleration -1.33 -2 -0.66 norm -1.33|0.41 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b788.csv b/data/performance/OpenAP/fixwing/wrap/b788.csv deleted file mode 100644 index e26ccb73ee..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b788.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,90.3,74.3,106.4,norm,90.3|11.1 -TO,to_d_tof,2.16,1.07,3.26,norm,2.16|0.67 -TO,to_acc_tof,1.61,1.15,2.07,norm,1.61|0.28 -IC,ic_va_avg,87,78,97,beta,3|4|69|39 -IC,ic_vz_avg,10.63,6.22,14.50,beta,7.70|6.24|-0.33|19.51 -CL,cl_d_range,297,150,446,norm,298|57 -CL,cl_v_cas_const,162,149,176,norm,162|8 -CL,cl_v_mach_const,0.85,0.80,0.89,norm,0.85|0.03 -CL,cl_h_cas_const,5.6,3.7,7.6,norm,5.6|1.0 -CL,cl_h_mach_const,11.0,8.5,12.2,beta,11.3|3.7|3.9|9.0 -CL,cl_vz_avg_pre_cas,9.95,7.48,12.42,norm,9.95|1.50 -CL,cl_vz_avg_cas_const,7.70,4.82,10.59,norm,7.70|1.75 -CL,cl_vz_avg_mach_const,4.86,2.46,7.27,norm,4.86|1.46 -CR,cr_d_range,918,231,11432,beta,1|1|189|12012 -CR,cr_v_cas_mean,132,125,146,gamma,7|117|2 -CR,cr_v_cas_max,140,129,166,gamma,5|118|4 -CR,cr_v_mach_mean,0.85,0.83,0.87,norm,0.85|0.01 -CR,cr_v_mach_max,0.87,0.85,0.91,gamma,20.73|0.79|0.00 -CR,cr_h_mean,12.0,11.1,12.9,norm,12.0|0.5 -CR,cr_h_max,12.3,11.5,13.1,norm,12.3|0.5 -DE,de_d_range,296,218,564,gamma,3|196|33 -DE,de_v_mach_const,0.83,0.79,0.88,norm,0.83|0.03 -DE,de_v_cas_const,155,140,170,norm,155|8 -DE,de_h_mach_const,10.2,8.5,11.8,norm,10.2|1.0 -DE,de_h_cas_const,7.2,4.1,9.6,beta,3.5|2.8|1.8|9.1 -DE,de_vz_avg_mach_const,-7.42,-13.94,-3.20,beta,3.86|2.59|-19.14|18.22 -DE,de_vz_avg_cas_const,-9.31,-13.84,-4.75,norm,-9.30|2.76 -DE,de_vz_avg_after_cas,-6.15,-7.94,-4.36,norm,-6.15|1.09 -FA,fa_va_avg,75,70,80,norm,75|3 -FA,fa_vz_avg,-3.91,-4.62,-3.19,norm,-3.90|0.44 -LD,ld_v_app,71.3,61.5,81.1,norm,71.3|6.8 -LD,ld_d_brk,2.08,0.45,3.71,norm,2.08|0.99 -LD,ld_acc_brk,-1.12,-1.79,-0.45,norm,-1.12|0.41 diff --git a/data/performance/OpenAP/fixwing/wrap/b788.txt b/data/performance/OpenAP/fixwing/wrap/b788.txt new file mode 100644 index 0000000000..e4bfe60f98 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b788.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 90.3 74.4 106.3 norm 90.35|11.10 +to_d_tof takeoff Takeoff distance 2.16 1.08 3.25 norm 2.16|0.66 +to_acc_tof takeoff Mean takeoff accelaration 1.61 1.16 2.07 norm 1.61|0.28 +ic_va_avg initial_climb Mean airspeed 87 78 97 beta 3.16|3.65|71.08|36.50 +ic_vs_avg initial_climb Mean vertical rate 10.65 6.27 14.45 beta 6.24|5.05|0.78|17.49 +cl_d_range climb Climb range 286 146 427 norm 287.05|54.42 +cl_v_cas_const climb Constant CAS 158 146 170 norm 158.68|7.34 +cl_v_mach_const climb Constant Mach 0.85 0.81 0.87 beta 14.41|4.76|0.69|0.21 +cl_h_cas_const climb Constant CAS crossover altitude 3.8 2.5 6.3 gamma 7.78|0.85|0.43 +cl_h_mach_const climb Constant Mach crossover altitude 9.5 8.4 10.6 norm 9.52|0.68 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 9.58 7.32 11.85 norm 9.58|1.38 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 9.02 6.12 11.93 norm 9.02|1.77 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.02 3.59 8.47 norm 6.03|1.48 +cr_d_range cruise Cruise range 1874 269 11619 beta 1.14|1.87|206.46|12053.57 +cr_v_cas_mean cruise Mean cruise CAS 132 126 146 gamma 6.84|119.57|2.28 +cr_v_cas_max cruise Maximum cruise CAS 139 130 164 gamma 4.29|122.70|5.16 +cr_v_mach_mean cruise Mean cruise Mach 0.85 0.83 0.87 norm 0.85|0.01 +cr_v_mach_max cruise Maximum cruise Mach 0.87 0.85 0.91 gamma 16.44|0.80|0.00 +cr_h_init cruise Initial cruise altitude 11.65 10.37 12.92 norm 11.65|0.78 +cr_h_mean cruise Mean cruise altitude 12.01 11.16 12.85 norm 12.01|0.51 +cr_h_max cruise Maximum cruise altitude 12.28 11.52 13.06 norm 12.29|0.47 +de_d_range descent Descent range 292 219 551 gamma 3.85|198.90|32.88 +de_v_mach_const descent Constant Mach 0.84 0.8 0.87 norm 0.84|0.02 +de_v_cas_const descent Constant CAS 153 140 167 norm 153.71|8.29 +de_h_mach_const descent Constant Mach crossover altitude 10.5 8.8 11.7 beta 3.43|2.59|7.57|4.76 +de_h_cas_const descent Constant CAS crossover altitude 7 4.4 9.7 norm 7.06|1.58 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -7.59 -14.16 -3.35 beta 3.41|2.37|-18.73|17.46 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.52 -13.91 -5.11 norm -9.51|2.67 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.2 -7.94 -4.45 norm -6.19|1.06 +fa_va_avg final_approach Mean airspeed 75 70 80 norm 75.58|3.36 +fa_vs_avg final_approach Mean vertical rate -3.99 -4.59 -3.11 gamma 24.44|-6.14|0.09 +fa_agl final_approach Approach angle 2.88 2.37 3.4 norm 2.88|0.31 +ld_v_app landing Touchdown speed 71.3 62 80.6 norm 71.29|6.48 +ld_d_brk landing Braking distance 2.08 0.45 3.71 norm 2.08|0.99 +ld_acc_brk landing Mean braking acceleration -1.12 -1.79 -0.46 norm -1.12|0.40 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/b789.csv b/data/performance/OpenAP/fixwing/wrap/b789.csv deleted file mode 100644 index 25813109dd..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/b789.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,96.1,81.0,111.3,norm,96.1|10.5 -TO,to_d_tof,2.49,1.30,3.70,norm,2.50|0.73 -TO,to_acc_tof,1.63,1.19,2.06,norm,1.63|0.26 -IC,ic_va_avg,93,81,103,beta,5|4|64|50 -IC,ic_vz_avg,10.60,6.45,14.77,norm,10.61|2.53 -CL,cl_d_range,282,181,453,beta,3|6|156|399 -CL,cl_v_cas_const,167,152,181,beta,3|3|140|51 -CL,cl_v_mach_const,0.85,0.81,0.88,norm,0.85|0.02 -CL,cl_h_cas_const,5.7,3.9,7.5,norm,5.7|0.9 -CL,cl_h_mach_const,10.1,8.3,11.9,norm,10.1|0.9 -CL,cl_vz_avg_pre_cas,9.19,7.72,12.61,gamma,6.07|6.08|0.62 -CL,cl_vz_avg_cas_const,7.33,4.54,10.14,norm,7.34|1.70 -CL,cl_vz_avg_mach_const,4.88,2.35,7.42,norm,4.88|1.54 -CR,cr_d_range,5206,-3385,13821,norm,5218|3340 -CR,cr_v_cas_mean,139,129,149,norm,139|6 -CR,cr_v_cas_max,146,133,173,beta,4|15|121|138 -CR,cr_v_mach_mean,0.85,0.83,0.87,norm,0.85|0.01 -CR,cr_v_mach_max,0.88,0.85,0.92,gamma,13.22|0.80|0.01 -CR,cr_h_mean,11.6,10.7,12.5,norm,11.6|0.5 -CR,cr_h_max,12.0,11.1,12.7,beta,16.2|8.0|8.4|5.3 -DE,de_d_range,303,211,548,gamma,5|171|26 -DE,de_v_mach_const,0.83,0.79,0.88,norm,0.83|0.03 -DE,de_v_cas_const,155,139,171,norm,155|9 -DE,de_h_mach_const,10.6,8.0,11.6,beta,7.4|2.6|3.7|8.5 -DE,de_h_cas_const,6.8,4.2,9.5,norm,6.9|1.6 -DE,de_vz_avg_mach_const,-6.87,-12.78,-2.78,beta,3.52|2.56|-17.01|16.40 -DE,de_vz_avg_cas_const,-8.86,-13.44,-4.26,norm,-8.85|2.79 -DE,de_vz_avg_after_cas,-6.15,-7.95,-4.34,norm,-6.15|1.10 -FA,fa_va_avg,77,72,83,norm,77|3 -FA,fa_vz_avg,-4.11,-4.78,-3.11,gamma,24.35|-6.53|0.10 -LD,ld_v_app,74.0,64.8,83.3,norm,74.1|6.4 -LD,ld_d_brk,2.49,0.67,4.31,norm,2.49|1.11 -LD,ld_acc_brk,-1.11,-1.74,-0.47,norm,-1.11|0.39 diff --git a/data/performance/OpenAP/fixwing/wrap/b789.txt b/data/performance/OpenAP/fixwing/wrap/b789.txt new file mode 100644 index 0000000000..248af707fc --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/b789.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 96.1 81.1 111.2 norm 96.15|10.46 +to_d_tof takeoff Takeoff distance 2.49 1.31 3.69 norm 2.50|0.72 +to_acc_tof takeoff Mean takeoff accelaration 1.63 1.2 2.06 norm 1.63|0.26 +ic_va_avg initial_climb Mean airspeed 92 81 103 beta 3.43|3.27|72.17|39.84 +ic_vs_avg initial_climb Mean vertical rate 10.6 6.48 14.74 norm 10.61|2.51 +cl_d_range climb Climb range 263 173 420 beta 2.91|5.02|156.74|333.07 +cl_v_cas_const climb Constant CAS 163 151 174 norm 163.14|7.01 +cl_v_mach_const climb Constant Mach 0.84 0.82 0.87 norm 0.84|0.02 +cl_h_cas_const climb Constant CAS crossover altitude 4.1 2.3 5.9 norm 4.10|1.07 +cl_h_mach_const climb Constant Mach crossover altitude 9.1 8.1 10.2 norm 9.14|0.66 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 9.46 7.18 11.75 norm 9.47|1.39 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.68 6.04 11.81 beta 3.89|4.77|3.94|10.92 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 6.05 3.63 8.47 norm 6.05|1.47 +cr_d_range cruise Cruise range 5695 -3140 14555 norm 5707.50|3434.94 +cr_v_cas_mean cruise Mean cruise CAS 139 129 149 norm 139.87|6.06 +cr_v_cas_max cruise Maximum cruise CAS 148 133 170 beta 2.67|4.04|125.61|63.39 +cr_v_mach_mean cruise Mean cruise Mach 0.85 0.83 0.87 norm 0.85|0.01 +cr_v_mach_max cruise Maximum cruise Mach 0.87 0.85 0.92 gamma 6.01|0.83|0.01 +cr_h_init cruise Initial cruise altitude 11.3 9.42 12.44 beta 4.59|2.75|7.61|5.49 +cr_h_mean cruise Mean cruise altitude 11.58 10.72 12.44 norm 11.58|0.52 +cr_h_max cruise Maximum cruise altitude 11.99 11.28 12.7 norm 11.99|0.43 +de_d_range descent Descent range 293 224 550 gamma 3.57|207.20|33.45 +de_v_mach_const descent Constant Mach 0.84 0.79 0.87 beta 7.21|3.08|0.71|0.17 +de_v_cas_const descent Constant CAS 154 139 169 norm 154.59|8.89 +de_h_mach_const descent Constant Mach crossover altitude 10.5 8.6 11.6 beta 3.21|2.22|7.41|4.75 +de_h_cas_const descent Constant CAS crossover altitude 7 4.3 9.6 norm 6.99|1.61 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -7.16 -12.88 -2.81 beta 2.83|2.31|-16.09|15.29 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.14 -13.41 -4.84 norm -9.13|2.61 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -6.19 -7.91 -4.46 norm -6.18|1.05 +fa_va_avg final_approach Mean airspeed 77 72 83 norm 77.88|3.71 +fa_vs_avg final_approach Mean vertical rate -4.15 -4.71 -3.15 gamma 11.92|-5.65|0.14 +fa_agl final_approach Approach angle 2.89 2.36 3.43 norm 2.89|0.32 +ld_v_app landing Touchdown speed 74 64.8 83.3 norm 74.07|6.42 +ld_d_brk landing Braking distance 2.49 0.68 4.31 norm 2.49|1.11 +ld_acc_brk landing Mean braking acceleration -1.11 -1.74 -0.47 norm -1.11|0.38 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/e190.csv b/data/performance/OpenAP/fixwing/wrap/e190.csv deleted file mode 100644 index f3619f69eb..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/e190.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,88.1,78.6,97.6,norm,88.1|6.6 -TO,to_d_tof,1.80,1.18,2.43,norm,1.81|0.38 -TO,to_acc_tof,1.83,1.37,2.28,norm,1.83|0.28 -IC,ic_va_avg,78,65,91,norm,78|8 -IC,ic_vz_avg,11.19,5.76,16.64,norm,11.20|3.31 -CL,cl_d_range,225,165,388,gamma,5|140|18 -CL,cl_v_cas_const,142,131,158,gamma,20|107|1 -CL,cl_v_mach_const,0.75,0.69,0.82,norm,0.75|0.04 -CL,cl_h_cas_const,4.1,2.6,7.4,gamma,7.8|1.1|0.4 -CL,cl_h_mach_const,10.1,7.6,11.5,beta,12.6|4.3|2.5|9.8 -CL,cl_vz_avg_pre_cas,11.22,8.15,14.30,norm,11.22|1.87 -CL,cl_vz_avg_cas_const,7.99,5.25,10.73,norm,7.99|1.67 -CL,cl_vz_avg_mach_const,4.35,2.26,6.45,norm,4.35|1.27 -CR,cr_d_range,690,460,2710,gamma,1|437|324 -CR,cr_v_cas_mean,131,120,141,norm,131|6 -CR,cr_v_cas_max,133,125,153,gamma,5|117|3 -CR,cr_v_mach_mean,0.77,0.74,0.81,norm,0.77|0.02 -CR,cr_v_mach_max,0.80,0.76,0.85,norm,0.80|0.03 -CR,cr_h_mean,11.1,10.0,12.1,norm,11.1|0.6 -CR,cr_h_max,11.2,10.2,12.2,norm,11.2|0.6 -DE,de_d_range,255,169,443,beta,2|5|156|379 -DE,de_v_mach_const,0.77,0.70,0.83,norm,0.77|0.04 -DE,de_v_cas_const,146,134,159,norm,146|7 -DE,de_h_mach_const,9.1,7.4,10.8,norm,9.1|1.0 -DE,de_h_cas_const,5.4,2.7,8.2,norm,5.4|1.7 -DE,de_vz_avg_mach_const,-5.90,-11.66,-1.69,beta,9.15|5.35|-22.01|24.73 -DE,de_vz_avg_cas_const,-8.77,-13.14,-4.39,norm,-8.76|2.66 -DE,de_vz_avg_after_cas,-5.91,-7.97,-3.84,norm,-5.90|1.25 -FA,fa_va_avg,70,64,76,norm,70|4 -FA,fa_vz_avg,-3.56,-4.43,-2.68,norm,-3.55|0.53 -LD,ld_v_app,66.9,56.3,77.5,norm,66.9|7.4 -LD,ld_d_brk,1.84,0.73,3.73,beta,2.16|3.24|0.23|4.74 -LD,ld_acc_brk,-0.94,-1.92,-0.39,beta,5.77|2.95|-3.12|3.07 diff --git a/data/performance/OpenAP/fixwing/wrap/e190.txt b/data/performance/OpenAP/fixwing/wrap/e190.txt new file mode 100644 index 0000000000..fc2637f3e7 --- /dev/null +++ b/data/performance/OpenAP/fixwing/wrap/e190.txt @@ -0,0 +1,36 @@ +variable flight phase name opt min max model parameters +to_v_lof takeoff Liftoff speed 88.1 78.7 97.5 norm 88.13|6.52 +to_d_tof takeoff Takeoff distance 1.8 1.19 2.42 norm 1.81|0.38 +to_acc_tof takeoff Mean takeoff accelaration 1.83 1.38 2.28 norm 1.83|0.27 +ic_va_avg initial_climb Mean airspeed 78 66 91 norm 78.72|8.77 +ic_vs_avg initial_climb Mean vertical rate 11.19 5.78 16.62 norm 11.20|3.30 +cl_d_range climb Climb range 215 162 389 gamma 4.36|144.99|21.11 +cl_v_cas_const climb Constant CAS 140 133 156 gamma 8.78|122.65|2.36 +cl_v_mach_const climb Constant Mach 0.75 0.71 0.8 norm 0.75|0.03 +cl_h_cas_const climb Constant CAS crossover altitude 3 1.8 6.5 gamma 4.03|0.82|0.73 +cl_h_mach_const climb Constant Mach crossover altitude 9.2 8.1 10.2 norm 9.19|0.63 +cl_vs_avg_pre_cas climb Mean climb rate, pre-constant-CAS 10.44 7.18 13.71 norm 10.44|1.98 +cl_vs_avg_cas_const climb Mean climb rate, constant-CAS 8.93 6.27 11.6 norm 8.93|1.62 +cl_vs_avg_mach_const climb Mean climb rate, constant-Mach 4.82 2.95 6.7 norm 4.82|1.14 +cr_d_range cruise Cruise range 734 464 2710 gamma 1.98|433.55|308.41 +cr_v_cas_mean cruise Mean cruise CAS 131 120 141 norm 131.14|6.25 +cr_v_cas_max cruise Maximum cruise CAS 137 124 150 norm 137.30|7.93 +cr_v_mach_mean cruise Mean cruise Mach 0.77 0.74 0.81 norm 0.77|0.02 +cr_v_mach_max cruise Maximum cruise Mach 0.79 0.76 0.85 gamma 17.56|0.68|0.01 +cr_h_init cruise Initial cruise altitude 10.99 9.99 12 norm 11.00|0.61 +cr_h_mean cruise Mean cruise altitude 11.05 10.05 12.05 norm 11.05|0.61 +cr_h_max cruise Maximum cruise altitude 11.16 10.21 12.13 norm 11.17|0.58 +de_d_range descent Descent range 253 171 425 beta 2.17|3.85|161.90|315.91 +de_v_mach_const descent Constant Mach 0.77 0.72 0.81 norm 0.77|0.03 +de_v_cas_const descent Constant CAS 148 134 159 beta 3.94|3.27|124.00|42.94 +de_h_mach_const descent Constant Mach crossover altitude 9.3 8 10.6 norm 9.29|0.78 +de_h_cas_const descent Constant CAS crossover altitude 5.4 2.6 8.2 norm 5.42|1.72 +de_vs_avg_mach_const descent Mean descent rate, constant-Mach -5.25 -11.79 -2.1 beta 4.50|2.29|-18.06|17.51 +de_vs_avg_cas_const descent Mean descent rate, constant-CAS -9.08 -13.29 -4.85 norm -9.07|2.56 +de_vs_avg_after_cas descent Mean descent rate, after-constant-CAS -5.9 -7.92 -3.88 norm -5.90|1.23 +fa_va_avg final_approach Mean airspeed 70 64 76 norm 70.46|4.17 +fa_vs_avg final_approach Mean vertical rate -3.57 -4.34 -2.81 norm -3.57|0.47 +fa_agl final_approach Approach angle 2.92 2.38 3.46 norm 2.92|0.33 +ld_v_app landing Touchdown speed 66.9 56.6 77.2 norm 66.90|7.16 +ld_d_brk landing Braking distance 2.1 0.7 3.52 norm 2.11|0.86 +ld_acc_brk landing Mean braking acceleration -1.08 -1.8 -0.36 norm -1.08|0.44 \ No newline at end of file diff --git a/data/performance/OpenAP/fixwing/wrap/f16.csv b/data/performance/OpenAP/fixwing/wrap/f16.csv deleted file mode 100644 index 5deffeff35..0000000000 --- a/data/performance/OpenAP/fixwing/wrap/f16.csv +++ /dev/null @@ -1,34 +0,0 @@ -phase,param,opt,min,max,model,pm -TO,to_v_lof,88.1,78.6,97.6,norm, -TO,to_d_tof,1.80,1.18,2.43,norm, -TO,to_acc_tof,1.83,1.37,2.28,norm, -IC,ic_va_avg,78,65,91,norm, -IC,ic_vz_avg,11.19,5.76,16.64,norm, -CL,cl_d_range,225,165,388,norm, -CL,cl_v_cas_const,142,131,158,norm, -CL,cl_v_mach_const,0.75,0.69,0.82,norm, -CL,cl_h_cas_const,4.1,2.6,7.4,norm, -CL,cl_h_mach_const,10.1,7.6,11.5,norm, -CL,cl_vz_avg_pre_cas,11.22,8.15,14.30,norm, -CL,cl_vz_avg_cas_const,7.99,5.25,10.73,norm, -CL,cl_vz_avg_mach_const,4.35,2.26,6.45,norm, -CR,cr_d_range,900,500,2000,norm, -CR,cr_v_cas_mean,600,600,600,norm, -CR,cr_v_cas_max,800,800,800,norm, -CR,cr_v_mach_mean,1,1,1,norm, -CR,cr_v_mach_max,2,2,2,norm, -CR,cr_h_mean,10,10,10,norm, -CR,cr_h_max,15,15,15,norm, -DE,de_d_range,255,169,443,norm, -DE,de_v_mach_const,0.77,0.70,0.83,norm, -DE,de_v_cas_const,146,134,159,norm, -DE,de_h_mach_const,9.1,7.4,10.8,norm, -DE,de_h_cas_const,5.4,2.7,8.2,norm, -DE,de_vz_avg_mach_const,-5.90,-11.66,-1.69,norm, -DE,de_vz_avg_cas_const,-8.77,-13.14,-4.39,norm, -DE,de_vz_avg_after_cas,-5.91,-7.97,-3.84,norm, -FA,fa_va_avg,70,64,76,norm, -FA,fa_vz_avg,-3.56,-4.43,-2.68,norm, -LD,ld_v_app,66.9,56.3,77.5,norm, -LD,ld_d_brk,1.84,0.73,3.73,norm, -LD,ld_acc_brk,-0.94,-1.92,-0.39,norm, diff --git a/docs/python_demo.ipynb b/docs/python_demo.ipynb new file mode 100644 index 0000000000..52e27ffd59 --- /dev/null +++ b/docs/python_demo.ipynb @@ -0,0 +1,328 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Bluesky interfacing demo\n", + "\n", + "This Jupyter Notebook demonstrates the use of Bluesky as a Python package and how it can be used to generate aircraft trajectories.\n", + "\n", + "**Note:** Make sure that you have Bluesky installed as a package in your Python environment.\n", + "\n", + "Let's start by importing the relevant modules." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Using Python-based geo functions\n" + ] + } + ], + "source": [ + "import numpy as np\n", + "\n", + "import bluesky as bs\n", + "from bluesky.simulation import ScreenIO\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We create a dummy class that acts as the screen of BlueSky. Since we don't want to actually load the screen from BlueSky here, a simple and small class is used instead to avoid errors when something within BlueSky is calling the *echo* function." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "class ScreenDummy(ScreenIO):\n", + " \"\"\"\n", + " Dummy class for the screen. Inherits from ScreenIO to make sure all the\n", + " necessary methods are there. This class is there to reimplement the echo\n", + " method so that console messages are printed.\n", + " \"\"\"\n", + " def echo(self, text='', flags=0):\n", + " \"\"\"Just print echo messages\"\"\"\n", + " print(\"BlueSky console:\", text)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The first step to take is to initialise bluesky (here imported as *bs*) as a disconnected, single simulation.\n", + "Next we replace the screen object with our derived variant so that bluesky console messages are printed." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reading config from settings.cfg\n", + "Creating directory \"scenario\", and copying default files\n", + "Creating directory \"plugins\", and copying default files\n", + "Reading magnetic variation data\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Unable to copy \"scenario\" files to \"scenario\"\n", + "Unable to copy \"plugins\" files to \"plugins\"\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading global navigation database...\n", + "Reading cache: data/cache/py3/navdata.p\n", + "Error loading plugin: plugin AREA not found.\n", + "Error loading plugin: plugin DATAFEED not found.\n" + ] + } + ], + "source": [ + "# initialize bluesky as non-networked simulation node\n", + "bs.init('sim-detached')\n", + "\n", + "# initialize dummy screen\n", + "bs.scr = ScreenDummy()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that everything is initialized, we can get started and and generate some traffic. Here, we generate 3 aircraft of the type *A320*." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# generate some trajectories\n", + "n = 3\n", + "\n", + "# create n aircraft with random positions, altitudes, speeds\n", + "bs.traf.mcre(n, actype=\"A320\")\n", + "\n", + "# alternative: individually initialize each aircraft by passing the initial\n", + "# position, heading, altitude, and speed.\n", + "# bs.traf.cre(acid=acids, actype=actypes, aclat=aclats, aclon=aclons,\n", + "# achdg=achdgs, acalt=acalts, acspd=acspds)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The traffic in this example are not given any initial conditions such as initial position and velocity. This is not needed when the traffic departs at an airport. You can also pass the initial conditions as additional parameters to the funciton if you want to initialize the traffic in flight.\n", + "\n", + "Next we want to assign some waypoints to the traffic. In this example we assign the same route to all the flights. This is just for the sake of simplicity and it obviously wouldn't make a lot of sense in a practical application, but it should highlight how waypoints are added." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# iterate over traffic and add the same waypoints\n", + "# Note that preferably, all simulation commands are initiated through the stack\n", + "# however, if you wish, you can also call the functions directly, such as the\n", + "# mcre command in the above cell.\n", + "for acid in bs.traf.id:\n", + " # set the origin (not needed if initialized in flight),\n", + " # and add some waypoints, here only the altitude (in m) is passed to the\n", + " # function, but you can additionally pass a speed as well\n", + " # finally turn on VNAV for each flight\n", + " bs.stack.stack(f'ORIG {acid} EGLL;'\n", + " f'ADDWPT {acid} BPK FL60;'\n", + " f'ADDWPT {acid} TOTRI FL107;'\n", + " f'ADDWPT {acid} MATCH FL115;'\n", + " f'ADDWPT {acid} BRAIN FL164;'\n", + " f'VNAV {acid} ON')\n", + "\n", + " # you can also set the way the waypoint should be flown\n", + " # bs.stack.stack(f'ADDWPT {acid} FLYOVER')\n", + "\n", + " # you can also set a destination\n", + " # bs.stack.stack(f'DEST {acid} EHAM')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that all our traffic has a route to fly, it's time to start the simulation." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "BlueSky console: Base dt set to 1.0\n", + "performance dt is unchanged.\n", + "asas dt is unchanged.\n" + ] + } + ], + "source": [ + "# set simulation time step, and enable fast-time running\n", + "bs.stack.stack('DT 1;FF')\n", + "\n", + "# we'll run the simulation for up to 4000 seconds\n", + "t_max = 4000\n", + "\n", + "ntraf = bs.traf.ntraf\n", + "n_steps = int(t_max + 1)\n", + "t = np.linspace(0, t_max, n_steps)\n", + "\n", + "# allocate some empty arrays for the results\n", + "res = np.zeros((n_steps, 4, ntraf))\n", + "\n", + "# iteratively simulate the traffic\n", + "for i in range(n_steps):\n", + " # Perform one step of the simulation\n", + " bs.sim.step()\n", + "\n", + " # save the results from the simulator in the results array,\n", + " # here we keep the latitude, longitude, altitude and TAS\n", + " res[i] = [bs.traf.lat,\n", + " bs.traf.lon,\n", + " bs.traf.alt,\n", + " bs.traf.tas]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, we do a bit of plotting to visualize the results. Again, the three trajectories are the same since we passed the same route to them, but this, of course, can be easily changed." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnQAAAPICAYAAABQFmZ1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAgAElEQVR4nOzde5hcd33n+c+3qvqm7j4tW2pJVZKNfBG2qggQ6DAEMjMBcjEZMgZiBpIwdjbOOuFJsmSW3UA2TwKZzDMMs2xgyASyXiDmamAcCEyAkCxk4iUYZ9rBgHXxWMYXyZKstiRL3S31paq++0edalW3+lLdXVWnflXv1+N6qup3fuf0t85Tbn36d87vHHN3AQAAIFyppAsAAADA5hDoAAAAAkegAwAACByBDgAAIHAEOgAAgMAR6AAAAAJHoAOQODNLm9mUmV2ddC0AECICHYB1i8NX9VE2s4s1739xvdtz95K7D7n7k5us65tm9kub2cYGf+4nzWzezHYuaf93cfuUmT1rZn9vZi9Z0uenzOxhM7tgZt+oDbVmljKz95rZGTM7bWbvNjOrWX6Nmf1dvO4hM3vFkm2/2cyeiH/+581sa82yfjO7y8zOm9kJM3tr4/cMgFYh0AFYtzh8Dbn7kKQnJf1sTdunlvY3s0zrq1yfODyt+3eimQ1Lep2k85J+YZkun4r306ik/0/Sf6lZd6ekeyT9jqRtkh6U9Omadd8i6WckPU/SCyW9XtLtNcs/J+l+SVdKeqekz5vZtnjbz5f0QUm/KGmXpHlJ/7lm3T+UtFfS1ZJ+UtL/YWY/sd7PD6A9EOgANFw8MvVZM7vbzCYlvdnMftTMvh2PVJ0wsw+YWU/cP2NmbmZ74/f9ZvZHZnbUzJ42sw+aWX/N9l9vZg/Go0tH4lGu90j6UUl/Go9IvT/u+2NmNm5m58zsH8zsn9Rs55tm9odmdp+kaUlvN7P7l3yWt5vZPat83DdImpD07yXdtlInd59XJaxdbWZXxM0/J+lBd/+8u1+U9C5JP2Jm18fLb5P0Xnc/7u5HJf2RpF+K68qrEvT+wN1n3P1zkg6rEi4l6c2S/sLdv+nuU5J+X9IbzGxLvPxWSf/W3Z9194ckfbS6bQDhIdABaJbXqRJgRiR9VlJR0lslbZf0ckk3SfrVFdZ9r6RrJD1f0j5VRpJ+V5LM7GWqhI+3Sdoq6RWSnnD3t0u6T9KvxSOFv2Vm2yV9WdL/pcoI2AckfaUmUEnSv5b0y5IiVUawbjCzfTXL3yzpE6t8ztviz3m3pB8ysxcs18nM+lQJUROqjOZJUkHSd6t93P28pMfi9suWx69rlx1x9+lVltdu+2FJZUn7zGxU0o5Vtg0gMAQ6AM3yTXf/r+5edveL7v7f3f1+dy+6+w8k3Snpny9dKT7s+SuSfsvdz8Yh592S3hR3uV3S/+PuX4+3fTQOK8v5WUkH3P3u+Od+UtIPJP2Lmj4fdfdD7j7v7pOqHBJ9c1zLCyVlJX1luY2b2TWS/qmkT7v7cUn/TZXQVusXzOxZSRdUCX+3uHspXjYk6dyS/uckDcfnym1ZsvycpOG11l1l+fl4+VBN/+XWBRAYAh2AZjla+8bMbjSzL5vZSTM7L+nfqjJat9QuSX2Svhsfnn1W0l+qMqIkSVdJerTOGnKSnljS9oSk3SvVKeljqpx3JlWC3Wfjw6XLuVXS9+NDlpL0KUm/aGbpmj6fdvetqnyuhyX9cM2yKVVGBmtFkibd3VUJgdHSZWutW8fyqZr3y60LIDAEOgDN4kve/9+SHpJ0vbtHqpzTZZetJT0taU7SDe6+NX6MuPtIvPyopOvq/JnHJT1nSdvVkp5aaR13/6YkmdnLJf28VjjcGo+g3SrpuXFIPSnpP0raKemnLyvMfUKVQ8z/rmY27AFJC4do4wkW18Ttly2PX9cuu77mnLjlltdu+7mq/M5/JK5lYpVtAwgMgQ5Aqwyrclhv2sz2a4Xz5+LDkR+W9H4zG7WKPWb2U3GXj0j6FTN7RTwzdY+Z3RAve1rStTWb+0tJBTN7Yzzx4hckXa8VDqHW+ISkD0madvdvr9Dnx1QZLRxTZQbqC1WZpPA5rTA5wt0PSPq6pP8tbvpzSS80s9fGkz7eKWnc3Y/Eyz8u6W1mljOzPZL+jaS74m0dVCWA/X48ieQWSfslfSFe95OSXmtmLzOzQVVGRP+Lu1+o2fbvmdnWeILFL1e3DSA8BDoArfI2VYLOpCqjdZ9do+8Tkv5BlRD416pMjpC7f0vS/6zKBIdzkv5WlWAlSe+X9PPxodo/ikei/qWkt0s6rUogeo27n1mj1o+rEs7WmgzxBXc/4O4nqw9J/0nSzbXXfFvi/5T0FjPb7u5PS/pXqozsnZX0Ii2+9MkHJX1NleD2PUlfVCXQVr1RlZm9Z1W5DMnPuftpSXL370n6DUmfkXRKlcPYv1mz7u+pMtp5VNI3JL3b3f/fVT4vgDZmldM0ACA5ZtYraVbS7nhyQdL1DKoSgp7n7o8lXQ8ArIUROgDt4HmqTAA4lXQhsV+X9PeEOQChaPurtwPobGb2Rkl/Ium33b3YBvUcU+WuCjcnXQsA1ItDrgAAAIHjkCsAAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAySRfQatu3b/e9e/cmXQYAAMCaHnjggWfcfXStfl0X6Pbu3avx8fGkywAAAFiTmT1RTz8OuQIAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQN9r1jz+o37/6OTk/NJl0KAADoEgS6BpuaLeq/fve4Dhw/n3QpAACgSxDoGqyQHZEkPXT8XMKVAACAbkGga7CRLT3ac8UAI3QAAKBlCHRNUMhFOkigAwAALUKga4JCbkSPPTOtqdli0qUAAIAuQKBrgkIukiQdOsEoHQAAaD4CXRM8b3dlYsSBp5gYAQAAmo9A1wQ7hvu0faiXiREAAKAlCHRNYGbK50b0EIEOAAC0AIGuSQq5SI88PanZYinpUgAAQIcj0DVJIRepWHY98vRU0qUAAIAOR6BrkkIunhjBHSMAAECTEeia5DlXbtFQX4aJEQAAoOkIdE2SSpny2YhABwAAmo5A10T5+BZgpbInXQoAAOhgBLomKuQiXZwv6bFnppMuBQAAdDACXRMxMQIAALQCga6J9u0cUm86pYOcRwcAAJqIQNdEPemUnrtriIkRAACgqQh0Tfa83IgOHD8ndyZGAACA5iDQNVkhF+nshXmdODeTdCkAAKBDBR/ozOzfmNkBM3vIzO42s/6ka6qVjydGPPQUEyMAAEBzBB3ozGy3pP9F0pi7P09SWtKbkq1qsf3ZYZmJ8+gAAEDTBB3oYhlJA2aWkbRF0vGE61lkS29G124fJNABAICmCTrQuftTkt4r6UlJJySdc/e/TraqyxVyIzrItegAAECTBB3ozOwKSTdLukZSTtKgmb15mX53mNm4mY1PTEy0ukw9b3ek4+dmdHZ6ruU/GwAAdL6gA52kn5D0mLtPuPu8pM9LetnSTu5+p7uPufvY6Ohoy4u8dMcIDrsCAIDGCz3QPSnppWa2xcxM0qskHUq4pssUcpEk6SEOuwIAgCYIOtC5+/2S7pH0j5K+r8rnuTPRopaxdUuvdm8d4BZgAACgKTJJF7BZ7v5OSe9Muo615HORDjBCBwAAmiDoEbqQFHKRfvDMtC7MFZMuBQAAdBgCXYvks5HcpUMnJpMuBQAAdBgCXYsUdldmunI9OgAA0GgEuhbJjfRr65YeLl0CAAAajkDXImamQi7SwRMEOgAA0FgEuhYq5EZ0+OSk5kvlpEsBAAAdhEDXQvlspLliWY9OTCVdCgAA6CAEuhaq3jHiwFMcdgUAAI1DoGuha0eH1N+TYmIEAABoKAJdC6VTpht3RTp4gkuXAACAxiHQtVghF+ng8fNy96RLAQAAHYJA12L5XKTzM0UdO3sx6VIAAECHINC1WCFXuWPEAe4YAQAAGoRA12I37hpWOmVMjAAAAA1DoGux/p60rhsd1EECHQAAaBACXQIKuRFG6AAAQMMQ6BJQyEU6eX5Gp6dmky4FAAB0AAJdAvLZ+I4RjNIBAIAGINAlIJ8j0AEAgMYh0CVg65Ze7d46wKVLAABAQxDoElLIRTp4ghE6AACweQS6hBRyI3rsmWlNzxaTLgUAAASOQJeQfC6Su3T4JKN0AABgcwh0CSkwMQIAADQIgS4h2ZF+XbGlRweeItABAIDNIdAlxMxUyI0wMQIAAGwagS5BhVykh09Oar5UTroUAAAQMAJdgvK5SHOlso6cmkq6FAAAEDACXYKYGAEAABqBQJega7YPaaAnzR0jAADAphDoEpROmW7MDjNCBwAANoVAl7BCLtKh4+fl7kmXAgAAAkWgS1ghN6LJ2aKOnrmYdCkAACBQBLqE5bPViRGcRwcAADaGQJewG3YNK50yzqMDAAAbRqBLWH9PWtePDjFCBwAANoxA1wYKuYhbgAEAgA0j0LWBfC7S0+dn9czUbNKlAACAABHo2kCeO0YAAIBNINC1gUJ2RBIzXQEAwMYQ6NrAyJYe7bligBE6AACwIQS6NlG9YwQAAMB6EejaRCE3osdOT2t6tph0KQAAIDAEujZRyEVylw5x+RIAALBOBLo2wUxXAACwUQS6NrEr6teVg73MdAUAAOtGoGsTZqZCLmKEDgAArBuBro3kc5EeeXpKc8Vy0qUAAICAEOjaSCE3orlSWUdOTSVdCgAACAiBro3ks9WJEZxHBwAA6kegayPXbB/UQE+a8+gAAMC6EOjaSDpl2p8d1kECHQAAWAcCXZsp5EZ08MR5lcuedCkAACAQBLo2U8hFmpot6ujZC0mXAgAAAkGgazPcMQIAAKwXga7NPHfnsNIpY6YrAACoG4GuzfT3pLVvxxAjdAAAoG4EujaUz0XMdAUAAHUj0LWhQm5EpyZnNTE5m3QpAAAgAAS6NlTIcccIAABQPwJdG9qfZaYrAACoH4GuDY0M9OiqKwc4jw4AANSFQNemCtkRDrkCAIC6EOjaVCEX6fHTFzQ1W0y6FAAA0OYIdG2qsLtyHt2hExx2BQAAqyPQtal8dkSSdOApDrsCAIDVEeja1M6oT9sGe5npCgAA1kSga1NmpnwuItABAIA1EejaWCE3okdOTWquWE66FAAA0MYIdG2skIs0X3I9cmoy6VIAAEAbI9C1sUu3AOOwKwAAWBmBro3t3TaoLb1p7hgBAABWRaBrY6mUaX824o4RAABgVQS6NlfIRTp0YlLlsiddCgAAaFMEujZXyEWami3qyTMXki4FAAC0KQJdmyvk4jtGcB4dAABYAYGuze3bOaRMyjiPDgAArCj4QGdmW83sHjM7bGaHzOxHk66pkfoyaV2/Y4gROgAAsKLgA52k/yTpr9z9RkkvkHQo4XoarpAbIdABAIAVBR3ozCyS9M8kfUSS3H3O3Z9NtqrGK+QiPTM1q1OTM0mXAgAA2lDQgU7StZImJP2ZmX3HzD5sZoNLO5nZHWY2bmbjExMTra9yk7hjBAAAWE3ogS4j6UWSPuTuPyxpWtI7lnZy9zvdfczdx0ZHR1td46btjwMdd4wAAADLCT3QHZN0zN3vj9/fo0rA6yhRf4+uvnILM10BAMCygg507n5S0lEzuyFuepWkgwmW1DSFXMQhVwAAsKygA13sNyV9ysy+J+mFkv59wvU0RSEX6YnTFzQ5M590KQAAoM1kki5gs9z9QUljSdfRbNU7Rhw6MamXXHNlwtUAAIB20gkjdF3h0kxXzqMDAACLEegCMTrcp+1DvZxHBwAALkOgC4SZKc8dIwAAwDIIdAEp5CI98vSkZoulpEsBAABthEAXkEIuUrHseuTpqaRLAQAAbYRAF5DqTFfuGAEAAGoR6ALynCu3aLA3zUxXAACwCIEuIKmUaX+WO0YAAIDFCHSBKeQiHTpxXuWyJ10KAABoEwS6wBRyI5qeK+mJMxeSLgUAALQJAl1g8twxAgAALEGgC8y+nUPKpIzz6AAAwAICXWD6Mmnt2zlMoAMAAAsIdAEq5CIdPH5O7kyMAAAABLogFXKRnpma08TkbNKlAACANkCgC1D1jhEcdgUAABKBLkj7s8OSmOkKAAAqCHQBGu7v0XO2bWGEDgAASCLQBauQ4xZgAACggkAXqEJuRE+euaDzM/NJlwIAABJGoAtU9Y4RhxilAwCg6xHoAlVYuAUYgQ4AgG5HoAvUjuF+bR/qI9ABAAACXcgqEyO4dAkAAN2OQBewQi7SkVNTmi2Wki4FAAAkiEAXsEJuRMWy65Gnp5IuBQAAJIhAF7BLEyM47AoAQDcj0AXs6iu3aKgvw8QIAAC6HIEuYKmUaX92mEAHAECXI9AFrpAb0aET51Uue9KlAACAhBDoApfPRrowV9ITZy4kXQoAAEgIgS5weSZGAADQ9Qh0gdu3c0iZlOkg59EBANC1CHSB68ukdf2OIR08QaADAKBbEeg6QCE3wkxXAAC6GIGuA+RzkSYmZ3VqcibpUgAAQAIIdB0gn61MjDh0YjLhSgAAQBIIdB2gOtOViREAAHQnAl0HGBno0Z4rBrh0CQAAXYpA1yHy2YiZrgAAdCkCXYco5Eb02DPTujBXTLoUAADQYgS6DpHPRXJnYgQAAN2IQNchFiZGcNgVAICuQ6DrELmRfo0M9DDTFQCALkSg6xBmpkIu0kFmugIA0HUIdB0kn410+OSkiqVy0qUAAIAWItB1kHwu0myxrMeemU66FAAA0EIEug5SyI1Ikg5wHh0AAF2FQNdBrh0dVG8mxUxXAAC6DIGug/SkU7ph5zAzXQEA6DIEug5TyEU6cPyc3D3pUgAAQIsQ6DpMPhfp7IV5nTw/k3QpAACgRQh0HSafje8YwWFXAAC6BoGuw9yYjWRGoAMAoJsQ6DrMUF9Ge7cNcukSAAC6CIGuA+WzEZcuAQCgixDoOlA+F+nJMxd0fmY+6VIAAEALEOg6UD5XmRhxiMOuAAB0BQJdBypUZ7py2BUAgK5AoOtAo8N92j7Uy0xXAAC6BIGuA5mZ8rkRZroCANAlCHQdKp+N9MipSc0Vy0mXAgAAmoxA16HyuUjzJdeRU1NJlwIAAJqMQNehCvFM1wPHzyVcCQAAaDYCXYfau21QAz1pZroCANAFCHQdKp0y3ZgdZqYrAABdgEDXwQq5yi3A3D3pUgAAQBMR6DpYPjuiyZmijp29mHQpAACgiQh0HSy/MDGCw64AAHQyAl0Hu3HXsFLGLcAAAOh0BLoO1t+T1nWjQzrIpUsAAOhoBLoOl89FzHQFAKDDEeg6XD4b6fi5GZ2dnku6FAAA0CQEug5XyI1I4jw6AAA6GYGuw+3PDksSh10BAOhgBLoOt22oT7uifkboAADoYAS6LlDIRTrATFcAADpWRwQ6M0ub2XfM7C+TrqUd5XORHp2Y1sx8KelSAABAE3REoJP0VkmHki6iXeWzkUpl1/94ejLpUgAAQBMEH+jMbI+kfyHpw0nX0q6qM125BRgAAJ0p+EAn6f2SfltSeaUOZnaHmY2b2fjExETrKmsTe64Y0HBfhpmuAAB0qKADnZm9RtIpd39gtX7ufqe7j7n72OjoaIuqax+plGl/NmKmKwAAHSroQCfp5ZL+pZk9Lukzkl5pZp9MtqT2lM9FOnTivMplT7oUAADQYEEHOnf/HXff4+57Jb1J0jfc/c0Jl9WW8rlIF+ZKevz0dNKlAACABgs60KF++WwkiVuAAQDQiTom0Ln7f3P31yRdR7vat3NImZQxMQIAgA7UMYEOq+vLpLVv5zCXLgEAoAMR6LpInpmuAAB0JAJdF8nnIk1MzurU5EzSpQAAgAYi0HWRQi6eGMFhVwAAOgqBrovsZ6YrAAAdiUDXRUYGerTnigFG6AAA6DAEui5TyEUEOgAAOgyBrsvksyN67PS0pmeLSZcCAAAahEDXZfK5SO7S4ZOTSZcCAAAahEDXZRZmujIxAgCAjkGg6zLZkX5t3dKjg8fPJV0KAABoEAJdlzGzyh0jmBgBAEDHINB1oUIu0uGTkyqWykmXAgAAGoBA14XyuUizxbJ+8Mx00qUAAIAGINB1oXx2RBK3AAMAoFMQ6LrQtaOD6s2kmOkKAECHINB1oZ50SjfuGtYBZroCANARCHRdqjrT1d2TLgUAAGwSga5L5XORzl6Y18nzM0mXAgAANolA16Wqd4w48BTn0QEAEDoCXZe6YVckM24BBgBAJyDQdamhvoz2bhvk0iUAAHQAAl0Xy+ciHTjBTFcAAEJHoOti+Wyko2cu6tzF+aRLAQAAm0Cg62L5eGLEYc6jAwAgaAS6Llad6crECAAAwkag62I7hvu1fahPB5gYAQBA0Ah0XS6fi5jpCgBA4Ah0XS6fjfTIqUnNFctJlwIAADaIQNflCrlI8yXXI6cmky4FAABsEIGuy1VnunLYFQCAcBHoutzebYMa6Ekz0xUAgIAR6LpcOmXanx1mpisAAAEj0EH5XKRDx8/L3ZMuBQAAbACBDspnRzQ5W9SxsxeTLgUAAGwAgQ4Ld4w4cPxcwpUAAICNINBBN+waVsqY6QoAQKgIdFB/T1rXjQ4x0xUAgEAR6CCpctiVEToAAMJEoIOkykzX4+dmdHZ6LulSAADAOhHoIKky01USh10BAAgQgQ6SuAUYAAAhI9BBknTlYK+yI/1cugQAgAAR6LAgn4045AoAQIAIdFiQz0V6dGJaM/OlpEsBAADrQKDDgkIuUqnsevjkZNKlAACAdSDQYQEzXQEACBOBDgv2XDGg4b4MM10BAAgMgQ4LUinT/lzETFcAAAJDoMMi+WykwycnVSp70qUAAIA6EeiwSD4X6cJcSU+cnk66FAAAUCcCHRYpxHeMOMB5dAAABINAh0X27RhWT9qY6QoAQEAIdFikN5PS9TuGmekKAEBACHS4TCHHLcAAAAgJgQ6XyWcjTUzO6tTkTNKlAACAOhDocJl8PDGCw64AAISBQIfL7M/GgY7DrgAABIFAh8uMDPToqisHuHQJAACBINBhWflspEMEOgAAgkCgw7Ly2RE9dnpa07PFpEsBAABrINBhWYVcJHfp8ElG6QAAaHcEOiyLma4AAISDQIdlZUf6tXVLDzNdAQAIAIEOyzIzFXIRM10BAAgAgQ4rymcjHT45qWKpnHQpAABgFQQ6rCifizRXLOsHz0wnXQoAAFgFgQ4rKuRGJEkHjp9LuBIAALAaAh1WdO32QfVmUsx0BQCgzRHosKJMOqUbdw0z0xUAgDZHoMOqCrlIB4+fl7snXQoAAFgBgQ6rymcjnb0wrxPnZpIuBQAArIBAh1VxxwgAANofgQ6runFXJDNxHh0AAG2MQIdVDfZldM22QS5dAgBAGyPQYU37cxEjdAAAtLGgA52ZXWVmf2tmh8zsgJm9NemaOlE+G+nomYs6d3E+6VIAAMAygg50koqS3ubu+yW9VNKvm1k+4Zo6TiGeGHGIUToAANpS0IHO3U+4+z/GryclHZK0O9mqOg8zXQEAaG9BB7paZrZX0g9Luj/ZSjrPjuF+bR/q4zw6AADaVEcEOjMbkvTnkn7L3S9LHWZ2h5mNm9n4xMRE6wvsAIVcpAOM0AEA0JaCD3Rm1qNKmPuUu39+uT7ufqe7j7n72OjoaGsL7BD5XKQjpyY1VywnXQoAAFgi6EBnZibpI5IOufsfJV1PJ8tnI82XXI+cmky6FAAAsETQgU7SyyX9a0mvNLMH48fPJF1UJyowMQIAgLaVSbqAzXD3b0qypOvoBs/ZNqgtvWkdOH5eb0i6GAAAsEjoI3RokXTKdOOuYWa6AgDQhgh0qFs+F+nQ8fNy96RLAQAANQh0qFshN6LJ2aKOnrmYdCkAAKAGgQ51y2fjiREnziVcCQAAqEWgQ91u2DWsdMqY6QoAQJsh0KFu/T1pXTc6yB0jAABoMwQ6rEs+GzHTFQCANkOgw7rkc5FOnJvRmem5pEsBAAAxAh3WpZAbkcQdIwAAaCcEOqzLfma6AgDQdgh0WJcrB3uVHelnhA4AgDZCoMO6FXIRM10BAGgjBDqsWz4b6dGJKc3Ml5IuBQAAiECHDcjnIpVdevjkZNKlAAAAEeiwAQszXbkeHQAAbYFAh3Xbc8WAhvsyOnCcma4AALQDAh3Wzcy0Pxcx0xUAgDZBoMOG5LORDp+cVKnsSZcCAEDXI9BhQwq5SBfmSnr89HTSpQAA0PUIdNiQfC6+YwSHXQEASByBDhuyb8ewetLGTFcAANoAgQ4b0ptJad+OYe4YAQBAGyDQYcPyzHQFAKAtEOiwYflspGemZjsmO7AAACAASURBVHVqcibpUgAA6GoEOmxYIZ4YwWFXAACSRaDDhu1npisAAG2BQIcNi/p7dNWVA8x0BQAgYQQ6bEohO6JDjNABAJAoAh02JZ+L9NjpaU3PFpMuBQCArkWgw6bks5HcpcMnGaUDACApBDpsSmE3EyMAAEgagQ6bsivq1xVberh0CQAACSLQYVPMrHLHCGa6AgCQGAIdNi2fjXT45KSKpXLSpQAA0JUIdNi0Qm5Ec8WyHp2YTroUAAC6EoEOm5av3jHixLmEKwEAoDsR6LBp124fVF8mxUxXAAASQqDDpmXSKd24a5iZrgAAJIRAh4aoznR196RLAQCg6xDo0BD5bKRnL8zrxLmZpEsBAKDrEOjQEPnciCRx2BUAgAQQ6NAQN+4alhm3AAMAIAkEOjTEYF9G12wb5NIlAAAkgECHhuEWYAAAJINAh4bJ5yIdPXNR5y7OJ10KAABdhUCHhslnK3eMOMQoHQAALUWgQ8Ms3AKMiREAALQUgQ4Ns2O4X6PDfVy6BACAFiPQoaHyWSZGAADQagQ6NFQ+F+nIqUnNFctJlwIAQNcg0KGhCrlI8yXX/3h6MulSAADoGgQ6NFR1piuHXQEAaB0CHRpq77ZBbelNM9MVAIAWItChoVIp0/5sRKADAKCFCHRouOpM13LZky4FAICuQKBDw+VzkaZmizp29mLSpQAA0BUIdGi4QvWOESfOJVwJAADdgUCHhnvuzmGlU8YdIwAAaBECHRquvyet60YHmRgBAECLEOjQFIXcCNeiAwCgRQh0aIp8NtKJczM6Mz2XdCkAAHQ8Ah2aIl+dGMFhVwAAmi6TdAHoTPlsJDPpt+/5rn7mh7K6fseQrtsxpF1Rv0aH+9Tfk066RAAA1lQuu05Pz+np8zM6NTmjp85e1KMT03p0Ykpv/JGr9Jrn55IuURKBDk1yxWCv3v/GF+qubz2uT3z7Cc0Wy4uWD/dlNDrcp+3Dfdo+1Kuov0fRQI+G+zKKBnoUDWQ03Be39Vfahvoy2tKbVk+agWUAQH3KZdfF+ZKmZ4uami3qwlxJU7NFTc8WNT1X0vmL8zp3cV7PXpiLn+f17MV5nb84r7MX5vTM1JxKSy6UP9ib1rWjQ5e1J8nc26eYVhgbG/Px8fGky+gqpbLr+LMX9ejElE6dn9XE1KwmJi89n56a1eRMUedn5jUzX15zez1p00BPWgO9aW3pzWigJ60tvdX3lbb+nurrSvtAT1p9mbT6Min1ZlI1z+mF9/09KfWm0+rrSak3nVp4zhAgAaChSmXXXLGsuVJ50fNssaSLcyXNzJc1Uyxpdr6ki/Px+/j54nylvfp+prpOsdLnwlxRF2YvhbYL8yXVE3X6e1LaOtCrrVt6NDLQs/C8Y7hfO6I+7Rju186oT9mRAe2M+mRmzd9RkszsAXcfW6sfI3RounTKdNWVW3TVlVvW7DtXLGtyZl7nZ4qV54vx88y8JmeKujhX+Z/7wlzlf+AL8yVdnLv0F9fE5KwuzFWX1/8/8lr1VwPeoiBYE/r6etLqTZsyqZQyaVNPOqWetCmTTqknFT9X21Ip9WRMPXHfTDq1zLrx61TNduI+vZnFfdMpU9pM6XT8nKo8Milr2S8cAK3n7iqVXcWya75UVqnsmi+5iuWyiqVKe7FUXmibL8X9S2XNx8+VPrXL4/7VZUu2USy5ZuMANl8TxOZL5Up7dVk1qBUr61aWlTRfcs3FtW5GbyalgZ60+ntS6u9Jqz+TVn9vWv2ZlEaH+jS4LRMf1cloqC+twb6MtvTFr3szGuyLH71pjQxUjgaFfioQgQ5tpTeT0rahPm0b6mvI9twrv0guzpUW/QU4M1/5pTO78Fxa+GVU/cUzWyzXtJUWltW2Vd+fuzgf/9Kr/MKbL5c1X7z0S7L6C3G+XN50wFwPMymTMqWsEvBScdC7FPpSSqVUebb4ObWkb01IXPSoCZG1/VPxspRJZpWfnbJKMLb4dWqZ5amUyeJlabv0+tKy5ddNpyptq267Znvp+OeYbGEfmSr9q/nX4nYt16dm35ourbNcW+372vWkpT/Lan7mCttd0kfSwnfJF977ovbL+/nq6y3pf6lluXVW2OYK7bXbcbnKLpXdK+99lfeqtrvKZS1+75Xtld0vratKv0XvV/lZi55V877sKrkvPJfKUqlcVinedql2ec3rYtkvLa/pe6ltybrxtpf+vGLJl1237FoU2Fqp+odi9ahGT7ryB25v/Fx9P9SXqbxe0l5Zx+J10vEyW/hDuScdh7NqSKsGtZ6UBnqrrytHWlIp/lhdikCHjmZmC78Y2kUp/mu6NvwVS9W2OAQWL7UXS5XQWftXdO261X9Eav+hKJWq/1AsfhTr7FP2+Llc+Zmlsmu2WFLJL/2jVorba7dXXbf6D2a5XPN64R/gxf+gAu1u0R8xqUt/oKSX/LFU7ZOqeb70R44W+vdmUpevW9123Lf2j6+ULX7OpKwyup9Kxa8rwSidWnxU4PJ+lVH9Re21RwcW3sfrx23pms+B9kWgA1qs8g9Be4XMpHjN6MjS4Fd2l5dXDoNlvzQSs9Ly2m2XypdGdaqHe1yu+L9FI0vui0evvLJg0ajTcn282lGXlvvS9XRpREw1y2tHuLxmm6r52bWjZItH/C6NOC55Wjjsfln/JestPTq/dL3V1lm6ba24vPKqOmqq2lHYhRHImlHZ2vc1o6xLn6sjsqbq+3hU1FZ5H/e1lBa/r9l2NZQBISDQAUhM9TBnSvyjCQCbwfQ9AACAwBHoAAAAAkegAwAACByBDgAAIHAEOgAAgMAR6AAAAAJHoAMAAAhc8IHOzG4ys4fN7IiZvSPpegAAAFot6EBnZmlJfyLp1ZLykn7ezPLJVgUAANBaQQc6SS+RdMTdf+Duc5I+I+nmhGsCAABoqdAD3W5JR2veH4vbFjGzO8xs3MzGJyYmWlYcAABAK4Qe6Ja7AaRf1uB+p7uPufvY6OhoC8oCAABondAD3TFJV9W83yPpeEK1AAAAJCL0QPffJe0zs2vMrFfSmyR9KeGaAAAAWiqTdAGb4e5FM/sNSV+TlJb0UXc/kHBZAAAALWXul51y1tHMbELSE0nX0QG2S3om6SK6EPs9Gez31mOfJ4P93npr7fPnuPuaEwC6LtChMcxs3N3Hkq6j27Dfk8F+bz32eTLY763XqH0e+jl0AAAAXY9ABwAAEDgCHTbqzqQL6FLs92Sw31uPfZ4M9nvrNWSfcw4dAABA4BihAwAACByBDgAAIHAEOlzGzG4ys4fN7IiZvWOZ5b9oZt+LH98ysxfUuy6Wt8l9/riZfd/MHjSz8dZWHrY69vvN8T5/0MzGzezH6l0Xy9vkPue7vkH1fl/N7EfMrGRmt6x3XSy2yX2+/u+6u/PgsfBQ5Y4bj0q6VlKvpO9Kyi/p8zJJV8SvXy3p/nrX5dHYfR6/f1zS9qQ/R2iPOvf7kC6da/x8SYfrXZdHY/d5/J7vepP2e02/b0j6iqRb1rMuj8bt87h93d91Ruiw1EskHXH3H7j7nKTPSLq5toO7f8vdz8Zvvy1pT73rYlmb2efYuHr2+5THv10lDUryetfFsjazz7Fx9X5ff1PSn0s6tYF1sdhm9vmGEOiw1G5JR2veH4vbVnK7pK9ucF1UbGafS5V/8P7azB4wszuaUF+nqmu/m9nrzOywpC9L+uX1rIvLbGafS3zXN2rN/W5muyW9TtKfrnddLGsz+1zawHc9s8FC0blsmbZl/0I2s1eoEi6q57jUvS4W2cw+l6SXu/txM9sh6W/M7LC739uEOjtNXfvd3b8g6Qtm9s8k/aGkn6h3XVxmM/tc4ru+UfXs9/dLeru7l8wWdee7vjGb2efSBr7rBDosdUzSVTXv90g6vrSTmT1f0oclvdrdT69nXVxmM/tc7n48fj5lZl9QZaiff+TWtq7vq7vfa2bXmdn29a6LBRve5+7+DN/1Datnv49J+kwcLLZL+hkzK9a5Li634X3u7n+xoe960icO8mivhyoh/weSrtGlEzkLS/pcLemIpJetd10eDd/ng5KGa15/S9JNSX+mEB517vfrdekE/RdJekqVv7z5rrd+n/Ndb+J+X9L/Ll2aFMF3vfX7fEPfdUbosIi7F83sNyR9TZXZNx919wNm9mvx8j+V9PuStkn6YPyXRdHdx1ZaN5EPEpDN7HNJO1U5NCVVfoF82t3/KoGPEZw69/vPSbrVzOYlXZT0Rq/8luW7vgGb2edmxnd9g+rc7+tatxV1h2wz+1wb/L3Orb8AAAACxyxXAACAwBHoAAAAAkegAwAACByBDgAAIHAEOgAAgMAR6AAAAAJHoAMAAAgcgQ4AACBwBDoAAIDAEegAAAACR6ADAAAIHIEOAAAgcAQ6AACAwBHoAAAAAkegAwAACByBDgAAIHAEOgAAgMAR6AAAAAJHoAMAAAhcJukCWm379u2+d+/epMsAAABY0wMPPPCMu4+u1a9pgc7MPirpNZJOufvz4rY3SHqXpP2SXuLu40vWuVrSQUnvcvf3xm0vlnSXpAFJX5H0Vnd3M+uT9HFJL5Z0WtIb3f3xterau3evxsfH1+oGAACQODN7op5+zTzkepekm5a0PSTp9ZLuXWGd90n66pK2D0m6Q9K++FHd5u2Szrr79fF679l8yQAAAOFpWqBz93slnVnSdsjdH16uv5m9VtIPJB2oactKitz9Pnd3VUbkXhsvvlnSx+LX90h6lZlZYz8FAABA+2uLSRFmNijp7ZL+YMmi3ZKO1bw/FrdVlx2VJHcvSjonadsK27/DzMbNbHxiYqKRpQMAACSuLQKdKkHufe4+taR9uRE3r2PZ4kb3O919zN3HRkfXPK8QAAAgKO0yy/WfSLrFzP6jpK2SymY2I+nPJe2p6bdH0vH49TFJV0k6ZmYZSSNacogXAACgG7RFoHP3f1p9bWbvkjTl7v85fj9pZi+VdL+kWyX9cdz1S5Juk3SfpFskfSM+zw4AAKCrNPOyJXdL+nFJ283smKR3qjKC9seSRiV92cwedPefXmNTb9Gly5Z8VZdmwX5E0ifM7Ei83Tc1+jMAAACEwLptUGtsbMy5Dh0AAAiBmT3g7mNr9WuXSREAAADYIAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASuaYHOzD5qZqfM7KGatjeY2QEzK5vZWE37T5rZA2b2/fj5lTXLXhy3HzGzD5iZxe19ZvbZuP1+M9vbrM8CAADQzpo5QneXpJuWtD0k6fWS7l3S/oykn3X3H5J0m6RP1Cz7kKQ7JO2LH9Vt3i7prLtfL+l9kt7TyOIBAABC0bRA5+73SjqzpO2Quz+8TN/vuPvx+O0BSf3xCFxWUuTu97m7S/q4pNfG/W6W9LH49T2SXlUdvQMAAOgm7XgO3c9J+o67z0raLelYzbJjcZvi56OS5O5FSeckbWthnQAAAG0hk3QBtcysoMqh05+qNi3TzetYtnS7d6hy2FZXX331JqsEAABoL20zQmdmeyR9QdKt7v5o3HxM0p6abnskHa9ZdlW8bkbSiJYc4q1y9zvdfczdx0ZHR5tRPgAAQGLaItCZ2VZJX5b0O+7+99V2dz8hadLMXhqfH3erpC/Gi7+kygQKSbpF0jfi8+wAAAC6SjMvW3K3pPsk3WBmx8zsdjN7nZkdk/Sjkr5sZl+Lu/+GpOsl/Z6ZPRg/dsTL3iLpw5KOSHpU0lfj9o9I2mZmRyT9r5Le0azPAgAA0M6s2wa1xsbGfHx8POkyAAAA1mRmD7j72Fr92uKQKwAAADaOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABI5ABwAAEDgCHQAAQOAIdAAAAIEj0AEAAASOQAcAABA4Ah0AAEDgCHQAAACBI9ABAAAEjkAHAAAQOAIdAABA4Ah0AAAAgSPQAQAABK5pgc7MPmpmp8zsoZq2N5jZATMrm9nYkv6/Y2ZHzOxhM/vpmvYXm9n342UfMDOL2/vM7LNx+/1mtrdZnwUAAKCdNXOE7i5JNy1pe0jS6yXdW9toZnlJb5JUiNf5oJml48UfknSHpH3xo7rN2yWddffrJb1P0nsa/xEAAADaX9MCnbvfK+nMkrZD7v7wMt1vlvQZd59198ckHZH0EjPLSorc/T53d0kfl/TamnU+Fr++R9KrqqN3AAAA3aRdzqHbLelozftjcdvu+PXS9kXruHtR0jlJ25bbuJndYWbjZjY+MTHR4NIBAACS1S6BbrmRNV+lfbV1Lm90v9Pdx9x9bHR0dIMlAgAAtKd2CXTHJF1V836PpONx+55l2hetY2YZSSNacogXAACgG7RLoPuSpDfFM1evUWXywz+4+wlJk2b20vj8uFslfbFmndvi17dI+kZ8nh0AAEBXyTRrw2Z2t6Qfl7TdzI5JeqcqI2h/LGlU0pfN7EF3/2l3P2Bmn5N0UFJR0q+7eyne1FtUmTE7IOmr8UOSPiLpE2Z2JN7um5r1WQAAANqZddug1tjYmI+PjyddBgAAwJrM7AF3H1urX7sccgUAAMAGEegAAAACR6ADAAAIHIEOAAAgcAQ6AACAwK162RIz+1Id2zjj7r/UmHIAAACwXmtdh26/pF9ZZblJ+pPGlQMAAID1WivQ/a67/91qHczsDxpYDwAAANZp1XPo3P1za22gnj4AAABonrpu/WVmY5J+V9Jz4nVMkrv785tYGwAAAOpQ771cPyXpf5f0fUnl5pUDAACA9ao30E24ez0zXgEAANBi9Qa6d5rZhyV9XdJstdHdP9+UqgAAAFC3egPd/yTpRkk9unTI1SUR6AAAABJWb6B7gbv/UFMrAQAAwIbUe+uvb5tZvqmVAAAAYEPqHaH7MUm3mdljqpxDx2VLAAAA2kS9ge6mplYBAACADasr0Ln7E80upFOcmpzRQ0+dS7oMAADQZNeNDuk52waTLkPSGoHOzP7R3V+02T7d5DtPPqtf/cQDSZcBAACa7B2vvlG/9s+vS7oMSWuP0O03s++tstwkjTSwnuC99Jpt+uKvvzzpMgAAQJNlR/qTLmHBWoHuxjq2UVqu0cw+Kuk1kk65+/PitislfVbSXkmPS/pX7n7WzHokfVjSi+KaPu7u747XebGkuyQNSPqKpLe6u5tZn6SPS3qxpNOS3ujuj9dRb1ONbOnRC7ZsTboMAADQRVa9bIm7P1HH49gKq9+lyydTvEPS1919nyp3nXhH3P4GSX3xte5eLOlXzWxvvOxDku6QtC9+VLd5u6Sz7n69pPdJek99HxkAAKCz1HsdunVz93slnVnSfLOkj8WvPybptdXukgbNLKPKSNycpPNmlpUUuft97u6qjMi9dplt3SPpVWZmTfkwAAAAbaxpgW4FO939hCTFzzvi9nskTUs6IelJSe919zOSdkuqHQE8Frcpfj4ab6so6Zykbcv9UDO7w8zGzWx8YmKisZ8IAAAgYXUFOjO77HDmcm2b8BJVzsXLSbpG0tvM7FpVJl0s5dUSVlm2uNH9Tncfc/ex0dHRRtQLAADQNuodofvJZdpevYGf93R8GFXx86m4/Rck/ZW7z7v7KUl/L2lMlRG5PTXr75F0PH59TNJV8bYyqsy2XXqIFwAAoOOtGujM7C1m9n1JN5jZ92oej0la7XImK/mSpNvi17dJ+mL8+klJr7SKQUkvlXQ4Piw7aWYvjc+Pu7Vmndpt3SLpG/F5dgAAAF1lrcuWfFrSVyW9W5dmpErSZHyO24rM7G5JPy5pu5kdk/ROSf9B0ufM7HZVQtwb4u5/IunPJD2kyqHUP3P3amB8iy5dtuSr8UOSPiLpE2Z2RJWRuTet8VkAAAA6kq02qBVfN25Fa4W6djQ2Nubj4+NJlwEAALAmM3vA3cfW6rfWCN0DWnkSgku6dgO1AQAAoIFWDXTufk2rCgEAAMDGrDVCt8DMrlDlTg0LNy6LLx4MAACABNUV6MzsVyS9VZXLhjyoyizU+yS9snmlAQAAoB71XofurZJ+RNIT7v4KST8siVsuAAAAtIF6A92Mu89Ikpn1ufthSTc0rywAAADUq95z6I6Z2VZJfyHpb8zsrC7dsQEAAAAJqivQufvr4pfvMrO/VeU2W3/VtKoAAABQt7pnuVa5+981oxAAAABsTL3n0AEAAKBNEegAAAACR6ADAAAIHIEOAAAgcAQ6AACAwBHoAAAAAkegAwAACByBDgAAIHAEOgAAgMAR6AAAAAJHoAMAAAgcgQ4AACBwTQt0ZvZRMztlZg/VtF1pZn9jZo/Ez1fULHu+md1nZgfM7Ptm1h+3vzh+f8TMPmBmFrf3mdln4/b7zWxvsz4LAABAO2vmCN1dkm5a0vYOSV93932Svh6/l5llJH1S0q+5e0HSj0uaj9f5kKQ7JO2LH9Vt3i7prLtfL+l9kt7TrA8CAADQzpoW6Nz9XklnljTfLOlj8euPSXpt/PqnJH3P3b8br3va3UtmlpUUuft97u6SPl6zTu227pH0quroHQAAQDdp9Tl0O939hCTFzzvi9udKcjP7mpn9o5n9dty+W9KxmvWPxW3VZUfjbRUlnZO0bbkfamZ3mNm4mY1PTEw09AMBAAAkLfP/t3f/UZaV9Z3v35/6hV5BgdBoh27TSMid+CO3wbqELBLi7yDJiK7oFWOQWaNDfsi6EjVOIxOHTOIENUImKys4CNxLEiLjHXBk/BHDApzETBamGhq6sTU0SrShpRuZCRCV/vW9f5xdzZmyqrqrqL2rT533a62zzt7PefY+z/c8i+ovz7OfvZe7AY0x4KeB/xP4LnBrko3AY7PUreZ9ttG4mqWMqroKuApgcnJy1jqSJEmDqusRuoebaVSa951N+Xbgv1XVI1X1XeBzwKlN+Zq+49cAD/Uds7Y51xjwHH5wileSJGnF6zqhuxk4v9k+H/h0s/0F4CeS/G9NcvazwFeaadnHk5zeXB/3tr5j+s/1RuC25jo7SZKkodLalGuST9BbrXpcku3AvwUuAz6Z5O3AN4E3AVTV/0hyOfB39KZNP1dVn21O9Wv0Vsw+E/h88wK4BvjTJNvojcyd21YskiRJh7MM26DW5ORkTU1NLXczJEmSDirJxqqaPFg9nxQhSZI04EzoJEmSBpwJnSRJ0oAzoZMkSRpwJnSSJEkDzoROkiRpwJnQSZIkDTgTOkmSpAFnQidJkjTgTOgkSZIGnAmdJEnSgDOhkyRJGnAmdJIkSQPOhE6SJGnAmdBJkiQNOBM6SZKkAWdCJ0mSNOBM6CRJkgacCZ0kSdKAM6GTJEkacK0ldEmuTbIzyZa+smOT3JLkvub9mBnHPD/JE0ne21f20iSbk2xL8odJ0pQfkeQ/NeV3JFnXViySJEmHszZH6P5f4KwZZRuAW6vqZODWZr/fFcDnZ5RdCVwAnNy8ps/5duB/VNWPNsd9aMlaLkmSNEBaS+iq6q+AR2cUnwNc12xfB7x++oMkrwe+DtzbV7YaeHZV/W1VFfAnfcf0n+s/A6+cHr2TJEkaJl1fQ/fcqtoB0LwfD5DkWcC/Bn57Rv0TgO19+9ubsunPvtWcay/wj8APtdZySZKkw9Thsijit4ErquqJGeWzjbjVIXz2v54kuSDJVJKpXbt2PY1mSpIkHX7GOv6+h5OsrqodzXTqzqb8J4E3JvkwcDSwP8n3gRuBNX3HrwEeara3A2uB7UnGgOfwg1O8AFTVVcBVAJOTk7MmfZIkSYOq6xG6m4Hzm+3zgU8DVNXPVNW6qloH/AHw76vqj5pp2ceTnN5cH/e26WNmnOuNwG3NdXaSJElDpbURuiSfAF4GHJdkO/BvgcuATyZ5O/BN4E2HcKpfo7di9pn0VsBOr4K9BvjTJNvojcydu5TtlyRJGhQZtkGtycnJmpqaWu5mSJIkHVSSjVU1ebB6h8uiCEmSJC2SCZ0kSdKAM6GTJEkacCZ0kiRJA86ETpIkacCZ0EmSJA04EzpJkqQBZ0InSZI04EzoJEmSBpwJnSRJ0oAzoZMkSRpwJnSSJEkDzoROkiRpwJnQSZIkDTgTOkmSpAFnQidJkjTgTOgkSZIGnAmdJEnSgDOhkyRJGnCpquVuQ6eS7AL+oeWvOQ54pOXvOJwNc/zDHDsMd/zGPryGOf5hjh26if9HqmrVwSoNXULXhSRTVTW53O1YLsMc/zDHDsMdv7EPZ+ww3PEPc+xweMXvlKskSdKAM6GTJEkacCZ07bhquRuwzIY5/mGOHYY7fmMfXsMc/zDHDodR/F5DJ0mSNOAcoZMkSRpwJnSSJEkDzoRuiSU5K8nXkmxLsmG529OGJA8k2ZxkU5KppuzYJLckua95P6av/sXN7/G1JD+3fC1fnCTXJtmZZEtf2YLjTfLS5nfbluQPk6TrWBZqjtgvTfJg0/+bkpzd99lKin1tktuTbE1yb5J3NeUrvu/niX1Y+v4ZSb6c5O4m/t9uyoeh7+eKfSj6HiDJaJK7knym2R+Mfq8qX0v0AkaB+4EXABPA3cALl7tdLcT5AHDcjLIPAxua7Q3Ah5rtFza/wxHAic3vM7rcMSww3jOBU4EtTyde4MvATwEBPg+8drljW2TslwLvnaXuSot9NXBqs30U8PdNjCu+7+eJfVj6PsCRzfY4cAdw+pD0/VyxD0XfN+1+N/DnwGea/YHod0foltZpwLaq+npV7QZuAM5Z5jZ15Rzgumb7OuD1feU3VNWTVfUNYBu932lgVNVfAY/OKF5QvElWA8+uqr+t3n/tf9J3zGFrjtjnstJi31FVdzbbjwNbgRMYgr6fJ/a5rJjYAarniWZ3vHkVw9H3c8U+lxUTO0CSNcDPA1f3FQ9Ev5vQLa0TY/x6mQAAIABJREFUgG/17W9n/j+Cg6qAv0yyMckFTdlzq2oH9P4xAI5vylfqb7LQeE9otmeWD6oLk9zTTMlOTz+s2NiTrANOoTdaMVR9PyN2GJK+b6bdNgE7gVuqamj6fo7YYTj6/g+A9wH7+8oGot9N6JbWbHPkK/G+MGdU1anAa4F3JjlznrrD8ptMmyvelfQ7XAmcBKwHdgAfbcpXZOxJjgRuBC6qqsfmqzpL2UDHP0vsQ9P3VbWvqtYDa+iNurx4nuorKv45Yl/xfZ/kF4CdVbXxUA+ZpWzZYjehW1rbgbV9+2uAh5apLa2pqoea953Ap+hNoT7cDDPTvO9sqq/U32Sh8W5vtmeWD5yqerj5g78f+DhPTaGvuNiTjNNLaK6vqpua4qHo+9liH6a+n1ZV/xP4InAWQ9L30/pjH5K+PwN4XZIH6F0y9Yokf8aA9LsJ3dL6O+DkJCcmmQDOBW5e5jYtqSTPSnLU9DbwGmALvTjPb6qdD3y62b4ZODfJEUlOBE6md7HooFtQvM0w/eNJTm9WO72t75iBMv2HrfEGev0PKyz2pq3XAFur6vK+j1Z8388V+xD1/aokRzfbzwReBXyV4ej7WWMfhr6vqourak1VraP37/dtVfXLDEq/t7niYhhfwNn0VoTdD1yy3O1pIb4X0FvVczdw73SMwA8BtwL3Ne/H9h1zSfN7fI0BWeU0I+ZP0Jti2EPv/7zevph4gUl6fwTvB/6I5kkth/Nrjtj/FNgM3EPvD9rqFRr7T9ObJrkH2NS8zh6Gvp8n9mHp+58A7mri3AJ8oCkfhr6fK/ah6Pu+tr+Mp1a5DkS/++gvSZKkAeeUqyRJ0oAzoZMkSRpwJnSSJEkDzoROkiRpwJnQSZIkDTgTOkmSpAFnQidJ80hydJJfn+OzdUm+1zz3cr5zXJ/k0SRvbKeVkoadCZ0kze9oYNaErnF/9Z57Oaeqeisr7Kkxkg4vJnSSNL/LgJOSbErykfkqNo/G+2ySu5NsSfLmjtooaciNLXcDJOkwtwF48cFG4RpnAQ9V1c8DJHlOqy2TpIYjdJK0dDYDr0ryoSQ/U1X/uNwNkjQcTOgkaYlU1d8DL6WX2P1ekg8sc5MkDQmnXCVpfo8DRx1KxSQ/DDxaVX+W5AngX7TZMEmaZkInSfOoqu8k+ZskW4DPV9VvzlP9JcBHkuwH9gC/1kkjJQ09EzpJOoiq+qVDrPcF4AstN0eSfoDX0EnS4u0DnnMoNxYGfhb4fietkjR0UlXL3QZJkiQ9DY7QSZIkDTgTOkmSpAE3dIsijjvuuFq3bt1yN0OSJOmgNm7c+EhVrTpYvU4SuiRrgT8BngfsB66qqv+Q5FLgXwG7mqrvr6rPJXk1vecnTgC7gd+sqttmOe+sx8/XlnXr1jE1NfX0g5IkSWpZkn84lHpdjdDtBd5TVXcmOQrYmOSW5rMrqur3Z9R/BPjnVfVQkhfTuw3ACXOce7bjJUmShkYnCV1V7QB2NNuPJ9nK3AkaVXVX3+69wDOSHFFVT7bbUkmSpMHT+aKIJOuAU4A7mqILk9yT5Nokx8xyyC8Cd82TzB3seEmSpBWt04QuyZHAjcBFVfUYcCVwErCe3gjeR2fUfxHwIeBX5jjlvMf3neeCJFNJpnbt2jVbFUmSpIHVWUKXZJxeMnd9Vd0EUFUPV9W+qtoPfBw4ra/+GuBTwNuq6v7Zzjnf8TPqXVVVk1U1uWrVQReKPC3/9ORetu18gu/v2dfq90iSJE3rJKFLEuAaYGtVXd5Xvrqv2huALU350cBngYur6m/mOe+sxy+nv77vEV51+X/j/l1PLHdTJEnSkOhqlesZwHnA5r5nHr4feEuS9UABD/DU1OqFwI8Cv5Xkt5qy11TVziRXAx+rqingw3Mcv2yOGOvlyHv2+Ug1SZLUja5WuX4JyCwfzXrPuKr6XeB35/jsHX3b5y1JA5fQ+Oh0Qrd/mVsiSZKGhY/+WmLjo728dfdeEzpJktQNE7olNt5Mue52hE6SJHXEhG6JTUxPuTpCJ0mSOmJCt8QmXBQhSZI6ZkK3xKYXReze533oJElSN0zoltiBEbq9jtBJkqRumNAtsQOrXF0UIUmSOmJCt8QmvA+dJEnqmAndEjtwDZ2rXCVJUkdM6JbYU6tcTegkSVI3TOiW2NjI9DV0LoqQJEndMKFbYkmYGB1xylWSJHXGhK4F46NxylWSJHXGhK4FE2MjJnSSJKkzJnQtGB81oZMkSd0xoWvB+OgIT3oNnSRJ6ogJXQt6U66ucpUkSd3oJKFLsjbJ7Um2Jrk3ybua8kuTPJhkU/M6u++Yi5NsS/K1JD83x3mPTXJLkvua92O6iOdgJkZH2OMInSRJ6khXI3R7gfdU1Y8DpwPvTPLC5rMrqmp98/ocQPPZucCLgLOAP04yOst5NwC3VtXJwK3N/rIbH3OVqyRJ6k4nCV1V7aiqO5vtx4GtwAnzHHIOcENVPVlV3wC2AafNUe+6Zvs64PVL1+rFGx8dYbcJnSRJ6kjn19AlWQecAtzRFF2Y5J4k1/ZNmZ4AfKvvsO3MngA+t6p2QC9pBI5vpdEL5I2FJUlSlzpN6JIcCdwIXFRVjwFXAicB64EdwEenq85y+KJXGSS5IMlUkqldu3Yt9jSHzPvQSZKkLnWW0CUZp5fMXV9VNwFU1cNVta+q9gMf56lp1e3A2r7D1wAPzXLah5Osbs6/Gtg523dX1VVVNVlVk6tWrVqagObRuw+dq1wlSVI3ulrlGuAaYGtVXd5Xvrqv2huALc32zcC5SY5IciJwMvDlWU59M3B+s30+8OmlbvtijI/GKVdJktSZsY6+5wzgPGBzkk1N2fuBtyRZT2869QHgVwCq6t4knwS+Qm+F7Durah9AkquBj1XVFHAZ8Mkkbwe+Cbypo3jmNTE26pSrJEnqTCcJXVV9idmvi/vcPMd8EPjgLOXv6Nv+DvDKpWjjUhofjatcJUlSZ3xSRAtc5SpJkrpkQteC3qIIEzpJktQNE7oW+CxXSZLUJRO6FvikCEmS1CUTuhZMNLctqXKUTpIktc+ErgXjo72fde9+EzpJktQ+E7oWTIz1flYXRkiSpC6Y0LVgeoRuz15H6CRJUvtM6Fow3ozQPblv3zK3RJIkDQMTuhYcMT1C561LJElSB0zoWjA+1nvK2R6fFiFJkjpgQteCA9fQuShCkiR1wISuBdMJ3ZOO0EmSpA6Y0LXA25ZIkqQumdC1YMJFEZIkqUMmdC3wGjpJktQlE7oWjI/2Vrnu9ho6SZLUgU4SuiRrk9yeZGuSe5O8a8bn701SSY5r9t+aZFPfa3+S9bOc99IkD/bVO7uLeA5m+hq63Y7QSZKkDox19D17gfdU1Z1JjgI2Jrmlqr6SZC3wauCb05Wr6nrgeoAkLwE+XVWb5jj3FVX1+y23f0EmnHKVJEkd6mSErqp2VNWdzfbjwFbghObjK4D3AXOtIHgL8InWG7mEpq+hc8pVkiR1ofNr6JKsA04B7kjyOuDBqrp7nkPezPwJ3YVJ7klybZJj5vjOC5JMJZnatWvXYpt+yMa9bYkkSepQpwldkiOBG4GL6E3DXgJ8YJ76Pwl8t6q2zFHlSuAkYD2wA/jobJWq6qqqmqyqyVWrVj2NCA7N9JTrbm9bIkmSOtBZQpdknF4yd31V3UQvETsRuDvJA8Aa4M4kz+s77FzmGZ2rqoeral9V7Qc+DpzWVvsX4sA1dE65SpKkDnSyKCJJgGuArVV1OUBVbQaO76vzADBZVY80+yPAm4Az5znv6qra0ey+AZhrJK9T42PNbUuccpUkSR3oaoTuDOA84BULuMXImcD2qvp6f2GSq5NMNrsfTrI5yT3Ay4HfWPKWL4IjdJIkqUudjNBV1ZeAHKTOuhn7XwROn6XeO/q2z1uaFi6t0ZGQuChCkiR1wydFtCAJ46MjLoqQJEmdMKFrycToiPehkyRJnTCha8nE2IhTrpIkqRMmdC0ZH40JnSRJ6sQhL4pI8oeHUO2xqvo3T6M9K0bvGjoTOkmS1L6FrHI9h3me6tDYAJjQ4TV0kiSpOwtJ6K6oquvmqzDXs1SHkdfQSZKkrhzyNXRV9QdLUWdYjI+OsMfblkiSpA4seFFEkg8neXaS8SS3JnkkyS+30bhBNj4ap1wlSVInFrPK9TVV9RjwC8B24MeA31zSVq0AE2MuipAkSd1YTEI33ryfDXyiqh5dwvasGL0pVxM6SZLUvsU8y/W/Jvkq8D3g15OsAr6/tM0afBMmdJIkqSOHPEKXZDVAVW0AfgqYrKo9wHfp3dJEfca9bYkkSerIQkborm1uS/JF4C+ALwFU1T8B/7T0TRtsvduWuMpVkiS175ATuqp6bZJnAC8D3gD8fpJv0kvu/qKqvtlOEweTI3SSJKkrC7qGrqq+T5PAASQ5EXgt8EdJnldVpy19EwfTxJjPcpUkSd1YzKIIAJI8G/hH4Ibm9cRSNWol8FmukiSpK4u5sfCvJHkYuAfY2Lymqmr3PMesTXJ7kq1J7k3yrhmfvzdJJTmu2V+X5HtJNjWvj81x3mOT3JLkvub9sHn02MToCHuccpUkSR1YzAjde4EXVdUjCzhmL/CeqrozyVHAxiS3VNVXkqwFXg3MvAbv/qpaf5DzbgBurarLkmxo9v/1AtrVmnEXRUiSpI4s5sbC99O7Vckhq6odVXVns/04sBU4ofn4CuB9wGKyn3OA65rt64DXL+IcrZiecq0yqZMkSe1azAjdxcB/T3IH8OR0YVX934dycJJ1wCnAHUleBzxYVXcnmVn1xCR3AY8B/6aq/nqW0z23qnY0378jyfFzfOcFwAUAz3/+8w+lmU/bxGgvnj37iomxH4hNkiRpySwmofuPwG3AZmBBF4klORK4EbiI3jTsJcBrZqm6A3h+VX0nyUuB/5LkRc0zZBesqq4CrgKYnJzsZMhsYqw3+Lln3/4D25IkSW1YTEK3t6revdCDkozTS+aur6qbkrwEOBGYHp1bA9yZ5LSq+jbN6F9VbUxyP/BjwNSM0z6cZHUzOrca2LmIeFoxPvpUQidJktSmxQwd3Z7kgiSrm1WmxyY5dr4D0svYrgG2VtXlAFW1uaqOr6p1VbUO2A6cWlXfTrIqyWhz7AuAk4Gvz3Lqm4Hzm+3zgU8vIp5WTCd03rpEkiS1bTEjdL/UvF/cV1bAC+Y55gzgPGBzkk1N2fur6nNz1D8T+HdJ9gL7gF+tqkcBklwNfKyqpoDLgE8meTu9VbJvWkQ8rZieZvVpEZIkqW0LTuiq6sRFHPMlYN6VAc0o3fT2jfSmZ2er946+7e8Ar1xoe7owcWDK1VWukiSpXYc85Zrk1KWoMyy8hk6SJHVlISN0/0+SlzH/SNs19G5JMvTGm9uWOOUqSZLatpCE7jn0HvM1X0K36+k1Z+U4cA2dI3SSJKllh5zQ9V/jpoM7cA2dI3SSJKll3vG2JeNjLoqQJEndMKFryVP3odu3zC2RJEkrnQldS6anXHfvdYROkiS1ayG3LfmRJM/p2395kv+Q5N1JJtpp3uCaGOutHfG2JZIkqW0LGaH7JPAsgCTrgf+P3tMZ/g/gj5e+aYPN+9BJkqSuLOS2Jc+sqoea7V8Grq2qjyYZATbNc9xQOnANnatcJUlSyxYyQtd//7lXALcCVJUZyywmxhyhkyRJ3VjICN1tST4J7ACOAW4DSLIa2N1C2wbaU6tcXRQhSZLatZCE7iLgzcBq4Kerak9T/jzg/UvdsEE34TV0kiSpIwt5UkQBN8zy0bOAc4C/XKpGrQQHHv3lNXSSJKllCxmhO6BZ5fpLwP8FfAO4cSkbtRKMjoSROEInSZLad8gJXZIfA84F3gJ8B/hPQKrq5S21beCNj46w24ROkiS1bCEjdF8F/hr451W1DSDJb7TSqhViYnTEKVdJktS6hdy25BeBbwO3J/l4klfyv97KZE5J1ia5PcnWJPcmedeMz9+bpJIc1+y/OsnGJJub91fMcd5LkzyYZFPzOnsB8bRuYmzEKVdJktS6hYzQ/deq+lSSZwGvB34DeG6SK4FPVdV8iyL2Au+pqjuTHAVsTHJLVX0lyVrg1fSeOjHtEXojgQ8leTHwBeCEOc59RVX9/gLi6Mz46Ah7fJarJElq2UJG6L4MUFX/VFXXV9UvAGvoPSViw3wHVtWOqrqz2X4c2MpTCdoVwPuA6qt/V99TKe4FnpHkiAW09bAwPhZH6CRJUusW+6QIAKrq0ar6j1U165TorCdJ1gGnAHckeR3wYFXdPc8hvwjcVVVPzvH5hUnuSXJtkmPm+M4Lkkwlmdq1a9ehNvVpGx8d4UkTOkmS1LKFTLmuSvLuuT6sqssPdoIkR9K7xclF9KZhLwFeM0/9FwEfmqfOlcDv0Bvd+x3go8C/nKVtVwFXAUxOTnY2BzoxOsIeF0VIkqSWLSShGwWO5BAXQsyUZJxeMnd9Vd2U5CXAicDdSaA3fXtnktOq6ttJ1gCfAt5WVffPds6qerjv/B8HPrOYtrXFRRGSJKkLC0nodlTVv1vMl6SXsV0DbJ0eyauqzcDxfXUeACar6pEkRwOfBS6uqr+Z57yrq2pHs/sGYMti2teW8dER9vgsV0mS1LKndQ3dApwBnAe84hBvMXIh8KPAb/XVPx4gydVJJpt6H25ubXIP8HJ6K28PG96HTpIkdWEhI3SvXOyXVNWXOEhCWFXr+rZ/F/jdOeq9o2/7vMW2qQvjYyN873t7lrsZkiRphTvkEbqqerTNhqxEE6PetkSSJLVvIVOuWqDeNXQmdJIkqV0mdC2aGPMaOkmS1D4Tuha5ylWSJHXBhK5F46Mj7HbKVZIktcyErkUuipAkSV0woWuR19BJkqQumNC1yFWukiSpCyZ0LZpeFFHlwghJktQeE7oWTYz1fl4XRkiSpDaZ0LVoYrT383rrEkmS1CYTuhaNj/YeX7vHhRGSJKlFJnQtGh+bHqEzoZMkSe0xoWvR9JTrk47QSZKkFpnQtWjCETpJktQBE7oWjbsoQpIkdcCErkVPJXSO0EmSpPZ0ktAlWZvk9iRbk9yb5F0zPn9vkkpyXF/ZxUm2Jflakp+b47zHJrklyX3N+zFtx7IQ01OuXkMnSZLa1NUI3V7gPVX148DpwDuTvBB6yR7wauCb05Wbz84FXgScBfxxktFZzrsBuLWqTgZubfYPGwduW+IInSRJalEnCV1V7aiqO5vtx4GtwAnNx1cA7wP6LzQ7B7ihqp6sqm8A24DTZjn1OcB1zfZ1wOtbaP6iTTjlKkmSOtD5NXRJ1gGnAHckeR3wYFXdPaPaCcC3+va381QC2O+5VbUDekkjcPwc33lBkqkkU7t27XqaERw6r6GTJEld6DShS3IkcCNwEb1p2EuAD8xWdZayRS8VraqrqmqyqiZXrVq12NMs2IFnuXoNnSRJalFnCV2ScXrJ3PVVdRNwEnAicHeSB4A1wJ1JnkdvRG5t3+FrgIdmOe3DSVY3518N7GwvgoWbHqHb7W1LJElSi7pa5RrgGmBrVV0OUFWbq+r4qlpXVevoJXGnVtW3gZuBc5MckeRE4GTgy7Oc+mbg/Gb7fODTLYeyIAeuoXOETpIktairEbozgPOAVyTZ1LzOnqtyVd0LfBL4CvAXwDurah9AkquTTDZVLwNeneQ+eitlL2sziIUaH+vNHO/2GjpJktSisS6+pKq+xOzXxfXXWTdj/4PAB2ep946+7e8Ar1yaVi49V7lKkqQu+KSIFo27KEKSJHXAhK5FEz7LVZIkdcCErkUHVrk6QidJklpkQtei0ZEwOhKvoZMkSa0yoWvZ+KgJnSRJapcJXcvGR0e8bYkkSWqVCV3Ljhgb8Ro6SZLUKhO6lo2PjjjlKkmSWmVC17JeQudtSyRJUntM6Fo2PhqvoZMkSa0yoWvZxNio19BJkqRWmdC1bMLblkiSpJaZ0LXMRRGSJKltJnQtGx8dYc9eF0VIkqT2mNC1bGJshCcdoZMkSS0yoWtZb4TOhE6SJLXHhK5lE2MuipAkSe3qJKFLsjbJ7Um2Jrk3ybua8t9Jck+STUn+MskPN+VvbcqmX/uTrJ/lvJcmebCv3tldxLMQEz7LVZIktayrEbq9wHuq6seB04F3Jnkh8JGq+omqWg98BvgAQFVdX1Xrm/LzgAeqatMc575ium5Vfa6DWBbEKVdJktS2ThK6qtpRVXc2248DW4ETquqxvmrPAmZbDvoW4BPtt7Id42Mj7PbRX5IkqUWdX0OXZB1wCnBHs//BJN8C3kozQjfDm5k/obuwmba9Nskxc3znBUmmkkzt2rXrabV/oSa8D50kSWpZpwldkiOBG4GLpkfnquqSqloLXA9cOKP+TwLfraotc5zySuAkYD2wA/jobJWq6qqqmqyqyVWrVi1NMIdoYmzER39JkqRWdZbQJRmnl8xdX1U3zVLlz4FfnFF2LvOMzlXVw1W1r6r2Ax8HTluq9i6VcR/9JUmSWtbVKtcA1wBbq+ryvvKT+6q9Dvhq32cjwJuAG+Y57+q+3TcAc43kLZvx0RH27i/27/c6OkmS1I6xjr7nDHqrVTcnmV6t+n7g7Un+d2A/8A/Ar/Ydcyawvaq+3n+iJFcDH6uqKeDDze1MCngA+JVWo1iE8dFezrxn/36OGBld5tZIkqSVqJOErqq+BGSWj+a8zUhVfZHeLU5mlr+jb/u8pWhfm44Y6yV0u/fu54gxEzpJkrT0fFJEyw6M0HnrEkmS1JKuplyH1nRC9+TefV5HJ0nSCpJAb5nA8jOha9mxz5oA4Kd+77ZlbokkSVpKG177z/jVnz1puZsBmNC17uX/bBWXnP3jfHf3vuVuiiRJWkKTPzLr8wyWhQldy44YG+VfnfmC5W6GJElawVwUIUmSNOBM6CRJkgacCZ0kSdKAM6GTJEkacCZ0kiRJA86ETpIkacClarieXpBkF/APLX/NccAjLX/H4WyY4x/m2GG44zf24TXM8Q9z7NBN/D9SVasOVmnoErouJJmqqsnlbsdyGeb4hzl2GO74jX04Y4fhjn+YY4fDK36nXCVJkgacCZ0kSdKAM6Frx1XL3YBlNszxD3PsMNzxG/vwGub4hzl2OIzi9xo6SZKkAecInSRJ0oAzoZMkSRpwJnRLLMlZSb6WZFuSDcvdnjYkeSDJ5iSbkkw1ZccmuSXJfc37MX31L25+j68l+bnla/niJLk2yc4kW/rKFhxvkpc2v9u2JH+YJF3HslBzxH5pkgeb/t+U5Oy+z1ZS7GuT3J5ka5J7k7yrKV/xfT9P7MPS989I8uUkdzfx/3ZTPgx9P1fsQ9H3AElGk9yV5DPN/mD0e1X5WqIXMArcD7wAmADuBl643O1qIc4HgONmlH0Y2NBsbwA+1Gy/sPkdjgBObH6f0eWOYYHxngmcCmx5OvECXwZ+CgjweeC1yx3bImO/FHjvLHVXWuyrgVOb7aOAv29iXPF9P0/sw9L3AY5stseBO4DTh6Tv54p9KPq+afe7gT8HPtPsD0S/O0K3tE4DtlXV16tqN3ADcM4yt6kr5wDXNdvXAa/vK7+hqp6sqm8A2+j9TgOjqv4KeHRG8YLiTbIaeHZV/W31/mv/k75jDltzxD6XlRb7jqq6s9l+HNgKnMAQ9P08sc9lxcQOUD1PNLvjzasYjr6fK/a5rJjYAZKsAX4euLqveCD63YRuaZ0AfKtvfzvz/xEcVAX8ZZKNSS5oyp5bVTug948BcHxTvlJ/k4XGe0KzPbN8UF2Y5J5mSnZ6+mHFxp5kHXAKvdGKoer7GbHDkPR9M+22CdgJ3FJVQ9P3c8QOw9H3fwC8D9jfVzYQ/W5Ct7RmmyNfifeFOaOqTgVeC7wzyZnz1B2W32TaXPGupN/hSuAkYD2wA/hoU74iY09yJHAjcFFVPTZf1VnKBjr+WWIfmr6vqn1VtR5YQ2/U5cXzVF9R8c8R+4rv+yS/AOysqo2HesgsZcsWuwnd0toOrO3bXwM8tExtaU1VPdS87wQ+RW8K9eFmmJnmfWdTfaX+JguNd3uzPbN84FTVw80f/P3Ax3lqCn3FxZ5knF5Cc31V3dQUD0Xfzxb7MPX9tKr6n8AXgbMYkr6f1h/7kPT9GcDrkjxA75KpVyT5Mwak303oltbfAScnOTHJBHAucPMyt2lJJXlWkqOmt4HXAFvoxXl+U+184NPN9s3AuUmOSHIicDK9i0UH3YLibYbpH09yerPa6W19xwyU6T9sjTfQ639YYbE3bb0G2FpVl/d9tOL7fq7Yh6jvVyU5utl+JvAq4KsMR9/PGvsw9H1VXVxVa6pqHb1/v2+rql9mUPq9zRUXw/gCzqa3Iux+4JLlbk8L8b2A3qqeu4F7p2MEfgi4FbiveT+275hLmt/jawzIKqcZMX+C3hTDHnr/5/X2xcQLTNL7I3g/8Ec0T2o5nF9zxP6nwGbgHnp/0Fav0Nh/mt40yT3ApuZ19jD0/TyxD0vf/wRwVxPnFuADTfkw9P1csQ9F3/e1/WU8tcp1IPrdR39JkiQNOKdcJUmSBpwJnSRJ0oAzoZMkSRpwJnSSJEkDzoROkiRpwJnQSZIkDTgTOkmaR5Kjk/z6HJ+tS/K95rmX853j+iSPJnljO62UNOxM6CRpfkcDsyZ0jfur99zLOVXVW1lhT42RdHgxoZOk+V0GnJRkU5KPzFexeTTeZ5PcnWRLkjd31EZJQ25suRsgSYe5DcCLDzYK1zgLeKiqfh4gyXNabZkkNRyhk6Slsxl4VZIPJfmZqvrH5W6QpOFgQidJS6Sq/h54Kb3E7veSfGCZmyRpSDjlKknzexw46lAqJvlh4NGq+rMkTwD/os2GSdI0EzpJmkdVfSfJ3yTZAny+qn5znuovAT6SZD+wB/i1ThopaeiZ0EnSQVTVLx1ivS8AX2i5OZL0A7yGTpIWbx/wnEO5sTDws8D3O2mVpKGTqlruNkiSJOlpcIROkiRpwJnQSZIkDTgTOkmSpAGCfQ7PAAAADUlEQVRnQidJkjTg/n9JHu8OXIPIGwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "# plot\n", + "import matplotlib.pyplot as plt\n", + "\n", + "for idx, acid in enumerate(bs.traf.id):\n", + " fig = plt.figure(figsize=(10, 15))\n", + " ax1 = plt.subplot2grid((4, 1), (0, 0), rowspan=2)\n", + " ax2 = plt.subplot2grid((4, 1), (2, 0))\n", + " ax3 = plt.subplot2grid((4, 1), (3, 0))\n", + "\n", + " ax1.plot(res[:, 1, idx], res[:, 0, idx])\n", + "\n", + " ax2.plot(t, res[:, 2, idx])\n", + " ax2.set_xlabel('t [s]')\n", + " ax2.set_ylabel('alt [m]')\n", + "\n", + " ax3.plot(t, res[:, 3, idx])\n", + " ax3.set_xlabel('t [s]')\n", + " ax3.set_ylabel('TAS [m/s]')\n", + " \n", + " fig.suptitle(f'Trajectory {acid}')\n", + "\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "interpreter": { + "hash": "ea503857f36d21b450d152aa103b3c6b332184b2225df50c3964ea06a7d8f5ea" + }, + "kernelspec": { + "display_name": "Python 3.6.15 64-bit", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.15" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/extra/CRmethods/Difgame.py b/extra/CRmethods/Difgame.py index 18f66a4204..e84bb4dd6c 100644 --- a/extra/CRmethods/Difgame.py +++ b/extra/CRmethods/Difgame.py @@ -108,7 +108,7 @@ def resolve(dbconf, traf): np.where(bankcontrol>0,traf.trk+90,traf.trk-90))%360 # Set bank angle for aircraft in conflict - traf.aphi=np.radians(np.where(dbconf.asasactive,np.abs(bankcontrol),25.)) + traf.ap.aphi=np.radians(np.where(dbconf.asasactive,np.abs(bankcontrol),25.)) # Change autopilot desired altitude traf.aalt=np.where(climbcontrol==0,traf.alt,\ diff --git a/plugins/trafgenclasses.py b/plugins/trafgenclasses.py index c4a4eeae2f..5287dd88a7 100644 --- a/plugins/trafgenclasses.py +++ b/plugins/trafgenclasses.py @@ -5,7 +5,7 @@ from bluesky import stack,traf,sim,tools,navdb from bluesky.tools.position import txt2pos from bluesky.tools.geo import kwikqdrdist,kwikpos,latlondist,qdrdist -from bluesky.tools.misc import degto180,txt2alt,txt2tas +from bluesky.tools.misc import degto180,txt2alt,txt2spd from bluesky.tools.aero import nm,ft # Default values @@ -163,11 +163,11 @@ def setalt(self,cmdargs): def setspd(self,cmdargs): if len(cmdargs)==1: - spd = txt2tas(cmdargs[0]) + spd = txt2spd(cmdargs[0]) self.startspdmin = spd self.startapdmax = spd elif len(cmdargs)>1: - spd0,spd1 = txt2tas(cmdargs[0]),txt2tas(cmdargs[1]) + spd0,spd1 = txt2spd(cmdargs[0]),txt2spd(cmdargs[1]) self.startspdmin = min(spd0,spd1) self.startspdmax = max(spd0,spd1) else: @@ -597,11 +597,11 @@ def setalt(self,cmdargs): def setspd(self,cmdargs): if len(cmdargs)==1: - spd = txt2tas(cmdargs[0]) + spd = txt2spd(cmdargs[0]) self.startspdmin = spd self.startapdmax = spd elif len(cmdargs)>1: - spd0,spd1 = txt2tas(cmdargs[0]),txt2tas(cmdargs[1]) + spd0,spd1 = txt2spd(cmdargs[0]),txt2spd(cmdargs[1]) self.startspdmin = min(spd0,spd1) self.startspdmax = max(spd0,spd1) else: diff --git a/scenario/LNAV_VNAV/LNAV-VNAV-TwoRoutesFlights.scn b/scenario/LNAV_VNAV/LNAV-VNAV-TwoRoutesFlights.scn index 2febbdad13..3c3dd8bf2b 100644 --- a/scenario/LNAV_VNAV/LNAV-VNAV-TwoRoutesFlights.scn +++ b/scenario/LNAV_VNAV/LNAV-VNAV-TwoRoutesFlights.scn @@ -3,12 +3,13 @@ # Test VNAV mode # Land in EHAM, runway 06 -00:00:00.00>CRE KL204,A320,EHAM,RWY18L,*,0,0 +00:00:00.00>CRE KL204,A320,EHAM,RWY18L,,0,0 00:00:00.00>ORIG KL204 EHAM RWY18L 00:00:00.00>DEST KL204 EHBK RWY21 00:00:00.00>SPD KL204 250 00:00:00.00>ALT KL204 3000 00:00:00.00>ADDWPT KL204 NV +00:00:00.00>KL204 AT NV STACK KL204 VNAV ON 00:00:00.00>ADDWPT KL204 RR, FL50 00:00:00.00>ADDWPT KL204 EHN, 3000 #0:00:00.00>ADDWPT KL204 THN, 2000,200 @@ -21,7 +22,7 @@ # A flight from EHAM 18L to Madrid (LEMD) -00:00:00.00>CRE KLM1705,A320,EHAM,RWY18L,*,0,0 +00:00:00.00>CRE KLM1705,A320,EHAM,RWY18L,,0,0 00:00:00.00>SPD KLM1705 250 00:00:00.00>ALT KLM1705 300 @@ -34,15 +35,16 @@ 00:00:00.00>KLM1705 ADDWPT 52'12'51,004'48'28 00:00:00.00>KLM1705 ADDWPT LEKKO FL100 -00:00:00.00>KLM1705 ADDWPT LARAS +00:00:00.00>KLM1705 AT LEKKO STACK KLM1705 VNAV ON +00:00:00.00>KLM1705 ADDWPT LARAS,,275 00:00:00.00>KLM1705 ADDWPT WOODY 00:00:00.00>KLM1705 ADDWPT NIK 00:00:00.00>KLM1705 ADDWPT DENOX 00:00:00.00>KLM1705 ADDWPT CIV,,0.80 00:00:00.00>KLM1705 ADDWPT MEDIL -00:00:00.00>KLM1705 ADDWPT KOVIN,FL350 +00:00:00.00>KLM1705 ADDWPT KOVIN,FL330 00:00:00.00>KLM1705 ADDWPT PON -00:00:00.00>KLM1705 ADDWPT DITAL,FL370 +00:00:00.00>KLM1705 ADDWPT DITAL,FL350 00:00:00.00>KLM1705 ADDWPT GODIX 00:00:00.00>KLM1705 ADDWPT DIDAK 00:00:00.00>KLM1705 ADDWPT KURIS @@ -52,7 +54,7 @@ 00:00:00.00>KLM1705 ADDWPT NEA # ToD -00:00:00.00>KLM1705 ADDWPT ORBIS,FL190,250 +00:00:00.00>KLM1705 ADDWPT ORBIS,FL100,250 00:00:00.00>KLM1705 ADDWPT LEMD RW18R @@ -65,6 +67,6 @@ # Switch VNAV on #(LNAV is already on due to ADDWPT) -#00:00:00.00>KLM1705 VNAV on -#00:00:00.00>pos klm1705 +00:00:00.00>KLM1705 VNAV on +00:00:00.00>pos klm1705