1
0
Fork 0
A320-family/Nasal/Displays/traffic.nas
2021-02-14 21:00:19 +01:00

358 lines
11 KiB
Text

# Traffic layer
var ATCSwitchAbvBlw = props.globals.getNode("/controls/atc/abv-blw");
var ATCSwitchThrtAll = props.globals.getNode("/controls/atc/thrt-all");
var colorByLevel = {
# 0: other
0: [0.8,0.8,0.8],
# 1: proximity
1: [0.8,0.8,0.8],
# 2: traffic advisory (TA)
2: [1,0.75,0],
# 3: resolution advisory (RA)
3: [1,0,0],
};
var doFill = {
0: 0,
1: 1,
2: 1,
3: 1,
};
var colorDefault = [0.8,0.8,0.8];
var drawBlip = func(elem, threatLvl) {
if (threatLvl == 3) {
# resolution advisory
elem.reset()
.setStrokeLineWidth(0)
.moveTo(-17,-17)
.horiz(34)
.vert(34)
.horiz(-34)
.close();
}
elsif (threatLvl == 2) {
# traffic advisory
elem.reset()
.moveTo(-17,0)
.setStrokeLineWidth(0)
.arcSmallCW(17,17,0,34,0)
.arcSmallCW(17,17,0,-34,0);
}
elsif (threatLvl == 1) {
# proximate traffic
elem.reset()
.setStrokeLineWidth(0)
.moveTo(-14,0)
.lineTo(0,-17)
.lineTo(14,0)
.lineTo(0,17)
.close();
}
else {
# other traffic
elem.reset()
.setStrokeLineWidth(4)
.moveTo(-10,0)
.lineTo(0,-14)
.lineTo(10,0)
.lineTo(0,14)
.lineTo(-10,0);
}
};
var TrafficLayer = {
new: func(camera, group) {
var m = {
parents: [TrafficLayer],
camera: camera,
refAlt: 0,
group: group,
items: {},
sorted: {}, # TODO - only the most 8 relevant advisories
values: {},
updateKeys: [],
addListener: nil,
delListener: nil,
};
return m;
},
makeElems: func () {
if (me.group == nil) return nil;
var elems = {};
elems['master'] = me.group.createChild('group');
elems['blip'] = elems.master.createChild('path')
.setStrokeLineWidth(0);
elems['text'] = elems.master.createChild('text')
.setDrawMode(canvas.Text.TEXT)
.setText(sprintf("0"))
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setColor(1,1,1)
.setFontSize(32)
.setAlignment("center-center");
elems['master'].hide();
elems['arrowUp'] = elems.master.createChild("text")
.setDrawMode(canvas.Text.TEXT)
.setText(sprintf("↑"))
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setColor(1,1,1)
.setFontSize(50)
.setTranslation(16, 2)
.setAlignment("left-center");
elems['arrowDown'] = elems.master.createChild("text")
.setDrawMode(canvas.Text.TEXT)
.setText(sprintf("↓"))
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setColor(1,1,1)
.setFontSize(50)
.setTranslation(16, 2)
.setAlignment("left-center");
return elems;
},
start: func() {
me.stop();
var self = me;
me.addListener = setlistener('/ai/models/model-added', func(changed, listen, mode, is_child) {
var path = changed.getValue();
if (path == nil) return;
#printf("ADD: %s", path);
me.values[path] = nil;
var masterProp = props.globals.getNode(path);
var prop = {
'master': masterProp,
};
if (me.items[path] == nil) {
me.items[path] = {
prop: prop,
elems: me.makeElems(),
data: {'threatLevel': -2},
};
}
else {
me.items[path].prop = prop;
me.items[path].data = {'threatLevel': -2};
}
}, 1, 1);
me.delListener = setlistener('/ai/models/model-removed', func(changed, listen, mode, is_child) {
var path = changed.getValue();
if (path == nil) return;
#printf("DEL: %s", path);
me.values[path] = nil;
if (me.items[path] == nil) return;
if (me.items[path] != nil) {
me.items[path].prop = nil;
me.items[path].elems.master.hide();
me.items[path].data = {};
}
}, 1, 1);
},
stop: func() {
if (me.addListener != nil) {
removelistener(me.addListener);
me.addListener = nil;
}
if (me.delListener != nil) {
removelistener(me.delListener);
me.delListener = nil;
}
me.items = {};
if (me.group != nil) {
me.group.removeAllChildren();
}
},
nxtupdatetime: 0,
update: func() {
var _tm = systime();
if (me.nxtupdatetime != 0) {
if (_tm<me.nxtupdatetime) return;
}
me.nxtupdatetime = _tm + 0.5; # refresh rate at 500ms
if (size(me.updateKeys) == 0) {
me.updateKeys = keys(me.items);
}
var path = pop(me.updateKeys);
foreach (var path; keys(me.items)) {
me.updateItem(path);
}
},
redraw: func() {
foreach (var path; keys(me.items)) {
me.redrawItem(me.items[path],me.values[path]);
}
},
setRefAlt: func(alt) {
me.refAlt = alt;
},
proplist: ['lat', 'lon', 'alt', 'threatLevel', 'callsign', 'vspeed', 'tas'],
updateItem: func(path) {
var item = me.items[path];
if (item == nil) return;
if (item.prop == nil) {
if (item.elems != nil) {
item.elems.master.hide();
}
return;
}
if (item.prop['lat'] == nil) {
item.prop['lat'] = item.prop.master.getNode('position/latitude-deg');
item.prop['lon'] = item.prop.master.getNode('position/longitude-deg');
item.prop['alt'] = item.prop.master.getNode('position/altitude-ft');
}
if (item.prop['threatLevel'] == nil) {
item.prop['threatLevel'] = item.prop.master.getNode('tcas/threat-level');
}
if (item.prop['callsign'] == nil) {
item.prop['callsign'] = item.prop.master.getNode('callsign');
}
if (item.prop['vspeed'] == nil) {
item.prop['vspeed'] = item.prop.master.getNode('velocities/vertical-speed-fps');
item.prop['tas'] = item.prop.master.getNode('velocities/true-airspeed-kt');
}
# this item has a prop associated with it
if (item.elems == nil) {
item.elems = me.makeElems();
}
var oldThreatLevel = item.data['threatLevel'];
foreach (var k; me.proplist) {
if (item.prop[k] != nil) {
item.data[k] = item.prop[k].getValue();
}
}
if (item.data['threatLevel'] == -1) { # airplane with TCAS not enabled or supported
me.values[path] = {visible: 0};
return;
}
if (oldThreatLevel != item.data['threatLevel']) {
item.data['threatLevelDirty'] = 1;
}
var newThrtAll = ATCSwitchThrtAll.getValue();
if (newThrtAll == 1) { # AUTO - display only proximate and higher advisories
if (item.data['threatLevel']==0) {
me.values[path] = {visible: 0};
return;
}
}
var _lat = item.data['lat'];
var _lon = item.data['lon'];
var alt = item.data['alt'];
var vspeed = item.data['vspeed'];
var tas = item.data['tas'];
me.values[path] = nil;
if (_lat != nil and _lon != nil and vspeed != nil) {
if (tas<80) { # flying airplane only
me.values[path] = {visible: 0};
return;
}
var top = 27;
var bottom = -27;
if (ATCSwitchAbvBlw.getValue() == -1) {
top = 99;
}
if (ATCSwitchAbvBlw.getValue() == 1) {
bottom = -99;
}
var altDiff100 = ((alt or me.refAlt) - me.refAlt) / 100;
if (altDiff100 > top or altDiff100 < bottom) { # check TCAS vertical range
me.values[path] = {visible: 0};
return;
}
var _val = {visible:1, lat:_lat, lon:_lon, dirty:item.data['threatLevelDirty']};
var spd = vspeed * 60;
_val.arrowup = (spd > 500);
_val.arrowdown = (spd < -500);
if (math.abs(altDiff100) > 0.5) {
_val.text = sprintf("%+03.0f ", altDiff100);
} else {
_val.text = "";
}
_val.textpy = (altDiff100 < 0) ? 34 : -30;
me.values[path] = _val;
}
},
redrawItem: func (item,val) {
#debug.dump("REDRAW ", item.data);
if (val != nil and val.visible == 1) {
var lat = val.lat;
var lon = val.lon;
var coords = geo.Coord.new();
coords.set_latlon(lat, lon);
var (x, y) = me.camera.project(coords);
item.elems.master.setTranslation(x, y);
#printf("%f %f", x, y);
if (val.dirty) {
#printf('%s THREAT LVL: %i', item.data['callsign'] or '???', item.data['threatLevel']);
var threatLevel = item.data['threatLevel'];
#debug.dump(item.data, threatLevel);
drawBlip(item.elems.blip, threatLevel);
var rgb = colorByLevel[threatLevel];
if (rgb == nil) rgb = colorDefault;
var color = canvas._getColor(rgb);
var (r, g, b) = rgb;
if (threatLevel > 0) {
item.elems.blip.setColorFill(r, g, b);
} else {
item.elems.blip.setColor(r, g, b);
}
item.elems.text.setColor(r, g, b);
item.elems.arrowUp.setColor(r, g, b);
item.elems.arrowDown.setColor(r, g, b);
item.elems.master.set('z-index', threatLevel + 2);
item.data['threatLevelDirty'] = 0;
val.dirty = 0;
}
item.elems.arrowUp.setVisible(val.arrowup);
item.elems.arrowDown.setVisible(val.arrowdown);
item.elems.text.setText(val.text);
item.elems.text.setTranslation(0, val.textpy);
item.elems.master.show();
} else {
item.elems.master.hide();
}
},
};