593 lines
20 KiB
Text
593 lines
20 KiB
Text
###
|
|
# map.nas - provide a high level method to create typical maps in FlightGear (airports, navaids, fixes and waypoints) for both, the GUI and instruments
|
|
# implements the notion of a "layer" by using canvas groups and adding geo-referenced elements to a layer
|
|
# layered maps are linked to boolean properties so that visibility can be easily toggled (GUI checkboxes or cockpit hotspots)
|
|
# without having to redraw other layers
|
|
#
|
|
# GOALS: have a single Nasal/Canvas wrapper for all sort of maps in FlightGear, that can be easily shared and reused for different purposes
|
|
#
|
|
# DESIGN: ... is slowly evolving, but still very much beta for the time being
|
|
#
|
|
# API: not yet documented, but see eventually design.txt (will need to add doxygen-strings then)
|
|
#
|
|
# PERFORMANCE: will be improved, probabaly by moving some features to C++ space and optimizing things there
|
|
#
|
|
#
|
|
# ISSUES: just look for the FIXME and TODO strings - currently, the priority is to create an OOP/MVC design with less specialized code in XML files
|
|
#
|
|
#
|
|
# ROADMAP: Generalize this further, so that:
|
|
#
|
|
# - it can be easily reused
|
|
# - use a MVC approach, where layer-specific data is provided by a Model object
|
|
# - other dialogs can use this without tons of custom code (airports.xml, route-manager.xml, map-canvas.xml)
|
|
# - generalize this further so that it can be used by instruments
|
|
# - implement additional layers (tcas, wxradar, agradar) - especially expose the required data to Nasal
|
|
# - implement better GUI support (events) so that zooming/panning via mouse can be supported
|
|
# - make the whole thing styleable
|
|
#
|
|
# - keep track of things getting added here and decide if they should better move to the core canvas module or the C++ code
|
|
#
|
|
#
|
|
# C++ RFEs:
|
|
# - overload findNavaidsWithinRange() to support an optional position argument, so that arbitrary navaids can be looked up
|
|
# - add Nasal extension function to get scenery vector data (landclass)
|
|
# -
|
|
# -
|
|
#
|
|
|
|
|
|
#FIXME: this is a hack so that dialogs can register their own
|
|
# callbacks that are automatically invoked at the end of the
|
|
# generic-canvas-map.xml file (canvas/nasal section)
|
|
var callbacks = [];
|
|
var register_callback = func(c) append(callbacks, c);
|
|
var run_callbacks = func foreach(var c; callbacks) c();
|
|
|
|
var DEBUG=0;
|
|
if (DEBUG) {
|
|
var benchmark = debug.benchmark;
|
|
|
|
}
|
|
|
|
else {
|
|
var benchmark = func(label, code) code(); # NOP
|
|
}
|
|
|
|
var assert = func(label, expr) expr and die(label);
|
|
|
|
# Mapping from surface codes to #TODO: make this XML-configurable
|
|
var SURFACECOLORS = {
|
|
1 : { type: "asphalt", r:0.2, g:0.2, b:0.2 },
|
|
2 : { type: "concrete", r:0.3, g:0.3, b:0.3 },
|
|
3 : { type: "turf", r:0.2, g:0.5, b:0.2 },
|
|
4 : { type: "dirt", r:0.4, g:0.3, b:0.3 },
|
|
5 : { type: "gravel", r:0.35, g:0.3, b:0.3 },
|
|
# Helipads
|
|
6 : { type: "asphalt", r:0.2, g:0.2, b:0.2 },
|
|
7 : { type: "concrete", r:0.3, g:0.3, b:0.3 },
|
|
8 : { type: "turf", r:0.2, g:0.5, b:0.2 },
|
|
9 : { type: "dirt", r:0.4, g:0.3, b:0.3 },
|
|
0 : { type: "gravel", r:0.35, g:0.3, b:0.3 },
|
|
};
|
|
|
|
|
|
###
|
|
# ALL LayeredMap "draws" go through this wrapper, which makes it easy to check what's going on:
|
|
var draw_layer = func(layer, callback, lod) {
|
|
var name= layer._view.get("id");
|
|
# print("Canvas:Draw op triggered"); # just to make sure that we are not adding unnecessary data when checking/unchecking a checkbox
|
|
if (DEBUG and name=="taxiways") fgcommand("profiler-start"); #without my patch, this is a no op, so no need to disable
|
|
#print("Work items:", size(layer._model._elements));
|
|
benchmark("Drawing Layer:"~layer._view.get("id"), func
|
|
foreach(var element; layer._model._elements) {
|
|
#print(typeof(layer._view));
|
|
#debug.dump(layer._view);
|
|
callback(layer._view, element, lod); # ISSUE here
|
|
});
|
|
if (! layer._model.hasData() ) print("Layer was EMPTY:", name);
|
|
if (DEBUG and name=="taxiways") fgcommand("profiler-stop");
|
|
layer._drawn=1; #TODO: this should be encapsulated
|
|
}
|
|
|
|
# Runway
|
|
#
|
|
var Runway = {
|
|
# Create Runway from hash
|
|
#
|
|
# @param rwy Hash containing runway data as returned from
|
|
# airportinfo().runways[ <runway designator> ]
|
|
new: func(rwy)
|
|
{
|
|
return {
|
|
parents: [Runway],
|
|
rwy: rwy
|
|
};
|
|
},
|
|
# Get a point on the runway with the given offset
|
|
#
|
|
# @param pos Position along the center line
|
|
# @param off Offset perpendicular to the center line
|
|
pointOffCenterline: func(pos, off = 0)
|
|
{
|
|
var coord = geo.Coord.new();
|
|
coord.set_latlon(me.rwy.lat, me.rwy.lon);
|
|
coord.apply_course_distance(me.rwy.heading, pos);
|
|
|
|
if( off )
|
|
coord.apply_course_distance(me.rwy.heading + 90, off);
|
|
|
|
return ["N" ~ coord.lat(), "E" ~ coord.lon()];
|
|
}
|
|
};
|
|
|
|
var make = func return {parents:arg};
|
|
|
|
##
|
|
# TODO: Create a cache to reuse layers and layer data (i.e. runways)
|
|
|
|
|
|
##
|
|
# Todo: wrap parsesvg and return a function that memoizes the created canvas group, so that svg files only need to be parsed once
|
|
#
|
|
|
|
##
|
|
# TODO: Implement a real MVC design for "LayeredMaps" that have:
|
|
# - a "DataProvider" (i.e. Positioned objects)
|
|
# - a View (i.e. a Canvas)
|
|
# - a controller (i.e. input/output properties)
|
|
#
|
|
var MapModel = {}; # navaids, waypoints, fixes etc
|
|
MapModel.new = func make(MapModel);
|
|
|
|
var MapView = {}; # the canvas view, including a layer for each feature
|
|
MapView.new = func make(MapView);
|
|
|
|
var MapController = {}; # the property tree interface to manipulate the model/view via properties
|
|
MapController.new = func make(MapController);
|
|
|
|
var LazyView = {}; # Gets drawables on demand from the model - via property toggle
|
|
|
|
var DataProvider = {};
|
|
DataProvider.new = func make(DataProvider);
|
|
|
|
###
|
|
# for airports, navaids, fixes, waypoints etc
|
|
var PositionedProvider = {};
|
|
PositionedProvider.new = func make(DataProvider, PositionedProvider);
|
|
|
|
##
|
|
# Drawable
|
|
#
|
|
|
|
## LayerElement (UNUSED ATM):
|
|
# for runways, navaids, fixes, waypoints etc
|
|
# TODO: we should differentiate between "fairly static" vs. "dynamic" layers - i.e. navaids vs. traffic
|
|
var LayerElement = {_drawable:nil};
|
|
LayerElement.new = func(drawable) {
|
|
var temp = make(LayerElement);
|
|
temp._drawable=drawable;
|
|
return temp;
|
|
}
|
|
# a drawable is either a Nasal callback or a scalar, i.e. a path to an SVG file
|
|
LayerElement.draw = func(group) {
|
|
(typeof(me._drawable)=='func') and drawable(group) or canvas.parsesvg(group,_drawable);
|
|
}
|
|
|
|
# For static targets like Navaids, Fixes - i.e. geographic position doesn't change
|
|
var StaticLayerElement = {};
|
|
|
|
# For moving targets such as aircraft, multiplayer, ai traffic etc
|
|
var DynamicLayerElement = {};
|
|
|
|
var AnimatedLayerElement = {};
|
|
|
|
# for elements whose appearance may change depending on selected range (i.e. LOD)
|
|
var RangeAwareLayerElement = {};
|
|
|
|
|
|
##
|
|
# A layer model is just a wrapper for a vector with elements
|
|
# either updated via a timer or via a listener
|
|
|
|
var LayerModel = {_elements:[], _view:, _controller: };
|
|
LayerModel.new = func make(LayerModel);
|
|
LayerModel.clear = func me._elements = [];
|
|
LayerModel.push = func (e) append(me._elements, e);
|
|
LayerModel.get = func me._elements;
|
|
LayerModel.update = func;
|
|
LayerModel.hasData = func size(me. _elements);
|
|
LayerModel.setView = func(v) me._view=v;
|
|
LayerModel.setController = func(c) me._controller=c;
|
|
|
|
|
|
var LayerController = {};
|
|
LayerController.new = func make(LayerController);
|
|
|
|
##
|
|
# use timers to update the model/view (canvas)
|
|
var TimeBasedLayerController = {};
|
|
LayerController.new = func make(TimeBasedLayerController);
|
|
|
|
##
|
|
# use listeners to update the model/view (canvas)
|
|
#
|
|
var ListenerBasedLayerController = {};
|
|
ListenerBasedLayerController.new = func make(ListenerBasedLayerController);
|
|
|
|
|
|
##
|
|
# Uses, both, listeners and timers to update the model/view (canvas)
|
|
#
|
|
|
|
var HybridLayerController = {};
|
|
HybridLayerController.new = func make(HybridLayerController);
|
|
|
|
var ModelEvents = {INIT:, RESET:, UPDATE:};
|
|
var ViewEvents = {INIT:, RESET:, UPDATE:};
|
|
var ControllerEvents = {INIT:, RESET: , UPDATE:, ZOOM:, PAN:, };
|
|
|
|
|
|
##
|
|
# A layer is mapped to a canvas group
|
|
# Layers are linked to a single boolean property to toggle them on/off
|
|
var Layer = { _model: ,
|
|
_view: ,
|
|
_controller: ,
|
|
_drawn:0,
|
|
};
|
|
|
|
Layer.new = func(group, name, model) {
|
|
#print("Setting up new Layer:", name);
|
|
var m = make(Layer);
|
|
m._model = model.new();
|
|
#print("Model name is:", m._model.name);
|
|
m._view = group.createChild("group",name);
|
|
m.name = name; #FIXME: not needed, there's already _view.get("id")
|
|
return m;
|
|
}
|
|
|
|
Layer.hide = func me._view.setVisible(0);
|
|
Layer.show = func me._view.setVisible(1);
|
|
#TODO: Unify toggle and update methods - and support lazy drawing (make it optional!)
|
|
Layer.toggle = func {
|
|
# print("Toggling layer");
|
|
var checkbox = getprop(me.display_layer);
|
|
if(checkbox and !me._drawn) {
|
|
# print("Lazy drawing");
|
|
me.draw();
|
|
}
|
|
|
|
#var state= me._view.getBool("visible");
|
|
#print("Toggle layer visibility ",me.display_layer," checkbox is", checkbox);
|
|
#print("Layer id is:", me._view.get("id"));
|
|
#print("Drawn is:", me._drawn);
|
|
checkbox?me._view.setVisible(1) : me._view.setVisible(0);
|
|
}
|
|
Layer.reset = func {
|
|
me._view.removeAllChildren(); # clear the "real" canvas drawables
|
|
me._model.clear(); # the vector is used for lazy rendering
|
|
assert("Model not emptied during layer reset!", me._model.hasData() );
|
|
me._drawn = 0;
|
|
}
|
|
#TODO: Unify toggle and update
|
|
Layer.update = func {
|
|
# print("Layer update: Check if layer is visible, if so, draw");
|
|
if (! getprop(me.display_layer)) return; # checkbox for layer not set
|
|
if (!me._model.hasData() ) return; # no data available
|
|
# print("Trying to draw");
|
|
me.draw();
|
|
}
|
|
|
|
Layer.setDraw = func(callback) me.draw = callback;
|
|
Layer.setController = func(c) me._controller=c; # TODO: implement
|
|
Layer.setModel = func(m) nil; # TODO: implement
|
|
|
|
|
|
##TODO: differentiate between layers with a single object (i.e. aircraft) and multiple objects (airports)
|
|
|
|
##
|
|
# We may need to display some stuff that isn't strictly a geopgraphic feature, but just a chart feature
|
|
#
|
|
var CartographicLayer = {};
|
|
|
|
#TODO:
|
|
var InteractiveLayer = {};
|
|
|
|
###
|
|
# PositionedLayer
|
|
#
|
|
# layer of positioned objects (i.e. have lat,lon,alt)
|
|
#
|
|
var PositionedLayer = {};
|
|
PositionedLayer.new = func() {
|
|
make( Layer.new() , PositionedLayer );
|
|
}
|
|
|
|
|
|
###
|
|
# CachedLayer
|
|
#
|
|
# when re-centering on an airport already loaded, we don't want to reload it
|
|
# but change the reference point and load missing airports
|
|
|
|
var CachedLayer = {};
|
|
|
|
##
|
|
#
|
|
var AirportProvider = {};
|
|
AirportProvider.new = func make(AirportProvider);
|
|
AirportProvider.get = func {
|
|
return airportinfo("ksfo");
|
|
}
|
|
|
|
### Data Providers (preparation for MVC version):
|
|
# TODO: should use the LayerModel class
|
|
#
|
|
|
|
##
|
|
# Manage a bunch of layers
|
|
#
|
|
|
|
var LayerManager = {};
|
|
|
|
# WXR ?
|
|
|
|
# TODO: Stub
|
|
var MapBehavior = {};
|
|
MapBehavior.new = make(MapBehavior);
|
|
MapBehavior.zoom = func;
|
|
MapBehavior.center = func;
|
|
|
|
##
|
|
# A layered map consists of several layers
|
|
# TODO: Support nested LayeredMaps, where a LayeredMap may contain other LayeredMaps
|
|
# TODO: use MapBehavior here and move the zoom/refpos methods there, so that map behavior can be easily customized
|
|
var LayeredMap = { ranges:[],
|
|
zoom_property:nil, listeners:[],
|
|
update_property:nil, layers:[],
|
|
};
|
|
LayeredMap.new = func(parent, name)
|
|
return make(LayeredMap, parent.createChild("map",name) );
|
|
|
|
LayeredMap.listen = func(p,c) { #FIXME: listening should be managed by each m/v/c separately
|
|
# print("Setting up LayeredMap-managed listener:", p);
|
|
append(me.listeners, setlistener(p, c));
|
|
}
|
|
|
|
LayeredMap.initializeLayers = func {
|
|
# print("initializing all layers and updating");
|
|
foreach(var l; me.layers)
|
|
l.update();
|
|
}
|
|
|
|
LayeredMap.setRefPos = func(lat, lon) {
|
|
# print("RefPos set");
|
|
me._node.getNode("ref-lat", 1).setDoubleValue(lat);
|
|
me._node.getNode("ref-lon", 1).setDoubleValue(lon);
|
|
me; # chainable
|
|
}
|
|
LayeredMap.setHdg = func(hdg) {
|
|
me._node.getNode("hdg",1).setDoubleValue(hdg);
|
|
me; # chainable
|
|
}
|
|
|
|
LayeredMap.updateZoom = func {
|
|
var z = me.zoom_property.getValue() or 0;
|
|
z = math.max(0, math.min(z, size(me.ranges) - 1));
|
|
me.zoom_property.setIntValue(z);
|
|
var zoom = me.ranges[size(me.ranges) - 1 - z];
|
|
# print("Setting zoom range to:", zoom);
|
|
benchmark("Zooming map:"~zoom, func
|
|
{
|
|
me._node.getNode("range", 1).setDoubleValue(zoom);
|
|
# TODO update center/limit translation to keep airport always visible
|
|
});
|
|
me; #chainable
|
|
}
|
|
|
|
# this is a huge hack at the moment, we need to encapsulate the setRefPos/setHdg methods, so that they are exposed to XML space
|
|
#
|
|
LayeredMap.updateState = func {
|
|
# center map on airport TODO: should be moved to a method and wrapped with a controller so that behavior can be customizeda
|
|
#var apt = me.layers[0]._model._elements[0];
|
|
# FIXME:
|
|
#me.setRefPos(lat:me._refpos.lat, lon:me._refpos.lon);
|
|
|
|
me.setHdg(0.0);
|
|
me.updateZoom();
|
|
}
|
|
|
|
#
|
|
# TODO: this is currently GUI specific and not re-usable for instruments
|
|
LayeredMap.setupZoom = func(dialog) {
|
|
var dlgroot = dialog.getNode("features/dialog-root").getValue();#FIXME: GUI specific - needs to be re-implemented for instruments
|
|
me.zoom_property = props.globals.getNode(dlgroot ~"/"~dialog.getNode("features/range-property").getValue(), 1); #FIXME: this doesn't belong here, need to be in ctor instead !!!
|
|
ranges=dialog.getNode("features/ranges").getChildren("range");
|
|
if( size(me.ranges) == 0 )
|
|
# TODO check why this gets called everytime the dialog is opened
|
|
foreach(var r; ranges)
|
|
append(me.ranges, r.getValue() );
|
|
|
|
# print("Setting up Zoom Ranges:", size(ranges)-1);
|
|
me.listen(me.zoom_property, func me.updateZoom() );
|
|
me.updateZoom();
|
|
me; #chainable
|
|
}
|
|
LayeredMap.setZoom = func {} #TODO
|
|
|
|
LayeredMap.resetLayers = func {
|
|
|
|
benchmark("Resetting LayeredMap", func
|
|
foreach(var l; me.layers) { #TODO: hide all layers, hide map
|
|
l.reset();
|
|
}
|
|
);
|
|
|
|
|
|
}
|
|
|
|
#FIXME: listener management should be done at the MVC level, for each component - not as part of the LayeredMap!
|
|
LayeredMap.cleanup_listeners = func {
|
|
# print("Cleaning up listeners");
|
|
foreach(var l; me.listeners)
|
|
removelistener(l);
|
|
# TODO check why me.listeners = []; doesn't work. Maybe this is a Nasal bug
|
|
# and the old vector is somehow used again.
|
|
setsize(me.listeners, 0);
|
|
}
|
|
|
|
###
|
|
# GenericMap: A generic map is a layered map that puts all supported features on a different layer (canvas group) so that
|
|
# they can be individually toggled on/off so that unnecessary updates are avoided, there are methods to link layers to boolean properties
|
|
# so that they can be easily associated with GUI properties (checkboxes) or cockpit hotspots
|
|
# TODO: generalize the XML-parametrization and move it to a helper class
|
|
|
|
var GenericMap = { };
|
|
GenericMap.new = func(parent, name) make(LayeredMap.new(parent:parent, name:name), GenericMap);
|
|
|
|
GenericMap.setupLayer = func(layer, property) {
|
|
var l = MAP_LAYERS[layer].new(me, layer); # Layer.new(me, layer);
|
|
l.display_layer = property; #FIXME: use controller object instead here and this overlaps with update_property
|
|
#print("Set up layer with toggle property=", property);
|
|
l._view.setVisible( getprop(property) ) ;
|
|
append(me.layers, l);
|
|
return l;
|
|
}
|
|
|
|
# features are layers - so this will do layer setup and then register listeners for each layer
|
|
GenericMap.setupFeature = func(layer, property, init ) {
|
|
var l=me.setupLayer( layer, property );
|
|
me.listen(property, func l.toggle() ); #TODO: should use the controller object here !
|
|
|
|
l._model._update_property=property; #TODO: move somewhere else - this is the property that is mapped to the CHECKBOX
|
|
l._model._view_handle = l; #FIXME: very crude, set a handle to the view(group), so that the model can notify it (for updates)
|
|
l._model._map_handle = me; #FIXME: added here so that layers can send update requests to the parent map
|
|
#print("Setting up layer init for property:", init);
|
|
|
|
l._model._input_property = init; # FIXME: init property = input property - needs to be improved!
|
|
me.listen(init, func l._model.init() ); #TODO: makes sure that the layer's init method for the MODEL is invoked
|
|
me; #chainable
|
|
};
|
|
|
|
# This will read in the config and procedurally instantiate all requested layers and link them to toggle properties
|
|
# FIXME: this is currently GUI specific and doesn't yet support instrument use, i.e. needs to be generalized further
|
|
GenericMap.pickupFeatures = func(DIALOG_CANVAS) {
|
|
var dlgroot = DIALOG_CANVAS.getNode("features/dialog-root").getValue();
|
|
# print("Picking up features for:", DIALOG_CANVAS.getPath() );
|
|
var layers=DIALOG_CANVAS.getNode("features").getChildren("layer");
|
|
foreach(var n; layers) {
|
|
var name = n.getNode("name").getValue();
|
|
var toggle = n.getNode("property").getValue();
|
|
var init = n.getNode("init-property").getValue();
|
|
init = dlgroot ~"/"~init;
|
|
var property = dlgroot ~"/"~toggle;
|
|
# print("Adding layer:",n.getNode("name").getValue() );
|
|
me.setupFeature(name, property, init);
|
|
}
|
|
me;
|
|
}
|
|
|
|
# NOT a method, cmdarg() is no longer meaningful when the canvas nasal block is executed
|
|
# so this needs to be called in the dialog's OPEN block instead - TODO: generalize
|
|
#FIXME: move somewhere else, this is a GUI helper and should probably be generalized and moved to gui.nas
|
|
GenericMap.setupGUI = func (dialog, group) {
|
|
var group = globals.gui.findElementByName(cmdarg() , group);
|
|
|
|
var layers=dialog.getNode("features").getChildren("layer");
|
|
var template = dialog.getNode("checkbox-toggle-template");
|
|
var dlgroot = dialog.getNode("features/dialog-root").getValue();
|
|
var zoom = dlgroot ~"/"~ dialog.getNode("features/range-property").getValue();
|
|
var i=0;
|
|
foreach(var n; layers) {
|
|
var name = n.getNode("name").getValue();
|
|
var toggle = dlgroot ~ "/" ~ n.getNode("property").getValue();
|
|
var label = n.getNode("description",1).getValue() or name;
|
|
|
|
var default = n.getNode("default",1).getValue();
|
|
default = (default=="enabled")?1:0;
|
|
#print("Layer default for", name ," is:", default);
|
|
setprop(toggle, default); # set the checkbox to its default setting
|
|
|
|
var hide_checkbox = n.getNode("hide-checkbox",1).getValue();
|
|
hide_checkbox = (hide_checkbox=="true")?1:0;
|
|
|
|
var checkbox = group.getChild("checkbox",i, 1); #FIXME: compute proper offset dynamically, will currently overwrite other existing checkboxes!
|
|
|
|
props.copy(template, checkbox);
|
|
checkbox.getNode("name").setValue("display-"~name);
|
|
checkbox.getNode("label").setValue(label);
|
|
checkbox.getNode("property").setValue(toggle);
|
|
checkbox.getNode("binding/object-name").setValue("display-"~name);
|
|
checkbox.getNode("enabled",1).setValue(!hide_checkbox);
|
|
i+=1;
|
|
}
|
|
|
|
#add zoom buttons procedurally:
|
|
var template = dialog.getNode("zoom-template");
|
|
template.getNode("button[0]/binding[0]/property[0]").setValue(zoom);
|
|
template.getNode("text[0]/property[0]").setValue(zoom);
|
|
template.getNode("button[1]/binding[0]/property[0]").setValue(zoom);
|
|
template.getNode("button[1]/binding[0]/max[0]").setValue( i );
|
|
props.copy(template, group);
|
|
}
|
|
|
|
###
|
|
# TODO: StylableGenericMap (colors, fonts, symbols)
|
|
#
|
|
|
|
var AirportMap = {};
|
|
AirportMap.new = func(parent,name) make(GenericMap.new(parent,name), AirportMap);
|
|
#TODO: Use real MVC (DataProvider/PositionedProvider) here
|
|
|
|
|
|
# this is currently "directly" invoked via a listener, needs to be changed
|
|
# to use the controller object instead
|
|
# TODO: adopt real MVC here
|
|
# FIXME: this must currently be explicitly called by the model, we need to use a wrapper to call it automatically instead!
|
|
LayerModel.notifyView = func () {
|
|
# print("View notified");
|
|
me._view_handle.update(); # update the layer/group
|
|
me._map_handle.updateState(); # update the map
|
|
}
|
|
|
|
# ID
|
|
var SingleAirportProvider = {};
|
|
|
|
# inputs: position, range
|
|
var MultiAirportProvider = {};
|
|
|
|
#TODO: remove and unify with update()
|
|
AirportMap.init = func {
|
|
me.resetLayers();
|
|
me.updateState();
|
|
}
|
|
|
|
# MultiObjectLayer:
|
|
# - Airports
|
|
# - Traffic (MP/AI)
|
|
# - Navaids
|
|
#
|
|
|
|
# TODO: a "MapLayer" is a full MVC implementation that is owned by a "LayeredMap"
|
|
|
|
var MAP_LAYERS = {};
|
|
var register_layer = func(name, layer) MAP_LAYERS[name]=layer;
|
|
|
|
var MVC_FOLDER = getprop("/sim/fg-root") ~ "/Nasal/canvas/map/";
|
|
var load_modules = func(vec) foreach(var file; vec) io.load_nasal(MVC_FOLDER~file, "canvas");
|
|
|
|
# TODO: read in the file names dynamically: *.draw, *.model, *.layer
|
|
|
|
var DRAWABLES = ["navaid.draw", "parking.draw", "runways.draw", "taxiways.draw", "tower.draw"];
|
|
load_modules(DRAWABLES);
|
|
|
|
var MODELS = ["airports.model", "navaids.model",];
|
|
load_modules(MODELS);
|
|
|
|
var LAYERS = ["runways.layer", "taxiways.layer", "parking.layer", "tower.layer", "navaids.layer","test.layer",];
|
|
load_modules(LAYERS);
|
|
|
|
#TODO: Implement!
|
|
var CONTROLLERS = [];
|
|
load_modules(CONTROLLERS);
|