Canvas Map API: Difference between revisions

From FlightGear wiki
Jump to navigation Jump to search
m (→‎Adding support for new Layer Types: removing stuff that should no longer be documented/used due to MapStructure)
Line 197: Line 197:
map.cleanup_listeners();  
map.cleanup_listeners();  
</syntaxhighlight>
</syntaxhighlight>
== How the whole thing works ==
This is targeted at people who are interested in helping improve the whole thing, so that they get a better understanding of how the whole thing hangs together at the moment.
For the time being, the implementation is specific to GUI dialogs - in that it will largely simplify (and even automate) the creation of GUI dialogs with an embedded canvas "map". This is accomplished using a handful of fixed assumptions, most of which will need to be addressed in order to support additional dialogs, and eventually, also non-GUI, i.e. instrument-use. Namely, that means:
* that people can simply instantiate a "GenericMap" by including an existing XML file
* dialog-specific settings can be overridden and customized (see above, or [[$FG_ROOT]]/gui/dialogs/airports.xml) - i.e. just copied/pasted
* the GenericMap is a LayeredMap where layers are created for different features and linked to toggle properties
* there are a handful of variables/functions that need to be declared in the body of the dialog's Nasal/open block
* in addition, 1-2 function calls need to be made during initialization, in the Nasal/OPEN section
* these are later on used by the system to instantiate all requested layers dynamically
* first of all, this is the "setupGUI" function, which will create the requested layers, and add GUI checkboxes and zoom buttons to the dialog automatically


== Also see ==
== Also see ==
* http://forum.flightgear.org/viewtopic.php?f=71&t=21139
* http://forum.flightgear.org/viewtopic.php?f=71&t=21139

Revision as of 18:44, 26 June 2014

IMPORTANT: Some, and possibly most, of the features/ideas discussed here are likely to be affected, and possibly even deprecated, by the ongoing work on providing a property tree-based 2D drawing API accessible from Nasal using the new Canvas system available since FlightGear 2.80 (08/2012). Please see: MapStructure for further information

You are advised not to start working on anything directly related to this without first discussing/coordinating your ideas with other FlightGear contributors using the FlightGear developers mailing list or the Canvas subforum This is a link to the FlightGear forum.. Anything related to Canvas Core Development should be discussed first of all with TheTom and Zakalawe. Nasal-space frameworks are being maintained by Philosopher and Hooray currently. talk page.

Canvasready.png


Background

Update 11/2013: please see MapStructure

As of FlightGear 2.9, the base package contains additional Canvas helpers to help with the creation of navigational displays (map dialogs or map instruments). These are currently under development and will change rapidly during the next weeks and months, so that we can come up with a generic and re-usable design for different dialogs/instruments.

Objective

Heavily reduce the amount of specialized Canvas/Nasal code in XML dialogs and instruments related to mapping/charting (airport-selection, map widget, navdisplay etc) and ensure that a maximum degree of code sharing is accomplished, regardless of the canvas placement mode that is used - bottom line being, it really shouldn't matter if a map is shown as part of an airport selection dialog, as part of the moving map dialog, as part of the navdisplay etc.

Open Issues

  • Supporting multi-symbol instancing at the core canvas/C++ level, i.e. using "pointers" to point to another group, instead of replicating it over and over again
  • re-implementing object "marking" (runways/parking) in the airports dialog
  • Refining the model API
  • Using the system for different dialogs & instruments
  • generalizing the currently required dialog "prologue" in the Nasal/open block (using caller() and closure())
  • supporting/addressing multiple canvases per dialog
  • support all major canvas placement modes (CanvasWidget, CanvasWindow, Instrument, Scenery?)
  • implementing a real MVC controller and porting the code to use it
  • improving the MVC separation further
  • resource cleanup (listeners & timers)
  • start using the canvas-generic-map dialog in more dialogs (route manager, map dialog)
  • port more dialogs
  • support use in instruments
  • add a stress test with multiple canvas maps per dialog, all showing different maps/locations, and being active

Design

The design follows the basic MVC (Model/View/Controller) principle, i.e.:

  • the Model contains the data to be shown
  • the View contains the Layer (Canvas group)
  • the Controller contains the interface to update the model and the view (using timers and/or listeners)


The basic idea is such that each layer is linked to a "control" property to easily toggle its visibility (this can be a GUI property for a checkbox or a cockpit hotspot), drawables (symbols) can be put in different layers and easily toggled on/off.

Supported Layers

As of 10/2012, the following "layers" are supported (not yet using MapStructure:

  • runways
  • taxiways
  • parking
  • tower
  • navaids


At the moment (11/2013), we are working on additional layers - in order to port the Map and Navigation display display to Canvas. This will probably require support for:

  • Routing (waypoints) 80}% completed (by Gijs)
  • Fixes 80}% completed (by Gijs)
  • multiplayer traffic 80}% completed (by Gijs)
  • AI traffic 80}% completed (by Gijs)

See the forum for details: http://forum.flightgear.org/viewtopic.php?f=71&t=21139

ToDo

Originally, the idea was that we want to avoid the ongoing degree of copy/paste programming that could be seen in the airport selection dialog - next, the 744 ND duplicates lots of code used in the airport dialog, but also in the underlying framework - all without using inheritance or delegates/predicates to customize things. So over time, we would end up with tons of custom code that is highly specific to each aircraft/display and GUI dialog.

From a low-level point of view, it shouldn't matter to the draw routines if they're called by an aircraft instrument or by a GUI dialog. However, once they contain use-case specific logic, such as getprop() calls and other conditionals, things do get messy pretty quickly. Thus, what I have been doing is simply copying things to separate *.draw files for different scenarios, i.e. I now ended up with airport.draw and airports-nd.draw for example - that isn't all that elegant, but at least it solves the problem of having to implement full models and controllers for each scenario (well for now), because all the embedded logic can stay, and only needs to be refactored later on.

Thus, the idea is basically this:

  • .draw files contain the low-level logic to draw arbitrary symbols (runway, waypoint, taxiways, airports, traffic) - without knowing anything about the frontend/user - so that such details needs to be separately encapsulated. The *.model files merely determine what is to be drawn, data-wise, as in NasalPositioned queries and their results - all stored in a single Nasal vector. The layer files turn everything into a dedicated canvas group that can be separately toggled/redrawn/updated - the currently missing bits are controllers that tie everything together, so that each frontend (instrument, MFD, GUI dialog, widget) can separately specify and customize behavior by registering their own callbacks.

Most of this is already working here (with very minor regressions so far that Gijs won't be too happy about ...), but otherwise I managed to turn Gijs code into layers/models that can be directly used by the old framework, i.e. I can now add a handful of new ND layers (fixes, vor, MP or AI traffic, route etc) to the airport selection dialog (or any other dialog actually), and things do show up properly.

What's still missing is the controller part, and styling for different aircraft/GUI purposes - also, Gijs' ND routines are currently used directly, without any LOD applied - but I think that can be easily solved by using the canvas's scale() method.

Just to clarify: stuff like getprop("instrumentation/nav[0]/frequencies/selected-mhz") doesn't belong into the visualization part of the code (i.e. the draw/view stuff) - simply because that's the sort of stuff that would "break" once we're using a different use-case, e.g. the map dialog - which is such conditions would need to be either specified in some form of condition hash, as part of the draw* signature, or via a dedicated Drawable class that's able to evaluate such conditions.

To be honest, that was the whole point of moving stuff to different *.layer and *.model files - so that these *.draw files can be shared, without requiring any modifications - your current example would still require using differrent *.draw files for each purpose.

  • Implementing proper LOD support for *.draw callbacks (i.e. using canvas/scaling)
  • Implementing support for different styles
  • Supporting multiple NDs per aircraft
  • Supporting multiple maps per dialog
  • Stress Test: render a dialog with 10 NDs, for 10 different MP/AI aircraft
  • clean up map.nas, get rid of stubs and old code
  • get rid of the PUI helpers in map.nas, and use real canvas dialogs instead
  • Optimize (Nasal/Canvas and C++ space)
  • come up with a framework for MFDs
  • use the MFD framework for creating a stylable ND class

Full Example: Creating dialogs with embedded Canvas Maps

This article or section contains out-of-date information

Please help improve this article by updating it. There may be additional information on the talk page.

At the moment, the system (and its design) is still evolving - so nothing is set in stone, and most things written here should be considered a "draft". This is also why the system makes currently some assumptions and requires certain variables/functions to be specified in the dialog XML.

(For the latest information, you'll want to refer to $FG_ROOT/gui/dialogs/airports.xml as the "de facto" example of how to use the system)

Add this to the "Nasal/open" tag of your XML dialog and customize it for your dialog:

 ## "prologue" currently required by the canvas-generic-map 
       var dialog_name ="airports"; #TODO: use substr() and cmdarg() to get this dynamically
       var dialog_property = func(p) return "/sim/gui/dialogs/airports/"~p; #TODO: generalize using cmdarg      
       var DIALOG_CANVAS = gui.findElementByName(cmdarg(), "airport-selection");
       canvas.GenericMap.setupGUI(DIALOG_CANVAS, "canvas-control"); #TODO: this is not a method!
      ## end of canvas-generic-map prologue

Note the string "airport-selection" which should match the name of of the subsequent canvas section.

Add this to a group in the dialog where you want the Canvas to appear

  <!-- Instantiate a generic canvas map and parametrize it via inclusion -->
      <!-- TODO: use params and aliasing -->
      <canvas include="/Nasal/canvas/generic-canvas-map.xml">

	<name>airport-selection</name>
        <valign>fill</valign>
        <halign>fill</halign>
        <stretch>true</stretch>
        <pref-width>600</pref-width>
        <pref-height>400</pref-height>
        <view n="0">600</view>
        <view n="1">400</view>


       <features>
	<!-- TODO: use params and aliases to make this shorter -->
	<!-- TODO: support styling, i.e. image sets/fonts and colors to be used -->
	<!-- this will set up individual "layers" and map them to boolean "toggle" properties -->
	<!-- providing an optional "description" tag here allows us to create all checkboxes procedurally -->
	<dialog-root>/sim/gui/dialogs/airports</dialog-root>
	<range-property>zoom</range-property>

	<!-- These are the ranges available for the map:       var ranges = [0.1, 0.25, 0.5, 1, 2.5, 5] -->

	<ranges>
		<range>0.1</range>
		<range>0.25</range>
		<range>0.5</range>
		<range>1</range>
		<range>2.5</range>
		<range>5</range>
	</ranges>

	<!-- available layers and their toggle property (appended to dialog-root specified above) -->

 	<layer>
                <name>airport_test</name>
                <init-property>selected-airport/id</init-property>      <!-- the init/input property that re-inits the layer -->
                <property>display-test</property>                    	<!-- property switch to toggle the layer on/off -->
                <description>Show TestLayer</description>               <!-- GUI label for the checkbox -->
                <default>disabled</default>                              <!-- default checkbox/layer state -->
                <hide-checkbox>true</hide-checkbox>                     <!-- for default layers so that the checkbox is hidden -->
        </layer>

	<layer>
		<name>runways</name>
		<init-property>selected-airport/id</init-property>	
		<property>display-runways</property>			
		<description>Show Runways</description> 		
		<default>enabled</default>				
		<hide-checkbox>true</hide-checkbox>  			
	</layer>
 	<layer>
                <name>taxiways</name>
		<init-property>selected-airport/id</init-property>
                <property>display-taxiways</property>
		<description>Show Taxiways</description>
		<default>disabled</default>
        </layer>

 	<layer>
                <name>parkings</name>
		<init-property>selected-airport/id</init-property>
                <property>display-parking</property>
		<description>Show Parking</description>
		<default>disabled</default>
        </layer>

 	<layer>
                <name>towers</name>
		<init-property>selected-airport/id</init-property>
                <property>display-tower</property>
		<description>Show Tower</description>
		<default>enabled</default>
        </layer>
<!--
 	<layer>
                <name>navaid_test</name>
		<init-property>selected-airport/id</init-property>
                <property>display-navaids</property>
		<default>disabled</default>
        </layer>
-->	

       </features>
      </canvas>

Add this to the "Nasal/close" tag to clean up all resources automatically:

map.cleanup_listeners();

Also see