Skip to content

Commit

Permalink
Centralise reference data in bs.ref
Browse files Browse the repository at this point in the history
  • Loading branch information
jooste committed Jan 15, 2025
1 parent 8bea773 commit c768cb4
Show file tree
Hide file tree
Showing 12 changed files with 179 additions and 146 deletions.
18 changes: 7 additions & 11 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
# TODO items
## Core
core.base.reset: call Base.reset for each derived class?
client-side reset is not called for everything?

## Network branch
- Move ECHO to client
- What to do with sharedstates that make changes both ways (like pan/zoom)? Is there more than pan/zoom? Otherwise a different approach can also be taken
Where are pan/zoom used in sim?
= implemented in screen(io).getviewctr() and getviewbounds()
- MCRE
- waypoint lookup
Possible approach: add explicit (but optional?) reflat/reflon args to these stack functions, with stack implementations on both sim and client side. The stack function on client side adds pan/zoom as ref if no ref specified, and then forwards command to sim
This also makes these commands (MCRE / WPT) more suitable to script in a
scenario file
- PAN/ZOOM as scenario init commands in SCN file.
=> Store client-stack commands for late client joiners?
=> OR: copy entire scenario stack on join / SCN load. Can also be used to show stack in UI
- PAN/ZOOM as scenario init commands in SCN file.
=> Store client-stack commands for late client joiners?
=> OR: copy entire scenario stack on join / SCN load. Can also be used to show stack in UI
## Common/plugin
- auto separation margin by sector
- time-based separation / RECAT WTC
Expand Down
5 changes: 5 additions & 0 deletions bluesky/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
gui = ''

# Main singleton objects in BlueSky
ref = None
net = None
traf = None
navdb = None
Expand Down Expand Up @@ -77,6 +78,10 @@ def init(mode='sim', configfile=None, scenfile=None, discoverable=False,
from bluesky import tools
tools.init()

# Initialise reference data object
from bluesky import refdata
globals()['ref'] = refdata.RefData()

# Load navdatabase in all versions of BlueSky
# Only the headless server doesn't need this
if mode == "sim" or gui is not None:
Expand Down
36 changes: 17 additions & 19 deletions bluesky/navdatabase/navdatabase.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,11 @@ def reset(self):
self.rwythresholds = rwythresholds

def defwpt(self,name=None,lat=None,lon=None,wptype=None):

# Prevent polluting the database: check arguments
if name==None or name=="":
return False,"Insufficient arguments"
if name is None or name == "":
return False, "Insufficient arguments"
elif name.isdigit():
return False,"Name needs to start with an alphabetical character"
return False, "Name needs to start with an alphabetical character"

# DEL command: give info on waypoint (shudl work wit or without lat,lon, may be clicked by accident
elif (not wptype==None and (wptype.upper()=="DEL" or wptype.upper() =="DELETE")) or \
Expand All @@ -111,17 +110,16 @@ def defwpt(self,name=None,lat=None,lon=None,wptype=None):

# No data: give info on waypoint
elif lat==None or lon==None:
reflat, reflon = bs.scr.getviewctr()
if self.wpid.count(name.upper()) > 0:
i = self.getwpidx(name.upper(),reflat,reflon)
txt = self.wpid[i]+" : "+str(self.wplat[i])+","+str(self.wplon[i])
if len(self.wptype[i])>0:
txt = txt+" "+self.wptype[i]
return True,txt
i = self.getwpidx(name.upper(), bs.ref.lat, bs.ref.lon)
txt = f"{self.wpid[i]} : {self.wplat[i]}, {self.wplon[i]}"
if len(self.wptype[i]) > 0:
txt = txt + " " + self.wptype[i]
return True, txt

# Waypoint name is free
else:
return True,"Waypoint "+name.upper()+" does not yet exist."
return True, f"Waypoint {name.upper()} does not yet exist."

# Still here? So there is data, then we add this waypoint
self.wpid.append(name.upper())
Expand All @@ -143,12 +141,12 @@ def defwpt(self,name=None,lat=None,lon=None,wptype=None):

return True #,name.upper()+" added to navdb."

def delwpt(self,name=None):
def delwpt(self,name=''):
""" Delete a waypoint"""
if self.wpid.count(name.upper()) <= 0:
return False,"Waypoint "+name.upper()+" does not exist."
return False,"Waypoint " + name.upper() + " does not exist."

idx = len(self.wpid)-self.wpid[::-1].index(name)-1 # Search from back of list
idx = len(self.wpid) - self.wpid[::-1].index(name) - 1 # Search from back of list

del self.wpid[idx] # wp name

Expand All @@ -161,12 +159,12 @@ def delwpt(self,name=None):
del self.wpfreq[idx] # frequency [kHz/MHz]
del self.wpdesc[idx] # description

# Update screen info 9delete necessary there?)
# Update screen info (delete necessary there?)
bs.scr.removenavwpt(name.upper())

return True #,name.upper()+" deleted from navdb."

def getwpidx(self, txt, reflat=999999., reflon=999999):
def getwpidx(self, txt, reflat: float|None = None, reflon: float|None = None):
"""Get waypoint index to access data"""
name = txt.upper()
try:
Expand All @@ -175,7 +173,7 @@ def getwpidx(self, txt, reflat=999999., reflon=999999):
return -1

# if no pos is specified, get first occurence
if not reflat < 99999.:
if reflat is None:
return i

# If pos is specified check for more and return closest
Expand All @@ -201,7 +199,7 @@ def getwpidx(self, txt, reflat=999999., reflon=999999):
dmin = d
return imin

def getwpindices(self, txt, reflat=999999., reflon=999999,crit=1852.0):
def getwpindices(self, txt, reflat: float|None = None, reflon: float|None = None, crit=1852.0):
"""Get waypoint index to access data"""
name = txt.upper()
try:
Expand All @@ -210,7 +208,7 @@ def getwpindices(self, txt, reflat=999999., reflon=999999,crit=1852.0):
return [-1]

# if no pos is specified, get first occurence
if not reflat < 99999.:
if reflat is None:
return [i]

# If pos is specified check for more and return closest
Expand Down
48 changes: 48 additions & 0 deletions bluesky/refdata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
''' Reference values for simulation data. '''
from bluesky.core.base import Base
from bluesky import stack
from bluesky.tools import areafilter


class RefData(Base):
def __init__(self) -> None:
super().__init__()
self.lat: float = 0.0
self.lon: float = 0.0
self.alt: float = 0.0
self.acidx: int = -1
self.hdg: float = 0.0
self.cas: float = 0.0
self.area: areafilter.Shape = areafilter.Box('refarea', (-1.0, -1.0, 1.0, 1.0))

def reset(self):
''' Reset reference data. '''
self.lat = 0.0
self.lon = 0.0
self.alt = 0.0
self.acidx = -1
self.hdg = 0.0
self.cas = 0.0
self.area = areafilter.Box('refarea', (-1.0, -1.0, 1.0, 1.0))

@stack.command
def near(self, lat: 'lat', lon: 'lon', cmdstr: 'string'):
'''Set reference lat/lon before executing command string. '''
self.lat = lat
self.lon = lon
stack.process(cmdstr)

@stack.commandgroup
def inside(self, lat0: 'lat', lon0: 'lon', lat1: 'lat', lon1: 'lon', cmdstr: 'string'):
self.area = areafilter.Box('refarea', (lat0, lon0, lat1, lon1))
stack.process(cmdstr)

@inside.altcommand
def insidearea(self, areaname: 'txt', cmdstr: 'string'):
self.area = areafilter.getArea(areaname)
stack.process(cmdstr)

@stack.command(aliases=('with',))
def withac(self, idx: 'acid', cmdstr: 'string'):
self.acidx = idx
stack.process(cmdstr)
16 changes: 0 additions & 16 deletions bluesky/simulation/screenio.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,22 +81,6 @@ def echo(self, text='', flags=0):
def cmdline(self, text):
bs.net.send(b'CMDLINE', text)

def getviewctr(self):
return self.client_pan.get(stack.sender()) or self.def_pan

def getviewbounds(self):
# Get appropriate lat/lon/zoom/aspect ratio
sender = stack.sender()
lat, lon = self.client_pan.get(sender) or self.def_pan
zoom = self.client_zoom.get(sender) or self.def_zoom
ar = self.client_ar.get(sender) or 1.0

lat0 = lat - 1.0 / (zoom * ar)
lat1 = lat + 1.0 / (zoom * ar)
lon0 = lon - 1.0 / (zoom * np.cos(np.radians(lat)))
lon1 = lon + 1.0 / (zoom * np.cos(np.radians(lat)))
return lat0, lat1, lon0, lon1

def showroute(self, acid):
''' Toggle show route for this aircraft '''
if not stack.sender():
Expand Down
1 change: 1 addition & 0 deletions bluesky/simulation/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ def reset(self):
self.set_dtmult(1.0)
hooks.reset.trigger()
core.reset()
bs.ref.reset()
bs.navdb.reset()
bs.traf.reset()
simstack.reset()
Expand Down
41 changes: 12 additions & 29 deletions bluesky/stack/argparser.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
''' Stack argument parsers. '''
import inspect
import re
from types import SimpleNamespace
from matplotlib import colors
from bluesky.tools.misc import txt2bool, txt2lat, txt2lon, txt2alt, txt2tim, \
txt2hdg, txt2vs, txt2spd
Expand All @@ -19,25 +18,12 @@
r'\s*[\'"]?((?<=[\'"])[^\'"]*|(?<![\'"])[^\s,]*)[\'"]?\s*,?\s*(.*)')
# re_getarg = re.compile(r'[\'"]?((?<=[\'"])[^\'"]+|(?<![\'"])[^\s,]+)[\'"]?\s*,?\s*')

# Stack reference data namespace
refdata = SimpleNamespace(lat=None, lon=None, alt=None, acidx=-1, hdg=None, cas=None)


def getnextarg(cmdstring):
''' Return first argument and remainder of command string from cmdstring. '''
return re_getarg.match(cmdstring).groups()


def reset():
''' Reset reference data. '''
refdata.lat = None
refdata.lon = None
refdata.alt = None
refdata.acidx = -1
refdata.hdg = None
refdata.cas = None


class Parameter:
''' Wrapper class for stack function parameters. '''
def __init__(self, param, annotation='', isopt=None):
Expand Down Expand Up @@ -154,9 +140,9 @@ def parse(self, argstring):
raise ArgumentError(f'Aircraft with callsign {acid} not found')

# Update ref position for navdb lookup
refdata.lat = bs.traf.lat[idx]
refdata.lon = bs.traf.lon[idx]
refdata.acidx = idx
bs.ref.lat = bs.traf.lat[idx]
bs.ref.lon = bs.traf.lon[idx]
bs.ref.acidx = idx
return idx, argstring


Expand All @@ -165,9 +151,9 @@ class WpinrouteArg(Parser):
def parse(self, argstring):
arg, argstring = re_getarg.match(argstring).groups()
wpname = arg.upper()
if refdata.acidx >= 0 and wpname in bs.traf.ap.route[refdata.acidx].wpname or wpname == '*':
if bs.ref.acidx >= 0 and wpname in bs.traf.ap.route[bs.ref.acidx].wpname or wpname == '*':
return wpname, argstring
raise ArgumentError(f'{wpname} not found in the route of {bs.traf.id[refdata.acidx]}')
raise ArgumentError(f'{wpname} not found in the route of {bs.traf.id[bs.ref.acidx]}')

class WptArg(Parser):
''' Argument parser for waypoints.
Expand Down Expand Up @@ -229,26 +215,23 @@ def parse(self, argstring):
# Check if lat/lon combination
if islat(argu):
nextarg, argstring = re_getarg.match(argstring).groups()
refdata.lat = txt2lat(argu)
refdata.lon = txt2lon(nextarg)
bs.ref.lat = txt2lat(argu)
bs.ref.lon = txt2lon(nextarg)
return txt2lat(argu), txt2lon(nextarg), argstring

# apt,runway ? Combine into one string with a slash as separator
if argstring[:2].upper() == "RW" and argu in bs.navdb.aptid:
arg, argstring = re_getarg.match(argstring).groups()
argu = argu + "/" + arg.upper()

if refdata.lat is None:
refdata.lat, refdata.lon = bs.scr.getviewctr()

posobj = Position(argu, refdata.lat, refdata.lon)
posobj = Position(argu, bs.ref.lat, bs.ref.lon)
if posobj.error:
raise ArgumentError(f'{argu} is not a valid waypoint, airport, runway, or aircraft id.')

# Update reference lat/lon
refdata.lat = posobj.lat
refdata.lon = posobj.lon
refdata.hdg = posobj.refhdg
bs.ref.lat = posobj.lat
bs.ref.lon = posobj.lon
bs.ref.hdg = posobj.refhdg

return posobj.lat, posobj.lon, argstring

Expand Down Expand Up @@ -301,7 +284,7 @@ def parse(self, argstring):
'spd': Parser(txt2spd),
'vspd': Parser(txt2vs),
'alt': Parser(txt2alt),
'hdg': Parser(lambda txt: txt2hdg(txt, refdata.lat, refdata.lon)),
'hdg': Parser(lambda txt: txt2hdg(txt, bs.ref.lat, bs.ref.lon)),
'time': Parser(txt2tim),
'colour': ColorArg(),
'color': ColorArg(),
Expand Down
5 changes: 5 additions & 0 deletions bluesky/tools/areafilter.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ def hasArea(areaname):
return areaname in basic_shapes


def getArea(areaname):
''' Return the area object corresponding to name '''
return basic_shapes.get(areaname, None)


def defineArea(name, shape, coordinates, top=1e9, bottom=-1e9):
"""Define a new area"""
if name == 'LIST':
Expand Down
Loading

0 comments on commit c768cb4

Please sign in to comment.