diff --git a/ATC/atis/eddh.xml b/ATC/atis/eddh.xml
index 513194f17..9fd37f560 100644
--- a/ATC/atis/eddh.xml
+++ b/ATC/atis/eddh.xml
@@ -31,11 +31,11 @@
rwy-to
- take off and landing runway
+ Take off and landing runway
rwy-land
- landing runway
+ Landing runway
rwy-land
and take off runway
rwy-to
@@ -45,7 +45,7 @@
transition-level
. Wind
wind-dir
- degrees
+ degrees,
wind-speed-kn
knots
@@ -53,7 +53,7 @@
gusts
- gusts up to
+ , gusts up to
gusts
knots
@@ -64,7 +64,7 @@
wind-to
- varying between
+ , varying between
wind-from
and
wind-to
@@ -108,13 +108,13 @@
. Temperature
temperature-deg
- dewpoint
+ , dewpoint
dewpoint-deg
. QNH
qnh
hektopascal. Trend
trend
- information
+ . information
id
out.
diff --git a/ATC/atis/other.xml b/ATC/atis/other.xml
index e3e1529eb..2e4de387d 100644
--- a/ATC/atis/other.xml
+++ b/ATC/atis/other.xml
@@ -33,22 +33,22 @@
rwy-to
- landing and departing runway
+ Landing and departing runway
rwy-land
- landing runway
+ Landing runway
rwy-land
and departing runway
rwy-to
- . Weather
- . Wind:
+ . Weather.
+ Wind
wind-dir
degrees at
wind-speed-kn
- knots
+ knots
gusts
@@ -106,5 +106,6 @@
trend
. Advise on initial contact you have information
id
+ .
diff --git a/Nasal/canvas/MapStructure.nas b/Nasal/canvas/MapStructure.nas
index 0afdcd213..1bc20fe01 100644
--- a/Nasal/canvas/MapStructure.nas
+++ b/Nasal/canvas/MapStructure.nas
@@ -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];
-# 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')
- return getpos_fromghost(obj);
- else
- die("bad ghost of type '"~ghosttype(obj)~"'");
- if (typeof(obj) == 'hash')
- if (isa(obj, geo.Coord))
- return subvec(obj.latlon(), 0, 2);
- if (isa(obj, 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'))
- return [obj.lat, obj.lon];
-
- debug.dump(obj);
- die("no suitable getpos() found! Of type: "~typeof(obj));
+# 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
};
-Symbol.Controller.equals = func(l, r) {
- 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;
+# Generic getpos: get lat/lon from any object:
+# (geo.Coord and positioned ghost currently)
+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;
+ }
}
- die("bad types");
+ 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/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 (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(p,'lat') and contains(p,'lon'))
+ return [obj.lat, obj.lon];
+ return nil;
+ }
+};
+
+Symbol.Controller.equals = func(l, r, p=nil) {
+ if (l == r) 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;
+ }
+ }
+ 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() {
- me.searcher.update();
- foreach (var e; me.list)
- e.update();
+ 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,63 +622,118 @@ 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);
+ 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")
+ .moveTo(-15,0)
+ .lineTo(-7.5,12.5)
+ .lineTo(7.5,12.5)
+ .lineTo(15,0)
+ .lineTo(7.5,-12.5)
+ .lineTo(-7.5,-12.5)
+ .close()
+ .setStrokeLineWidth(width)
+ .setColor( color );
+ # debug.dump( bbox.getBoundingBox() );
+ };
- var drawVOR = func(color, width=3) return func(group) {
- # print("drawing vor");
- var bbox = group.createChild("path")
- .moveTo(-15,0)
- .lineTo(-7.5,12.5)
- .lineTo(7.5,12.5)
- .lineTo(15,0)
- .lineTo(7.5,-12.5)
- .lineTo(-7.5,-12.5)
- .close()
- .setStrokeLineWidth(width)
- .setColor( color );
- # debug.dump( bbox.getBoundingBox() );
- };
+ var cachedVOR1 = SymbolCache32x32.add( "VOR-BLUE", drawVOR( color:[0, 0.6, 0.85], width:3), SymbolCache.DRAW_CENTERED );
+ var cachedVOR2 = SymbolCache32x32.add( "VOR-RED" , drawVOR( color:[1.0, 0, 0], width: 3), SymbolCache.DRAW_CENTERED );
+ 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 );
- var cachedVOR1 = SymbolCache32x32.add( "VOR-BLUE", drawVOR( color:[0, 0.6, 0.85], width:3), SymbolCache.DRAW_CENTERED );
- var cachedVOR2 = SymbolCache32x32.add( "VOR-RED" , drawVOR( color:[1.0, 0, 0], width: 3), SymbolCache.DRAW_CENTERED );
- 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
diff --git a/Nasal/canvas/api.nas b/Nasal/canvas/api.nas
index a1f346292..815980ce5 100644
--- a/Nasal/canvas/api.nas
+++ b/Nasal/canvas/api.nas
@@ -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,30 +475,70 @@ 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];
- call(layer.update, arg, layer);
+ # 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()
{
diff --git a/Nasal/canvas/map/APS.lcontroller b/Nasal/canvas/map/APS.lcontroller
new file mode 100644
index 000000000..f39224be7
--- /dev/null
+++ b/Nasal/canvas/map/APS.lcontroller
@@ -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];
+};
+
diff --git a/Nasal/canvas/map/APS.scontroller b/Nasal/canvas/map/APS.scontroller
new file mode 100644
index 000000000..733e26db7
--- /dev/null
+++ b/Nasal/canvas/map/APS.scontroller
@@ -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
+
diff --git a/Nasal/canvas/map/APS.symbol b/Nasal/canvas/map/APS.symbol
new file mode 100644
index 000000000..c54749973
--- /dev/null
+++ b/Nasal/canvas/map/APS.symbol
@@ -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());
+
diff --git a/Nasal/canvas/map/APT.lcontroller b/Nasal/canvas/map/APT.lcontroller
new file mode 100644
index 000000000..643cdb484
--- /dev/null
+++ b/Nasal/canvas/map/APT.lcontroller
@@ -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);
+};
+
diff --git a/Nasal/canvas/map/APT.scontroller b/Nasal/canvas/map/APT.scontroller
new file mode 100644
index 000000000..4269dadbc
--- /dev/null
+++ b/Nasal/canvas/map/APT.scontroller
@@ -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" );
+
diff --git a/Nasal/canvas/map/APT.symbol b/Nasal/canvas/map/APT.symbol
new file mode 100644
index 000000000..751d159d7
--- /dev/null
+++ b/Nasal/canvas/map/APT.symbol
@@ -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);
+ }
+};
+
diff --git a/Nasal/canvas/map/DME.lcontroller b/Nasal/canvas/map/DME.lcontroller
index b2d3c714e..0994a71b6 100644
--- a/Nasal/canvas/map/DME.lcontroller
+++ b/Nasal/canvas/map/DME.lcontroller
@@ -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);
};
diff --git a/Nasal/canvas/map/DME.scontroller b/Nasal/canvas/map/DME.scontroller
index 0e54d4dc2..a565d51b1 100644
--- a/Nasal/canvas/map/DME.scontroller
+++ b/Nasal/canvas/map/DME.scontroller
@@ -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
diff --git a/Nasal/canvas/map/DME.symbol b/Nasal/canvas/map/DME.symbol
index fbf523227..5e685a0ff 100644
--- a/Nasal/canvas/map/DME.symbol
+++ b/Nasal/canvas/map/DME.symbol
@@ -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();
};
diff --git a/Nasal/canvas/map/FIX.lcontroller b/Nasal/canvas/map/FIX.lcontroller
index ed89fb06b..e549446ad 100644
--- a/Nasal/canvas/map/FIX.lcontroller
+++ b/Nasal/canvas/map/FIX.lcontroller
@@ -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);
};
diff --git a/Nasal/canvas/map/FIX.scontroller b/Nasal/canvas/map/FIX.scontroller
index 5726f86ab..0e54a5d98 100644
--- a/Nasal/canvas/map/FIX.scontroller
+++ b/Nasal/canvas/map/FIX.scontroller
@@ -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" );
diff --git a/Nasal/canvas/map/FIX.symbol b/Nasal/canvas/map/FIX.symbol
index c2eee2c11..afb34099c 100644
--- a/Nasal/canvas/map/FIX.symbol
+++ b/Nasal/canvas/map/FIX.symbol
@@ -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);
-};
-
+}
diff --git a/Nasal/canvas/map/NDB.lcontroller b/Nasal/canvas/map/NDB.lcontroller
index 3250fc0f7..1eee1a3a0 100644
--- a/Nasal/canvas/map/NDB.lcontroller
+++ b/Nasal/canvas/map/NDB.lcontroller
@@ -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);
};
diff --git a/Nasal/canvas/map/NDB.scontroller b/Nasal/canvas/map/NDB.scontroller
index 7d1011db0..255a24313 100644
--- a/Nasal/canvas/map/NDB.scontroller
+++ b/Nasal/canvas/map/NDB.scontroller
@@ -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" );
diff --git a/Nasal/canvas/map/NDB.symbol b/Nasal/canvas/map/NDB.symbol
index 541b4cebb..8154ee9e4 100644
--- a/Nasal/canvas/map/NDB.symbol
+++ b/Nasal/canvas/map/NDB.symbol
@@ -1,3 +1,4 @@
+# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'NDB';
var parents = [DotSym];
diff --git a/Nasal/canvas/map/TFC.lcontroller b/Nasal/canvas/map/TFC.lcontroller
index 41767e876..368761233 100644
--- a/Nasal/canvas/map/TFC.lcontroller
+++ b/Nasal/canvas/map/TFC.lcontroller
@@ -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))
- append(result, TrafficModel.new(t, nil, me.layer));
+ 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;
};
diff --git a/Nasal/canvas/map/TFC.scontroller b/Nasal/canvas/map/TFC.scontroller
index a36364aec..09e818891 100644
--- a/Nasal/canvas/map/TFC.scontroller
+++ b/Nasal/canvas/map/TFC.scontroller
@@ -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;
+};
diff --git a/Nasal/canvas/map/TFC.symbol b/Nasal/canvas/map/TFC.symbol
index 25fc56dad..70b9c563c 100644
--- a/Nasal/canvas/map/TFC.symbol
+++ b/Nasal/canvas/map/TFC.symbol
@@ -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);
}
};
diff --git a/Nasal/canvas/map/VOR.lcontroller b/Nasal/canvas/map/VOR.lcontroller
index e7adb3d2a..c0edca10e 100644
--- a/Nasal/canvas/map/VOR.lcontroller
+++ b/Nasal/canvas/map/VOR.lcontroller
@@ -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);
};
diff --git a/Nasal/canvas/map/VOR.scontroller b/Nasal/canvas/map/VOR.scontroller
index 9b07c3d43..ec14ce929 100644
--- a/Nasal/canvas/map/VOR.scontroller
+++ b/Nasal/canvas/map/VOR.scontroller
@@ -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");
diff --git a/Nasal/canvas/map/VOR.symbol b/Nasal/canvas/map/VOR.symbol
index faf37c206..1b30ee0c4 100644
--- a/Nasal/canvas/map/VOR.symbol
+++ b/Nasal/canvas/map/VOR.symbol
@@ -1,3 +1,4 @@
+# See: http://wiki.flightgear.org/MapStructure
# Class things:
var name = 'VOR';
var parents = [DotSym];
@@ -5,28 +6,102 @@ 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;
+
+###
+# 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)
+ .lineTo(15,0)
+ .lineTo(7.5,-12.5)
+ .lineTo(-7.5,-12.5)
+ .close()
+ .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)", ""), 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 (me.icon_vor == nil) {
- me.icon_vor = me.element.createChild("path")
- .moveTo(-15,0)
- .lineTo(-7.5,12.5)
- .lineTo(7.5,12.5)
- .lineTo(15,0)
- .lineTo(7.5,-12.5)
- .lineTo(-7.5,-12.5)
- .close()
- .setStrokeLineWidth(3)
- .setColor(0,0.6,0.85);
+ 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)) {
- if (me.range_vor == nil) {
- var rangeNm = me.controller.query_range();
+ # 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) {
+ # 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) {
- me.range_vor.hide();
- me.radial_vor.hide();
+ } else { # inactive station (not tuned)
+ if (me.range_vor != nil) {
+ me.range_vor.hide();
+ me.radial_vor.hide();
+ }
}
};
diff --git a/Nasal/canvas/map/WPT.lcontroller b/Nasal/canvas/map/WPT.lcontroller
index 1192d4cc5..f22c043d7 100644
--- a/Nasal/canvas/map/WPT.lcontroller
+++ b/Nasal/canvas/map/WPT.lcontroller
@@ -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();
@@ -32,7 +33,6 @@ var searchCmd = func {
for (var i = 1; i 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;
@@ -1019,19 +1020,41 @@ var NavDisplay = {
userHdgTrkTru = userHdgTru;
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";
- # 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;
+ # 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);
+
+ # 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;
@@ -1133,8 +1142,8 @@ var NavDisplay = {
me.symbols.curHdgPtr.setRotation((userHdg-userTrk)*D2R);
me.symbols.curHdgPtr2.setRotation((userHdg-userTrk)*D2R);
}
- else
- {
+ else
+ {
me.symbols.trkInd.setRotation((userTrk-userHdg)*D2R);
me.symbols.trkInd2.setRotation((userTrk-userHdg)*D2R);
me.symbols.curHdgPtr.setRotation(0);
@@ -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);
+
}
};
diff --git a/Nasal/canvas/map/parking.draw b/Nasal/canvas/map/parking.draw
index 1d79bd544..5e4edff59 100644
--- a/Nasal/canvas/map/parking.draw
+++ b/Nasal/canvas/map/parking.draw
@@ -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()) {
diff --git a/Nasal/canvas/map/parking.layer b/Nasal/canvas/map/parking.layer
index 052448711..34f26e593 100644
--- a/Nasal/canvas/map/parking.layer
+++ b/Nasal/canvas/map/parking.layer
@@ -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) {
diff --git a/Nasal/canvas/map/route.draw b/Nasal/canvas/map/route.draw
index 77a714bc8..695eea88e 100644
--- a/Nasal/canvas/map/route.draw
+++ b/Nasal/canvas/map/route.draw
@@ -1,3 +1,4 @@
+# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# Draw a route with tracks and waypoints
#
diff --git a/Nasal/canvas/map/route.layer b/Nasal/canvas/map/route.layer
index 5981abe82..a40eddf36 100644
--- a/Nasal/canvas/map/route.layer
+++ b/Nasal/canvas/map/route.layer
@@ -1,3 +1,4 @@
+# WARNING: *.layer files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var RouteLayer = {};
RouteLayer.new = func(group,name) {
diff --git a/Nasal/canvas/map/route.model b/Nasal/canvas/map/route.model
index 27dfc3487..7ec4e6a22 100644
--- a/Nasal/canvas/map/route.model
+++ b/Nasal/canvas/map/route.model
@@ -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);
diff --git a/Nasal/canvas/map/runway-nd.draw b/Nasal/canvas/map/runway-nd.draw
index e045bd705..2284daefc 100644
--- a/Nasal/canvas/map/runway-nd.draw
+++ b/Nasal/canvas/map/runway-nd.draw
@@ -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");
diff --git a/Nasal/canvas/map/runway-nd.layer b/Nasal/canvas/map/runway-nd.layer
index e7c27cf0f..21176ce69 100644
--- a/Nasal/canvas/map/runway-nd.layer
+++ b/Nasal/canvas/map/runway-nd.layer
@@ -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 );
diff --git a/Nasal/canvas/map/runway-nd.model b/Nasal/canvas/map/runway-nd.model
index ee7db60da..f1fb46e2d 100644
--- a/Nasal/canvas/map/runway-nd.model
+++ b/Nasal/canvas/map/runway-nd.model
@@ -1,3 +1,4 @@
+# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var RunwayNDModel = {};
RunwayNDModel.new = func make( LayerModel, RunwayNDModel );
diff --git a/Nasal/canvas/map/runways.draw b/Nasal/canvas/map/runways.draw
index ab4b1d504..fcbe87ae8 100644
--- a/Nasal/canvas/map/runways.draw
+++ b/Nasal/canvas/map/runways.draw
@@ -1,3 +1,4 @@
+# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
#TODO: split: draw_single_runway(pos)
diff --git a/Nasal/canvas/map/runways.layer b/Nasal/canvas/map/runways.layer
index 2845acaad..f31615eef 100644
--- a/Nasal/canvas/map/runways.layer
+++ b/Nasal/canvas/map/runways.layer
@@ -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) {
diff --git a/Nasal/canvas/map/storm.draw b/Nasal/canvas/map/storm.draw
index 37f8589f2..ec4dc3bbf 100644
--- a/Nasal/canvas/map/storm.draw
+++ b/Nasal/canvas/map/storm.draw
@@ -1,3 +1,4 @@
+# WARNING: *.draw files will be deprecated, see: http://wiki.flightgear.org/MapStructure
##
# draw a single storm symbol
#
diff --git a/Nasal/canvas/map/storms.layer b/Nasal/canvas/map/storms.layer
index 6504af495..54ea7b7d7 100644
--- a/Nasal/canvas/map/storms.layer
+++ b/Nasal/canvas/map/storms.layer
@@ -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);
diff --git a/Nasal/canvas/map/storms.model b/Nasal/canvas/map/storms.model
index 0bc6447b2..5b4c19ad6 100644
--- a/Nasal/canvas/map/storms.model
+++ b/Nasal/canvas/map/storms.model
@@ -1,3 +1,4 @@
+# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var StormModel = {};
StormModel.new = func make( LayerModel, StormModel );
diff --git a/Nasal/canvas/map/taxiways.draw b/Nasal/canvas/map/taxiways.draw
index 522627d3f..743e446fa 100644
--- a/Nasal/canvas/map/taxiways.draw
+++ b/Nasal/canvas/map/taxiways.draw
@@ -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
diff --git a/Nasal/canvas/map/taxiways.layer b/Nasal/canvas/map/taxiways.layer
index 4eb06d2d9..f26bb5455 100644
--- a/Nasal/canvas/map/taxiways.layer
+++ b/Nasal/canvas/map/taxiways.layer
@@ -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) {
diff --git a/Nasal/canvas/map/tcas_arrow_a500.draw b/Nasal/canvas/map/tcas_arrow_a500.draw
index 23b05a0bf..122f979ea 100644
--- a/Nasal/canvas/map/tcas_arrow_a500.draw
+++ b/Nasal/canvas/map/tcas_arrow_a500.draw
@@ -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)
diff --git a/Nasal/canvas/map/tcas_arrow_b500.draw b/Nasal/canvas/map/tcas_arrow_b500.draw
index 0d07dea65..18174096a 100644
--- a/Nasal/canvas/map/tcas_arrow_b500.draw
+++ b/Nasal/canvas/map/tcas_arrow_b500.draw
@@ -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")
diff --git a/Nasal/canvas/map/test.layer b/Nasal/canvas/map/test.layer
index d2bd296b8..85e47dac7 100644
--- a/Nasal/canvas/map/test.layer
+++ b/Nasal/canvas/map/test.layer
@@ -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);
diff --git a/Nasal/canvas/map/tower.draw b/Nasal/canvas/map/tower.draw
index 0cac8f54e..728d4651b 100644
--- a/Nasal/canvas/map/tower.draw
+++ b/Nasal/canvas/map/tower.draw
@@ -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)
diff --git a/Nasal/canvas/map/tower.layer b/Nasal/canvas/map/tower.layer
index 099b2af78..900f4d7f7 100644
--- a/Nasal/canvas/map/tower.layer
+++ b/Nasal/canvas/map/tower.layer
@@ -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!!
diff --git a/Nasal/canvas/map/traffic.draw b/Nasal/canvas/map/traffic.draw
index 12602a41b..e90d78aab 100644
--- a/Nasal/canvas/map/traffic.draw
+++ b/Nasal/canvas/map/traffic.draw
@@ -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;
diff --git a/Nasal/canvas/map/traffic.layer b/Nasal/canvas/map/traffic.layer
index 121d1e1cc..8185b0c80 100644
--- a/Nasal/canvas/map/traffic.layer
+++ b/Nasal/canvas/map/traffic.layer
@@ -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);
diff --git a/Nasal/canvas/map/traffic.model b/Nasal/canvas/map/traffic.model
index ee9fe2df3..e5d2bf98f 100644
--- a/Nasal/canvas/map/traffic.model
+++ b/Nasal/canvas/map/traffic.model
@@ -1,3 +1,4 @@
+# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var MPTrafficModel = {};
MPTrafficModel.new = func make(LayerModel, MPTrafficModel);
diff --git a/Nasal/canvas/map/vor.draw b/Nasal/canvas/map/vor.draw
index edde296f2..fcf26c518 100644
--- a/Nasal/canvas/map/vor.draw
+++ b/Nasal/canvas/map/vor.draw
@@ -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) {
diff --git a/Nasal/canvas/map/vor.layer b/Nasal/canvas/map/vor.layer
index c7ebb32f6..19fb17c7b 100644
--- a/Nasal/canvas/map/vor.layer
+++ b/Nasal/canvas/map/vor.layer
@@ -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 );
diff --git a/Nasal/canvas/map/vor.model b/Nasal/canvas/map/vor.model
index 37c0db71f..47d3ca31a 100644
--- a/Nasal/canvas/map/vor.model
+++ b/Nasal/canvas/map/vor.model
@@ -1,3 +1,4 @@
+# WARNING: *.model files will be deprecated, see: http://wiki.flightgear.org/MapStructure
var VORModel = {};
VORModel.new = func make( LayerModel, VORModel );
diff --git a/Nasal/canvas/map/waypoint.draw b/Nasal/canvas/map/waypoint.draw
index 52cd51d01..5916c1100 100644
--- a/Nasal/canvas/map/waypoint.draw
+++ b/Nasal/canvas/map/waypoint.draw
@@ -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)
diff --git a/Nasal/geo.nas b/Nasal/geo.nas
index d83609868..a08d4e486 100644
--- a/Nasal/geo.nas
+++ b/Nasal/geo.nas
@@ -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),
diff --git a/Sounds/rednoise.wav b/Sounds/rednoise.wav
new file mode 100644
index 000000000..46d3e732c
Binary files /dev/null and b/Sounds/rednoise.wav differ
diff --git a/Translations/en/atc.xml b/Translations/en/atc.xml
index 9a54dffca..07b11d3e5 100644
--- a/Translations/en/atc.xml
+++ b/Translations/en/atc.xml
@@ -44,7 +44,7 @@
uniform
victor
whiskey
- xray
+ x-ray
yankee
zulu