123 lines
3.6 KiB
Text
123 lines
3.6 KiB
Text
# See: http://wiki.flightgear.org/MapStructure
|
|
# FIXME: this needs to be configurable via the ctor to optionally differentiate between MP and AI traffic, and exclude ground/sea traffic
|
|
# or at least use a different, non-TCAS symbol for those (looking pretty weird ATM)
|
|
#
|
|
# Class things:
|
|
var name = 'TFC';
|
|
var parents = [SymbolLayer.Controller];
|
|
var __self__ = caller(0)[0];
|
|
SymbolLayer.Controller.add(name, __self__);
|
|
SymbolLayer.add(name, {
|
|
parents: [MultiSymbolLayer],
|
|
type: name, # Symbol type
|
|
df_controller: __self__, # controller to use by default -- this one
|
|
df_style: {
|
|
},
|
|
df_options: { # default configuration options
|
|
floor_ft: 120000, # Display target from this height below the aircraft
|
|
ceiling_ft: 120000, # Display targets up to this height above the aircraft
|
|
display_id: 1, # Display aircraft ID
|
|
},
|
|
});
|
|
|
|
var model_root = props.globals.getNode("/ai/models/");
|
|
|
|
var new = func(layer) {
|
|
var m = {
|
|
parents: [__self__],
|
|
layer: layer,
|
|
map: layer.map,
|
|
listeners: [],
|
|
searchCmd: searchCmd_default,
|
|
};
|
|
layer.searcher._equals = func(l,r) l.equals(r);
|
|
m.addVisibilityListener();
|
|
|
|
return m;
|
|
};
|
|
var del = func() {
|
|
#print(name~".lcontroller.del()");
|
|
foreach (var l; me.listeners)
|
|
removelistener(l);
|
|
};
|
|
|
|
var TrafficModel = {
|
|
new: func(node, id=nil, layer=nil) {
|
|
if (id == nil) id = node.getValue("id");
|
|
var m = {
|
|
# Note: because this inherits from props.Node, Symbol.Controller.equals
|
|
# will call l.equals(r) -- the one defined below
|
|
parents: [TrafficModel, geo.Coord, node], # note we don't implement a full geo.Coord API
|
|
id: id,
|
|
node: node,
|
|
pos: node.getNode("position",1),
|
|
type: node.getName(),
|
|
};
|
|
return m;
|
|
},
|
|
equals: func(other) other.id == me.id,
|
|
latlon: func() { # this makes sure to look like a geo.Coord to MapStructure, but will internally use the AI/MP traffic properties instead
|
|
return [
|
|
me.pos.getValue("latitude-deg"),
|
|
me.pos.getValue("longitude-deg"),
|
|
me.pos.getValue("altitude-ft")
|
|
];
|
|
},
|
|
# these are helper methods related to TCAS handling (TAs/RAs)
|
|
get_threat_lvl: func() me.getValue("tcas/threat-level"),
|
|
get_vspd: func() (me.getValue("velocities/vertical-speed-fps") or 0)*60,
|
|
get_alt: func() (me.getValue("position/altitude-ft") or 0),
|
|
};
|
|
|
|
var get_alt_diff = func(model) {
|
|
# debug.dump( keys(me) );
|
|
var model_alt = model.get_alt();
|
|
var alt = me.map.getAlt();
|
|
if (alt == nil or model_alt == nil) return 0;
|
|
return alt - model_alt;
|
|
};
|
|
|
|
##
|
|
# dummy/placeholder (will be overridden in ctor and set to the default callback)
|
|
var searchCmd = func;
|
|
|
|
var searchCmd_default = func {
|
|
# TODO: this would be a good candidate for splitting across frames
|
|
printlog(_MP_dbg_lvl, "Doing query: "~name);
|
|
|
|
if (me.map.getRange() == nil) return;
|
|
|
|
var result = [];
|
|
var models = 0;
|
|
|
|
var alt = me.map.getAlt();
|
|
if (alt == nil) alt = 0;
|
|
|
|
var min_alt = alt - me.layer.options.floor_ft;
|
|
var max_alt = alt + me.layer.options.ceiling_ft;
|
|
|
|
# AI and Multiplayer traffic
|
|
foreach (var t; model_root.getChildren()) {
|
|
if (!t.getValue("valid")) continue;
|
|
var t_id = t.getNode("id");
|
|
if (t_id == nil or t_id.getValue() == -1) continue;
|
|
models += 1;
|
|
var (lat,lon) = (t.getValue("position/latitude-deg"),
|
|
t.getValue("position/longitude-deg"));
|
|
if (lat == nil or lon == nil) {
|
|
printlog("alert", "lat/lon was nil for AI node "~t.getPath());
|
|
continue;
|
|
}
|
|
|
|
var tm = TrafficModel.new(t, nil, me.layer);
|
|
|
|
if ((min_alt < tm.get_alt()) and
|
|
(tm.get_alt() < max_alt) and
|
|
me.map.controller.in_range(lat, lon) ) {
|
|
append(result, tm);
|
|
}
|
|
}
|
|
|
|
#print("Found "~size(result)~" TrafficModel's in range out of "~models~" total.");
|
|
return result;
|
|
};
|