1
0
Fork 0

Many MapStructure/NavDisplay updates

See the clone at https://gitorious.org/fg/canvas-hackers-fgdata/source/topics/canvas-radar:
This commit is contained in:
Philosopher 2014-04-28 21:26:32 -05:00
parent 1e2bf918da
commit 37c005c222
73 changed files with 1180 additions and 398 deletions

View file

@ -1,4 +1,9 @@
################################################################################
## MapStructure mapping/charting framework for Nasal/Canvas, by Philosopher
## See: http://wiki.flightgear.org/MapStructure
###############################################################################
var _MP_dbg_lvl = "info";
#var _MP_dbg_lvl = "alert";
var dump_obj = func(m) {
var h = {};
@ -9,32 +14,41 @@ var dump_obj = func(m) {
};
##
# must be either of:
# 1) draw* callback, 2) SVG filename, 3) Drawable class (with styling/LOD support)
var SymbolDrawable = {
new: func() {
# for LOD handling (i.e. airports/taxiways/runways)
var RangeAware = {
new: func {
return {parents:[RangeAware] };
},
del: func,
notifyRangeChange: func die("RangeAware.notifyRangeChange() must be provided by sub-class"),
};
## wrapper for each element
## i.e. keeps the canvas and texture map coordinates
## wrapper for each cached element
## i.e. keeps the canvas and texture map coordinates for the corresponding raster image
var CachedElement = {
new: func(canvas_path, name, source, offset) {
new: func(canvas_path, name, source, size, offset) {
var m = {parents:[CachedElement] };
if (isa(canvas_path, canvas.Canvas)) {
canvas_path = canvas_path.getPath();
}
m.canvas_src = canvas_path;
m.name = name;
m.source = source;
m.size = size;
m.offset = offset;
return m;
}, # new()
render: func(group) {
render: func(group, trans0=0, trans1=0) {
# create a raster image child in the render target/group
return
group.createChild("image", me.name)
var n = group.createChild("image", me.name)
.setFile( me.canvas_src )
# TODO: fix .setSourceRect() to accept a single vector for coordinates ...
.setSourceRect(left:me.source[0],top:me.source[1],right:me.source[2],bottom:me.source[3] , normalized:0)
.setTranslation(me.offset); # FIXME: make sure this stays like this and isn't overridden
# TODO: fix .setSourceRect() to accept a single vector for texture map coordinates ...
.setSourceRect(left:me.source[0],top:me.source[1],right:me.source[2],bottom:me.source[3], normalized:0)
.setSize(me.size)
.setTranslation(trans0,trans1);
n.createTransform().setTranslation(me.offset);
return n;
}, # render()
}; # of CachedElement
@ -77,10 +91,13 @@ var SymbolCache = {
"view": m.canvas_sz,
"mipmapping": 1
});
m.canvas_texture.setColorBackground(0, 0, 0, 0); #rgba
# add a placement
m.canvas_texture.addPlacement( {"type": "ref"} );
m.path = m.canvas_texture.getPath();
m.root = m.canvas_texture.createGroup("entries");
return m;
},
add: func(name, callback, draw_mode=0) {
@ -90,7 +107,7 @@ var SymbolCache = {
# get canvas texture that we use as cache
# get next free spot in texture (column/row)
# run the draw callback and render into a group
var gr = me.canvas_texture.createGroup();
var gr = me.root.createChild("group",name);
gr.setTranslation( me.next_free[0] + me.image_sz[0]*draw_mode0,
me.next_free[1] + me.image_sz[1]*draw_mode1);
#settimer(func debug.dump ( gr.getTransformedBounds() ), 0); # XXX: these are only updated when rendered
@ -102,17 +119,27 @@ var SymbolCache = {
# get the bounding box, i.e. coordinates for texture map, or use the .setTranslation() params
var coords = me.next_free~me.next_free;
foreach (var i; [0,1])
coords[i+2] += me.image_sz[i];
coords[i+1] += me.image_sz[i];
foreach (var i; [0,1])
coords[i*2+1] = me.canvas_sz[i] - coords[i*2+1];
# get the offset we used to position correctly in the bounds of the canvas
var offset = [me.image_sz[0]*draw_mode0, me.image_sz[1]*draw_mode1];
# store texture map coordinates in lookup map using the name as identifier
me.dict[name] = CachedElement.new(me.canvas_texture.getPath(), name, coords, offset );
var offset = [-me.image_sz[0]*draw_mode0, -me.image_sz[1]*draw_mode1];
# update next free position in cache (column/row)
me.next_free[0] += me.image_sz[0];
if (me.next_free[0] >= me.canvas_sz[0])
{ me.next_free[0] = 0; me.next_free[1] += me.image_sz[1] }
if (me.next_free[1] >= me.canvas_sz[1])
die("SymbolCache: ran out of space after adding '"~name~"'");
# store texture map coordinates in lookup map using the name as identifier
return me.dict[name] = CachedElement.new(
canvas_path: me.path,
name: name,
source: coords,
size:me.image_sz,
offset: offset,
);
}, # add()
get: func(name) {
if(!contains(me.dict,name)) die("No SymbolCache entry for key:"~ name);
@ -131,8 +158,9 @@ var Symbol = {
else return class,
# Calls corresonding symbol constructor
# @param group #Canvas.Group to place this on.
new: func(type, group, arg...) {
var ret = call((var class = me.get(type)).new, [group]~arg, class);
# @param layer The #SymbolLayer this is a child of.
new: func(type, group, layer, arg...) {
var ret = call((var class = me.get(type)).new, [group, layer]~arg, class);
ret.element.set("symbol-type", type);
return ret;
},
@ -147,6 +175,7 @@ var Symbol = {
die("del() not implemented for this symbol type!"),
}; # of Symbol
Symbol.Controller = {
# Static/singleton:
registry: {},
@ -176,43 +205,80 @@ Symbol.Controller = {
var getpos_fromghost = func(positioned_g)
return [positioned_g.lat, positioned_g.lon];
# to add support for additional ghosts, just append them to the vector below, possibly at runtime:
var supported_ghosts = ['positioned','Navaid','Fix','flightplan-leg','FGAirport'];
var is_positioned_ghost = func(obj) {
var gt = ghosttype(obj);
foreach(var ghost; supported_ghosts) {
if (gt == ghost) return 1; # supported ghost was found
}
return 0; # not a known/supported ghost
};
# Generic getpos: get lat/lon from any object:
# (geo.Coord and positioned ghost currently)
Symbol.Controller.getpos = func(obj) {
if (typeof(obj) == 'ghost')
if (ghosttype(obj) == 'positioned' or ghosttype(obj) == 'Navaid' or ghosttype(obj)=='Fix' or ghosttype(obj)=='flightplan-leg')
Symbol.Controller.getpos = func(obj, p=nil) {
if (obj == nil) die("Symbol.Controller.getpos received nil");
if (p == nil) {
var ret = Symbol.Controller.getpos(obj, obj);
if (ret != nil) return ret;
if (contains(obj, "parents")) {
foreach (var p; obj.parents) {
var ret = Symbol.Controller.getpos(obj, p);
if (ret != nil) return ret;
}
}
debug.dump(obj);
die("no suitable getpos() found! Of type: "~typeof(obj));
} else {
if (typeof(p) == 'ghost')
if ( is_positioned_ghost(p) )
return getpos_fromghost(obj);
else
die("bad ghost of type '"~ghosttype(obj)~"'");
if (typeof(obj) == 'hash')
if (isa(obj, geo.Coord))
die("bad/unsupported ghost of type '"~ghosttype(obj)~"' (see MapStructure.nas Symbol.Controller.getpos() to add new ghosts)");
if (typeof(p) == 'hash')
if (p == geo.Coord)
return subvec(obj.latlon(), 0, 2);
if (isa(obj, props.Node))
if (p == props.Node)
return [
obj.getValue("position/latitude-deg") or obj.getValue("latitude-deg"),
obj.getValue("position/longitude-deg") or obj.getValue("longitude-deg")
];
if (contains(obj,'lat') and contains(obj,'lon'))
if (contains(p,'lat') and contains(p,'lon'))
return [obj.lat, obj.lon];
debug.dump(obj);
die("no suitable getpos() found! Of type: "~typeof(obj));
return nil;
}
};
Symbol.Controller.equals = func(l, r) {
Symbol.Controller.equals = func(l, r, p=nil) {
if (l == r) return 1;
var t = typeof(l);
if (t == 'ghost')
return 0;#l.id == r.id;
if (t == 'hash')
if (isa(l, props.Node))
return l.equals(r);
else {
foreach (var k; keys(l))
if (l[k] != r[k]) return 0;
return 1;
if (p == nil) {
var ret = Symbol.Controller.equals(l, r, l);
if (ret != nil) return ret;
if (contains(l, "parents")) {
foreach (var p; l.parents) {
var ret = Symbol.Controller.equals(l, r, p);
if (ret != nil) return ret;
}
die("bad types");
}
debug.dump(obj);
die("no suitable equals() found! Of type: "~typeof(obj));
} else {
if (typeof(p) == 'ghost')
if ( is_positioned_ghost(p) )
return l.id == r.id;
else
die("bad/unsupported ghost of type '"~ghosttype(l)~"' (see MapStructure.nas Symbol.Controller.getpos() to add new ghosts)");
if (typeof(p) == 'hash')
# Somewhat arbitrary convention:
# * l.equals(r) -- instance method, i.e. uses "me" and "arg[0]"
# * parent._equals(l,r) -- class method, i.e. uses "arg[0]" and "arg[1]"
if (contains(p, "equals"))
return l.equals(r);
if (contains(p, "_equals"))
return p._equals(l,r);
}
return nil;
};
@ -225,32 +291,28 @@ var assert_ms = func(hash, members...)
var DotSym = {
parents: [Symbol],
parents: [Symbol], # TODO: use StyleableSymbol here to support styling and caching
element_id: nil,
# Static/singleton:
makeinstance: func(name, hash) {
if (!isa(hash, DotSym))
die("OOP error");
#assert_ms(hash,
# "element_type", # type of Canvas element
# #"element_id", # optional Canvas id
# #"init", # initialize routine
# "draw", # init/update routine
# #getpos", # get position from model in [x_units,y_units] (optional)
#);
return Symbol.add(name, hash);
},
# For the instances returned from makeinstance:
# @param group The Canvas group to add this to.
# @param group The #Canvas.Group to add this to.
# @param layer The #SymbolLayer this is a child of.
# @param model A correct object (e.g. positioned ghost) as
# expected by the .draw file that represents
# metadata like position, speed, etc.
# @param controller Optional controller "glue". Each method
# is called with the model as the only argument.
new: func(group, model, controller=nil) {
new: func(group, layer, model, controller=nil) {
if (me == nil) die();
var m = {
parents: [me],
group: group,
layer: layer,
model: model,
controller: controller == nil ? me.df_controller : controller,
element: group.createChild(
@ -274,7 +336,7 @@ var DotSym = {
if (me.controller != nil)
me.controller.del(me.model);
call(func me.model.del(), nil, var err=[]); # try...
if (err[0] != "No such member: del") # ... and either catch or rethrow
if (size(err) and err[0] != "No such member: del") # ... and either catch or rethrow
die(err[0]);
me.element.del();
},
@ -297,6 +359,7 @@ var DotSym = {
if (size(pos) == 3)
var (lat,lon,rotation) = pos;
else die("bad position: "~debug.dump(pos));
# print(me.model.id, ": Position lat/lon: ", lat, "/", lon);
me.element.setGeoPosition(lat,lon);
if (rotation != nil)
me.element.setRotation(rotation);
@ -313,20 +376,36 @@ var SymbolLayer = {
if ((var class = me.registry[type]) == nil)
die("unknown type '"~type~"'");
else return class,
# Non-static:
# Default implementations/values:
df_controller: nil, # default controller
df_priority: nil, # default priority for display sorting
df_style: nil,
type: nil, # type of #Symbol to add (MANDATORY)
id: nil, # id of the group #canvas.Element (OPTIONAL)
# @param group A group to place this on.
# @param map The #Canvas.Map this is a member of.
# @param controller A controller object (parents=[SymbolLayer.Controller])
# or implementation (parents[0].parents=[SymbolLayer.Controller]).
new: func(group, controller=nil) {
new: func(group, map, controller=nil, style=nil, options=nil) {
#print("Creating new Layer instance");
if (me == nil) die();
var m = {
parents: [me],
group: group.createChild("group", me.id), # TODO: the id is not properly set, but would be useful for debugging purposes (VOR, FIXES, NDB etc)
map: map,
group: group.createChild("group", me.type), # TODO: the id is not properly set, but would be useful for debugging purposes (VOR, FIXES, NDB etc)
list: [],
options: options,
};
m.setVisible();
# print("Layer setup options:", m.options!=nil);
# do no overwrite the default style if style is nil:
if (style != nil and typeof(style)=='hash') {
#print("Setting up a custom style!");
m.style = style;
} else m.style = me.df_style;
# debug.dump(m.style);
m.searcher = geo.PositionedSearch.new(me.searchCmd, me.onAdded, me.onRemoved, m);
# FIXME: hack to expose type of layer:
if (caller(1)[1] == Map.addLayer) {
@ -349,10 +428,39 @@ var SymbolLayer = {
return m;
},
update: func() {
if (!me.getVisible()) {
return;
}
# TODO: add options hash processing here
var updater = func {
me.searcher.update();
foreach (var e; me.list)
e.update();
}
if (me.options != nil and me.options['update_wrapper'] !=nil) {
me.options.update_wrapper( me, updater ); # call external wrapper (usually for profiling purposes)
# print("calling update_wrapper!");
}
else {
# print("not using wrapper");
updater();
# debug.dump(me.options);
}
#var start=systime();
#var end=systime();
# print(me.type, " layer update:", end-start);
# HACK: hard-coded ...
#setprop("/gui/navdisplay/layers/"~me.type~"/delay-ms", (end-start)*1000 );
},
##
# useful to support checkboxes in dialogs (e.g. Map dialog)
# so that we can show/hide layers directly by registering a listener
# TODO: should also allow us to update the navdisplay logic WRT to visibility
hide: func me.group.hide(),
show: func me.group.show(),
getVisible: func me.group.getVisible(),
setVisible: func(visible = 1) me.group.setVisible(visible),
del: func() {
printlog(_MP_dbg_lvl, "SymbolLayer.del()");
me.controller.del();
@ -375,17 +483,27 @@ var SymbolLayer = {
}
return nil;
},
searchCmd: func() me.controller.searchCmd(),
searchCmd: func() {
var result = me.controller.searchCmd();
# some hardening TODO: don't do this always - only do it once during initialization, i.e. layer creation ?
var type=typeof(result);
if(type != 'nil' and type != 'vector')
die("MapStructure: searchCmd() method MUST return a vector of valid objects or nil! (was:"~type~")");
return result;
},
# Adds a symbol.
onAdded: func(model)
append(me.list, Symbol.new(me.type, me.group, model)),
onAdded: func(model) {
printlog(_MP_dbg_lvl, "Adding symbol of type "~me.type);
if (model == nil) die("Model was nil for "~debug.string(me.type));
append(me.list, Symbol.new(me.type, me.group, me, model));
},
# Removes a symbol.
onRemoved: func(model) {
if (me.findsym(model, 1)) die("model not found");
call(func model.del, nil, var err = []);
# ignore errors
# TODO: ignore only missing member del() errors? and only from the above line?
# Note: die(err[0]) rethrows it; die(err[0]~"") does not.
printlog(_MP_dbg_lvl, "Deleting symbol of type "~me.type);
if (me.findsym(model, 1) == nil) die("model not found");
call(func model.del(), nil, var err = []); # try...
if (size(err) and err[0] != "No such member: del") # ... and either catch or rethrow
die(err[0]);
},
}; # of SymbolLayer
@ -406,10 +524,8 @@ SymbolLayer.Controller = {
# @param layer The #SymbolLayer this controller is responsible for.
new: func(type, layer, arg...)
return call((var class = me.get(type)).new, [layer]~arg, class),
# Non-static:
run_update: func() {
me.layer.update();
},
# Default implementations:
run_update: func() me.layer.update(),
# @return List of positioned objects.
searchCmd: func()
die("searchCmd() not implemented for this SymbolLayer.Controller type!"),
@ -424,6 +540,10 @@ var CompassLayer = {
var AltitudeArcLayer = {
};
###
# set up a cache for 32x32 symbols
var SymbolCache32x32 = nil;#SymbolCache.new(1024,32);
var load_MapStructure = func {
Map.Controller = {
# Static/singleton:
@ -436,8 +556,41 @@ var load_MapStructure = func {
else return class,
# Calls corresonding controller constructor
# @param map The #SymbolMap this controller is responsible for.
new: func(type, layer, arg...)
return call((var class = me.get(type)).new, [map]~arg, class),
new: func(type, map, arg...) {
var m = call((var class = me.get(type)).new, [map]~arg, class);
if (!contains(m, "map"))
m.map = map;
# FIXME: fails on no member
elsif (m.map != map and !isa(m.map, map) and (
m.get_position != Map.Controller.get_position
or m.query_range != Map.Controller.query_range
or m.in_range != Map.Controller.in_range))
{ die("m must store the map handle as .map if it uses the default method(s)"); }
},
# Default implementations:
get_position: func() {
debug.warn("get_position is deprecated");
return me.map.getLatLon()~[me.map.getAlt()];
},
query_range: func() {
debug.warn("query_range is deprecated");
return me.map.getRange() or 30;
},
in_range: func(lat, lon, alt=0) {
var range = me.map.getRange();
if(range == nil) die("in_range: Invalid query range!");
# print("Query Range is:", range );
if (lat == nil or lon == nil) die("in_range: lat/lon invalid");
var pos = geo.Coord.new();
pos.set_latlon(lat, lon, alt or 0);
var map_pos = me.map.getPosCoord();
if (map_pos == nil)
return 0; # should happen *ONLY* when map is uninitialized
var distance_m = pos.distance_to( map_pos );
var is_in_range = distance_m < range * NM2M;
# print("Distance:",distance_m*M2NM," nm in range check result:", is_in_range);
return is_in_range;
},
};
####### LOAD FILES #######
@ -448,7 +601,7 @@ var load_MapStructure = func {
#print(file);
if (name == nil)
var name = split("/", file)[-1];
if (substr(name, size(name)-4) == ".draw")
if (substr(name, size(name)-4) == ".draw") # we don't need this anylonger, right ?
name = substr(name, 0, size(name)-5);
#print("reading file");
var code = io.readfile(file);
@ -469,25 +622,73 @@ var load_MapStructure = func {
return;
}
#print("calling code");
call(code, nil, nil, var hash = {});
#debug.dump(keys(hash));
# validate
var url = ' http://wiki.flightgear.org/MapStructure#';
# TODO: these rules should be extended for all main files lcontroller/scontroller and symbol
# TODO move this out of here, so that we can use these checks in other places (i.e. searchCmd validation)
var checks = [
{ extension:'symbol', symbol:'update', type:'func', error:' update() must not be overridden:', id:300},
{ extension:'symbol', symbol:'draw', type:'func', required:1, error:' symbol files need to export a draw() routine:', id:301},
{ extension:'lcontroller', symbol:'searchCmd', type:'func', required:1, error:' lcontroller without searchCmd method:', id:100},
];
var makeurl = func(scope, id) url ~ scope ~ ':' ~ id;
var bailout = func(file, message, scope, id) die(file~message~"\n"~makeurl(scope,id) );
var current_ext = split('.', file)[-1];
foreach(var check; checks) {
# check if we have any rules matching the current file extension
if (current_ext == check.extension) {
# check for fields that must not be overridden
if (check['error'] != nil and
hash[check.symbol]!=nil and !check['required'] and
typeof(hash[check.symbol])==check.type ) {
bailout(file,check.error,check.extension,check.id);
}
# check for required fields
if (check['required'] != nil and
hash[check.symbol]==nil and
typeof( hash[check.symbol]) != check.type) {
bailout(file,check.error,check.extension,check.id);
}
}
}
if(file==FG_ROOT~'/Nasal/canvas/map/DME.scontroller') {
# var test = hash.new(nil);
# debug.dump( id(hash.new) );
}
# TODO: call self tests/sanity checks here
# and consider calling .symbol::draw() to ensure that certain APIs are NOT used, such as setGeoPosition() and setColor() etc (styling)
return hash;
};
# sets up a shared symbol cache, which will be used by all MapStructure maps and layers
# TODO: need to encode styling information as part of the keys/hash lookup, name - so that
# different maps/layers do not overwrite symbols accidentally
#
canvas.SymbolCache32x32 = SymbolCache.new(1024,32);
var load_deps = func(name) {
load(FG_ROOT~"/Nasal/canvas/map/"~name~".lcontroller", name);
load(FG_ROOT~"/Nasal/canvas/map/"~name~".symbol", name);
load(FG_ROOT~"/Nasal/canvas/map/"~name~".scontroller", name);
}
foreach( var name; ['VOR','FIX','NDB','DME','WPT','TFC'] )
# add your own MapStructure layers here, see the wiki for details: http://wiki.flightgear.org/MapStructure
foreach( var name; ['APT','VOR','FIX','NDB','DME','WPT','TFC','APS',] )
load_deps( name );
load(FG_ROOT~"/Nasal/canvas/map/aircraftpos.controller", name);
###
# set up a cache for 32x32 symbols
var SymbolCache32x32 = SymbolCache.new(1024,32);
# disable this for now
if(0) {
var drawVOR = func(color, width=3) return func(group) {
# print("drawing vor");
var bbox = group.createChild("path")
@ -508,24 +709,31 @@ var load_MapStructure = func {
var cachedVOR3 = SymbolCache32x32.add( "VOR-GREEN" , drawVOR( color:[0, 1, 0], width: 3), SymbolCache.DRAW_CENTERED );
var cachedVOR4 = SymbolCache32x32.add( "VOR-WHITE" , drawVOR( color:[1, 1, 1], width: 3), SymbolCache.DRAW_CENTERED );
# visually verify VORs were placed:
# var dlg2 = canvas.Window.new([1024,1024], "dialog");
# dlg2.setCanvas(SymbolCache32x32.canvas_texture);
# use one:
# var dlg = canvas.Window.new([120,120],"dialog");
# var my_canvas = dlg.createCanvas().setColorBackground(1,1,1,1);
# var root = my_canvas.createGroup();
# SymbolCache32x32.get(name:"VOR-RED").render( group: root ).setTranslation(60,60);
}
# STRESS TEST
if (0) {
for(var i=0;i <= 1024/32*4 - 4; i+=1)
SymbolCache32x32.add( "VOR-YELLOW"~i , drawVOR( color:[1, 1, 0], width: 3) );
var dlg = canvas.Window.new([640,320],"dialog");
var my_canvas = dlg.createCanvas().setColorBackground(1,1,1,1);
var root = my_canvas.createGroup();
SymbolCache32x32.get(name:"VOR-BLUE").render( group: root ).setGeoPosition(getprop("/position/latitude-deg"),getprop("/position/longitude-deg"));
#for(var i=0;i <= 1024/32*4 - 4; i+=1)
# SymbolCache32x32.add( "VOR-YELLOW"~i , drawVOR( color:[1, 1, 0], width: 3) );
}
})();
#print("finished loading files");
####### TEST SYMBOL #######
canvas.load_MapStructure = func;
canvas.load_MapStructure = func; # @Philosopher: is this intended/needed ??
}; # load_MapStructure
setlistener("/nasal/canvas/loaded", load_MapStructure); # end ugly module init listener hack
setlistener("/nasal/canvas/loaded", load_MapStructure); # end ugly module init listener hack. FIXME: do smart Nasal bootstrapping, quod est callidus!
# Actually, it would be even better to support reloading MapStructure files, and maybe even MapStructure itself by calling the dtor/del method for each Map and then re-running the ctor

View file

@ -244,6 +244,7 @@ var Element = {
#
# @param color Vector of 3 or 4 values in [0, 1]
setColorFill: func me.set('fill', _getColor(arg)),
getColorFill: func me.get('fill'),
#
getTransformedBounds: func me.getTightBoundingBox(),
# Calculate the transformation center based on bounding box and center-offset
@ -420,7 +421,7 @@ var Map = {
df_controller: nil,
new: func(ghost)
{
return { parents: [Map, Group.new(ghost)], layers:{} }.setController();
return { parents: [Map, Group.new(ghost)], layers:{}, controller:nil }.setController();
},
del: func()
{
@ -437,6 +438,7 @@ var Map = {
},
setController: func(controller=nil)
{
if (me.controller != nil) me.controller.del(me);
if (controller == nil)
controller = Map.df_controller;
elsif (typeof(controller) != 'hash')
@ -447,17 +449,24 @@ var Map = {
} else {
if (!isa(controller, Map.Controller))
die("OOP error: controller needs to inherit from Map.Controller");
me.controller = controller.new(me);
if (!isa(me.controller, controller))
die("OOP error: created instance needs to inherit from specific controller class");
me.controller = call(func controller.new(me), nil, var err=[]); # try...
if (size(err)) {
if (err[0] != "No such member: new") # ... and either catch or rethrow
die(err[0]);
else
me.controller = controller;
} elsif (me.controller == nil) {
me.controller = controller;
} elsif (me.controller != controller and !isa(me.controller, controller))
die("OOP error: created instance needs to inherit from or be the specific controller class");
}
return me;
},
addLayer: func(factory, type_arg=nil, priority=nil)
addLayer: func(factory, type_arg=nil, priority=nil, style=nil, options=nil)
{
if(contains(me.layers, type_arg))
print("addLayer() warning: overwriting existing layer:", type_arg);
printlog("warn", "addLayer() warning: overwriting existing layer:", type_arg);
# print("addLayer():", type_arg);
@ -466,29 +475,69 @@ var Map = {
var type = factory.get(type_arg);
else var type = factory;
me.layers[type_arg]= type.new(me);
me.layers[type_arg] = type.new(group:me, map:me, style:style,options:options);
if (priority == nil)
priority = type.df_priority;
if (priority != nil)
me.layers[type_arg].setInt("z-index", priority);
me.layers[type_arg].group.setInt("z-index", priority);
return me;
},
getLayer: func(type_arg) me.layers[type_arg],
setPos: func(lat, lon, hdg=nil, range=nil)
setRange: func(range) me.set("range",range),
getRange: func me.get('range'),
setPos: func(lat, lon, hdg=nil, range=nil, alt=nil)
{
me.set("ref-lat", lat);
me.set("ref-lon", lon);
if (hdg != nil)
me.set("hdg", hdg);
if (range != nil)
me.set("range", range);
me.setRange(range);
if (alt != nil)
me.set("altitude", hdg);
},
getPos: func
{
return [me.get("ref-lat"),
me.get("ref-lon"),
me.get("hdg"),
me.get("range"),
me.get("altitude")];
},
getLat: func me.get("ref-lat"),
getLon: func me.get("ref-lon"),
getHdg: func me.get("hdg"),
getAlt: func me.get("altitude"),
getRange: func me.get("range"),
getLatLon: func [me.get("ref-lat"), me.get("ref-lon")],
getPosCoord: func
{
var (lat, lon) = (me.get("ref-lat"),
me.get("ref-lon"));
var alt = me.get("altitude");
if (lat == nil or lon == nil) {
if (contains(me, "coord")) {
debug.warn("canvas.Map: lost ref-lat and/or ref-lon source");
}
return nil;
}
if (!contains(me, "coord")) {
me.coord = geo.Coord.new();
}
me.coord.set_latlon(lat,lon,alt or 0);
return me.coord;
},
# Update each layer on this Map. Called by
# me.controller.
update: func
update: func(predicate=nil)
{
foreach (var l; keys(me.layers)) {
var layer = me.layers[l];
# Only update if the predicate allows
if (predicate == nil or predicate(layer))
call(layer.update, arg, layer);
}
return me;
@ -570,7 +619,10 @@ var Text = {
me.setDouble("max-width", w);
},
setColor: func me.set('fill', _getColor(arg)),
setColorFill: func me.set('background', _getColor(arg))
getColor: func me.get('fill'),
setColorFill: func me.set('background', _getColor(arg)),
getColorFill: func me.get('background'),
};
# Path
@ -826,8 +878,10 @@ var Path = {
},
setColor: func me.setStroke(_getColor(arg)),
setColorFill: func me.setFill(_getColor(arg)),
getColor: func me.getStroke(),
setColorFill: func me.setFill(_getColor(arg)),
getColorFill: func me.getColorFill(),
setFill: func(fill)
{
me.set('fill', fill);
@ -836,6 +890,8 @@ var Path = {
{
me.set('stroke', stroke);
},
getStroke: func me.get('stroke'),
setStrokeLineWidth: func(width)
{
me.setDouble('stroke-width', width);
@ -997,6 +1053,7 @@ var Canvas = {
#
# @param color Vector of 3 or 4 values in [0, 1]
setColorBackground: func () { me.texture.getNode('background', 1).setValue(_getColor(arg)); me; },
getColorBackground: func me.texture.get('background'),
# Get path of canvas to be used eg. in Image::setFile
getPath: func()
{

View file

@ -0,0 +1,27 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APS';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var new = func(layer) {
layer.searcher._equals = func(a,b) {
a == b;
}
return {
parents: [__self__],
map: layer.map,
};
};
var del = func;
var searchCmd = func {
var c = me.map.getPosCoord();
return [c];
};

View file

@ -0,0 +1,9 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APS';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance

View file

@ -0,0 +1,16 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APS';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group";
var element_id = "airplane";
var init = func {
canvas.parsesvg(me.element, "Nasal/canvas/map/boeingAirplane.svg");
me.draw();
};
var draw = func me.element.setRotation(me.layer.map.getHdg());

View file

@ -0,0 +1,35 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APT';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var a_instance = nil;
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
};
__self__.a_instance = m;
return m;
};
var del = func() {
#print(name,".lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
#print("Running query:", name);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findAirportsWithinRange(me.map.getPosCoord(), range);
};

View file

@ -0,0 +1,13 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APT';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die( name~".scontroller.query_range /MUST/ be provided by implementation" );

View file

@ -0,0 +1,52 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'APT';
var parents = [DotSym];
var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
var icon_fix = nil;
var text_fix = nil;
# add the draw routine from airports-nd.draw here
var draw = func {
if (me.icon_fix != nil) return;
var icon_apt = me.element.createChild("path", name ~ " icon" )
.moveTo(-17,0)
.arcSmallCW(17,17,0,34,0)
.arcSmallCW(17,17,0,-34,0)
.close()
.setColor(0,0.6,0.85)
.setStrokeLineWidth(3);
var text_apt = me.element.createChild("text", name ~ " label")
.setDrawMode( canvas.Text.TEXT )
.setTranslation(17,35)
.setText(me.model.id)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setColor(0,0.6,0.85)
.setFontSize(28);
#me.element.setGeoPosition(lat, lon)
# .set("z-index",1); # FIXME: this needs to be configurable!!
# disabled:
if(0) {
# the fix symbol
me.icon_fix = me.element.createChild("path")
.moveTo(-15,15)
.lineTo(0,-15)
.lineTo(15,15)
.close()
.setStrokeLineWidth(3)
.setColor(0,0.6,0.85)
.setScale(0.5,0.5); # FIXME: do proper LOD handling here - we need to scale according to current texture dimensions vs. original/design dimensions
# the fix label
me.text_fix = me.element.createChild("text")
.setDrawMode( canvas.Text.TEXT )
.setText(me.model.id)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setFontSize(28)
.setTranslation(5,25);
}
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'DME';
var parents = [SymbolLayer.Controller];
@ -7,17 +8,39 @@ SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: {
line_width: 3,
scale_factor: 1,
animation_test: 0,
debug: 1,
color_default: [0,0.6,0.85],
color_tuned: [0,1,0],
}
});
var a_instance = nil;
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
query_range_nm: 25,
query_type:'dme',
};
__self__.a_instance = m;
##
# default styling parameters - can be overridden via addLayer( style:{key:value, ...} )
if (contains(m.layer,'style')) return m; # we already have a proper style
# otherwise, set up a default style:
m.layer.style={};
m.layer.style.debug = 0; # HACK: setting this enables benchmarking and printlog statements
m.layer.style.animation_test = 0;
# these are used by the draw() routines, see DME.symbol
m.layer.style.scale_factor = 1.0 ; # applied to the whole group for now
m.layer.style.line_width = 3.0;
m.layer.style.color_tuned = [0,1,0];
m.layer.style.color_default = [0,0.6,0.85];
return m;
};
var del = func() {
@ -26,7 +49,9 @@ var del = func() {
};
var searchCmd = func {
#print("Running query:", me.query_type);
return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};

View file

@ -1,12 +1,9 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'DME';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var is_tuned = func()
die( name~".scontroller.is_tuned /MUST/ be provided by implementation" );
var new = func(model, symbol) ; # this controller doesn't need an instance

View file

@ -1,3 +1,15 @@
# See: http://wiki.flightgear.org/MapStructure
# This layer is currently being restructured to better support 1) styling, 2) LOD and 3) caching of instanced symbols
# The corresponding APIs in MapStructure.nas and api.nas should probably be extended acccordingly
#
# We can look into adapting the other layers once we have a single use-case that works properly - including styling, LOD and caching -
# at that point, the framework should have evolved sufficiently.
#
# It would probably be a good idea to incrementally port some other layers and provide the corresponding helpers/APIs, to reduce the amount
# of specialized/identical code in the .symbol files.
#
# Class things:
var name = 'DME';
var parents = [DotSym];
@ -5,11 +17,101 @@ var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
var timer = nil;
###
# symbols (canvas groups) managed by this file
#
var icon_dme = nil;
var draw = func {
# Init
if (me.icon_dme == nil) {
var scale_animation = 1;
var animate = func {
if (me.scale_animation >= 1)
me.scale_animation -= .5;
else
me.scale_animation += .5;
me.element.setScale( me.scale_animation );
}
var del = func {
# me.timer.stop();
}
# CachedElement
# StyleAttribute
# Styleable
# RangeAwareElement
# var DMEIcon = StyleableElement.new( [{color:IconColor}, ] );
###
# helper to tell if the symbol is already initialized or not
# TODO: encapsulate API-wise (this is a very common thing to do...)
var is_initialized = func me.icon_dme != nil;
###
# FIXME: these should probably be part of MapStructure itself
# TODO: Need to come up with a StyleableElement class for this sort of stuff
var apply_styling = func {
# add all styleable groups here
# no need to do this whenever draw is called - typically, attributes won't change at all - so this is kinda redundant
var current_color = me.icon_dme.getColor();
var required_color = nil;
if (typeof(me.layer.map.controller["is_tuned"]) == 'func' and me.layer.map.controller.is_tuned(me.model.frequency/100))
#TODO: once we support caching/instancing, we cannot just change the symbol like this - we need to use a different symbol from the cache instead
# which is why these things need to be done ONCE during init to set up a cache entry for each symbol variation to come up with a corresponding raster image
# TODO: API-wise it would make sense to maintain a vector of required keys, so that the style hash can be validated in the ctor of the layer
# to ensure that all mandatory fields are supplied
required_color = canvas._getColor( me.layer.style.color_tuned);
else
required_color = canvas._getColor( me.layer.style.color_default);
if (current_color != required_color) {
# print("Setting color!");
# TODO: this is where we would select another cached symbol
me.icon_dme.setColor( required_color );
}
# else print("Not changing color (unnecessary)");
# debug.dump(me.layer.style);
}
###
# NOTE: This is only applied during initialization
# TODO: expose via addLayer/style param
# TODO: also needs to be aware of current range, so that proper LOD handling can be implemented
var apply_scale = func {
# add all symbols here that need scaling
# print("Scaling:", me.layer.style.scale_factor);
me.icon_dme.setScale( me.layer.style.scale_factor );
}
###
# draw routines must be called here to create a lookup table
# with different symbols, based on styling (colors etc)
# because we can no longer change instanced symbols later on
# as they will be shared, so we need one cache entry for each
# variation in style
var init_cache = func {
}
##
# init is done separately to prepare support for caching (instanced symbols)
# NOTE: People should not be "hard-coding" things like color/size here
# these need to be encapsulated via a Hash lookup, so that things can be
# easily customized
#
var init_symbols = func {
# debug.dump(me.layer.style);
me.icon_dme = me.element.createChild("path")
.moveTo(-15,0)
.line(-12.5,-7.5)
@ -25,11 +127,31 @@ var draw = func {
.horiz(-14.5)
.vert(-14.5)
.close()
.setStrokeLineWidth(3);
.setStrokeLineWidth( me.layer.style.line_width ); #TODO: this should be style-able
# finally scale the symbol as requested, this is done last so that people can override this when creating the layer
me.apply_scale();
if (me.layer.style.animation_test) {
me.timer = maketimer(0.33, func me.animate() );
me.timer.start();
}
if (me.controller != nil and me.controller.is_tuned(me.model.frequency/100))
me.icon_dme.setColor(0,1,0);
else
me.icon_dme.setColor(0,0.6,0.85);
}
var updateRun = func {
# check if the current station is tuned or not - and style the symbol accordingly (color)
me.apply_styling();
}
##
# this method is REQUIRED (basically, the entry point for drawing - most others are just helpers)
var draw = func {
# print("DME:draw()");
# Init: will set up the symbol if it isn't already
if ( !me.is_initialized() )
me.init_symbols();
# wrapper for custom styling, based on tuned/default colors (see lookup hash above)
me.updateRun();
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'FIX';
var parents = [SymbolLayer.Controller];
@ -7,27 +8,35 @@ SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: {
line_width: 3,
scale_factor: 0.5,
debug: 1,
color_default: [0, 0.6, 0.85],
}
});
var a_instance = nil;
var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
query_range_nm: 25,
query_type:'fix',
};
__self__.a_instance = m;
return m;
};
var del = func() {
#print("VOR.lcontroller.del()");
printlog(_MP_dbg_lvl, "VOR.lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
var searchCmd = func {
#print("Running query:", me.query_type);
return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'FIX';
var parents = [Symbol.Controller];
@ -5,8 +6,4 @@ var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die( name~".scontroller.query_range /MUST/ be provided by implementation" );

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'FIX';
var parents = [DotSym];
@ -8,23 +9,41 @@ var element_type = "group"; # we want a group, becomes "me.element"
var icon_fix = nil;
var text_fix = nil;
var draw = func {
if (me.icon_fix != nil) return;
# the fix symbol
me.icon_fix = me.element.createChild("path")
##
# used during initialization to populate the symbol cache with a FIX symbol
#
var drawFIX = func(color, width) func(group) {
var symbol = group.createChild("path")
.moveTo(-15,15)
.lineTo(0,-15)
.lineTo(15,15)
.close()
.setStrokeLineWidth(3)
.setColor(0,0.6,0.85)
.setStrokeLineWidth(width)
.setColor(color)
.setScale(0.5,0.5); # FIXME: do proper LOD handling here - we need to scale according to current texture dimensions vs. original/design dimensions
# the fix label
return symbol;
}
var icon_fix_cached = [
SymbolCache32x32.add(
name: "FIX",
callback: drawFIX( color:[0, 0.6, 0.85], width:3 ), # TODO: use the style hash to encapsulate styling stuff
draw_mode: SymbolCache.DRAW_CENTERED
)
];
var draw = func {
if (me.icon_fix != nil) return; # fix icon already initialized
# initialize the fix symbol
me.icon_fix = icon_fix_cached[0].render(me.element);
# non-cached stuff:
# FIXME: labels need to be LOD-aware (i.e. aware of MapController range, so that we can hide/show them)
me.text_fix = me.element.createChild("text")
.setDrawMode( canvas.Text.TEXT )
.setText(me.model.id)
.setFont("LiberationFonts/LiberationSans-Regular.ttf")
.setFontSize(28)
.setFont("LiberationFonts/LiberationSans-Regular.ttf") # TODO: encapsulate styling stuff
.setFontSize(28) # TODO: encapsulate styling stuff
.setTranslation(5,25);
};
}

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'NDB';
var parents = [SymbolLayer.Controller];
@ -12,8 +13,8 @@ var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
query_range_nm: 25,
query_type:'ndb',
};
return m;
@ -24,7 +25,9 @@ var del = func() {
};
var searchCmd = func {
#print("Running query:", me.query_type);
return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'NDB';
var parents = [Symbol.Controller];
@ -6,7 +7,4 @@ Symbol.Controller.add(name, __self__);
Symbol.registry[ name ].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[ name ];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die( name~".scontroller.query_range /MUST/ be provided by implementation" );

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'NDB';
var parents = [DotSym];

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'TFC';
var parents = [SymbolLayer.Controller];
@ -7,6 +8,7 @@ SymbolLayer.add(name, {
parents: [SymbolLayer],
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
df_style: { debug: 0 }, # style to use by default
});
var model_root = props.globals.getNode("/ai/models/");
@ -15,30 +17,10 @@ var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
query_range_nm: 25,
searchCmd: searchCmd_default,
};
# Listen to ai model events
append(m.listeners, setlistener(
model_root.getNode("model-added"), func(n) {
#printlog(_MP_dbg_lvl, "Dynamically adding model at "~n.getValue());
var node = props.globals.getNode(n.getValue());
var name = node.getName();
if (name == "aircraft" or name == "multiplayer")
if (m.in_range(node.getValue("position/latitude-deg"), node.getValue("position/longitude-deg")))
layer.onAdded(TrafficModel.new(node));
}
));
append(m.listeners, setlistener(
model_root.getNode("model-removed"), func(n) {
#printlog(_MP_dbg_lvl, "Dynamically deleting model at "~n.getValue());
var node = props.globals.getNode(n.getValue());
var name = node.getName();
if (name == "aircraft" or name == "multiplayer")
if (m.in_range(node.getValue("position/latitude-deg"), node.getValue("position/longitude-deg")))
layer.onRemoved(TrafficModel.new(node));
}
));
layer.searcher._equals = func(l,r) l.equals(r);
return m;
};
@ -47,19 +29,6 @@ var del = func() {
foreach (var l; me.listeners)
removelistener(l);
};
var in_range = func(lat,lon,myPositionVec=nil,max_dist_m=nil) {
if (lat == nil or lon == nil) return 0;
var pos = geo.Coord.new();
pos.set_latlon(lat,lon);
var myPosition = geo.Coord.new();
# FIXME: need a Map Controller for this, and all query_range's/get_position's
if (myPositionVec == nil)
var myPositionVec = me.get_position();
myPosition.set_latlon( myPositionVec[0], myPositionVec[1]);
if (max_dist_m == nil)
var max_dist_m = me.query_range()*NM2M;
return (pos.distance_to( myPosition ) <= max_dist_m )
};
var TrafficModel = {
new: func(node, id=nil, layer=nil) {
@ -72,43 +41,51 @@ var TrafficModel = {
node: node,
pos: node.getNode("position",1),
};
if (m.pos == nil)
m.latlon = func [nil,nil,nil];
#debug.dump(m); # why doesn't this print?
return m;
},
equals: func(other) other.id == me.id,
latlon: func() {
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 searchCmd = func {
##
# 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
#print("Doing query: "~name);
printlog(_MP_dbg_lvl, "Doing query: "~name);
var result = [];
# FIXME: need a Map Controller for this, and all query_range's/get_position's
var myPositionVec = me.get_position();
var max_dist_m = me.query_range()*NM2M;
var models = 0;
# AI and Multiplayer traffic
foreach (var traffic; [model_root.getChildren("aircraft"), model_root.getChildren("multiplayer")]) {
foreach(var t; traffic) {
if (me.in_range(t.getValue("position/latitude-deg"),
t.getValue("position/longitude-deg"),
myPositionVec,
max_dist_m))
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;
}
if (me.map.controller.in_range(lat, lon))
append(result, TrafficModel.new(t, nil, me.layer));
}
}
#debug.dump(result);
#return [];
#print("Found "~size(result)~" TrafficModel's in range out of "~models~" total.");
return result;
};

View file

@ -1,12 +1,16 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'TFC';
var parents = [Symbol.Controller];
var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[name].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
# XXX: this is more model-ish than controller-ish
var get_threat_lvl = func(model) model.getValue("tcas/threat-level");
var get_vspd = func(model) (model.getValue("velocities/vertical-speed-fps") or 0)*60;
var get_alt_diff = func(model) (model.getValue("position/altitude-ft") or 0) - (getprop("/position/altitude-ft") or 0);
var new = func(model, symbol) ; # this controller doesn't need an instance
var get_alt_diff = func(model) {
# debug.dump( keys(me) );
var model_alt = model.get_alt();
var alt = getprop("/position/altitude-ft"); # FIXME: hardcoded - right, we should probably generalize the "NDSourceDriver logic found in navdisplay.mfd and make it part of MapStructure
if (alt == nil or model_alt == nil) return 0;
return alt-model_alt;
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'TFC';
var parents = [DotSym];
@ -13,15 +14,15 @@ var arrow_type = nil;
var draw_tcas_arrow = nil;
var draw = func {
if (me.draw_tcas_arrow == nil)
me.draw_tcas_arrow = [
if (draw_tcas_arrow == nil)
draw_tcas_arrow = [
draw_tcas_arrow_above_500,
draw_tcas_arrow_below_500
];
#var callsign = me.model.getNode("callsign").getValue();
# print("Drawing traffic for:", callsign );
var threatLvl = me.controller.get_threat_lvl(me.model);
var vspeed = me.controller.get_vspd(me.model);
var threatLvl = me.model.get_threat_lvl();
var vspeed = me.model.get_vspd();
var altDiff = me.controller.get_alt_diff(me.model);
# Init
if (me.text_tcas == nil) {
@ -59,7 +60,7 @@ var draw = func {
.setColor(1,0,0)
.setColorFill(1,0,0);
me.text_tcas.setColor(1,0,0);
me.arrow_tcas.setColor(1,0,0);
me.arrow_tcas[me.arrow_type].setColor(1,0,0);
} elsif (threatLvl == 2) {
# traffic advisory
me.icon_tcas.moveTo(-17,0)
@ -68,7 +69,7 @@ var draw = func {
.setColor(1,0.5,0)
.setColorFill(1,0.5,0);
me.text_tcas.setColor(1,0.5,0);
me.arrow_tcas.setColor(1,0.5,0);
me.arrow_tcas[me.arrow_type].setColor(1,0.5,0);
} elsif (threatLvl == 1) {
# proximate traffic
me.icon_tcas.moveTo(-10,0)
@ -78,6 +79,8 @@ var draw = func {
.close()
.setColor(1,1,1)
.setColorFill(1,1,1);
me.text_tcas.setColor(1,1,1);
me.arrow_tcas[me.arrow_type].setColor(1,1,1);
} else {
# other traffic
me.icon_tcas.moveTo(-10,0)
@ -86,6 +89,8 @@ var draw = func {
.lineTo(0,17)
.close()
.setColor(1,1,1);
me.text_tcas.setColor(1,1,1);
me.arrow_tcas[me.arrow_type].setColor(1,1,1);
}
};

View file

@ -1,17 +1,29 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name ='VOR';
var parents = [SymbolLayer.Controller];
var __self__ = caller(0)[0];
SymbolLayer.Controller.add("VOR", __self__);
SymbolLayer.add("VOR", {
SymbolLayer.Controller.add(name, __self__);
SymbolLayer.add(name, {
parents: [SymbolLayer],
type: "VOR", # Symbol type
type: name, # Symbol type
df_controller: __self__, # controller to use by default -- this one
});
var a_instance = nil;
var new = func(layer) {
if(0) {
# TODO: generalize and move to MapStructure.nas
var required_controllers = [ {name: 'query_range',type:'func'}, ];
foreach(var c; required_controllers) {
if (!contains(layer.map.controller, c.name) or typeof(layer.map.controller[c.name]) !=c.type)
die("Required controller is missing/invalid:"~ c.name ~ ' ['~c.type~']' );
}
}
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
active_vors: [],
navNs: props.globals.getNode("instrumentation").getChildren("nav"),
listeners: [],
@ -26,11 +38,10 @@ var new = func(layer) {
}
#call(debug.dump, keys(layer));
m.changed_freq(update:0);
__self__.a_instance = m;
return m;
};
var del = func() {
printlog(_MP_dbg_lvl, "VOR.lcontroller.del()");
printlog(_MP_dbg_lvl, name,".lcontroller.del()");
foreach (var l; me.listeners)
removelistener(l);
};
@ -50,6 +61,8 @@ var changed_freq = func(update=1) {
};
var searchCmd = func {
printlog(_MP_dbg_lvl, "Running query:", me.query_type);
return positioned.findWithinRange(100, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
var range = me.map.getRange();
if (range == nil) return;
return positioned.findWithinRange(me.map.getPosCoord(), range, me.query_type);
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'VOR';
var parents = [Symbol.Controller];
@ -5,8 +6,4 @@ var __self__ = caller(0)[0];
Symbol.Controller.add(name, __self__);
Symbol.registry[name].df_controller = __self__;
var new = func(model) ; # this controller doesn't need an instance
var LayerController = SymbolLayer.Controller.registry[name];
var isActive = func(model) LayerController.a_instance.isActive(model);
var query_range = func()
die(name~".scontroller.query_range /MUST/ be provided by implementation");

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'VOR';
var parents = [DotSym];
@ -5,14 +6,32 @@ var __self__ = caller(0)[0];
DotSym.makeinstance( name, __self__ );
var element_type = "group"; # we want a group, becomes "me.element"
var icon_vor = nil;
var icon_vor = nil; # a handle to the cached icon
var range_vor = nil; # two elements that get drawn when needed
var radial_vor = nil; # if one is nil, the other has to be nil
var active = -1;
var draw = func {
# Init
if (me.icon_vor == nil) {
me.icon_vor = me.element.createChild("path")
###
# this function returns a new function that renders the symbol
# into a canvas group
# the signature of the first func should contain all customizable
# parameters (such as color/width etc)
#
# you will typically have one such function for each cacheable symbol
# and only call it once during initialization (once for each cached style/variation)
# the signature of the 2nd func remains untouched, it's internally used
#
# NOTE: callbacks that create symbols for caching MUST render them using a
# transparent background !!
#
var drawVOR = func(color, width=3) return func(group) {
# init_calls += 1; # TODO: doing this here is too fragile, this should be done by MapStructure ...
# if( init_calls >= 2)
# print("Warning: draw() routine for a cached element has been called more than once, should only happen during reset/reinit");
# print("drawing vor");
var symbol = group.createChild("path")
.moveTo(-15,0)
.lineTo(-7.5,12.5)
.lineTo(7.5,12.5)
@ -20,13 +39,69 @@ var draw = func {
.lineTo(7.5,-12.5)
.lineTo(-7.5,-12.5)
.close()
.setStrokeLineWidth(3)
.setColor(0,0.6,0.85);
.setStrokeLineWidth(width)
.setColor( color );
return symbol; # make this explicit, not everybody knows Nasal internals inside/out ...
};
##
# a vector with pre-created symbols that are cached during initialization
# this needs to be done for each variation of each symbol, i.e. color/width etc
# note that scaling should not be done here, likewise for positioning via setGeoPosition()
#
# FIXME: We still need to encode styling information as part of the key/name lookup
# so that the cache entry's name contains styling info, or different maps/layers may
# overwrite their symbols
#
# icon_vor_cache[0] - inactive symbol
# icon_vor_cache[1] - active symbol
var icon_vor_cached = [
SymbolCache32x32.add(
name: "VOR-INACTIVE",
callback: drawVOR( color:[0, 0.6, 0.85], width:3 ),
draw_mode: SymbolCache.DRAW_CENTERED
),
SymbolCache32x32.add(
name: "VOR-ACTIVE",
callback: drawVOR( color:[0, 1, 0], width:3 ),
draw_mode: SymbolCache.DRAW_CENTERED
),
];
##
# TODO: make this a part of the framework, for use by other layers/symbols etc
#
var controller_check = func(layer, controller, arg...) {
var ret = call(compile("call(layer.controller."~controller~", arg, layer.controller)", "<test>"), arg, var err=[]);
if (size(err))
if (err[0] == "No such member: "~controller)
print("MapStructure Warning: Required controller not found: ", name, ":", controller);
else die(err[0]); # rethrow
return ret; # nil if not found
}
var draw = func {
# Init
if (0 and me.model.id == "SAC") # hack to test isActive() around KSMF
setprop("instrumentation/nav[0]/frequencies/selected-mhz", me.model.frequency/100);
var active = controller_check(me.layer, 'isActive', me.model);
#print(me.model.id,"/", me.model.frequency/100, " isActive:", active);
if (active != me.active) {
#print("VOR.symbol: active != me.active: del/render event triggered");
if (me.icon_vor != nil) me.icon_vor.del();
# look up the correct symbol from the cache and render it into the group as a raster image
me.icon_vor = icon_vor_cached[active or 0].render(me.element);
me.active = active; # update me.active flag
}
# Update
if (me.controller.isActive(me.model)) {
# Update (also handles non-cached stuff, such as text labels or animations)
# TODO: we can use a func generator to pre-create the callback/data structure for this
if (active) {
if (me.range_vor == nil) {
var rangeNm = me.controller.query_range();
# initialize me.range and me.radial_vor
var rangeNm = me.layer.map.getRange();
# print("VOR is tuned:", me.model.id);
var radius = (me.model.range_nm/rangeNm)*345;
me.range_vor = me.element.createChild("path")
@ -37,7 +112,7 @@ var draw = func {
.setStrokeDashArray([5, 15, 5, 15, 5])
.setColor(0,1,0);
var course = me.controller.get_tuned_course(me.model.frequency/100);
var course = me.layer.map.controller.get_tuned_course(me.model.frequency/100);
me.radial_vor = me.element.createChild("path")
.moveTo(0,-radius)
.vert(2*radius)
@ -45,13 +120,14 @@ var draw = func {
.setStrokeDashArray([15, 5, 15, 5, 15])
.setColor(0,1,0)
.setRotation(course*D2R);
me.icon_vor.setColor(0,1,0);
}
me.range_vor.show();
me.radial_vor.show();
} elsif (me.range_vor != nil) {
} else { # inactive station (not tuned)
if (me.range_vor != nil) {
me.range_vor.hide();
me.radial_vor.hide();
}
}
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'WPT'; # for waypoints
var parents = [SymbolLayer.Controller];
@ -12,8 +13,8 @@ var new = func(layer) {
var m = {
parents: [__self__],
layer: layer,
map: layer.map,
listeners: [],
query_range_nm: 25,
};
return m;
};
@ -24,7 +25,7 @@ var del = func() {
};
var searchCmd = func {
#print("Running query: "~name);
printlog(_MP_dbg_lvl, "Running query: "~name);
var fp = flightplan();
var fpSize = fp.getPlanSize();
@ -33,6 +34,5 @@ var searchCmd = func {
append(result, fp.getWP(i).path()[0] );
return result;
# return positioned.findWithinRange(me.query_range_nm, me.query_type); # the range should also be exposed, it will typically be controlled via a GUI widget or NavDisplay switch
};

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'WPT';
var parents = [Symbol.Controller];

View file

@ -1,3 +1,4 @@
# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'WPT';
var parents = [DotSym];

View file

@ -3,39 +3,96 @@ var parents = [Map.Controller];
var __self__ = caller(0)[0];
Map.Controller.add("Aircraft position", __self__);
#Map.df_controller = __self__;
var new = func(map) {
##
# encapsulate type of aircraft (main, ai, mp or ai+mp)
# This is so that we can use reuse the aircraft controller also
# for AI and/or MP traffic, which may use different properties
# and to fix up hard-coded property references in layers like TFC
var SOURCES = {};
SOURCES["main"] = {
getPosition: func subvec(geo.aircraft_position().latlon(), 0, 2),
getAltitude: func getprop('/position/altitude-ft'),
getHeading: func getprop('/orientation/heading-deg'),
};
# Layers which get updated every frame
var update_instant = [
"TFC",
];
var new = func(map, source='main') {
if (source != 'main')
die ("AI/MP traffic not yet supported (WIP)!");
var m = {
parents: [__self__],
map: map,
_pos: nil, _time: nil,
source: SOURCES[source], # main, ai, mp or ai+mp
_pos: nil, _time: nil, _range: nil,
};
m.timer = maketimer(0, m, update_pos);
m.timer.start();
m.timer1 = maketimer(0, m, update_pos);
m.timer2 = maketimer(0, m, update_layers);
m.timer1.start();
m.timer2.start();
m.update_pos();
return m;
};
var del = func(map) {
if (map != me.map) die();
me.timer.stop();
me.timer1.stop();
me.timer2.stop();
};
# Controller methods
var update_pos = func {
var (lat,lon) = (var pos = geo.aircraft_position()).latlon();
var (lat,lon) = me.source.getPosition();
me.map.setPos(lat:lat, lon:lon,
hdg:getprop("/orientation/heading-deg"),
alt:me.source.getAltitude());
foreach (var t; update_instant)
if ((var l=me.map.getLayer(t)) != nil)
l.update();
};
var update_layers = func {
var pos = me.map.getPosCoord();
var time = systime();
me.map.setPos(lat, lon, getprop("/orientation/heading-deg"));
var range = me.map.getRange();
if (me._pos == nil)
me._pos = geo.Coord.new(pos);
else {
var dist = me._pos.direct_distance_to(pos);
# Always update if range changed
elsif (range == me._range) {
var dist_m = me._pos.direct_distance_to(pos);
# 2 NM until we update again
if (dist < 2 * NM2M) return;
# Update at most every 4 seconds to avoid escessive stutter:
if (dist_m < 2 * NM2M) return;
# Update at most every 4 seconds to avoid excessive stutter:
elsif (time - me._time < 4) return;
}
#print("update aircraft position");
printlog(_MP_dbg_lvl, "update aircraft position");
var (x,y,z) = pos.xyz();
me._pos.set_xyz(x,y,z);
me._time = time;
me._range = range;
me.map.update();
};
# predicate for the draw controller
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;
}
var get_tuned_course = 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");
}
var get_position = func {
delete(caller(0)[0], "me"); # remove local me, inherit outer one
return [
me.aircraft_source.get_lat(), me.aircraft_source.get_lon()
];
}

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# draw a single airplane symbol
#

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var AirplaneSymbolLayer = {};
AirplaneSymbolLayer.new = func(group,name, controller) {
var m=Layer.new(group, name, AirplaneSymbolModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var AirplaneSymbolModel = {};
AirplaneSymbolModel.new = func make( LayerModel, AirplaneSymbolModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# draws a single airport (ND style)
#

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var AirportsNDLayer = {};
AirportsNDLayer.new = func(group, name) {
var m = Layer.new(group, name, AirportsNDModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var AirportsNDModel = {};
AirportsNDModel.new = func make(AirportsNDModel, LayerModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var AirportModel = {};
AirportModel.new = func make(AirportModel, LayerModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# Draw a altitude profile position on the route with text
#

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
###
#
#

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var DMELayer = {};
DMELayer.new = func(group,name,controller=nil) {
var m=Layer.new(group, name, DMEModel, controller);

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var DMEModel = {};
DMEModel.new = func make( LayerModel, DMEModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# draw a single fix symbol
#

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var FixLayer = {};
FixLayer.new = func(group,name, controller) {
var m=Layer.new(group, name, FixModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var FixModel = {};
FixModel.new = func make( LayerModel, FixModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# FIXME: until we have better instancing support for symbols, it would be better to return a functor here
# so that symbols are only parsed once

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var NavLayer = {};
NavLayer.new = func(group,name) {
var m=Layer.new(group, name, NavaidModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var NavaidModel = {};
NavaidModel.new = func make(LayerModel, NavaidModel);
NavaidModel.init = func {

View file

@ -1,5 +1,6 @@
# ==============================================================================
# Boeing Navigation Display by Gijs de Rooy
# See: http://wiki.flightgear.org/Canvas_ND_Framework
# ==============================================================================
##
@ -44,21 +45,10 @@ var NDStyles = {
##
layers: [
{ name:'fixes', disabled:1, update_on:['toggle_range','toggle_waypoints'],
predicate: func(nd, layer) {
# print("Running fixes predicate");
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if (visible) {
# print("fixes update requested!");
trigger_update( layer );
}
layer._view.setVisible(visible);
}, # end of layer update predicate
}, # end of fixes layer
{ name:'FIX', isMapStructure:1, update_on:['toggle_range','toggle_waypoints'],
# FIXME: this is a really ugly place for controller code
predicate: func(nd, layer) {
# print("Running vor layer predicate");
# print("Running fix layer predicate");
# toggle visibility here
var visible=nd.get_switch('toggle_waypoints') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
layer.group.setVisible( nd.get_switch('toggle_waypoints') );
@ -69,6 +59,7 @@ var NDStyles = {
}
}, # end of layer update predicate
}, # end of FIX layer
# Should redraw every 10 seconds
{ name:'storms', update_on:['toggle_range','toggle_weather','toggle_display_mode'],
predicate: func(nd, layer) {
@ -81,7 +72,8 @@ var NDStyles = {
layer._view.setVisible(visible);
}, # end of layer update predicate
}, # end of storms layer
{ name:'airplaneSymbol', update_on:['toggle_display_mode'],
{ name:'airplaneSymbol', disabled:1, update_on:['toggle_display_mode'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_display_mode') == "PLAN";
if (visible) {
@ -89,78 +81,56 @@ var NDStyles = {
} layer._view.setVisible(visible);
},
},
{ name:'airports-nd', update_on:['toggle_range','toggle_airports','toggle_display_mode'],
{ name:'APS', isMapStructure:1, update_on:['toggle_display_mode'],
predicate: func(nd, layer) {
# print("Running airports-nd predicate");
var visible = nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
var visible = nd.get_switch('toggle_display_mode') == "PLAN";
layer.group.setVisible( visible );
if (visible) {
trigger_update( layer ); # clear & redraw
layer.update();
}
},
},
{ name:'APT', isMapStructure:1, update_on:['toggle_range','toggle_airports','toggle_display_mode'],
predicate: func(nd, layer) {
# toggle visibility here
var visible=nd.get_switch('toggle_airports') and nd.in_mode('toggle_display_mode', ['MAP']);
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: APT");
layer.update();
}
layer._view.setVisible( visible );
}, # end of layer update predicate
}, # end of airports layer
}, # end of APT layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ name:'vor', disabled:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
predicate: func(nd, layer) {
# print("Running vor layer predicate");
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible) {
trigger_update( layer ); # clear & redraw
}
layer._view.setVisible( nd.get_switch('toggle_stations') );
}, # end of layer update predicate
}, # end of VOR layer
{ name:'VOR', isMapStructure:1, update_on:['toggle_range','toggle_stations','toggle_display_mode'],
# FIXME: this is a really ugly place for controller code
predicate: func(nd, layer) {
# print("Running vor layer predicate");
# toggle visibility here
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: VOR");
# (Hopefully) smart update
layer.update();
}
}, # end of layer update predicate
}, # end of VOR layer
# Should distinct between low and high altitude navaids. Hiding above 40 NM for now, to prevent clutter/lag.
{ name:'dme', disabled:1, update_on:['toggle_range','toggle_stations'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
if(visible) {
trigger_update( layer ); # clear & redraw
}
layer._view.setVisible( nd.get_switch('toggle_stations') );
}, # end of layer update predicate
}, # end of DME layers
{ name:'DME', isMapStructure:1, update_on:['toggle_range','toggle_stations'],
# FIXME: this is a really ugly place for controller code
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_stations') and nd.in_mode('toggle_display_mode', ['MAP']) and (nd.rangeNm() <= 40);
# print("Running vor layer predicate");
# toggle visibility here
layer.group.setVisible( visible );
if (visible) {
#print("Updating MapStructure ND layer: DME");
# (Hopefully) smart update
layer.update();
}
}, # end of layer update predicate
}, # end of DME layer
{ name:'mp-traffic', update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_traffic');
layer._view.setVisible( visible );
if (visible) {
trigger_update( layer ); # clear & redraw
}
}, # end of layer update predicate
}, # end of traffic layer
{ name:'TFC', disabled:1, isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
{ name:'TFC', isMapStructure:1, update_on:['toggle_range','toggle_traffic'],
predicate: func(nd, layer) {
var visible = nd.get_switch('toggle_traffic');
layer.group.setVisible( visible );
@ -707,22 +677,15 @@ var update_weather = func {
}
update_weather();
# Hack to update airplane symbol location on PLAN mode every second
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();
##
# TODO:
# - introduce a MFD class (use it also for PFD/EICAS)
# - introduce a SGSubsystem class and use it here
# - introduce a Boeing NavDisplay class
var NavDisplay = {
# static
id:0,
# reset handler
del: func {
print("Cleaning up NavDisplay");
# shut down all timers and other loops here
@ -735,6 +698,7 @@ var NavDisplay = {
if (me.canvas_handle != nil)
me.canvas_handle.del();
me.inited = 0;
NavDisplay.id -= 1;
},
listen: func(p,c) {
@ -780,6 +744,7 @@ var NavDisplay = {
# TODO: the ctor should allow customization, for different aircraft
# especially properties and SVG files/handles (747, 757, 777 etc)
new : func(prop1, switches=default_switches, style='Boeing') {
NavDisplay.id +=1;
var m = { parents : [NavDisplay]};
m.inited = 0;
@ -834,7 +799,7 @@ var NavDisplay = {
return m;
},
newMFD: func(canvas_group, parent=nil)
newMFD: func(canvas_group, parent=nil, options=nil)
{
if (me.inited) die("MFD already was added to scene");
me.inited = 1;
@ -872,7 +837,6 @@ var NavDisplay = {
"staArrowL","staArrowR","staToL","staFromL","staToR","staFromR"] )
me.symbols[element] = me.nd.getElementById(element).updateCenter();
# this should probably be using Philosopher's new SymbolLayer ?
me.map = me.nd.createChild("map","map")
.set("clip", "rect(124, 1024, 1024, 0)")
.set("screen-range", "700");
@ -908,19 +872,40 @@ var NavDisplay = {
# 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
#
var controller = {
parents: [canvas.Map.Controller],
_pos: nil, _time: nil,
query_range: func get_range(),
is_tuned:is_tuned,
get_tuned_course:get_course_by_freq,
get_position: get_current_position,
new: func(map) return { parents:[controller], map:map },
should_update_all: func {
# TODO: this is just copied from aircraftpos.controller,
# it really should be moved to somewhere common and reused
# and extended to fully differentiate between "static"
# and "volatile" layers.
var pos = me.map.getPosCoord();
if (pos == nil) return 0;
var time = systime();
if (me._pos == nil)
me._pos = geo.Coord.new(pos);
else {
var dist_m = me._pos.direct_distance_to(pos);
# 2 NM until we update again
if (dist_m < 2 * NM2M) return 0;
# Update at most every 4 seconds to avoid excessive stutter:
elsif (time - me._time < 4) return 0;
}
#print("update aircraft position");
var (x,y,z) = pos.xyz();
me._pos.set_xyz(x,y,z);
me._time = time;
return 1;
},
};
# FIXME: MapStructure: big hack
canvas.Symbol.Controller.get("VOR").query_range = controller.query_range;
canvas.Symbol.Controller.get("VOR").get_tuned_course = controller.get_tuned_course;
canvas.Symbol.Controller.get("DME").is_tuned = controller.is_tuned;
canvas.SymbolLayer.Controller.get("TFC").query_range = controller.query_range;
canvas.SymbolLayer.Controller.get("TFC").get_position = controller.get_position;
me.map.setController(controller);
###
# set up various layers, controlled via callbacks in the controller hash
@ -938,11 +923,13 @@ var NavDisplay = {
var render_target = (!contains(layer,'not_a_map') or !layer.not_a_map) ? me.map : me.nd;
var the_layer = nil;
if(!layer['isMapStructure'])
if(!layer['isMapStructure']) # set up an old layer
the_layer = me.layers[layer.name] = canvas.MAP_LAYERS[layer.name].new( render_target, layer.name, controller );
else {
printlog(_MP_dbg_lvl, "Setting up MapStructure-based layer for ND, name:", layer.name);
render_target.addLayer(factory: canvas.SymbolLayer, type_arg: layer.name);
var opt = options != nil and options[layer.name] != nil ? options[layer.name] :nil;
# print("Options is: ", opt!=nil?"enabled":"disabled");
render_target.addLayer(factory: canvas.SymbolLayer, type_arg: layer.name, options:opt);
the_layer = me.layers[layer.name] = render_target.getLayer(layer.name);
}
@ -990,6 +977,14 @@ var NavDisplay = {
# and update each model accordingly
update: func() # FIXME: This stuff is still too aircraft specific, cannot easily be reused by other aircraft
{
var _time = systime();
# Variables:
var userLat = me.aircraft_source.get_lat();
var userLon = me.aircraft_source.get_lon();
var userGndSpd = me.aircraft_source.get_gnd_spd();
var userVSpd = me.aircraft_source.get_vspd();
var dispLCD = me.get_switch('toggle_display_type') == "LCD";
# Heading update
var userHdgMag = me.aircraft_source.get_hdg_mag();
var userHdgTru = me.aircraft_source.get_hdg_tru();
@ -1005,6 +1000,12 @@ var NavDisplay = {
var userHdg=userHdgMag;
var userTrk=userTrkMag;
}
# this should only ever happen when testing the experimental AI/MP ND driver hash (not critical)
# or when an error occurs (critical)
if (!userHdg or !userTrk or !userLat or !userLon) {
print("aircraft source invalid, returning !");
return;
}
if (me.aircraft_source.get_gnd_spd() < 80)
userTrk = userHdg;
@ -1020,18 +1021,40 @@ var NavDisplay = {
me.symbols.hdgTrk.setText("HDG");
}
var userLat = me.aircraft_source.get_lat();
var userLon = me.aircraft_source.get_lon();
var userGndSpd = me.aircraft_source.get_gnd_spd();
var userVSpd = me.aircraft_source.get_vspd();
var dispLCD = me.get_switch('toggle_display_type') == "LCD";
# First, update the display position of the map
var pos = {
lat: nil, lon: nil,
alt: nil, hdg: nil,
range: nil,
};
pos.range = me.rangeNm(); # avoid this here, use a listener instead
# reposition the map, change heading & range:
if(me.in_mode('toggle_display_mode', ['PLAN'])) {
pos.hdg = 0;
if (getprop(me.efis_path ~ "/inputs/plan-wpt-index") >= 0) {
pos.lat = getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/latitude-deg");
pos.lon = getprop("/autopilot/route-manager/route/wp["~getprop(me.efis_path ~ "/inputs/plan-wpt-index")~"]/longitude-deg");
} else {
pos.lat = me.map.getLat();
pos.lon = me.map.getLon();
}
} else {
pos.hdg = userHdgTrkTru;
pos.lat = userLat;
pos.lon = userLon;
}
call(me.map.setPos, [pos.lat, pos.lon], me.map, pos);
# this should only ever happen when testing the experimental AI/MP ND driver hash (not critical)
if (!userHdg or !userTrk or !userLat or !userLon) {
print("aircraft source invalid, returning !");
return;
# MapStructure update!
if (me.map.controller.should_update_all()) {
me.map.update();
} else {
# TODO: ugly list here
me.map.update(func(layer) (var n=layer.type) == "TFC" or n == "APS");
}
# Other symbol update
# TODO: should be refactored!
if(me.in_mode('toggle_display_mode', ['PLAN']))
me.map.setTranslation(512,512);
elsif(me.get_switch('toggle_centered'))
@ -1105,22 +1128,8 @@ var NavDisplay = {
me.symbols.range.setText(sprintf("%3.0f",me.rangeNm()/2));
# reposition the map, change heading & range:
if(me.in_mode('toggle_display_mode', ['PLAN'])) {
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.map._node.getNode("ref-lat",1).setDoubleValue(userLat);
me.map._node.getNode("ref-lon",1).setDoubleValue(userLon);
}
# The set range of the map does not correspond to what we see in-sim!!
me.map._node.getNode("range",1).setDoubleValue(me.rangeNm()); # avoid this here, use a listener instead
# Hide heading bug 10 secs after change
var vhdg_bug = getprop("autopilot/settings/heading-bug-deg");
var vhdg_bug = getprop("autopilot/settings/heading-bug-deg") or 0;
var hdg_bug_active = getprop("autopilot/settings/heading-bug-active");
if (hdg_bug_active == nil)
hdg_bug_active = 1;
@ -1149,7 +1158,6 @@ var NavDisplay = {
me.symbols.selHdgLine2.setRotation(hdgBugRot);
me.symbols.compass.setRotation(-userHdgTrk*D2R);
me.symbols.compassApp.setRotation(-userHdgTrk*D2R);
me.map._node.getNode("hdg",1).setDoubleValue(userHdgTrkTru);
}
if(me.get_switch('toggle_centered')) {
if (me.in_mode('toggle_display_mode', ['APP','VOR']))
@ -1336,5 +1344,9 @@ var NavDisplay = {
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']));
# Okay, _how_ do we hook this up with FGPlot?
#print("ND update took "~(systime()-_time)~" seconds");
setprop("/instrumentation/navdisplay["~ NavDisplay.id ~"]/update-ms", systime() - _time);
}
};

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_parking = func(group, apt, lod) {
var group = group.createChild("group", "apt-"~apt.id);
foreach(var park; apt.parking()) {

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
#TODO: use custom Model/DataProvider
var ParkingLayer = {}; # make(Layer);
ParkingLayer.new = func(group, name) {

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# Draw a route with tracks and waypoints
#

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var RouteLayer = {};
RouteLayer.new = func(group,name) {

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var RouteModel = {route_monitor:nil};
RouteModel.new = func make(LayerModel, RouteModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_rwy_nd = func (group, rwy, controller=nil, lod=nil) {
# print("drawing runways-nd");

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var RunwayNDLayer = {};
RunwayNDLayer.new = func(group, name) {
var m=Layer.new(group, name, RunwayNDModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var RunwayNDModel = {};
RunwayNDModel.new = func make( LayerModel, RunwayNDModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
#TODO: split: draw_single_runway(pos)

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
#TODO: use custom Model/DataProvider
var RunwayLayer = {}; # make(Layer);
RunwayLayer.new = func(group, name) {

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# draw a single storm symbol
#

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var StormLayer = {};
StormLayer.new = func(group,name, controller) {
var m=Layer.new(group, name, StormModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var StormModel = {};
StormModel.new = func make( LayerModel, StormModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_taxiways = func(group, apt, lod) { # TODO: the LOD arg isn't stricly needed here,
# the layer is a conventional canvas group, so it can access its map
# parent and just read the "range" property to do LOD handling

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
#TODO: use custom Model/DataProvider
var TaxiwayLayer = {}; # make(Layer);
TaxiwayLayer.new = func(group, name) {

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_tcas_arrow_above_500 = func(group, lod=0) {
group.createChild("path")
.moveTo(0,-17)

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_tcas_arrow_below_500 = func(group) {
group.createChild("path")

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
#TODO: use custom Model/DataProvider
var TestLayer = {}; # make(Layer);

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_tower = func (group, apt,lod) {
var group = group.createChild("group", "tower");
# TODO: move to map_elements.nas (tower, runway, parking etc)

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var TowerLayer = {};
TowerLayer.new = func(group, name) {
var m=Layer.new(group, name, AirportModel ); #FIXME: AirportModel can be shared by Taxiways, Runways etc!!

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_traffic = func(group, traffic, lod=0)
{
var a = traffic;

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var MPTrafficLayer = {};
MPTrafficLayer.new = func(group,name, controller=nil) {
var m=Layer.new(group, name, MPTrafficModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var MPTrafficModel = {};
MPTrafficModel.new = func make(LayerModel, MPTrafficModel);

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var draw_vor = func (group, vor, controller=nil, lod = 0) {
if (0) {

View file

@ -1,3 +1,4 @@
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var VORLayer = {};
VORLayer.new = func(group,name, controller) {
var m=Layer.new(group, name, VORModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var VORModel = {};
VORModel.new = func make( LayerModel, VORModel );

View file

@ -1,3 +1,4 @@
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# Draw a waypoint symbol and waypoint name (Gijs' 744 ND.nas code)

View file

@ -328,7 +328,8 @@ var viewer_position = func {
# searchCmd executes and returns the actual search,
# onAdded and onRemoved are callbacks,
# and obj is a "me" reference (defaults to "me" in the
# caller's namespace).
# caller's namespace). If searchCmd returns nil, nothing
# happens, i.e. the diff is cancelled.
var PositionedSearch = {
new: func(searchCmd, onAdded, onRemoved, obj=nil) {
return {
@ -351,6 +352,8 @@ var PositionedSearch = {
return ret;
},
diff: func(old, new) {
if (new == nil)
return [old, [], []];
var removed = old~[]; #copyvec
var added = new~[];
# Mark common elements from removed and added:
@ -369,6 +372,10 @@ var PositionedSearch = {
# Optimized search using C code
var old = me.result~[]; #copyvec
me.result = call(searchCmd, nil, me.obj);
if (me.result == nil)
{ me.result = old; return }
if (typeof(me.result) != 'vector') die("geo.PositionedSearch(): A searchCmd must return a vector of elements or nil !!"); # TODO: Maybe make this a hash instead to wrap a vector, so that we can implement basic type-checking - e.g. doing isa(PositionedSearchResult, me.result) would be kinda neat and could help troubleshooting
else
positioned.diff( old,
me.result,
func call(me.onAdded, arg, me.obj),