2013-12-01 12:29:22 +00:00
# ==============================================================================
2013-12-28 15:18:35 +00:00
# Boeing Navigation Display by Gijs de Rooy
2013-12-01 12:29:22 +00:00
# ==============================================================================
##
# do we really need to keep track of each drawable here ??
var i = 0;
##
# pseudo DSL-ish: use these as placeholders in the config hash below
var ALWAYS = func 1;
var NOTHING = func nil;
##
# so that we only need to update a single line ...
#
var trigger_update = func(layer) layer._model.init();
##
# TODO: move ND-specific implementation details into this lookup hash
# so that other aircraft and ND types can be more easily supported
#
# any aircraft-specific ND behavior should be wrapped here,
# to isolate/decouple things in the generic NavDisplay class
#
# TODO: move this to an XML config file
#
2014-01-08 20:45:48 +00:00
var NDStyles =
{
2013-12-01 12:29:22 +00:00
##
2014-01-08 20:45:48 +00:00
# this configures the Boeing ND to help generalize the NavDisplay class itself
'Boeing': {
2013-12-01 12:29:22 +00:00
font_mapper: func(family, weight) {
if( family == "Liberation Sans" and weight == "normal" )
return "LiberationFonts/LiberationSans-Regular.ttf";
},
# where all the symbols are stored
2013-12-04 22:19:22 +00:00
# TODO: SVG elements should be renamed to use boeing/airbus prefix
2013-12-01 12:29:22 +00:00
# aircraft developers should all be editing the same ND.svg image
# the code can deal with the differences now
2013-12-04 22:19:22 +00:00
svg_filename: "Nasal/canvas/map/boeingND.svg",
2013-12-01 12:29:22 +00:00
##
## this loads and configures existing layers (currently, *.layer files in Nasal/canvas/map)
##
layers: [
2013-12-28 15:18:35 +00:00
{ name:'fixes', update_on:['toggle_range','toggle_waypoints','toggle_display_mode'], predicate: func(nd, layer) {
2014-01-08 20:45:48 +00:00
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible)
2013-12-01 12:29:22 +00:00
trigger_update( layer );
2014-01-08 20:45:48 +00:00
layer._view.setVisible(visible);
2013-12-01 12:29:22 +00:00
}, # end of layer update predicate
}, # end of fixes layer
2013-12-04 22:19:22 +00:00
# Should redraw every 10 seconds
{ name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'], predicate: func(nd, layer) {
var visible=nd.get_switch('toggle_weather') and nd.get_switch('toggle_display_mode') != "PLAN";
2014-01-08 20:45:48 +00:00
if (visible)
trigger_update( layer );
layer._view.setVisible(visible);
2013-12-28 15:18:35 +00:00
}, # end of layer update predicate
}, # end of storms layer
{ name:'airplaneSymbol', update_on:['toggle_range','toggle_display_mode'], predicate: func(nd, layer) {
var visible=nd.get_switch('toggle_display_mode') == "PLAN";
2014-01-08 20:45:48 +00:00
if (visible)
trigger_update( layer );
layer._view.setVisible(visible);
},
},
2013-12-01 12:29:22 +00:00
{ name:'airports-nd', update_on:['toggle_range','toggle_airports','toggle_display_mode'], predicate: func(nd, layer) {
2013-12-28 15:18:35 +00:00
var visible = nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
2014-01-08 20:45:48 +00:00
if (visible)
2013-12-28 15:18:35 +00:00
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible);
2013-12-01 12:29:22 +00:00
}, # end of layer update predicate
}, # end of airports layer
2014-01-08 20:45:48 +00:00
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
2013-12-01 12:29:22 +00:00
{ name:'vor', update_on:['toggle_range','toggle_stations','toggle_display_mode'], predicate: func(nd, layer) {
2014-01-08 20:45:48 +00:00
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible)
2013-12-01 12:29:22 +00:00
trigger_update( layer ); # clear & redraw
2013-12-28 15:18:35 +00:00
layer._view.setVisible( visible );
2013-12-01 12:29:22 +00:00
}, # end of layer update predicate
}, # end of VOR layer
2014-01-08 20:45:48 +00:00
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
2013-12-28 15:18:35 +00:00
{ name:'dme', update_on:['toggle_range','toggle_stations','toggle_display_mode'], predicate: func(nd, layer) {
2014-01-08 20:45:48 +00:00
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible)
2013-12-01 12:29:22 +00:00
trigger_update( layer ); # clear & redraw
2013-12-28 15:18:35 +00:00
layer._view.setVisible( visible );
2013-12-01 12:29:22 +00:00
}, # end of layer update predicate
}, # end of DME layer
{ name:'mp-traffic', update_on:['toggle_range','toggle_traffic'], predicate: func(nd, layer) {
trigger_update( layer ); # clear & redraw
2013-12-28 15:18:35 +00:00
layer._view.setVisible( nd.get_switch('toggle_traffic') );
2013-12-01 12:29:22 +00:00
}, # end of layer update predicate
}, # end of traffic layer
{ name:'runway-nd', update_on:['toggle_range','toggle_display_mode'], predicate: func(nd, layer) {
2014-01-08 20:45:48 +00:00
var visible = (nd.rangeNm() <= 40) and getprop("autopilot/route-manager/active") and nd.in_mode('toggle_display_mode', ['MAP','PLAN']) ;
2013-12-01 12:29:22 +00:00
if (visible)
trigger_update( layer ); # clear & redraw
2014-01-08 20:45:48 +00:00
layer._view.setVisible( visible );
2013-12-01 12:29:22 +00:00
}, # end of layer update predicate
}, # end of airports-nd layer
2013-12-28 15:18:35 +00:00
{ name:'route', update_on:['toggle_display_mode',], predicate: func(nd, layer) {
var visible= (nd.in_mode('toggle_display_mode', ['MAP','PLAN']));
2014-01-08 20:45:48 +00:00
if (visible)
2013-12-28 15:18:35 +00:00
trigger_update( layer ); # clear & redraw
layer._view.setVisible( visible );
2013-12-01 12:29:22 +00:00
}, # end of layer update predicate
}, # end of route layer
## add other layers here, layer names must match the registered names as used in *.layer files for now
## this will all change once we're using Philosopher's MapStructure framework
], # end of vector with configured layers
# This is where SVG elements are configured by providing "behavior" hashes, i.e. for animations
# to animate each SVG symbol, specify behavior via callbacks (predicate, and true/false implementation)
# SVG identifier, callback etc
# TODO: update_on([]), update_mode (update() vs. timers/listeners)
# TODO: support putting symbols on specific layers
features: [ {
# TODO: taOnly doesn't need to use getprop polling in update(), use a listener instead!
id: 'taOnly', # the SVG ID
impl: { # implementation hash
init: func(nd, symbol), # for updateCenter stuff, called during initialization in the ctor
predicate: func(nd) getprop("instrumentation/tcas/inputs/mode") == 2, # the condition
is_true: func(nd) nd.symbols.taOnly.show(), # if true, run this
is_false: func(nd) nd.symbols.taOnly.hide(), # if false, run this
}, # end of taOnly behavior/callbacks
}, # end of taOnly
{
id: 'tas',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/velocities/airspeed-kt") > 100,
is_true: func(nd) {
nd.symbols.tas.setText(sprintf("%3.0f",getprop("/velocities/airspeed-kt") ));
nd.symbols.tas.show();
},
is_false: func(nd) nd.symbols.tas.hide(),
}, # end of tas behavior callbacks
}, # end of tas hash
2013-12-04 22:19:22 +00:00
{
id: 'wpActiveId',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/id") != nil and getprop("autopilot/route-manager/active"),
is_true: func(nd) {
nd.symbols.wpActiveId.setText(getprop("/autopilot/route-manager/wp/id"));
nd.symbols.wpActiveId.show();
},
is_false: func(nd) nd.symbols.wpActiveId.hide(),
}, # of wpActiveId.impl
}, # of wpActiveId
{
id: 'wpActiveDist',
impl: {
init: func(nd,symbol),
predicate: func(nd) getprop("/autopilot/route-manager/wp/dist") != nil and getprop("autopilot/route-manager/active"),
is_true: func(nd) {
nd.symbols.wpActiveDist.setText(sprintf("%3.01fNM",getprop("/autopilot/route-manager/wp/dist")));
nd.symbols.wpActiveDist.show();
},
is_false: func(nd) nd.symbols.wpActiveDist.hide(),
}, # of wpActiveDist.impl
}, # of wpActiveDist
{
2013-12-01 12:29:22 +00:00
id: 'eta',
impl: {
init: func(nd,symbol),
2013-12-04 22:19:22 +00:00
predicate: func(nd) getprop("autopilot/route-manager/wp/eta") != nil and getprop("autopilot/route-manager/active"),
2013-12-01 12:29:22 +00:00
is_true: func(nd) {
2013-12-04 22:19:22 +00:00
var etaSec = getprop("/sim/time/utc/day-seconds")+getprop("autopilot/route-manager/wp/eta-seconds");
var h = math.floor(etaSec/3600);
if (h>24) h=h-24;
etaSec=etaSec-3600*h;
var m = math.floor(etaSec/60);
etaSec=etaSec-60*m;
var s = etaSec;
2013-12-01 12:29:22 +00:00
nd.symbols.eta.setText(sprintf("%02.0f%02.0f.%02.0fz",h,m,s));
nd.symbols.eta.show();
},
is_false: func(nd) nd.symbols.eta.hide(),
}, # of eta.impl
2013-12-04 22:19:22 +00:00
}, # of eta
{ id:'hdg',
2013-12-01 12:29:22 +00:00
impl: {
init: func(nd,symbol),
predicate: ALWAYS, # always true
2013-12-04 22:19:22 +00:00
is_true: func(nd) nd.symbols.hdg.setText(sprintf("%03.0f", nd.aircraft_source.get_hdg_mag() )),
2013-12-01 12:29:22 +00:00
is_false: NOTHING,
}, # of hdg.impl
2013-12-04 22:19:22 +00:00
}, # of hdg
2013-12-01 12:29:22 +00:00
{ id:'gs',
impl: {
init: func(nd,symbol),
common: func(nd) nd.symbols.gs.setText(sprintf("%3.0f",nd.aircraft_source.get_spd() )),
predicate: func(nd) nd.aircraft_source.get_spd() >= 30,
is_true: func(nd) {
nd.symbols.gs.setFontSize(36);
},
is_false: func(nd) nd.symbols.gs.setFontSize(52),
}, # of gs.impl
}, # of gs
2013-12-04 22:19:22 +00:00
{ id:'rangeArcs',
2013-12-01 12:29:22 +00:00
impl: {
2013-12-04 22:19:22 +00:00
init: func(nd,symbol),
2013-12-28 15:18:35 +00:00
predicate: func(nd) ((nd.in_mode('toggle_display_mode', ['APP','VOR']) and nd.get_switch('toggle_weather')) or (nd.get_switch('toggle_display_mode') == "MAP" and !nd.get_switch('toggle_centered'))),
2013-12-04 22:19:22 +00:00
is_true: func(nd) nd.symbols.rangeArcs.show(),
is_false: func(nd) nd.symbols.rangeArcs.hide(),
}, # of rangeArcs.impl
}, # of rangeArcs
2013-12-01 12:29:22 +00:00
], # end of vector with features
2014-01-08 20:45:48 +00:00
}, # end of Boeing ND style
};
2013-12-01 12:29:22 +00:00
##
# encapsulate hdg/lat/lon source, so that the ND may also display AI/MP aircraft in a pilot-view at some point (aka stress-testing)
#
var NDSourceDriver = {};
2014-01-08 20:45:48 +00:00
NDSourceDriver.new = func
{
var m = {parents:[NDSourceDriver]};
m.get_hdg_mag= func getprop("/orientation/heading-magnetic-deg");
m.get_hdg_tru= func getprop("/orientation/heading-deg");
m.get_hgg = func getprop("instrumentation/afds/settings/heading");
m.get_trk_mag= func
{
if(getprop("/velocities/groundspeed-kt") > 80)
{
getprop("/orientation/track-magnetic-deg");
}
else
{
getprop("/orientation/heading-magnetic-deg");
}
};
m.get_trk_tru = func
{
if(getprop("/velocities/groundspeed-kt") > 80)
{
getprop("/orientation/track-deg");
}
else
{
getprop("/orientation/heading-deg");
}
};
m.get_lat= func getprop("/position/latitude-deg");
m.get_lon= func getprop("/position/longitude-deg");
m.get_spd= func getprop("/velocities/groundspeed-kt");
m.get_vspd= func getprop("/velocities/vertical-speed-fps");
return m;
2013-12-01 12:29:22 +00:00
}
##
# configure aircraft specific cockpit switches here
# these are some defaults, can be overridden when calling NavDisplay.new() -
# see the 744 ND.nas file the backend code should never deal directly with
# aircraft specific properties using getprop.
# To get started implementing your own ND, just copy the switches hash to your
# ND.nas file and map the keys to your cockpit properties - and things will just work.
# TODO: switches are ND specific, so move to the NDStyle hash!
var default_switches = {
2014-01-08 20:45:48 +00:00
'toggle_range': {path: '/inputs/range-nm', value:10, type:'INT'},
'toggle_weather': {path: '/inputs/wxr', value:0, type:'BOOL'},
'toggle_airports': {path: '/inputs/arpt', value:0, type:'BOOL'},
'toggle_stations': {path: '/inputs/sta', value:0, type:'BOOL'},
'toggle_waypoints': {path: '/inputs/wpt', value:0, type:'BOOL'},
'toggle_position': {path: '/inputs/pos', value:0, type:'BOOL'},
'toggle_data': {path: '/inputs/data',value:0, type:'BOOL'},
'toggle_terrain': {path: '/inputs/terr',value:0, type:'BOOL'},
'toggle_traffic': {path: '/inputs/tfc',value:0, type:'BOOL'},
'toggle_centered': {path: '/inputs/nd-centered',value:0, type:'BOOL'},
'toggle_lh_vor_adf': {path: '/inputs/lh-vor-adf',value:0, type:'INT'},
'toggle_rh_vor_adf': {path: '/inputs/rh-vor-adf',value:0, type:'INT'},
'toggle_display_mode': {path: '/mfd/display-mode', value:'MAP', type:'STRING'}, # valid values are: APP, MAP, PLAN or VOR
'toggle_display_type': {path: '/mfd/display-type', value:'CRT', type:'STRING'}, # valid values are: CRT or LCD
'toggle_true_north': {path: '/mfd/true-north', value:0, type:'BOOL'},
2013-12-01 12:29:22 +00:00
};
2013-12-04 22:19:22 +00:00
# Hack to update weather radar once every 10 seconds
var update_weather = func {
if (getprop("/instrumentation/efis/inputs/wxr") != nil)
setprop("/instrumentation/efis/inputs/wxr",getprop("/instrumentation/efis/inputs/wxr"));
settimer(update_weather, 10);
}
update_weather();
2013-12-28 15:18:35 +00:00
# Hack to update airplane symbol location on PLAN mode every 5 seconds
var update_apl_sym = func {
if (getprop("/instrumentation/efis/mfd/display-mode") == "PLAN")
setprop("/instrumentation/efis/mfd/display-mode","PLAN");
settimer(update_apl_sym, 5);
}
update_apl_sym();
2013-12-01 12:29:22 +00:00
##
# TODO:
# - introduce a MFD class (use it also for PFD/EICAS)
# - introduce a SGSubsystem class and use it here
# - introduce a Boeing NavDisplay class
2014-01-08 20:45:48 +00:00
var NavDisplay =
{
2013-12-01 12:29:22 +00:00
# reset handler
2014-01-08 20:45:48 +00:00
handle_reinit: func
{
# shut down all timers and other loops here
me.update_timer.stop();
foreach(var l; me.listeners)
removelistener(l);
2013-12-01 12:29:22 +00:00
},
2014-01-08 20:45:48 +00:00
listen: func(p,c)
{
append(me.listeners, setlistener(p,c));
2013-12-01 12:29:22 +00:00
},
# listeners for cockpit switches
2014-01-08 20:45:48 +00:00
listen_switch: func(s,c)
{
me.listen( me.get_full_switch_path(s), func
{
c();
});
},
2013-12-01 12:29:22 +00:00
# get the full property path for a given switch
get_full_switch_path: func (s) {
# debug.dump( me.efis_switches[s] );
return me.efis_path ~ me.efis_switches[s].path; # FIXME: should be using props.nas instead of ~
},
# helper method for getting configurable cockpit switches (which are usually different in each aircraft)
2014-01-08 20:45:48 +00:00
get_switch: func(s)
{
var switch = me.efis_switches[s];
var path = me.efis_path ~ switch.path ;
return getprop( path );
},
2013-12-01 12:29:22 +00:00
# for creating NDs that are driven by AI traffic instead of the main aircraft (generalization rocks!)
2014-01-08 20:45:48 +00:00
connectAI: func(source=nil)
{
me.aircraft_source =
{
2013-12-28 15:18:35 +00:00
get_hdg_mag: func source.getNode('orientation/heading-magnetic-deg').getValue(),
get_hdg_tru: func source.getNode('orientation/heading-deg').getValue(),
get_trk_mag: func source.getNode('orientation/track-magnetic-deg').getValue(),
get_trk_tru: func source.getNode('orientation/track-deg').getValue(),
get_lat: func source.getNode('position/latitude-deg').getValue(),
get_lon: func source.getNode('position/longitude-deg').getValue(),
get_spd: func source.getNode('velocities/true-airspeed-kt').getValue(),
get_vspd: func source.getNode('velocities/vertical-speed-fps').getValue(),
};
2013-12-01 12:29:22 +00:00
}, # of connectAI
2014-01-08 20:45:48 +00:00
# TODO: the ctor should allow customization, for different aircraft
2013-12-01 12:29:22 +00:00
# especially properties and SVG files/handles (747, 757, 777 etc)
2014-01-08 20:45:48 +00:00
new : func(prop1, switches=default_switches, style='Boeing')
{
var m = { parents : [NavDisplay]};
2013-12-01 12:29:22 +00:00
2014-01-08 20:45:48 +00:00
m.listeners=[]; # for cleanup handling
m.aircraft_source = NDSourceDriver.new(); # uses the main aircraft as the driver/source (speeds, position, heading)
2013-12-01 12:29:22 +00:00
2014-01-08 20:45:48 +00:00
m.nd_style = NDStyles[style]; # look up ND specific stuff (file names etc)
2013-12-01 12:29:22 +00:00
m.radio_list=["instrumentation/comm/frequencies","instrumentation/comm[1]/frequencies",
"instrumentation/nav/frequencies","instrumentation/nav[1]/frequencies"];
m.mfd_mode_list=["APP","VOR","MAP","PLAN"];
2014-01-08 20:45:48 +00:00
m.efis_path = prop1;
m.efis_switches = switches;
m.rangeNm = func m.get_switch('toggle_range');
2013-12-01 12:29:22 +00:00
m.efis = props.globals.initNode(prop1);
m.mfd = m.efis.initNode("mfd");
# TODO: unify this with switch handling
m.mfd_mode_num = m.mfd.initNode("mode-num",2,"INT");
m.std_mode = m.efis.initNode("inputs/setting-std",0,"BOOL");
2014-01-08 20:45:48 +00:00
m.previous_set = m.efis.initNode("inhg-previous",29.92);
2013-12-01 12:29:22 +00:00
m.kpa_mode = m.efis.initNode("inputs/kpa-mode",0,"BOOL");
m.kpa_output = m.efis.initNode("inhg-kpa",29.92);
m.kpa_prevoutput = m.efis.initNode("inhg-kpa-previous",29.92);
m.temp = m.efis.initNode("fixed-temp",0);
m.alt_meters = m.efis.initNode("inputs/alt-meters",0,"BOOL");
m.fpv = m.efis.initNode("inputs/fpv",0,"BOOL");
m.mins_mode = m.efis.initNode("inputs/minimums-mode",0,"BOOL");
m.mins_mode_txt = m.efis.initNode("minimums-mode-text","RADIO","STRING");
m.minimums = m.efis.initNode("minimums",250,"INT");
m.mk_minimums = props.globals.getNode("instrumentation/mk-viii/inputs/arinc429/decision-height");
2014-01-08 20:45:48 +00:00
# TODO: these are switches, can be unified with switch handling hash above (eventually):
2013-12-01 12:29:22 +00:00
2014-01-08 20:45:48 +00:00
m.nd_plan_wpt = m.efis.initNode("inputs/plan-wpt-index", 0, "INT"); # ditto
2013-12-01 12:29:22 +00:00
return m;
2014-01-08 20:45:48 +00:00
},
2013-12-01 12:29:22 +00:00
newMFD: func(canvas_group)
{
me.listen("/sim/signals/reinit", func me.handle_reinit() );
me.update_timer = maketimer(0.05, func me.update() ); # TODO: make interval configurable via ctor
me.nd = canvas_group;
# load the specified SVG file into the me.nd group and populate all sub groups
canvas.parsesvg(me.nd, me.nd_style.svg_filename, {'font-mapper': me.nd_style.font_mapper});
me.symbols = {}; # storage for SVG elements, to avoid namespace pollution (all SVG elements end up here)
foreach(var feature; me.nd_style.features ) {
me.symbols[feature.id] = me.nd.getElementById(feature.id);
if(contains(feature.impl,'init')) feature.impl.init(me.nd, feature); # call The element's init code (i.e. updateCenter)
}
### this is the "old" method that's less flexible, we want to use the style hash instead (see above)
# because things are much better configurable that way
# now look up all required SVG elements and initialize member fields using the same name to have a convenient handle
2014-01-08 20:45:48 +00:00
foreach(var element; ["wind","dmeLDist","dmeRDist","dmeL","dmeR","vorL","vorR","vorLId","vorRId",
"range","status.wxr","status.wpt","hdgGroup","status.sta","status.arpt"])
2013-12-01 12:29:22 +00:00
me.symbols[element] = me.nd.getElementById(element);
# load elements from vector image, and create instance variables using identical names, and call updateCenter() on each
# anything that needs updatecenter called, should be added to the vector here
#
2014-01-08 20:45:48 +00:00
foreach(var element; ["windArrow","compassApp","northUp","aplSymMap","aplSymMapCtr","aplSymVor",
"staFromL2","staToL2","staFromR2","staToR2",
"locPtr","hdgTrk","truMag","altArc","planArcs",
"trkInd","compass","HdgBugCRT","TrkBugLCD","HdgBugLCD","selHdgLine","curHdgPtr",
"staFromL","staToL","staFromR","staToR"] )
me.symbols[element] = me.nd.getElementById(element).updateCenter();
foreach(var element; ["HdgBugCRT2","TrkBugLCD2","HdgBugLCD2","selHdgLine2","curHdgPtr2","vorCrsPtr2"] )
me.symbols[element] = me.nd.getElementById(element).setCenter(512,565);
2013-12-01 12:29:22 +00:00
# this should probably be using Philosopher's new SymbolLayer ?
me.map = me.nd.createChild("map","map")
.set("clip", "rect(124, 1024, 1024, 0)");
2014-01-08 20:45:48 +00:00
# this callback will be passed onto the model via the controller hash, and used for the positioned queries, to specify max query range:
2013-12-01 12:29:22 +00:00
var get_range = func me.get_switch('toggle_range');
# predicate for the draw controller
2014-01-08 20:45:48 +00:00
var is_tuned = func(freq)
{
var nav1=getprop("instrumentation/nav[0]/frequencies/selected-mhz");
var nav2=getprop("instrumentation/nav[1]/frequencies/selected-mhz");
if(freq == nav1 or freq == nav2) return 1;
return 0;
2013-12-01 12:29:22 +00:00
}
# another predicate for the draw controller
2014-01-08 20:45:48 +00:00
var get_course_by_freq = func(freq)
{
if(freq == getprop("instrumentation/nav[0]/frequencies/selected-mhz"))
return getprop("instrumentation/nav[0]/radials/selected-deg");
else
return getprop("instrumentation/nav[1]/radials/selected-deg");
2013-12-01 12:29:22 +00:00
}
2014-01-08 20:45:48 +00:00
var get_current_position = func
{
return [me.aircraft_source.get_lat(), me.aircraft_source.get_lon()];
2013-12-01 12:29:22 +00:00
}
# a hash with controller callbacks, will be passed onto draw routines to customize behavior/appearance
# the point being that draw routines don't know anything about their frontends (instrument or GUI dialog)
# so we need some simple way to communicate between frontend<->backend until we have real controllers
# for now, a single controller hash is shared by most layers - unsupported callbacks are simply ignored by the draw files
#
2014-01-08 20:45:48 +00:00
var controller =
{
query_range: func get_range(),
is_tuned:is_tuned,
get_tuned_course:get_course_by_freq,
get_position: get_current_position,
};
2013-12-01 12:29:22 +00:00
###
# set up various layers, controlled via callbacks in the controller hash
# revisit this code once Philosopher's "Smart MVC Symbols/Layers" work is committed and integrated
# helper / closure generator
var make_event_handler = func(predicate, layer) func predicate(me, layer);
me.layers={}; # storage container for all ND specific layers
# look up all required layers as specified per the NDStyle hash and do the initial setup for event handling
2014-01-08 20:45:48 +00:00
foreach(var layer; me.nd_style.layers)
{
# huge hack for the alt-arc, which is not rendered as a map group, but directly as part of the toplevel ND group
var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd;
var the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
# now register all layer specific notification listeners and their corresponding update predicate/callback
# pass the ND instance and the layer handle to the predicate when it is called
# so that it can directly access the ND instance and its own layer (without having to know the layer's name)
2013-12-01 12:29:22 +00:00
2014-01-08 20:45:48 +00:00
var event_handler = make_event_handler(layer.predicate, the_layer);
foreach(var event; layer.update_on)
{
me.listen_switch(event, event_handler ) ;
} # foreach event subscription
# and now update/init each layer once by calling its update predicate for initialization
event_handler();
} # foreach layer
2013-12-01 12:29:22 +00:00
# start the update timer, which makes sure that the update() will be called
me.update_timer.start();
2014-01-08 20:45:48 +00:00
# next, radio & autopilot & listeners
# TODO: move this to .init field in layers hash or to model files
foreach(var n; var radios = [ "instrumentation/nav/frequencies/selected-mhz",
2013-12-01 12:29:22 +00:00
"instrumentation/nav[1]/frequencies/selected-mhz"])
2014-01-08 20:45:48 +00:00
me.listen(n, func()
{
# me.drawvor();
# me.drawdme();
});
# TODO: move this to the route.model
# Hack to draw the route on rm activation
me.listen("/autopilot/route-manager/active", func(active)
{
if(active.getValue())
{
setprop(me.get_full_switch_path('toggle_display_mode'),getprop(me.get_full_switch_path('toggle_display_mode')));
}
else
{
#me.route_group.removeAllChildren(); # HACK!
}
});
me.listen("/autopilot/route-manager/current-wp", func(activeWp)
{
canvas.updatewp( activeWp.getValue() );
});
2013-12-01 12:29:22 +00:00
},
2014-01-08 20:45:48 +00:00
in_mode:func(switch, modes)
{
foreach(var m; modes) if(me.get_switch(switch)==m) return 1;
2013-12-01 12:29:22 +00:00
return 0;
},
# each model should keep track of when it last got updated, using current lat/lon
# in update(), we can then check if the aircraft has traveled more than 0.5-1 nm (depending on selected range)
# and update each model accordingly
update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
{
##
# important constants
var m1 = 111132.92;
var m2 = -559.82;
var m3 = 1.175;
var m4 = -0.0023;
var p1 = 111412.84;
var p2 = -93.5;
var p3 = 0.118;
var latNm = 60;
var lonNm = 60;
# fgcommand('profiler-start');
2013-12-28 15:18:35 +00:00
# Heading update
2013-12-04 22:19:22 +00:00
var userHdgMag = me.aircraft_source.get_hdg_mag();
2013-12-28 15:18:35 +00:00
var userHdgTru = me.aircraft_source.get_hdg_tru();
var userTrkMag = me.aircraft_source.get_trk_mag();
var userTrkTru = me.aircraft_source.get_trk_tru();
2014-01-08 20:45:48 +00:00
if(me.get_switch('toggle_true_north')) {
2013-12-28 15:18:35 +00:00
me.symbols.truMag.setText("TRU");
var userHdg=userHdgTru;
var userTrk=userTrkTru;
} else {
me.symbols.truMag.setText("MAG");
var userHdg=userHdgMag;
var userTrk=userTrkMag;
}
if (me.aircraft_source.get_spd() < 80)
userTrk = userHdg;
var userLat = me.aircraft_source.get_lat();
var userLon = me.aircraft_source.get_lon();
var userSpd = me.aircraft_source.get_spd();
var userVSpd = me.aircraft_source.get_vspd();
2014-01-08 20:45:48 +00:00
var dispLCD = me.get_switch('toggle_display_type') == "LCD";
2013-12-01 12:29:22 +00:00
# this should only ever happen when testing the experimental AI/MP ND driver hash (not critical)
2013-12-28 15:18:35 +00:00
if (!userHdg or !userTrk or !userLat or !userLon) {
print("aircraft source invalid, returning !");
2014-01-08 20:45:48 +00:00
return;
2013-12-01 12:29:22 +00:00
}
2014-01-08 20:45:48 +00:00
if(me.in_mode('toggle_display_mode', ['PLAN']))
2013-12-28 15:18:35 +00:00
me.map.setTranslation(512,512);
2014-01-08 20:45:48 +00:00
elsif(me.get_switch('toggle_centered'))
me.map.setTranslation(512,565);
2013-12-28 15:18:35 +00:00
else
me.map.setTranslation(512,824);
2013-12-01 12:29:22 +00:00
# Calculate length in NM of one degree at current location TODO: expose as methods, for external callbacks
var userLatR = userLat*D2R;
var userLonR = userLon*D2R;
var latlen = m1 + (m2 * math.cos(2 * userLatR)) + (m3 * math.cos(4 * userLatR)) + (m4 * math.cos(6 * userLatR));
var lonlen = (p1 * math.cos(userLatR)) + (p2 * math.cos(3 * userLatR)) + (p3 * math.cos(5 * userLatR));
latNm = latlen*M2NM; #60 at equator
lonNm = lonlen*M2NM; #60 at equator
2014-01-08 20:45:48 +00:00
me.symbols.windArrow.setRotation((getprop("/environment/wind-from-heading-deg")-userHdg)*D2R);
2013-12-01 12:29:22 +00:00
me.symbols.wind.setText(sprintf("%3.0f / %2.0f",getprop("/environment/wind-from-heading-deg"),getprop("/environment/wind-speed-kt")));
2014-01-08 20:45:48 +00:00
if(me.get_switch('toggle_lh_vor_adf') == 1)
{
me.symbols.vorL.setText("VOR L");
me.symbols.vorL.setColor(0.195,0.96,0.097);
me.symbols.dmeL.setText("DME");
me.symbols.dmeL.setColor(0.195,0.96,0.097);
if(getprop("instrumentation/nav/in-range"))
me.symbols.vorLId.setText(getprop("instrumentation/nav/nav-id"));
else
me.symbols.vorLId.setText(getprop("instrumentation/nav/frequencies/selected-mhz-fmt"));
me.symbols.vorLId.setColor(0.195,0.96,0.097);
if(getprop("instrumentation/nav/dme-in-range"))
me.symbols.dmeLDist.setText(sprintf("%3.1f",getprop("instrumentation/nav/nav-distance")*0.000539));
else me.symbols.dmeLDist.setText(" ---");
me.symbols.dmeLDist.setColor(0.195,0.96,0.097);
} elsif(me.get_switch('toggle_lh_vor_adf') == -1) {
me.symbols.vorL.setText("ADF L");
me.symbols.vorL.setColor(0,0.6,0.85);
me.symbols.dmeL.setText("");
me.symbols.dmeL.setColor(0,0.6,0.85);
if((var navident=getprop("instrumentation/adf/ident")) != "")
me.symbols.vorLId.setText(navident);
else me.symbols.vorLId.setText(sprintf("%3d",getprop("instrumentation/adf/frequencies/selected-khz")));
me.symbols.vorLId.setColor(0,0.6,0.85);
me.symbols.dmeLDist.setText("");
me.symbols.dmeLDist.setColor(0,0.6,0.85);
} else {
me.symbols.vorL.setText("");
me.symbols.dmeL.setText("");
me.symbols.vorLId.setText("");
me.symbols.dmeLDist.setText("");
}
if(me.get_switch('toggle_rh_vor_adf') == 1) {
me.symbols.vorR.setText("VOR R");
me.symbols.vorR.setColor(0.195,0.96,0.097);
me.symbols.dmeR.setText("DME");
me.symbols.dmeR.setColor(0.195,0.96,0.097);
if(getprop("instrumentation/nav[1]/in-range"))
me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/nav-id"));
else
me.symbols.vorRId.setText(getprop("instrumentation/nav[1]/frequencies/selected-mhz-fmt"));
me.symbols.vorRId.setColor(0.195,0.96,0.097);
if(getprop("instrumentation/nav[1]/dme-in-range"))
me.symbols.dmeRDist.setText(sprintf("%3.1f",getprop("instrumentation/nav[1]/nav-distance")*0.000539));
else me.symbols.dmeRDist.setText(" ---");
me.symbols.dmeRDist.setColor(0.195,0.96,0.097);
} elsif(me.get_switch('toggle_rh_vor_adf') == -1) {
me.symbols.vorR.setText("ADF R");
me.symbols.vorR.setColor(0,0.6,0.85);
me.symbols.dmeR.setText("");
me.symbols.dmeR.setColor(0,0.6,0.85);
if((var navident=getprop("instrumentation/adf[1]/ident")) != "")
me.symbols.vorRId.setText(navident);
else me.symbols.vorRId.setText(sprintf("%3d",getprop("instrumentation/adf[1]/frequencies/selected-khz")));
me.symbols.vorRId.setColor(0,0.6,0.85);
me.symbols.dmeRDist.setText("");
me.symbols.dmeRDist.setColor(0,0.6,0.85);
} else {
me.symbols.vorR.setText("");
me.symbols.dmeR.setText("");
me.symbols.vorRId.setText("");
me.symbols.dmeRDist.setText("");
}
me.symbols.range.setText(sprintf("%3.0f",me.rangeNm()/2));
2013-12-01 12:29:22 +00:00
# reposition the map, change heading & range:
2013-12-28 15:18:35 +00:00
if(me.in_mode('toggle_display_mode', ['PLAN'])) {
2014-01-08 20:45:48 +00:00
me.symbols.windArrow.setVisible(!dispLCD);
2013-12-28 15:18:35 +00:00
me.map._node.getNode("hdg",1).setDoubleValue(0);
if (getprop(me.efis_path ~ "/inputs/plan-wpt-index") >= 0) {
me.map._node.getNode("ref-lat",1).setDoubleValue(getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/latitude-deg"));
me.map._node.getNode("ref-lon",1).setDoubleValue(getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/longitude-deg"));
}
} else {
me.symbols.windArrow.show();
me.map._node.getNode("ref-lat",1).setDoubleValue(userLat);
me.map._node.getNode("ref-lon",1).setDoubleValue(userLon);
}
2014-01-08 20:45:48 +00:00
# The set range of the map does not correspond to what we see in-sim!!
me.map._node.getNode("range",1).setDoubleValue(me.rangeNm()/3.2); # avoid this here, use a listener instead
2013-12-01 12:29:22 +00:00
2014-01-08 20:45:48 +00:00
var vhdg_bug = getprop("autopilot/settings/heading-bug-deg");
2013-12-28 15:18:35 +00:00
if(me.in_mode('toggle_display_mode', ['MAP'])) {
2014-01-08 20:45:48 +00:00
me.symbols.HdgBugCRT.setRotation((vhdg_bug-userTrk)*D2R);
me.symbols.HdgBugLCD.setRotation((vhdg_bug-userTrk)*D2R);
me.symbols.TrkBugLCD.setRotation((vhdg_bug-userTrk)*D2R);
me.symbols.selHdgLine.setRotation((vhdg_bug-userTrk)*D2R);
me.symbols.HdgBugCRT2.setRotation((vhdg_bug-userTrk)*D2R);
me.symbols.TrkBugLCD2.setRotation((vhdg_bug-userTrk)*D2R);
me.symbols.selHdgLine2.setRotation((vhdg_bug-userTrk)*D2R);
2013-12-04 22:19:22 +00:00
me.symbols.trkInd.setRotation(0);
2014-01-08 20:45:48 +00:00
me.symbols.curHdgPtr.setRotation((userHdg-userTrk)*D2R);
me.symbols.curHdgPtr2.setRotation((userHdg-userTrk)*D2R);
me.map._node.getNode("hdg",1).setDoubleValue(userTrkTru);
2013-12-28 15:18:35 +00:00
me.symbols.compass.setRotation(-userTrk*D2R);
me.symbols.compassApp.setRotation(-userTrk*D2R);
2013-12-04 22:19:22 +00:00
me.symbols.hdgTrk.setText("TRK");
}
2013-12-28 15:18:35 +00:00
if(me.in_mode('toggle_display_mode', ['APP','VOR'])) {
2014-01-08 20:45:48 +00:00
me.symbols.HdgBugCRT.setRotation((vhdg_bug-userHdg)*D2R);
me.symbols.HdgBugLCD.setRotation((vhdg_bug-userHdg)*D2R);
me.symbols.selHdgLine.setRotation((vhdg_bug-userHdg)*D2R);
me.symbols.HdgBugCRT2.setRotation((vhdg_bug-userHdg)*D2R);
me.symbols.HdgBugLCD2.setRotation((vhdg_bug-userHdg)*D2R);
me.symbols.selHdgLine2.setRotation((vhdg_bug-userHdg)*D2R);
2013-12-28 15:18:35 +00:00
me.symbols.trkInd.setRotation((userTrk-userHdg)*D2R);
2014-01-08 20:45:48 +00:00
me.symbols.curHdgPtr.setRotation(0);
me.symbols.curHdgPtr2.setRotation(0);
me.map._node.getNode("hdg",1).setDoubleValue(userHdgTru);
2013-12-28 15:18:35 +00:00
me.symbols.compass.setRotation(-userHdg*D2R);
me.symbols.compassApp.setRotation(-userHdg*D2R);
2013-12-04 22:19:22 +00:00
me.symbols.hdgTrk.setText("HDG");
2013-12-28 15:18:35 +00:00
}
if(me.get_switch('toggle_centered')) {
if (me.in_mode('toggle_display_mode', ['APP','VOR'])) {
me.symbols.vorCrsPtr2.show();
me.symbols.compassApp.show();
if(getprop("instrumentation/nav/in-range")) {
var deflection = getprop("instrumentation/nav/heading-needle-deflection-norm");
me.symbols.locPtr.show();
me.symbols.locPtr.setTranslation(deflection*150,0);
if(abs(deflection < 0.99))
me.symbols.locPtr.setColorFill(1,0,1,1);
else
me.symbols.locPtr.setColorFill(1,0,1,0);
} else {
me.symbols.locPtr.hide();
}
me.symbols.vorCrsPtr2.setRotation((getprop("instrumentation/nav/radials/selected-deg")-userHdg)*D2R);
me.symbols.hdgGroup.setTranslation(0,100);
} else {
me.symbols.vorCrsPtr2.hide();
me.symbols.hdgGroup.setTranslation(0,100*me.in_mode('toggle_display_mode', ['MAP']));
me.symbols.compassApp.setVisible(me.in_mode('toggle_display_mode', ['MAP']));
}
} else {
me.symbols.vorCrsPtr2.hide();
me.symbols.hdgGroup.setTranslation(0,0);
me.symbols.compassApp.hide();
}
if ((me.get_switch('toggle_centered') and !me.in_mode('toggle_display_mode', ['PLAN'])) or me.in_mode('toggle_display_mode', ['PLAN'])) {
me.symbols.compass.hide();
} else {
me.symbols.compass.show();
2013-12-04 22:19:22 +00:00
}
2013-12-01 12:29:22 +00:00
2013-12-28 15:18:35 +00:00
var staPtrVis = !me.in_mode('toggle_display_mode', ['APP','PLAN']);
2014-01-08 20:45:48 +00:00
var magVar = getprop("environment/magnetic-variation-deg");
if(me.in_mode('toggle_display_mode', ['APP','MAP','VOR','PLAN']))
{
if(getprop("instrumentation/nav/heading-deg") != nil)
var nav0hdg=getprop("instrumentation/nav/heading-deg") - userHdg - magVar;
if(getprop("instrumentation/nav[1]/heading-deg") != nil)
var nav1hdg=getprop("instrumentation/nav[1]/heading-deg") - userHdg - magVar;
var adf0hdg=getprop("instrumentation/adf/indicated-bearing-deg");
var adf1hdg=getprop("instrumentation/adf[1]/indicated-bearing-deg");
if(!me.get_switch('toggle_centered'))
{
if(me.in_mode('toggle_display_mode', ['PLAN']))
me.symbols.trkInd.hide();
else
me.symbols.trkInd.show();
if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) {
me.symbols.staFromL.setVisible(staPtrVis);
me.symbols.staToL.setVisible(staPtrVis);
me.symbols.staFromL.setColor(0.195,0.96,0.097);
me.symbols.staToL.setColor(0.195,0.96,0.097);
me.symbols.staFromL.setRotation((nav0hdg+180)*D2R);
me.symbols.staToL.setRotation(nav0hdg*D2R);
}
elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) {
me.symbols.staFromL.setVisible(staPtrVis);
me.symbols.staToL.setVisible(staPtrVis);
me.symbols.staFromL.setColor(0,0.6,0.85);
me.symbols.staToL.setColor(0,0.6,0.85);
me.symbols.staFromL.setRotation((adf0hdg+180)*D2R);
me.symbols.staToL.setRotation(adf0hdg*D2R);
} else {
me.symbols.staFromL.hide();
me.symbols.staToL.hide();
}
if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) {
me.symbols.staFromR.setVisible(staPtrVis);
me.symbols.staToR.setVisible(staPtrVis);
me.symbols.staFromR.setColor(0.195,0.96,0.097);
me.symbols.staToR.setColor(0.195,0.96,0.097);
me.symbols.staFromR.setRotation((nav1hdg+180)*D2R);
me.symbols.staToR.setRotation(nav1hdg*D2R);
} elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) {
me.symbols.staFromR.setVisible(staPtrVis);
me.symbols.staToR.setVisible(staPtrVis);
me.symbols.staFromR.setColor(0,0.6,0.85);
me.symbols.staToR.setColor(0,0.6,0.85);
me.symbols.staFromR.setRotation((adf1hdg+180)*D2R);
me.symbols.staToR.setRotation(adf1hdg*D2R);
} else {
me.symbols.staFromR.hide();
me.symbols.staToR.hide();
}
me.symbols.staFromL2.hide();
me.symbols.staToL2.hide();
me.symbols.staFromR2.hide();
me.symbols.staToR2.hide();
me.symbols.curHdgPtr2.hide();
me.symbols.HdgBugCRT2.hide();
me.symbols.TrkBugLCD2.hide();
me.symbols.HdgBugLCD2.hide();
me.symbols.selHdgLine2.hide();
me.symbols.curHdgPtr.setVisible(staPtrVis);
me.symbols.TrkBugLCD.hide();
me.symbols.HdgBugCRT.setVisible(staPtrVis and !dispLCD);
me.symbols.HdgBugLCD.setVisible(staPtrVis and dispLCD);
me.symbols.selHdgLine.setVisible(staPtrVis);
} else {
me.symbols.trkInd.hide();
if((getprop("instrumentation/nav/in-range") and me.get_switch('toggle_lh_vor_adf') == 1)) {
me.symbols.staFromL2.setVisible(staPtrVis);
me.symbols.staToL2.setVisible(staPtrVis);
me.symbols.staFromL2.setColor(0.195,0.96,0.097);
me.symbols.staToL2.setColor(0.195,0.96,0.097);
me.symbols.staFromL2.setRotation((nav0hdg+180)*D2R);
me.symbols.staToL2.setRotation(nav0hdg*D2R);
} elsif(getprop("instrumentation/adf/in-range") and (me.get_switch('toggle_lh_vor_adf') == -1)) {
me.symbols.staFromL2.setVisible(staPtrVis);
me.symbols.staToL2.setVisible(staPtrVis);
me.symbols.staFromL2.setColor(0,0.6,0.85);
me.symbols.staToL2.setColor(0,0.6,0.85);
me.symbols.staFromL2.setRotation((adf0hdg+180)*D2R);
me.symbols.staToL2.setRotation(adf0hdg*D2R);
} else {
me.symbols.staFromL2.hide();
me.symbols.staToL2.hide();
}
if((getprop("instrumentation/nav[1]/in-range") and me.get_switch('toggle_rh_vor_adf') == 1)) {
me.symbols.staFromR2.setVisible(staPtrVis);
me.symbols.staToR2.setVisible(staPtrVis);
me.symbols.staFromR2.setColor(0.195,0.96,0.097);
me.symbols.staToR2.setColor(0.195,0.96,0.097);
me.symbols.staFromR2.setRotation((nav1hdg+180)*D2R);
me.symbols.staToR2.setRotation(nav1hdg*D2R);
} elsif(getprop("instrumentation/adf[1]/in-range") and (me.get_switch('toggle_rh_vor_adf') == -1)) {
me.symbols.staFromR2.setVisible(staPtrVis);
me.symbols.staToR2.setVisible(staPtrVis);
me.symbols.staFromR2.setColor(0,0.6,0.85);
me.symbols.staToR2.setColor(0,0.6,0.85);
me.symbols.staFromR2.setRotation((adf1hdg+180)*D2R);
me.symbols.staToR2.setRotation(adf1hdg*D2R);
} else {
me.symbols.staFromR2.hide();
me.symbols.staToR2.hide();
}
me.symbols.staFromL.hide();
me.symbols.staToL.hide();
me.symbols.staFromR.hide();
me.symbols.staToR.hide();
me.symbols.curHdgPtr.hide();
me.symbols.HdgBugCRT.hide();
me.symbols.TrkBugLCD.hide();
me.symbols.HdgBugLCD.hide();
me.symbols.selHdgLine.hide();
me.symbols.curHdgPtr2.setVisible(staPtrVis);
me.symbols.TrkBugLCD2.hide();
me.symbols.HdgBugCRT2.setVisible(staPtrVis and !dispLCD);
me.symbols.HdgBugLCD2.setVisible(staPtrVis and dispLCD);
me.symbols.selHdgLine2.setVisible(staPtrVis);
}
2013-12-28 15:18:35 +00:00
}
2014-01-08 20:45:48 +00:00
2013-12-28 15:18:35 +00:00
me.symbols.hdgGroup.setVisible(!me.in_mode('toggle_display_mode', ['PLAN']));
me.symbols.northUp.setVisible(me.in_mode('toggle_display_mode', ['PLAN']));
me.symbols.aplSymMap.setVisible(me.in_mode('toggle_display_mode', ['APP','MAP','VOR']) and !me.get_switch('toggle_centered'));
me.symbols.aplSymMapCtr.setVisible(me.in_mode('toggle_display_mode', ['MAP']) and me.get_switch('toggle_centered'));
me.symbols.aplSymVor.setVisible(me.in_mode('toggle_display_mode', ['APP','VOR']) and me.get_switch('toggle_centered'));
me.symbols.planArcs.setVisible(me.in_mode('toggle_display_mode', ['PLAN']));
if (abs(userVSpd) > 5) {
var altDiff = getprop("autopilot/settings/target-altitude-ft")-getprop("instrumentation/altimeter/indicated-altitude-ft");
if (abs(altDiff) > 50 and altDiff/userVSpd > 0) {
var altRangeNm = altDiff/userVSpd*userSpd*KT2MPS*M2NM;
if(altRangeNm > 1) {
var altRangePx = (350/me.rangeNm())*altRangeNm;
if (altRangePx > 700)
altRangePx = 700;
me.symbols.altArc.setTranslation(0,-altRangePx);
}
me.symbols.altArc.show();
} else
me.symbols.altArc.hide();
2013-12-04 22:19:22 +00:00
} else {
me.symbols.altArc.hide();
2013-12-28 15:18:35 +00:00
}
2013-12-01 12:29:22 +00:00
## run all predicates in the NDStyle hash and evaluate their true/false behavior callbacks
## this is in line with the original design, but normally we don't need to getprop/poll here,
## using listeners or timers would be more canvas-friendly whenever possible
## because running setprop() on any group/canvas element at framerate means that the canvas
## will be updated at frame rate too - wasteful ... (check the performance monitor!)
foreach(var feature; me.nd_style.features ) {
2014-01-08 20:45:48 +00:00
# for stuff that always needs to be updated
if (contains(feature.impl, 'common')) feature.impl.common(me);
# conditional stuff
if(!contains(feature.impl, 'predicate')) continue; # no conditional stuff
if ( var result=feature.impl.predicate(me) )
feature.impl.is_true(me, result); # pass the result to the predicate
else
feature.impl.is_false( me, result ); # pass the result to the predicate
2013-12-01 12:29:22 +00:00
}
## update the status flags shown on the ND (wxr, wpt, arpt, sta)
# this could/should be using listeners instead ...
2013-12-28 15:18:35 +00:00
me.symbols['status.wxr'].setVisible( me.get_switch('toggle_weather') and me.in_mode('toggle_display_mode', ['MAP']));
me.symbols['status.wpt'].setVisible( me.get_switch('toggle_waypoints') and me.in_mode('toggle_display_mode', ['MAP']));
me.symbols['status.arpt'].setVisible( me.get_switch('toggle_airports') and me.in_mode('toggle_display_mode', ['MAP']));
me.symbols['status.sta'].setVisible( me.get_switch('toggle_stations') and me.in_mode('toggle_display_mode', ['MAP']));
2013-12-01 12:29:22 +00:00
}
2014-01-08 20:45:48 +00:00
};