37c005c222
See the clone at https://gitorious.org/fg/canvas-hackers-fgdata/source/topics/canvas-radar:
739 lines
25 KiB
Text
739 lines
25 KiB
Text
################################################################################
|
|
## 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 = {};
|
|
foreach (var k; keys(m))
|
|
if (k != "parents")
|
|
h[k] = m[k];
|
|
debug.dump(h);
|
|
};
|
|
|
|
##
|
|
# 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 cached element
|
|
## i.e. keeps the canvas and texture map coordinates for the corresponding raster image
|
|
var CachedElement = {
|
|
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, trans0=0, trans1=0) {
|
|
# create a raster image child in the render target/group
|
|
var n = group.createChild("image", me.name)
|
|
.setFile( me.canvas_src )
|
|
# 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
|
|
|
|
var SymbolCache = {
|
|
# We can draw symbols either with left/top, centered,
|
|
# or right/bottom alignment. Specify two in a vector
|
|
# to mix and match, e.g. left/centered would be
|
|
# [SymbolCache.DRAW_LEFT_TOP,SymbolCache.DRAW_CENTERED]
|
|
DRAW_LEFT_TOP: 0.0,
|
|
DRAW_CENTERED: 0.5,
|
|
DRAW_RIGHT_BOTTOM: 1.0,
|
|
new: func(dim...) {
|
|
var m = { parents:[SymbolCache] };
|
|
# to keep track of the next free caching spot (in px)
|
|
m.next_free = [0, 0];
|
|
# to store each type of symbol
|
|
m.dict = {};
|
|
if (size(dim) == 1 and typeof(dim[0]) == 'vector')
|
|
dim = dim[0];
|
|
# Two sizes: canvas and symbol
|
|
if (size(dim) == 2) {
|
|
var canvas_x = var canvas_y = dim[0];
|
|
var image_x = var image_y = dim[1];
|
|
# Two widths (canvas and symbol) and then height/width ratio
|
|
} else if (size(dim) == 3) {
|
|
var (canvas_x,image_x,ratio) = dim;
|
|
var canvas_y = canvas_x * ratio;
|
|
var image_y = image_x * ratio;
|
|
# Explicit canvas and symbol widths/heights
|
|
} else if (size(dim) == 4) {
|
|
var (canvas_x,canvas_y,image_x,image_y) = dim;
|
|
}
|
|
m.canvas_sz = [canvas_x, canvas_y];
|
|
m.image_sz = [image_x, image_y];
|
|
|
|
# allocate a canvas
|
|
m.canvas_texture = canvas.new( {
|
|
"name": "SymbolCache"~canvas_x~'x'~canvas_y,
|
|
"size": m.canvas_sz,
|
|
"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) {
|
|
if (typeof(draw_mode) == 'scalar')
|
|
var draw_mode0 = var draw_mode1 = draw_mode;
|
|
else var (draw_mode0,draw_mode1) = draw_mode;
|
|
# 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.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
|
|
#debug.dump ( gr.getTransformedBounds() );
|
|
gr.update(); # apparently this doesn't result in sane output from .getTransformedBounds() either
|
|
#debug.dump ( gr.getTransformedBounds() );
|
|
# draw the symbol
|
|
callback(gr);
|
|
# 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+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];
|
|
|
|
# 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);
|
|
return me.dict[name];
|
|
}, # get()
|
|
};
|
|
|
|
var Symbol = {
|
|
# Static/singleton:
|
|
registry: {},
|
|
add: func(type, class)
|
|
me.registry[type] = class,
|
|
get: func(type)
|
|
if ((var class = me.registry[type]) == nil)
|
|
die("unknown type '"~type~"'");
|
|
else return class,
|
|
# Calls corresonding symbol constructor
|
|
# @param group #Canvas.Group to place this on.
|
|
# @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;
|
|
},
|
|
# Non-static:
|
|
df_controller: nil, # default controller
|
|
# Update the drawing of this object (position and others).
|
|
update: func()
|
|
die("update() not implemented for this symbol type!"),
|
|
draw: func(group, model, lod)
|
|
die("draw() not implemented for this symbol type!"),
|
|
del: func()
|
|
die("del() not implemented for this symbol type!"),
|
|
}; # of Symbol
|
|
|
|
|
|
Symbol.Controller = {
|
|
# Static/singleton:
|
|
registry: {},
|
|
add: func(type, class)
|
|
me.registry[type] = class,
|
|
get: func(type)
|
|
if ((var class = me.registry[type]) == nil)
|
|
die("unknown type '"~type~"'");
|
|
else return class,
|
|
# Calls corresonding symbol constructor
|
|
# @param model Model to place this on.
|
|
new: func(type, model, arg...)
|
|
return call((var class = me.get(type)).new, [model]~arg, class),
|
|
# Non-static:
|
|
# Update anything related to a particular model. Returns whether the object needs updating:
|
|
update: func(model) return 1,
|
|
# Initialize a controller to an object (or initialize the controller itself):
|
|
init: func(model) ,
|
|
# Delete an object from this controller (or delete the controller itself):
|
|
del: func(model) ,
|
|
# Return whether this symbol/object is visible:
|
|
isVisible: func(model) return 1,
|
|
# Get the position of this symbol/object:
|
|
getpos: func(model) , # default provided below
|
|
}; # of Symbol.Controller
|
|
|
|
var getpos_fromghost = func(positioned_g)
|
|
return [positioned_g.lat, positioned_g.lon];
|
|
|
|
# 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
|
|
};
|
|
|
|
# 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;
|
|
}
|
|
}
|
|
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;
|
|
};
|
|
|
|
|
|
var assert_m = func(hash, member)
|
|
if (!contains(hash, member))
|
|
die("required field not found: '"~member~"'");
|
|
var assert_ms = func(hash, members...)
|
|
foreach (var m; members)
|
|
if (m != nil) assert_m(hash, m);
|
|
|
|
|
|
var DotSym = {
|
|
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");
|
|
return Symbol.add(name, hash);
|
|
},
|
|
# 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
|
|
),
|
|
};
|
|
if (m.controller != nil) {
|
|
temp = m.controller.new(m.model,m);
|
|
if (temp != nil)
|
|
m.controller = temp;
|
|
m.controller.init(model);
|
|
}
|
|
else die("default controller not found");
|
|
|
|
m.init();
|
|
return m;
|
|
},
|
|
del: func() {
|
|
printlog(_MP_dbg_lvl, "DotSym.del()");
|
|
me.deinit();
|
|
if (me.controller != nil)
|
|
me.controller.del(me.model);
|
|
call(func me.model.del(), nil, var err=[]); # try...
|
|
if (size(err) and err[0] != "No such member: del") # ... and either catch or rethrow
|
|
die(err[0]);
|
|
me.element.del();
|
|
},
|
|
# Default wrappers:
|
|
init: func() me.draw(),
|
|
deinit: func(),
|
|
update: func() {
|
|
if (me.controller != nil) {
|
|
if (!me.controller.update(me.model)) return;
|
|
elsif (!me.controller.isVisible(me.model)) {
|
|
me.element.hide();
|
|
return;
|
|
}
|
|
} else
|
|
me.element.show();
|
|
me.draw();
|
|
var pos = me.controller.getpos(me.model);
|
|
if (size(pos) == 2)
|
|
pos~=[nil]; # fall through
|
|
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);
|
|
},
|
|
}; # of DotSym
|
|
|
|
# A layer that manages a list of symbols (using delta positioned handling).
|
|
var SymbolLayer = {
|
|
# Static/singleton:
|
|
registry: {},
|
|
add: func(type, class)
|
|
me.registry[type] = class,
|
|
get: func(type)
|
|
if ((var class = me.registry[type]) == nil)
|
|
die("unknown type '"~type~"'");
|
|
else return class,
|
|
# 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, map, controller=nil, style=nil, options=nil) {
|
|
#print("Creating new Layer instance");
|
|
if (me == nil) die();
|
|
var m = {
|
|
parents: [me],
|
|
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) {
|
|
var this_type = caller(1)[0].type_arg;
|
|
if (this_type != nil)
|
|
m.group.set("symbol-layer-type", this_type);
|
|
}
|
|
if (controller == nil)
|
|
#controller = SymbolLayer.Controller.new(me.type, m);
|
|
controller = me.df_controller;
|
|
assert_m(controller, "parents");
|
|
if (controller.parents[0] == SymbolLayer.Controller)
|
|
controller = controller.new(m);
|
|
assert_m(controller, "parents");
|
|
assert_m(controller.parents[0], "parents");
|
|
if (controller.parents[0].parents[0] != SymbolLayer.Controller)
|
|
die("OOP error");
|
|
m.controller = controller;
|
|
m.update();
|
|
return m;
|
|
},
|
|
update: func() {
|
|
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();
|
|
foreach (var e; me.list)
|
|
e.del();
|
|
},
|
|
findsym: func(model, del=0) {
|
|
forindex (var i; me.list) {
|
|
var e = me.list[i];
|
|
if (Symbol.Controller.equals(e.model, model)) {
|
|
if (del) {
|
|
# Remove this element from the list
|
|
# TODO: maybe C function for this? extend pop() to accept index?
|
|
var prev = subvec(me.list, 0, i);
|
|
var next = subvec(me.list, i+1);
|
|
me.list = prev~next;
|
|
}
|
|
return e;
|
|
}
|
|
}
|
|
return nil;
|
|
},
|
|
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) {
|
|
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) {
|
|
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
|
|
|
|
# Class to manage controlling a #SymbolLayer.
|
|
# Currently handles:
|
|
# * Searching for new symbols (positioned ghosts or other objects with unique id's).
|
|
# * Updating the layer (e.g. on an update loop or on a property change).
|
|
SymbolLayer.Controller = {
|
|
# Static/singleton:
|
|
registry: {},
|
|
add: func(type, class)
|
|
me.registry[type] = class,
|
|
get: func(type)
|
|
if ((var class = me.registry[type]) == nil)
|
|
die("unknown type '"~type~"'");
|
|
else return class,
|
|
# Calls corresonding controller constructor
|
|
# @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),
|
|
# Default implementations:
|
|
run_update: func() me.layer.update(),
|
|
# @return List of positioned objects.
|
|
searchCmd: func()
|
|
die("searchCmd() not implemented for this SymbolLayer.Controller type!"),
|
|
}; # of SymbolLayer.Controller
|
|
|
|
var AnimatedLayer = {
|
|
};
|
|
|
|
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:
|
|
registry: {},
|
|
add: func(type, class)
|
|
me.registry[type] = class,
|
|
get: func(type)
|
|
if ((var class = me.registry[type]) == nil)
|
|
die("unknown type '"~type~"'");
|
|
else return class,
|
|
# Calls corresonding controller constructor
|
|
# @param map The #SymbolMap this controller is responsible for.
|
|
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 #######
|
|
#print("loading files");
|
|
(func {
|
|
var FG_ROOT = getprop("/sim/fg-root");
|
|
var load = func(file, name) {
|
|
#print(file);
|
|
if (name == nil)
|
|
var name = split("/", file)[-1];
|
|
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);
|
|
#print("compiling file");
|
|
# This segfaults for some reason:
|
|
#var code = call(compile, [code], var err=[]);
|
|
var code = call(func compile(code, file), [code], var err=[]);
|
|
if (size(err)) {
|
|
#print("handling error");
|
|
if (substr(err[0], 0, 12) == "Parse error:") { # hack around Nasal feature
|
|
var e = split(" at line ", err[0]);
|
|
if (size(e) == 2)
|
|
err[0] = string.join("", [e[0], "\n at ", file, ", line ", e[1], "\n "]);
|
|
}
|
|
for (var i = 1; (var c = caller(i)) != nil; i += 1)
|
|
err ~= subvec(c, 2, 2);
|
|
debug.printerror(err);
|
|
return;
|
|
}
|
|
#print("calling code");
|
|
|
|
call(code, nil, nil, var 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);
|
|
}
|
|
|
|
# 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);
|
|
|
|
# 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 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) );
|
|
}
|
|
|
|
})();
|
|
#print("finished loading files");
|
|
####### TEST SYMBOL #######
|
|
|
|
canvas.load_MapStructure = func; # @Philosopher: is this intended/needed ??
|
|
|
|
}; # load_MapStructure
|
|
|
|
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
|