Talk:Canvas MapStructure

From FlightGear wiki
Jump to navigation Jump to search

Article Maintenance

Most of the diagrams were created using the free (no registration required!) UML service at:


getPos() refactoring

just went through the list of commits here and noticed that I forgot to commit this, but I was actually going to revise getPos - primarily because it was doing two things at once 1) determining if a ghost/hash is supported and 2) getting the position stuff out of it. I would prefer splitting these two tasks - for the simple reason that #1 would be useful in and of itself, especially for further framework hardening - i.e. as part of validating searchCmd results

Well you do kinda need to store both pieces of information, so the current scheme works pretty well. The thing about "hardening" searchCmd results is that the same check is already done when retrieving the lat/lon just a few steps later - i.e. you could only move it up by a few steps in the process, so I don't see what that would do.

Also, I forgot to mention it in the commit message, but this theoretically allows for inheriting from positioned objects via parents too, so I can add more custom models instead of .scontroller methods.

I was primarily thinking in terms of providing an API that allows registering layers without MapStructure.nas having to be touched - and as part of this, I would prefer some kind of simple "validation mode" where some of the most-common pitfalls are checked for, such as wrong return types or wrong inheritance/OOP etc - like one of your comments said, we could then even provide a mechanism that allows ghosts/handlers to be registered at runtime. So I am thinking along the lines of being able to load/reload layers from disk and validating them according to a handful of rules - you probably noticed my additions to load_MapStructure() which were pretty much about the same thing. So once we expose mechanism's like getPos() ghost handling as building blocks, we can reuse those in other parts for hardening/validation purposes. Basically, there are now 2 guys working on MapStructure layers, and Gijs also said that he was going to look into porting things - so we better provide some mechanism that allows people to do "layer development" without having to be expert Nasal coders ...

Supporting reload() (symbol/scontroller/lcontroller)

To enable contributors to easily create, develop and test MapStructure layers, having a way to reload them from disk would be great, this would include:

  • saving all ctor args in a separate data structure for each object/instance
  • suspending all loops, i.e. calling .del() on each object
  • reloading files from disk
  • re-running each ctor with the previously stored args

I am thinking of having an interface that needs to be implemented by DotSym et al:

var Reloadable = {
 new: func,
 store: func(dtor, ctor) append(me.callbacks, {dtor:dtor, ctor:ctor} ),
 handleReload: func die("not implemented by sub-class!"),
 reload: func(name) foreach(var obj; {
  obj.dtor(); # call the object's del() 
  # reload files from disk here via a generalized version of load_MapStructure
  obj.ctor(); # re-create all objects using the args stored in the closure of each ctor callback
 }, #foreach

For instance, currently the DotSym ctor looks like this:

# For the instances returned from makeinstance:
        # @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, 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(
                                me.element_type, me.element_id

                # store a  handle to the object's dtor and ctor (with args)
                # this will allow us to .del() objects and re-create them after reloading files from
       dtor: m.del, ctor: func,layer,model,controller) );

                if (m.controller != nil) {
                        temp =,m);
                        if (temp != nil)
                                m.controller = temp;
                else die("default controller not found");

                return m;

the main idea here is that I was going to rework lcontroller/scontroller/symbol handling such that each class needs to implement a "ReloadableClass" interface, so that we reload these files from disk. The only thing required to make this work is 1) being able to suspend all running callbacks (timers/listeners), 2) call all dtors, 3) register a notify() callback in each ctor, so that "front-end" code like navDisplay receives a notification when things are reloaded, so that it can re-run its own init() routines.

That way, I could pull this off with rougly ~40-60 lines of additional Nasal code on the framework side.

For example, the ctor could accept an "onReload=nil" arg to take an optional callback, which will be invoked to allow the MapStructure framework to notify each front-end that all callbacks were suspended, files reloaded - so that the the front-end code (e.g. ND) would simply re-run its ctor via onReload.

That would then allow us to not only reload dialogs from disk, but even MapStructure files which implement the "Reloadable" interface.

TFC target altitudes

There's another issue with the TFC layer - while lat/lon are correctly set, the threat-level stuff isn't working properly at the moment - it seems this is because of the TCAS helpers (TA/RA) - just compare the map output with the ND output, and you'll see that -100 is typically shown, i.e. altitude etc isn't properly kept into account


the "init_calls" stuff was a hack that I didn't mean to commit - the idea was to provide a mechanism that tracks those draw* calls that would normally only be done once to populate the cache, and return some diagnostics because this shouldn't be typically the case at runtime, unless a reset/re-init was triggered - at which point, we'd probably want to reset the counter, too ?

overall, this kind of stuff doesn't belong into layer/symbol files - it should really be handled by the surrounding framework and be done transparently.

We cannot expect people to deal with such "low level" things, i.e. the amount of Nasal/Canvas expertise required is a bit too much to ask for


Additional Layer Types

Allowing other layer types is a good idea, and I think I already did that using a class-factory setup in the .addLayer() method, so its a matter of the caller requesting a "AnimationLayer" of type "Compass", for example. —Philosopher (talk) 19:09, 1 December 2013 (UTC)

I played with having an AnimatedSymbol type that would use maketimer() to invoke methods in order to change font/size/color etc, which works really nicely. I would just suggest to implement this on top of a "StyleableSymbol" class because it's clearly overlapping - that way, an animated symbol would merely be a container for a bunch of symbol styles that are conditionally applied/updated. --Hooray (talk) 10:42, 16 February 2014 (UTC)
Going to revisit this after 3.2 because establishing support for this could greatly help simplify & structure the ND code. --Hooray (talk) 14:55, 29 June 2014 (UTC)

LOD handling

LOD handling via .setScale() - each symbol file contains drawing instructions specific to the original texture dimensions, this needs to be encoded at the top of the .symbol file, so that it can be scaled according to the current canvas dimensions, otherwise things may be out of scale. So better use the LOD arg here and require each symbol file to specify its assumed texture dimensions, which are 1024x1024 in the case of Gijs' ND draw routines (texture and view).
I think we can scale each symbol individually to get the desired effect? That might also be cheap to handle via an alias/reference to a property in the map, so we can just update that. —Philosopher (talk) 19:09, 1 December 2013 (UTC)
sure, but it's probably a good idea to reduce the amount of identical calls - i.e. better scale a symbol once and then reference it subsequently like you say, rather than scaling each symbol individually - i.e. being more canvas friendly by reducing workload. At the moment, all our "draw" routines are harvested from other places, so were not specifically written for MapStructure - as such as they do contain assumptions about the canvas size, once the canvas size/viewport changes, things get out of scale. Not sure if there's a better/more efficient way than just keeping track of the original texture dimensions and scaling/transforming accordingly.--Hooray (talk) 20:34, 2 December 2013 (UTC)

MapController: Positioning

  • similarly, centering the map should read in the dimensions and divide by 2 rather than using hardcoded dimensions
    • Yes, like I mentioned that can be a default position, but dragging on a map will need to move it. —Philosopher (talk) 19:09, 1 December 2013 (UTC)
  • styling support

Interactive Layers

Use cases:

  • zooming
  • panning
  • GUI layers (e.g. route planning)
  • editor-like functionality for missions/tutorials[1] or weather[2]


canvas protractor prototyping

FlightGear 4.xx roadmap

  • Gijs' Map dialog projection code needs to be implemented for Canvas/Map !!
  • port runways.draw and taxiways.draw to MapStructure
  • refine/improve caching support and review existing layers accordingly
  • introduce a MapStructure widget for Canvas dialogs (route manager, tutorials, map-canvas)
  • review existing controllers, introduce additional controllers (AI traffic, editing)
  • encapsulate FGPositioned/NavCache APIs so that people can use a different navaid source?
  • explore supporting SQLite based persistent layers (as per TheTom's comments)
  • more/better hardening against end-user mistakes (missing/wrong methods/hashes, e.g. styling & equals etc)
  • more pre-defined layer times
  • establish difference between moving/stationary layers (navaids vs. AI traffic)
  • add support for vertical layers (VSD, orbital)
  • interactive layers (e.g. for editing tutorial targets)


the "framework" is sufficiently basic that you won't need to understand much/any OOP - it basically works by traversing a vector of hashes that contain callbacks that invoke each other, i.e. the first callback checks some configurable condition, and then there are true/false callbacks that will be invoked depending on the conditional check.

So it really is rather basic (but very convoluted) - you can definitely review the Boeing/Airbus code to learn how everything works, and even copy the existing code and modify it according to your requirements.

There are some "todo" items on our list (see the wiki), but for those you will want to really understand the .mfd file itself, which provides the underlying framework to make the *.styles stuff work - which really is all you (should) need to understand to add/customie other styles.[1]