Canvas MapStructure: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
m (→‎Roadmap post 3.0: Map 1st, then airport-select ...)
Line 495: Line 495:


This has the most interesting jobs: it manages how the whole map is positioned and re-rendered. Example: [https://gitorious.org/fg/hoorays-fgdata/source/591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14:Nasal/canvas/map/aircraftpos.controller]. It will have to manage it's own updating routines, i.e. keeping track of timers/listeners that are hooked to update it. For example, like in the above, one can make a timer object which calls an "update_pos" method, which will reposition the map (via me.map.setPos) and call update on all layers if necessary (via me.map.update()), which will often call positioned searches and thus should be spaced out, e.g. ~4 or more seconds apart. Obviously one should also check other conditions other than time, like difference in position since last query.
This has the most interesting jobs: it manages how the whole map is positioned and re-rendered. Example: [https://gitorious.org/fg/hoorays-fgdata/source/591ac36c64f4ba30d9cbff7cb96e9a2006e9bd14:Nasal/canvas/map/aircraftpos.controller]. It will have to manage it's own updating routines, i.e. keeping track of timers/listeners that are hooked to update it. For example, like in the above, one can make a timer object which calls an "update_pos" method, which will reposition the map (via me.map.setPos) and call update on all layers if necessary (via me.map.update()), which will often call positioned searches and thus should be spaced out, e.g. ~4 or more seconds apart. Obviously one should also check other conditions other than time, like difference in position since last query.
Some things to keep in mind when working on Map Controllers:
* we may also want to support optional mouse-panning (see airport-select dialog)
* we may also want to support optional tooltips for layer elements


==== Styling & LOD Handling ====
==== Styling & LOD Handling ====

Revision as of 20:11, 14 February 2014

This article describes content/features that may not yet be available in the latest stable version of FlightGear (2020.3).
You may need to install some extra components, use the latest development (Git) version or even rebuild FlightGear from source, possibly from a custom topic branch using special build settings: .

This feature is scheduled for FlightGear (unknown). 10}% completed

If you'd like to learn more about getting your own ideas into FlightGear, check out Implementing new features for FlightGear.


MapStructure
MapStructureDialog.png
Started in 11/2013
Description Charting abstraction layer for Canvas/Nasal maps
Maintainer(s) Philosopher, Hooray
Contributor(s) User:Philosopher (since 11/2013),
Status Under active development as of 11/2013
Topic branches:
fgdata main fgdata repo


Targeted FlightGear versions: 3.00+

Canvasready.png

Note  This article is intended for people wanting to add custom mapping/charting displays to FlightGear (aircraft, GUI dialogs, HUDs etc) - it assumes familiarity with Nasal scripting, Object Oriented Programming with Nasal and Canvas - while an existing layer can be easily added and used by non-programmers within a few minutes, creating new MapStructure layers requires additional knowledge which is detailed below. In its simplest form, MapStructure is just a library of a handful of existing layers that can be easily -and quickly- reused for all kinds of different purposes. For people familiar with Nasal coding, MapStructure is also a framework to create new fully reusable layers.

MapStructure is a Nasal-space framework (designed and maintained by Philosopher) for managing layers of symbols in Nasal/Canvas-based mapping displays, which can be used in both aircraft MFDs/instruments and GUI dialogs, like the airport selection or Map dialogs. MapStructure is all about separating the visualization of the map from the visualized data itself, and the way it is shown to, and controlled by, the user. MapStructure is designed as a Model/View/Controller (MVC) framework.

MapStructure-MVC-Framework

The primary challenge here is, that these different front-ends (e.g. a GUI dialog or cockpit) will typically all have different means to interact with the map and its rendered layers.

MapStructure MVC separation

A GUI dialog may respond to mouse events (panning/zooming etc), while a cockpit display would typically respond to cockpit hot-spots (bindings), such as clicking virtual CDU buttons. Therefore, user input handling must be well encapsulated and handled by delegates, so that the back-end doesn't need to know anything about its front-end. Otherwise, back-ends designed for aircraft would no longer work when used in GUI dialogs and vice versa.

For instance, imagine a piece of code showing navaids within a range that can be set in the cockpit: This would stop working when used in a different cockpit, or when used in a GUI dialog. Likewise, referencing properties that are specific to a certain GUI dialog, would stop working once the layer is used in a different GUI dialog or in an aircraft.

Conventionally, a simple navaid layer showing NDBs would be implemented as a single piece of code that's doing these steps:

  • run a query to find navaids within 50 nm
  • get the positions of each navaid
  • for each navaid, render a symbol onto a canvas map

Now, once such a piece of code needs to do something else, it would be typically copied and adapted, e.g. to change the range of the query, the type of symbol, the type of map or even just the type of query (VORs vs NDBs). MapStructure encourages a more modular design, so that each stage is implemented via separate files that can be easily reused, and that can be augmented by adding new files to accomplish a related task.

The next complication is that different front-ends may require different data to be shown - such as taxiways, runways, fixes, waypoints or routing (waypoints). Therefore, the framework works in terms of "layers", where each layer manages its own set of symbols - symbols typically represent geographic positions (latitude/longitude), as well as a drawable (symbol, SVG file name or a callback handling the implementation).

Control of individual layers is delegated to callbacks that can be overridden by the front-end, i.e. to hide/show a layer.

MapStructure Controllers

The primary concern here is to ensure that the DRY-principle (don't-repeat-yourself) is not violated, so that maps, layers and symbols can be easily reused without requring any copy&paste- and so that new use-cases can be easily supported by adding custom controllers that interact with maps/layers/symbols as needed. By establishing this separation of concerns, the design is heavily focused on gathering new contributions in a single place, rather than having custom-coded solutions in various places - such as GUI dialogs or aircraft, which used to be the predominant practice prior to this framework, and which often meant that useful code was only available for certain purposes.

MapStructure-UseCases.png

Thus, to the framework itself it doesn't matter if you need a layered map to be shown on an instrument or in a GUI dialog, or even as part of the scenery (VGDS) or maybe as a livery. By following this approach, we can use a shared back-end to implement layered maps for different needs, without having to duplicate any code, where people usually would use "copy & paste" and customize things afterwards, a very error-prone and tedious process, that doesn't lend itself to long-term maintenance.

As of 11/2013, all this is work in progress and not yet finished, we're hoping to completely replace the old map.nas code by FG 3.2.

Aircraft developers working on airliners or modern biz jets (and the corresponding MFD avionics) will probably want to get in touch with Philosopher and Hooray to coordinate things a little. We also appreciate any related feature requests and other constructive feedback. Flexibility is the ultimate design goal of this effort.

At the moment, the primary users of the framework are the 747-400 and the 777-200ER - both make use of the new ND framework, which internally uses the MapStructure framework.

If you just need an ND, you won't need to deal with MapStructure directly, it is all done transparently by the NavDisplay code.

However, if you'd like to create custom charting displays, or GUI dialogs with embedded charts (map dialog, instructor console, ATC or RADAR displays etc), you'll probably want to use the MapStructure framework, because it reduces the amount of specialized Nasal code significantly - typically to ~10-15 lines of configuration code per layer.

To learn more about the motivation of using a MVC design, see Canvas Map API.

MapStructure Internals

Demonstration

To show a moving map layer with all AI/MP traffic within a range of 25 nm, only 10-15 lines of code are needed. Here's a full example of a dialog that shows a corresponding map layer and that contains buttons for reloading and closing the dialog (for rapid prototyping). Most of this is boilerplate XML - the actual code for this is just ~10 lines.

For some more details, see: embedded canvas in an PUI/XML dialog. To affect the appearance of the dialog, see $FG_ROOT/Docs/README.gui and README.layout

<?xml version="1.0"?>
<PropertyList>
<name>traffic-mapstructure</name>
<modal>false</modal>
<layout>vbox</layout>

<group>
        <layout>hbox</layout>
        <empty><stretch>1</stretch></empty>
 
        <text>
            <label>MapStructure Traffic Demo</label>
        </text>

       <button>
            <legend>Reload</legend>
            <default>1</default>
            <border>2</border>
 
            <binding>
                <command>reinit</command>
                <subsystem>gui</subsystem>
            </binding>
        </button> 

        <empty><stretch>1</stretch></empty>
 
        <button>
            <pref-width>16</pref-width>
            <pref-height>16</pref-height>
            <legend></legend>
            <default>1</default>
            <keynum>27</keynum>
            <border>2</border>
 
            <binding>
                <command>dialog-close</command>
            </binding>
        </button>
    </group>
    <hrule/>

<canvas>
               <name>traffic-map</name>
               <valign>fill</valign>
               <halign>fill</halign>
               <stretch>true</stretch>
               <pref-width>600</pref-width>
               <pref-height>400</pref-height>
<nasal>      
<!-- 
     this is the Canvas-specific Nasal section where you can run your own Nasal code 
     to access and animate the canvas region.
     If you need to access existing code from other places (i.e. aircraft specific stuff), 
     just use io.load_nasal() or io.include() to pull in all required dependencies.
-->
<load><![CDATA[
## 
# you can add your own dependencies here
#
var dependencies = [
];

foreach(var path; dependencies)
 io.include( path );

var myCanvas = canvas.get( cmdarg() );
var TestMap = myCanvas.createGroup().createChild("map");
foreach(var type; var layer_types = ['TFC',] )
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type); 
# Center the map's origin:
TestMap.setTranslation(300,200); 
# Initialize a range (TODO: LayeredMap.Controller):
TestMap.set("range", 100);
# Little cursor of current position:
TestMap.createChild("path").rect(-5,-5,10,10).setColorFill(1,1,1).setColor(0,1,0);
# And make it move with our aircraft:
TestMap.setController("Aircraft position"); # from aircraftpos.controller
]]></load>
<!-- all the code here will be executed once the dialog is closed, so you
should put your cleanup code here - so free all resources here (timers, listeners, loops)
-->
<unload><![CDATA[
TestMap.del();
]]></unload>

</nasal>
</canvas>

</PropertyList>

You will want to save this file in $FG_ROOT/gui/dialogs/traffic-mapstructure.xml, and then use the following piece of Nasal to open/show the dialog via the Nasal Console:

gui.showDialog("traffic-mapstructure");

As can be seen, using just ~10-15 lines of Nasal code, creates a fully working map that shows AI/MP traffic.

Layers

Initial Waypoint support for Philosopher's MapStructure framework

(most of these exist already in various *.layer/*.draw/*.model files, they just need to be ported to the new framework and used in navdisplay.mfd)

  • APT (airports) ticket #1320 80}% completed [1]
  • NDB 60}% completed
  • VOR Done Done
    • navaid layers need to register autopilot/navradio listeners in their controller, because the ND highlights certain stations based on selected frequency/OBS settings
  • FIX (fixes) Done Done
  • DME Done Done
  • WPT (waypoints) 80}% completed
    • see requirements for route layer
    • also note that this is drawn inside of the route, not used directly (but it would be a good idea to eventually split waypoint and route rendering, so that we can use these independently - i.e. for the Map dialog)
  • RTE (route, waypoints and legs) Not done Not done
    • the route layer needs to use listeners to receive "onChange" notifications from the route manager, or it may take a few seconds until the route shows up
    • navdisplay.mfd still contains a few stubs from the original code - they should be replaced once we respond to RM events
    • the Nasal Flightplan API seems to support multiple routes (active/inactive), this is based on real RL avionics - which makes sense to also support in the RTE/WPT layers eventually
  • TFC (ai/mp traffic) 30}% completed [2]
    • needs listener to receive notifications once enabling/disabling AI/MP Done Done
    • needs optimizations (e.g. draw vs. just update)
  • WXR (weather) 30}% completed (Gijs' storms)
    • needs to use listeners to receive notifications on enabling/disabling AW, and any settings related to it.
  • ATC/RADAR (see Canvas Radar) 10}% completed

These are currently being used in the airport-selection.xml dialog, and should only be ported once the map-canvas dialog works properly, or we may be missing features:

  • altitude profile Not done Not done
  • tower (trivial) Not done Not done
  • airplaneSymbol (trivial) Not done Not done
  • runways Not done Not done
  • taxiways Not done Not done
  • parking Not done Not done

Issues

(link to canvas-navdisplay label in issue tracker)

We currently have quite a few old files doing basically identical stuff, just with different draw routines - such as e.g. "runway-nd", "airports", "airports-nd". It would probably be a good idea to generalize this by implementing LOD and styling support - so that we can use a single APT layer that supports all necessary customizations.

Basically, we could unify things a bit by using LOD support to show APT in different modes, so that taxiways etc would be only shown if necessary. It would still make sense to maintain separate draw/symbol files for these, so that things can be easily reused.

Porting the map dialog

20}% completed


Porting the airport-select dialog

10}% completed


APT

  • query_range helpers are not properly used according to Gijs (confirmed, it's due to our way of overwriting/singleton map controllers, see[3])

TFC

  • watch out for node.getValue(path) uses - they're causing nil issues
  • stop using listeners for added/removed handling, use searchCmd processing, analogous to Map
  • multi-inheritance (Node, geo.Coord) in TrafficModel is causing issues, so that the getpos/latlon methods are not even called currently
  • generalize aircraft position controller to support AI/MP traffic
  • fix up TFC.symbol to use me.arrow_tcas[arrow_type].foo() when calling methods (currently broken in git)
  • investigate adopting our caching scheme to make the layer faster

Hooray's Todo List

  • make logging a part of the framework, overload print/printlog
  • make profiling via systime() a part of the framework, for each controller/model/view (optional)
  • investigate hardening, i.e.
    • type-checking (typeof)
    • e.g. searchCmd should always return a vector, and isa(type) should be supported by getpos
    • checking available symbols (and typeof) for scontroller/lcontroller and symbol files
  • clean up existing files and add more comments 20}% completed
    • including a MapStructure URL (wiki) to each file so that people know where to look for further info Done Done
  • add a new section: Porting Layers (i.e. from the old format to the new one)
  • provide an option to suspend/restart MapStructure
  • provide an option to reload MapStructure (layers) from disk (RAD)
  • styling - will involve: 30}% completed (currently being prototyped inside the DME layer)
    • allow colors/fonts to be overridden, i.e. any draw .symbol callback would use a lookup hash that can be customized (instead of hard-coding things)
    • allow size to be overridden (overlapping with LOD support)
    • allow custom file names or callbacks to be provided for symbols (draw routines)
    • i.e. come up with a "Styleable" class that exposes an interfaces that is implemented by StyleableColor, StyleableFont, StyleableSymbol
    • this means that style-specific things should never be directly part of the draw routine itself, but need to be encapsulated, i.e. setColor/setColorFill/setSize/setText/setFont/setFontSize etc - so that these methods can be afterwards called on the canvas group - the styleable class should probably just sub-class Symbol.Controller to make this work

Roadmap post 3.0

  • port missing layers 70}% completed
  • handle signals to trigger controller updates for radio, autopilot, route manager or AI/MP and weather changes 60}% completed
  • adapt NavDisplay framework to use MapStructure internally 80}% completed
  • look into extending the controller framework for different use cases (ND/airport selection dialog) 30}% completed
  • look into porting the Map dialog 20}% completed
  • look into updating the airport-selection dialog Not done Not done
  • add a MapStructure-based chart to the route manager dialog Not done Not done
  • integrate the ND into other airliner cockpits (757,767 and Airbus series) to ensure that things remain generic 20}% completed

Framework

  • we should add some helper functions for people to more easily do RAD when developing new layers/controllers, this would include supporting reloading from disk, and creating a simple canvas window to show the corresponding layers, maybe with a "reload" button that suspends and restarts everything after reloading - this would probably even help us Not done Not done
  • investigate adding a dedicated "Filter" class for positionedSearches, so that things like custom object filters (think ATC/RADAR etc) can be more easily implemented by implementing a corresponding interface (mirage2000) [4] Not done Not done
  • logging should probably be handled by the framework directly, so that messages can be redirected or stored Not done Not done
  • support benchmarking/profiling of layers via systime() - as mentioned by Philosopher on the forum Not done Not done
  • a simple form of unit testing (sanity checks) would make sense for regression testing purposes, i.e. to ensure that the number of drawables never grows beyond the total number of elements (ditto for layers) Not done Not done
  • properly implement (separate) init/update at the framework level Not done Not done
  • consider supporting loops that are split across frames Not done Not done
  • add LOD support 20}% completed
  • add styling support (colors, different SVG images or draw callbacks for symbols) Not done Not done
  • many features would greatly benefit from having a simple animation framework, i.e. for changing/scaling symbols etc (not specific to positioned objects) Not done Not done
  • implement a caching scheme 70}% completed

Using Layers

Using MapStructure Layers

the kind of object that is typically needed by MapStructure is just an geo-referenced position, which is just fancy lingo for anything that has a 3D position (lat,lon,altitude) MapStructure will internally handle all the details to implement each layer efficiently.

The framework works in terms of "symbols" and "layers" where each layer would have symbols, along with controllers for each symbol (i.e. to animate things, to change the color/style etc) - also, each layer can be controlled using a layer-controller, e.g. to change the range for example.

I suggest to look at some of the simpler examples, e.g. the NDB symbol to see how everything is implemented. You can basically load a SVG file or draw your own symbols using OpenVG instructions.

Then, the layer controller will determine where symbols are to be drawn, and do any filtering (range, altitude, mountains, azimuth).

So the result will just return a vector (resizable array) to the layer controller with drawables (symbols).

You will probably want to experiment first with a really simple example to see how everything works.

I suggest to try out the demo/example mentioned in the article.

Ultimately, you can them combine many layers to form a single map, and each layer can have "many" different symbols

Creating new Layers

Adding-new-MapStructure-Layers.png

Copy three files (all in Nasal/canvas/map/):

  • *.symbol (drawing/update routine(s))
  • *.scontroller (symbol controller)
  • *.lcontroller (layer controller)

Each file represents a virtual class (no explicit hash needed - just use caller(0)[0]).

Note

It makes sense to use existing layers as templates, i.e. copy a set of files that is close to what you want to implement - e.g. a RADAR or ATC layer will typically involve processing AI/MP traffic (changing positions) - which is what the TCAS (TFC) layer is already doing - so it's a good idea to use that as a template for your new layer.

Using other layers like VOR, NDB or DME would also be possible - but think about what these represent: navaids, with fixed geographic coordinates, while an ATC/RADAR layer would be all about showing live traffic, so it would be less work to reuse and customize a similar layer instead.

Then again, using a complex layer to represent a simple thing would also be more customizing work than necessary obviously. You can also borrow things from different layers-for example, the VOR/DME layers contain support for animating symbols and range-selection based display modes. Thus, it's a good idea to spend 5-10 minutes looking through existing files and playing with them, to see where you can borrow code from.

First, we need to set up class things, so each file "bootstraps" itself.

Class members/local variables:

name Description *.symbol *.scontroller *.lcontroller *.controller Links
parents = Set up class inheritance. [DotSym] [Symbol.Controller] [Layer.Controller] [Map.Controller]
__self__ = Current class being generated. caller(0)[0] [5] [6] [7]
name = Name to use for referencing (via Symbol[Layer][.Controller].get(name), etc.) and console messages. name of file before the extension, or otherwise as appropriate; e.g. "VOR"
element_type = Type of Canvas Element to use; becomes me.element. "group" or "path" (usually) N/A N/A N/A VOR FIX

Adding this instance to MapStructure's dictionaries and make it be the default controller (where "name" is an arbitrary-but unique-handle used to reference it inside of OOP abstraction):

*.symbol *.scontroller *.lcontroller *.controller
DotSym.makeinstance( name, __self__ );. Symbol.registry[ name ].df_controller = __self__; SymbolLayer.Controller.add( name, __self__); Map.Controller.add( name, __self__);
N/A Symbol.registry[ name ].df_controller = __self__; N/A, see below regarding the SymbolLayer Map.df_controller = __self__;

Also in *.lcontroller, we need to set up the actual SymbolLayer, whose methods are already handled by MapStructure, so we only need to specify a few items:

SymbolLayer.add(name, {
    parents: [SymbolLayer],
    type: name, # Symbol type, i.e. for Symbol.get( ... )
    df_controller: __self__, # controller to use by default -- this one
});

Symbols

MapStructure-Symbol-UML.png

Symbols are pretty simple - just stick to the members/methods outlined above. Future implementation tweak: support init() and update(), or such. Also support caching. Currently there's just update(), with this documentation:

First, one is provided with a "model" object (me.model) that is ultimately specified by the one creating the Canvas.Symbol - aka the SymbolLayer.Controller. This is usually a positioned object, though it could be anything. In the case of the TFC layer (handling AI/MP traffic), it is a hash wrapping props.Node and geo.Coord. The position of the symbol is automatically handled by MapStructure, so the programmer only has to worry about drawing it.

Second, one is also provided with a "controller" object (me.controller), which is typically obtained from the corresponding *.scontroller file (as a default, see Symbol.df_controller), but can be overridden by the creator of the symbol (hopefully it supports the same API to be compatible!). This will handle the rest of information not handled by the model, typically via query methods - stuff like whether this symbol is selected in some way (e.g., by radio setting), settings of the parent map (like range), etc.

Finally, one can use the automatically created canvas element (me.element) to draw the actual symbol. These are usually static images, so it's best to set up lazily-rendered-yet-persistent elements that are simply hidden or shown as needed. Currently, the draw method will just if the required symbols are nil and drawn them, or if they're valid, simply update them (init vs update). This is both handled inside the draw() callback for the time being.


Note  This is currently being prototyped, see the DME.symbol files in the topics/canvas-radar branch for examples.

To support LOD-handling and symbol-specific styling, we will also need to expose a handful of methods to encourage separation of style-specific things into symbol files:

  • setColor()
  • setColorFill()
  • setLineWidth()
  • setScale()
  • setSize()
  • setFont()
  • setFontSize()
  • setText()
  • setImage()

Currently, we have a bunch of symbol files calling directly the corresponding canvas equivalents via method chaining - however, to allow LOD-handling and styling, we must encourage separating these things, so that things can be customized as needed, i.e. by calling the symbol method after the draw/update routine has finished-which will allow aircraft developers to easily use custom fonts/size/colors or symbols - without having to edit -or worse- duplicate the .symbol-file itself

Controllers

MapStructure-Controller-UML.png

With "models" (=what is to be drawn) and "views" (=how it is to be drawn) being typically generic -and thus- shared, controllers are meant to handle all the specific implementation details and behaviors of the corresponding object itself (Symbol, SymbolLayer, Canvas Map, etc.). The idea being that people will normally only need to parametrize an existing controller, or at worst, copy and customize an existing controller file to be able to use existing layers.

For most of the API, the .new() should be optional and return nil, but if resource management is required (listeners or timers), set up listeners/timers during .new(), store them in a member list, and remove them in .del() using removelistener().

Timers should be added using the maketimer API.

All model objects or canvas map objects should be passed as the first argument to controllers' methods (FIXME: need to make sure this holds, see comment: [8]).

Symbol Controller

Note  The draw.init and draw.update steps are not currently separated in MapStructure/FG3.0 - these are implemented via conditionals in the draw() method. However, separating the underlying logic would make sense, because it would simplify adopting the caching scheme (better frame rate). So, expect this to be changed in FG 3.2, i.e. separation of init/update when drawing. In addition, it would make sense to also extend controllers to be the foundation for LOD-handling and symbol-styling (font,size,color etc), so this would be another likely future change.

MapStructure-SymbolController-UML.png

This is very simple: default symbol controllers can just be wrappers for the corresponding SymbolLayer controller, e.g. [9].

There's a simple API, which currently just passes data on to the layer controller.

In general, you will want to make sure that your classes implement the geo.Coord interface (easy to do by inheriting from geo.coord) - otherwise, you will typically need to add special code to handle custom classes, to extract the required values, such as latitude-deg and longitude-deg.

For this, you will want to refer to the getpos method in Symbol.Controller (see MapStructure.nas). This is also where Nasal ghosts are handled (see for example: positioned/Navaid or Fix).

Another example, [10], uses the controller as a wrapper for the model, but this should really be moved to the model object itself.

FIXME: we need wrapper objects for positioned, so that a class can handle higher-level operations (e.g. like .isActive()). Something like collections.UserDict in Python.

Yet another use would be to have the controller manage listeners for updating its symbol, like the Layer Controller does for the whole layer. This would be useful for, e.g. keeping track of if a certain symbol changes place, or such. Make sure to implement .new() and .del() functions!

FIXME: I don't think that updating a single symbol can be handled currently.

searchCmd: Filtering

The searchCmd() method is responsible for populating a vector with results that are to be drawn by the draw() method in the *.symbol file. This serves as the back-end for a positionedSearch using a delta of the current result, compared to the previous result. This is done to enable a layer to tell how many results are new/missing, to selectively update those - rather than having to always delete all previous results and add all new ones.

Whenever a new element (object) is added to the vector, the .onAdded() method will be invoked to add a new symbol to list of managed symbols - once an element is removed, the .onRemoved() method will be called to call the symbol's destructor.

Some of the more straightforward examples are to be found in the implementation of navaid layers:

  • NDB
  • VOR
  • DME
  • FIX

Here, searchCmd is typically just a one-liner calling an existing positionedSearch API, such as findNavaidsWithinRange(), which always returns a vector of positioned ghosts (which are automatically supported by MapStructure).


The only thing that's typically done there is to get the query range (1st argument of the API) via a delegate-callback out of the layer controller, so that navaid range can be easily provided by the MapStructure front-end - such as a GUI dialog or a cockpit display like the ND.

A more sophisticated example is to be found in the traffic layer (TFC) which handles AI/MP traffic and processes the corresponding properties. This is also where you can see a bunch of helper functions used to "filter" results, e.g. based on range. So you could add other filtering heuristics there.

For other more involved examples, see the implementation of the ROUTE (RTE) and WAYPOINT (WPT) layers.

SymbolLayer Controller

For now, VOR.lcontroller simply handles the individual controller operations on a per-layer scale, e.g. looking up if the VOR is a selected frequency via a list of current frequencies copied from property tree.

This is also responsible for the searchCmd (mandatory!), which handles searching for the model objects used by symbols, and returns a current list of models. The models are compared to the previous list, and models are added/removed to match the new list. If custom equality comparison is needed (i.e. outside of id(a) == id(b)), set layer.searcher._equals(a,b) to an appropriate function during construction.


TFC (TCAS) is a bit problematic to test. Assuming that the map's range is set to ~ 50 nm, we could simply use the .apply_course_distance() method in geo.nas to create 10 objects in a range of 20 nm with a 36 degree spacing using something along the lines of.

var searchCmdTest = func {
var mainPos = geo.aircraft_position();
var results = [];

for(var course=0; course<360;course+=36) {
 var newPos = mainPos
 # TODO: add offset altitude here, i.e. +/-500 ft
 newPos.apply_course_distance(course, 25*NM2M);
 append(results,newPos);
 }

return results;
}

This should result in an evenly-spaced circle of objects around the aircraft, no matter where you're going.

To make this a bit more fancy, we could just use TrafficModel objects instead of plain geo.Coord instances here

Thinking about it, we could make such a testing facility a part of MapStructure itself, such as that lcontrollers need to provide a "testSearch" implementation, so that MapStructure developers do not need to be familiar with each and every FG system...

Canvas Map Controller

WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.

This has the most interesting jobs: it manages how the whole map is positioned and re-rendered. Example: [11]. It will have to manage it's own updating routines, i.e. keeping track of timers/listeners that are hooked to update it. For example, like in the above, one can make a timer object which calls an "update_pos" method, which will reposition the map (via me.map.setPos) and call update on all layers if necessary (via me.map.update()), which will often call positioned searches and thus should be spaced out, e.g. ~4 or more seconds apart. Obviously one should also check other conditions other than time, like difference in position since last query.

Some things to keep in mind when working on Map Controllers:

  • we may also want to support optional mouse-panning (see airport-select dialog)
  • we may also want to support optional tooltips for layer elements

Styling & LOD Handling

see DME layer for now (just a crude prototype for now, without any kind of API-level support - still need to introduce some helper classes here) Sooner or later, we hope to implement something along these lines:

MapStructure Interfaces to support caching, LOD and styling (WIP)