Many MapStructure/NavDisplay updates
See the clone at https://gitorious.org/fg/canvas-hackers-fgdata/source/topics/canvas-radar:
This commit is contained in:
parent
1e2bf918da
commit
37c005c222
73 changed files with 1180 additions and 398 deletions
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
27
Nasal/canvas/map/APS.lcontroller
Normal file
27
Nasal/canvas/map/APS.lcontroller
Normal 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];
|
||||
};
|
||||
|
9
Nasal/canvas/map/APS.scontroller
Normal file
9
Nasal/canvas/map/APS.scontroller
Normal 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
|
||||
|
16
Nasal/canvas/map/APS.symbol
Normal file
16
Nasal/canvas/map/APS.symbol
Normal 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());
|
||||
|
35
Nasal/canvas/map/APT.lcontroller
Normal file
35
Nasal/canvas/map/APT.lcontroller
Normal 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);
|
||||
};
|
||||
|
13
Nasal/canvas/map/APT.scontroller
Normal file
13
Nasal/canvas/map/APT.scontroller
Normal 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" );
|
||||
|
52
Nasal/canvas/map/APT.symbol
Normal file
52
Nasal/canvas/map/APT.symbol
Normal 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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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" );
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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" );
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'NDB';
|
||||
var parents = [DotSym];
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'WPT';
|
||||
var parents = [Symbol.Controller];
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# See: http://wiki.flightgear.org/MapStructure
|
||||
# Class things:
|
||||
var name = 'WPT';
|
||||
var parents = [DotSym];
|
||||
|
|
|
@ -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()
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
##
|
||||
# draw a single airplane symbol
|
||||
#
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var AirplaneSymbolModel = {};
|
||||
AirplaneSymbolModel.new = func make( LayerModel, AirplaneSymbolModel );
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
##
|
||||
# draws a single airport (ND style)
|
||||
#
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var AirportsNDModel = {};
|
||||
AirportsNDModel.new = func make(AirportsNDModel, LayerModel);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var AirportModel = {};
|
||||
AirportModel.new = func make(AirportModel, LayerModel);
|
||||
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
###
|
||||
#
|
||||
#
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var DMEModel = {};
|
||||
DMEModel.new = func make( LayerModel, DMEModel );
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
##
|
||||
# draw a single fix symbol
|
||||
#
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var FixModel = {};
|
||||
FixModel.new = func make( LayerModel, FixModel );
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
##
|
||||
# Draw a route with tracks and waypoints
|
||||
#
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var RouteLayer = {};
|
||||
|
||||
RouteLayer.new = func(group,name) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var RunwayNDModel = {};
|
||||
RunwayNDModel.new = func make( LayerModel, RunwayNDModel );
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
|
||||
#TODO: split: draw_single_runway(pos)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
##
|
||||
# draw a single storm symbol
|
||||
#
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var StormModel = {};
|
||||
StormModel.new = func make( LayerModel, StormModel );
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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!!
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var MPTrafficModel = {};
|
||||
MPTrafficModel.new = func make(LayerModel, MPTrafficModel);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
|
||||
var VORModel = {};
|
||||
VORModel.new = func make( LayerModel, VORModel );
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in a new issue