From 9761c6e3728f5f413cb54921e3f4b00799ee11ed Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Mon, 28 Apr 2014 14:06:14 +0200 Subject: [PATCH 1/4] correct spelling of "x-ray" --- Translations/en/atc.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 8a52e912dc6267f068bb6423993109d78f11c373 Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Mon, 28 Apr 2014 15:42:54 +0200 Subject: [PATCH 2/4] some ATIS format tweaks --- ATC/atis/eddh.xml | 14 +++++++------- ATC/atis/other.xml | 11 ++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) 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 + . From 1e2bf918da93d50ffc189cc4d7291343c79b2213 Mon Sep 17 00:00:00 2001 From: Torsten Dreyer Date: Mon, 28 Apr 2014 23:53:22 +0200 Subject: [PATCH 3/4] add noise sound "red noise" aka "brown noise" can be used for radios. Created with audacities "create noise" function and filtered with "am radio" filter. --- Sounds/rednoise.wav | Bin 0 -> 88244 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Sounds/rednoise.wav diff --git a/Sounds/rednoise.wav b/Sounds/rednoise.wav new file mode 100644 index 0000000000000000000000000000000000000000..46d3e732cec0bab56ba8885d83f6c8235c367f8e GIT binary patch literal 88244 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 i>vZR5 Date: Mon, 28 Apr 2014 21:26:32 -0500 Subject: [PATCH 4/4] Many MapStructure/NavDisplay updates See the clone at https://gitorious.org/fg/canvas-hackers-fgdata/source/topics/canvas-radar: --- Nasal/canvas/MapStructure.nas | 456 +++++++++++++++++------- Nasal/canvas/api.nas | 85 ++++- Nasal/canvas/map/APS.lcontroller | 27 ++ Nasal/canvas/map/APS.scontroller | 9 + Nasal/canvas/map/APS.symbol | 16 + Nasal/canvas/map/APT.lcontroller | 35 ++ Nasal/canvas/map/APT.scontroller | 13 + Nasal/canvas/map/APT.symbol | 52 +++ Nasal/canvas/map/DME.lcontroller | 35 +- Nasal/canvas/map/DME.scontroller | 7 +- Nasal/canvas/map/DME.symbol | 138 ++++++- Nasal/canvas/map/FIX.lcontroller | 21 +- Nasal/canvas/map/FIX.scontroller | 5 +- Nasal/canvas/map/FIX.symbol | 41 ++- Nasal/canvas/map/NDB.lcontroller | 9 +- Nasal/canvas/map/NDB.scontroller | 4 +- Nasal/canvas/map/NDB.symbol | 1 + Nasal/canvas/map/TFC.lcontroller | 83 ++--- Nasal/canvas/map/TFC.scontroller | 14 +- Nasal/canvas/map/TFC.symbol | 17 +- Nasal/canvas/map/VOR.lcontroller | 27 +- Nasal/canvas/map/VOR.scontroller | 5 +- Nasal/canvas/map/VOR.symbol | 118 ++++-- Nasal/canvas/map/WPT.lcontroller | 8 +- Nasal/canvas/map/WPT.scontroller | 1 + Nasal/canvas/map/WPT.symbol | 1 + Nasal/canvas/map/aircraftpos.controller | 81 ++++- Nasal/canvas/map/airplane-symbol.draw | 1 + Nasal/canvas/map/airplane-symbol.layer | 1 + Nasal/canvas/map/airplane-symbol.model | 1 + Nasal/canvas/map/airports-nd.draw | 1 + Nasal/canvas/map/airports-nd.layer | 1 + Nasal/canvas/map/airports-nd.model | 1 + Nasal/canvas/map/airports.model | 1 + Nasal/canvas/map/altitude-profile.draw | 1 + Nasal/canvas/map/dme.draw | 1 + Nasal/canvas/map/dme.layer | 1 + Nasal/canvas/map/dme.model | 1 + Nasal/canvas/map/fix.draw | 1 + Nasal/canvas/map/fixes.layer | 1 + Nasal/canvas/map/fixes.model | 1 + Nasal/canvas/map/navaid.draw | 1 + Nasal/canvas/map/navaids.layer | 1 + Nasal/canvas/map/navaids.model | 1 + Nasal/canvas/map/navdisplay.mfd | 216 +++++------ Nasal/canvas/map/parking.draw | 1 + Nasal/canvas/map/parking.layer | 1 + Nasal/canvas/map/route.draw | 1 + Nasal/canvas/map/route.layer | 1 + Nasal/canvas/map/route.model | 1 + Nasal/canvas/map/runway-nd.draw | 1 + Nasal/canvas/map/runway-nd.layer | 1 + Nasal/canvas/map/runway-nd.model | 1 + Nasal/canvas/map/runways.draw | 1 + Nasal/canvas/map/runways.layer | 1 + Nasal/canvas/map/storm.draw | 1 + Nasal/canvas/map/storms.layer | 1 + Nasal/canvas/map/storms.model | 1 + Nasal/canvas/map/taxiways.draw | 1 + Nasal/canvas/map/taxiways.layer | 1 + Nasal/canvas/map/tcas_arrow_a500.draw | 1 + Nasal/canvas/map/tcas_arrow_b500.draw | 1 + Nasal/canvas/map/test.layer | 1 + Nasal/canvas/map/tower.draw | 1 + Nasal/canvas/map/tower.layer | 1 + Nasal/canvas/map/traffic.draw | 1 + Nasal/canvas/map/traffic.layer | 1 + Nasal/canvas/map/traffic.model | 1 + Nasal/canvas/map/vor.draw | 1 + Nasal/canvas/map/vor.layer | 1 + Nasal/canvas/map/vor.model | 1 + Nasal/canvas/map/waypoint.draw | 1 + Nasal/geo.nas | 9 +- 73 files changed, 1180 insertions(+), 398 deletions(-) create mode 100644 Nasal/canvas/map/APS.lcontroller create mode 100644 Nasal/canvas/map/APS.scontroller create mode 100644 Nasal/canvas/map/APS.symbol create mode 100644 Nasal/canvas/map/APT.lcontroller create mode 100644 Nasal/canvas/map/APT.scontroller create mode 100644 Nasal/canvas/map/APT.symbol 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),