Add Canvas Map Support for Slippy Map
- Include OSM and OpenAIP in the canvas-map dialog.
This commit is contained in:
parent
cfa967db1d
commit
85b7665c19
7 changed files with 472 additions and 27 deletions
|
@ -16,7 +16,7 @@
|
||||||
## - parents
|
## - parents
|
||||||
## - __self__
|
## - __self__
|
||||||
## - del (managing all listeners and timers)
|
## - del (managing all listeners and timers)
|
||||||
## - searchCmd -> filtering
|
## - searchCmd -> filtering
|
||||||
##
|
##
|
||||||
## APIs to be wrapped for each layer:
|
## APIs to be wrapped for each layer:
|
||||||
## printlog(), die(), debug.bt(), benchmark()
|
## printlog(), die(), debug.bt(), benchmark()
|
||||||
|
@ -79,10 +79,16 @@ var MapStructure_selfTest = func() {
|
||||||
# TODO: we'll need some z-indexing here, right now it's just random
|
# TODO: we'll need some z-indexing here, right now it's just random
|
||||||
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?
|
# TODO: use foreach/keys to show all layers in this case by traversing SymbolLayer.registry direclty ?
|
||||||
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?
|
# maybe encode implicit z-indexing for each lcontroller ctor call ? - i.e. preferred above/below order ?
|
||||||
foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR'),r('APS'), ] )
|
foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR'),r('APS'), ] )
|
||||||
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,
|
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,
|
||||||
visible: type.vis, priority: type.zindex,
|
visible: type.vis, priority: type.zindex,
|
||||||
);
|
);
|
||||||
|
foreach(var type; [ r('OSM'), r('OpenAIP') ]) {
|
||||||
|
TestMap.addLayer(factory: canvas.OverlayLayer, type_arg: type.name,
|
||||||
|
visible: type.vis, priority: type.zindex,
|
||||||
|
style: Styles.get(type.name),
|
||||||
|
options: Options.get(type.name) );
|
||||||
|
}
|
||||||
}; # MapStructure_selfTest
|
}; # MapStructure_selfTest
|
||||||
|
|
||||||
|
|
||||||
|
@ -854,7 +860,7 @@ var LineSymbol = {
|
||||||
var path = me.model;
|
var path = me.model;
|
||||||
if(typeof(path) == 'hash'){
|
if(typeof(path) == 'hash'){
|
||||||
path = me.model.path;
|
path = me.model.path;
|
||||||
if(path == nil)
|
if(path == nil)
|
||||||
__die("LineSymbol model requires a 'path' member (vector)");
|
__die("LineSymbol model requires a 'path' member (vector)");
|
||||||
}
|
}
|
||||||
foreach (var m; path) {
|
foreach (var m; path) {
|
||||||
|
@ -862,7 +868,7 @@ var LineSymbol = {
|
||||||
var (lat,lon) = me.controller.getpos(m);
|
var (lat,lon) = me.controller.getpos(m);
|
||||||
append(coords,"N"~lat);
|
append(coords,"N"~lat);
|
||||||
append(coords,"E"~lon);
|
append(coords,"E"~lon);
|
||||||
append(cmds,cmd);
|
append(cmds,cmd);
|
||||||
cmd = canvas.Path.VG_LINE_TO;
|
cmd = canvas.Path.VG_LINE_TO;
|
||||||
} else {
|
} else {
|
||||||
cmd = canvas.Path.VG_MOVE_TO;
|
cmd = canvas.Path.VG_MOVE_TO;
|
||||||
|
@ -934,7 +940,7 @@ var SymbolLayer = {
|
||||||
# print("SymbolLayer setup options:", m.options!=nil);
|
# print("SymbolLayer setup options:", m.options!=nil);
|
||||||
m.style = default_hash(style, m.df_style);
|
m.style = default_hash(style, m.df_style);
|
||||||
m.options = default_hash(options, m.df_options);
|
m.options = default_hash(options, m.df_options);
|
||||||
|
|
||||||
if (controller == nil)
|
if (controller == nil)
|
||||||
controller = m.df_controller;
|
controller = m.df_controller;
|
||||||
assert_m(controller, "parents");
|
assert_m(controller, "parents");
|
||||||
|
@ -1074,15 +1080,15 @@ var MultiSymbolLayer = {
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
},
|
},
|
||||||
searchCmd: func() {
|
searchCmd: func() {
|
||||||
if (me.map.getPosCoord() == nil or me.map.getRange() == nil) {
|
if (me.map.getPosCoord() == nil or me.map.getRange() == nil) {
|
||||||
print("Map not yet initialized, returning empty result set!");
|
print("Map not yet initialized, returning empty result set!");
|
||||||
return []; # handle maps not yet fully initialized
|
return []; # handle maps not yet fully initialized
|
||||||
}
|
}
|
||||||
var result = me.controller.searchCmd();
|
var result = me.controller.searchCmd();
|
||||||
# some hardening
|
# some hardening
|
||||||
var type=typeof(result);
|
var type=typeof(result);
|
||||||
if(type != 'nil' and type != 'vector')
|
if(type != 'nil' and type != 'vector')
|
||||||
__die("MultiSymbolLayer: searchCmd() method MUST return a vector of valid positioned ghosts/Geo.Coord objects or nil! (was:"~type~")");
|
__die("MultiSymbolLayer: searchCmd() method MUST return a vector of valid positioned ghosts/Geo.Coord objects or nil! (was:"~type~")");
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
|
@ -1127,7 +1133,7 @@ var NavaidSymbolLayer = {
|
||||||
|
|
||||||
###
|
###
|
||||||
## TODO: wrappers for Horizontal vs. Vertical layers ?
|
## TODO: wrappers for Horizontal vs. Vertical layers ?
|
||||||
##
|
##
|
||||||
|
|
||||||
var SingleSymbolLayer = {
|
var SingleSymbolLayer = {
|
||||||
parents: [SymbolLayer],
|
parents: [SymbolLayer],
|
||||||
|
@ -1177,6 +1183,306 @@ var SingleSymbolLayer = {
|
||||||
},
|
},
|
||||||
}; # of SingleSymbolLayer
|
}; # of SingleSymbolLayer
|
||||||
|
|
||||||
|
##
|
||||||
|
# Base class for a OverlayLayer, e.g. a TileLayer
|
||||||
|
#
|
||||||
|
var OverlayLayer = {
|
||||||
|
# Default implementations/values:
|
||||||
|
df_controller: nil, # default controller
|
||||||
|
df_priority: nil, # default priority for display sorting
|
||||||
|
df_style: nil,
|
||||||
|
df_options: nil,
|
||||||
|
type: nil, # type of #Symbol to add (MANDATORY)
|
||||||
|
id: nil, # id of the group #canvas.Element (OPTIONAL)
|
||||||
|
# Static/singleton:
|
||||||
|
registry: {},
|
||||||
|
add: func(type, class) {
|
||||||
|
me.registry[type] = class;
|
||||||
|
},
|
||||||
|
get: func(type) {
|
||||||
|
foreach(var invalid; var invalid_types = [nil,'vector','hash'])
|
||||||
|
if ( (var t=typeof(type)) == invalid) __die(" invalid OverlayLayer type (non-scalar) of type:"~t);
|
||||||
|
if ((var class = me.registry[type]) == nil)
|
||||||
|
__die("OverlayLayer.get(): unknown type '"~type~"'");
|
||||||
|
else return class;
|
||||||
|
},
|
||||||
|
# Calls corresonding layer constructor
|
||||||
|
# @param group #Canvas.Group to place this on.
|
||||||
|
# @param map The #Canvas.Map this is a member of.
|
||||||
|
# @param style An alternate style.
|
||||||
|
# @param options Extra options/configurations.
|
||||||
|
# @param visible Initially set it up as visible or invisible.
|
||||||
|
new: func(type, group, map, controller=nil, style=nil, options=nil, visible=1, arg...) {
|
||||||
|
# XXX: Extra named arguments are (obviously) not preserved well...
|
||||||
|
if (me == nil) __die("OverlaySymbolLayer constructor needs to know its parent class");
|
||||||
|
|
||||||
|
var ret = call((var class = me.get(type)).new, [group, map, controller, style, options, visible], class);
|
||||||
|
ret.type = type;
|
||||||
|
ret.group.set("layer-type", type);
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
# Private constructor:
|
||||||
|
_new: func(m, style, controller, options) {
|
||||||
|
m.style = default_hash(style, m.df_style);
|
||||||
|
m.options = default_hash(options, m.df_options);
|
||||||
|
|
||||||
|
if (controller == nil) {
|
||||||
|
if (m.df_controller == nil) {
|
||||||
|
controller = OverlayLayer.Controller;
|
||||||
|
} else {
|
||||||
|
controller = m.df_controller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_m(controller, "parents");
|
||||||
|
if (controller.parents[0] == OverlayLayer.Controller)
|
||||||
|
controller = controller.new(m);
|
||||||
|
assert_m(controller, "parents");
|
||||||
|
assert_m(controller.parents[0], "parents");
|
||||||
|
if(options != nil){
|
||||||
|
var listeners = opt_member(controller, 'listeners');
|
||||||
|
var listen = opt_member(options, 'listen');
|
||||||
|
if (listen != nil and listeners != nil){
|
||||||
|
var listen_tp = typeof(listen);
|
||||||
|
if(listen_tp != 'vector' and listen_tp != 'scalar')
|
||||||
|
__die("Options 'listen' cannot be a "~ listen_tp);
|
||||||
|
if(typeof(listen) == 'scalar')
|
||||||
|
listen = [listen];
|
||||||
|
foreach(var node_name; listen){
|
||||||
|
var node = opt_member(options, node_name);
|
||||||
|
if(node == nil)
|
||||||
|
node = node_name;
|
||||||
|
append(controller.listeners,
|
||||||
|
setlistener(node, func call(m.update,[],m),0,0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.controller = controller;
|
||||||
|
},
|
||||||
|
# For instances:
|
||||||
|
del: func() if (me.controller != nil) { me.controller.del(); me.controller = nil },
|
||||||
|
update: func() { _die("Abstract OverlayLayer.update() not implemented for this Layer"); },
|
||||||
|
};
|
||||||
|
|
||||||
|
var TileLayer = {
|
||||||
|
parents: [OverlayLayer],
|
||||||
|
# Default implementations/values:
|
||||||
|
# @param group A group to place this on.
|
||||||
|
# @param map The #Canvas.Map this is a member of.
|
||||||
|
# @param controller A controller object (parents=[OverlayLayer.Controller])
|
||||||
|
# or implementation (parents[0].parents=[OverlayLayer.Controller]).
|
||||||
|
# @param style An alternate style.
|
||||||
|
# @param options Extra options/configurations.
|
||||||
|
# @param visible Initially set it up as visible or invisible.
|
||||||
|
new: func(group, map, controller=nil, style=nil, options=nil, visible=1) {
|
||||||
|
if (me == nil) __die("TileLayer constructor needs to know its parent class");
|
||||||
|
var m = {
|
||||||
|
parents: [me],
|
||||||
|
map: map,
|
||||||
|
group: group.createChild("group", me.type),
|
||||||
|
maps_base: "",
|
||||||
|
num_tiles: [5,5],
|
||||||
|
makeURL: nil,
|
||||||
|
makePath: nil,
|
||||||
|
center_tile_offset : [],
|
||||||
|
tile_size: 256,
|
||||||
|
zoom: 9,
|
||||||
|
tile_type: "map",
|
||||||
|
last_tile_type: "map",
|
||||||
|
last_tile : [-1,-1],
|
||||||
|
tiles: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
# Determine the number of tiles dynamically based on the canvas size
|
||||||
|
#var width = map.getCanvas().get("size[0]");
|
||||||
|
#var height = map.getCanvas().get("size[1]");
|
||||||
|
#m.num_tiles= [ math.ceil(width / m.tile_size),
|
||||||
|
# math.ceil(height / m.tile_size) ];
|
||||||
|
|
||||||
|
|
||||||
|
m.maps_base = getprop("/sim/fg-home") ~ '/cache/maps';
|
||||||
|
m.tiles = setsize([], m.num_tiles[0]);
|
||||||
|
m.center_tile_offset = [
|
||||||
|
(m.num_tiles[0] - 1.0) / 2.0,
|
||||||
|
(m.num_tiles[1] - 1.0) / 2.0
|
||||||
|
];
|
||||||
|
|
||||||
|
append(m.parents, m.group);
|
||||||
|
m.setVisible(visible);
|
||||||
|
OverlayLayer._new(m, style, controller, options);
|
||||||
|
#m.group.setCenter(0,0);
|
||||||
|
|
||||||
|
for(var x = 0; x < m.num_tiles[0]; x += 1)
|
||||||
|
{
|
||||||
|
m.tiles[x] = setsize([], m.num_tiles[1]);
|
||||||
|
for(var y = 0; y < m.num_tiles[1]; y += 1) {
|
||||||
|
m.tiles[x][y] = m.group.createChild("image", "map-tile");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m.update();
|
||||||
|
return m;
|
||||||
|
},
|
||||||
|
updateLayer: func()
|
||||||
|
{
|
||||||
|
# get current position
|
||||||
|
var lat = me.map.getLat();
|
||||||
|
var lon = me.map.getLon();
|
||||||
|
var range_nm = me.map.getRange();
|
||||||
|
var screen_range = me.map.getScreenRange();
|
||||||
|
|
||||||
|
if (screen_range == nil) screen_range = 200;
|
||||||
|
|
||||||
|
# Screen resolution m/pixel is range/screen_range
|
||||||
|
var screen_resolution = range_nm * globals.NM2M / screen_range;
|
||||||
|
|
||||||
|
# Slippy map resolution is
|
||||||
|
# 156543.03 meters/pixel * cos(latitude) / (2 ^ zoomlevel)
|
||||||
|
# Determine the closest zoom level and scaling ratio. Each increase in zoom level doubles resolution.
|
||||||
|
var ideal_zoom = math.ln(156543.03 * math.cos(lat * math.pi/180.0) / screen_resolution) / math.ln(2);
|
||||||
|
me.zoom = math.ceil(ideal_zoom);
|
||||||
|
var ratio = 1 / math.pow(2,me.zoom - ideal_zoom);
|
||||||
|
|
||||||
|
for(var x = 0; x < me.num_tiles[0]; x += 1)
|
||||||
|
{
|
||||||
|
for(var y = 0; y < me.num_tiles[1]; y += 1) {
|
||||||
|
me.tiles[x][y].setTranslation(int((x - me.center_tile_offset[0]) * me.tile_size * ratio + 0.5),
|
||||||
|
int((y - me.center_tile_offset[1]) * me.tile_size * ratio + 0.5));
|
||||||
|
me.tiles[x][y].setScale(ratio);
|
||||||
|
me.tiles[x][y].scale_factor = ratio;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#var heading = me.map.getHdg();
|
||||||
|
#me.group.setRotation(heading);
|
||||||
|
|
||||||
|
var ymax = math.pow(2, me.zoom);
|
||||||
|
|
||||||
|
# Slippy map location of center point
|
||||||
|
var slippy_center = [
|
||||||
|
math.floor(ymax * ((lon + 180.0) / 360.0)),
|
||||||
|
math.floor((1 - math.ln(math.tan(lat * math.pi/180.0) + 1 / math.cos(lat * math.pi/180.0)) / math.pi) / 2.0 * ymax)
|
||||||
|
];
|
||||||
|
|
||||||
|
# This is the Slippy Map location of the 0,0 tile
|
||||||
|
var offset = [slippy_center[0] - me.center_tile_offset[0],
|
||||||
|
slippy_center[1] - me.center_tile_offset[1]];
|
||||||
|
|
||||||
|
var tile_index = [math.floor(offset[0]), math.floor(offset[1])];
|
||||||
|
|
||||||
|
# Find the lon, lat of the center tile
|
||||||
|
var center_tile_lon = slippy_center[0]/ymax * 360.0 - 180.0;
|
||||||
|
var nn = math.pi - 2.0 * math.pi * slippy_center[1]/ ymax;
|
||||||
|
var center_tile_lat = 180.0 / math.pi * math.atan(0.5 * (math.exp(nn) - math.exp(-nn)));
|
||||||
|
|
||||||
|
me.group.setGeoPosition(center_tile_lat, center_tile_lon);
|
||||||
|
|
||||||
|
if( tile_index[0] != me.last_tile[0]
|
||||||
|
or tile_index[1] != me.last_tile[1]
|
||||||
|
or me.tile_type != me.last_tile_type )
|
||||||
|
{
|
||||||
|
for(var x = 0; x < me.num_tiles[0]; x += 1) {
|
||||||
|
for(var y = 0; y < me.num_tiles[1]; y += 1) {
|
||||||
|
var pos = {
|
||||||
|
z: me.zoom,
|
||||||
|
x: int(tile_index[0] + x),
|
||||||
|
y: int(tile_index[1] + y),
|
||||||
|
tms_y: ymax - int(tile_index[1] + y) - 1,
|
||||||
|
type: me.tile_type
|
||||||
|
};
|
||||||
|
|
||||||
|
(func {
|
||||||
|
var img_path = me.makePath(pos);
|
||||||
|
var tile = me.tiles[x][y];
|
||||||
|
|
||||||
|
if( io.stat(img_path) == nil ) {
|
||||||
|
# image not found, save in $FG_HOME
|
||||||
|
var img_url = me.makeURL(pos);
|
||||||
|
#print('requesting ' ~ img_url);
|
||||||
|
http.save(img_url, img_path)
|
||||||
|
.done(func { tile.set("src", img_path);})
|
||||||
|
.fail(func (r) print('Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason));
|
||||||
|
} else {
|
||||||
|
# Re-use cached image
|
||||||
|
#print('loading ' ~ img_path);
|
||||||
|
tile.set("src", img_path)
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
me.last_tile = tile_index;
|
||||||
|
me.last_type = me.type;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update: func() {
|
||||||
|
if (!me.getVisible())
|
||||||
|
return;
|
||||||
|
#debug.warn("update traceback for "~me.type);
|
||||||
|
|
||||||
|
if (me.options != nil and me.options['update_wrapper'] !=nil) {
|
||||||
|
me.options.update_wrapper( me, me.updateLayer ); # call external wrapper (usually for profiling purposes)
|
||||||
|
} else {
|
||||||
|
me.updateLayer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
del: func() {
|
||||||
|
printlog(_MP_dbg_lvl, "SymbolLayer.del()");
|
||||||
|
call(OverlayLayer.del, nil, me);
|
||||||
|
},
|
||||||
|
}; # of TileLayer
|
||||||
|
|
||||||
|
# Class to manage controlling a OverlayLayer.
|
||||||
|
# Currently handles:
|
||||||
|
# * Simple update() call
|
||||||
|
OverlayLayer.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 corresponding controller constructor
|
||||||
|
# @param layer The #OverlayLayer this controller is responsible for.
|
||||||
|
new: func(type, layer, arg...)
|
||||||
|
return call((var class = me.get(type)).new, [layer]~arg, class),
|
||||||
|
# Default implementations for derived classes:
|
||||||
|
# @return List of positioned objects.
|
||||||
|
updateLayer: func()
|
||||||
|
__die("Abstract method updateLayer() not implemented for this OverlayLayer.Controller type!"),
|
||||||
|
addVisibilityListener: func() {
|
||||||
|
var m = me;
|
||||||
|
append(m.listeners, setlistener(
|
||||||
|
m.layer._node.getNode("visible"),
|
||||||
|
func m.layer.update(),
|
||||||
|
#compile("m.layer.update()", "<layer visibility on node "~m.layer._node.getNode("visible").getPath()~" for layer "~m.layer.type~">"),
|
||||||
|
0,0
|
||||||
|
));
|
||||||
|
},
|
||||||
|
addRangeListener: func() {
|
||||||
|
var m = me;
|
||||||
|
append(m.listeners, setlistener(
|
||||||
|
m.layer._node.getNode("range",1),
|
||||||
|
func m.layer.update(),
|
||||||
|
#compile("m.layer.update()", "<layer visibility on node "~m.layer._node.getNode("visible").getPath()~" for layer "~m.layer.type~">"),
|
||||||
|
0,0
|
||||||
|
));
|
||||||
|
},
|
||||||
|
addScreenRangeListener: func() {
|
||||||
|
var m = me;
|
||||||
|
append(m.listeners, setlistener(
|
||||||
|
m.layer._node.getNode("screen-range",1),
|
||||||
|
func m.layer.update(),
|
||||||
|
#compile("m.layer.update()", "<layer visibility on node "~m.layer._node.getNode("visible").getPath()~" for layer "~m.layer.type~">"),
|
||||||
|
0,0
|
||||||
|
));
|
||||||
|
},
|
||||||
|
}; # of OverlayLayer.Controller
|
||||||
|
|
||||||
|
|
||||||
###
|
###
|
||||||
# set up a cache for 32x32 symbols (initialized below in load_MapStructure)
|
# set up a cache for 32x32 symbols (initialized below in load_MapStructure)
|
||||||
var SymbolCache32x32 = nil;
|
var SymbolCache32x32 = nil;
|
||||||
|
@ -1307,6 +1613,7 @@ var load_MapStructure = func {
|
||||||
"symbol",
|
"symbol",
|
||||||
"scontroller",
|
"scontroller",
|
||||||
"controller",
|
"controller",
|
||||||
|
"overlay"
|
||||||
];
|
];
|
||||||
var deps = {};
|
var deps = {};
|
||||||
foreach (var d; dep_names) deps[d] = [];
|
foreach (var d; dep_names) deps[d] = [];
|
||||||
|
|
|
@ -476,11 +476,13 @@ var Map = {
|
||||||
setController: func(controller=nil, arg...)
|
setController: func(controller=nil, arg...)
|
||||||
{
|
{
|
||||||
if (me.controller != nil) me.controller.del(me);
|
if (me.controller != nil) me.controller.del(me);
|
||||||
if (controller == nil)
|
if (controller == nil) {
|
||||||
controller = Map.df_controller;
|
controller = Map.df_controller;
|
||||||
elsif (typeof(controller) != 'hash')
|
}
|
||||||
|
elsif (typeof(controller) != 'hash') {
|
||||||
controller = Map.Controller.get(controller);
|
controller = Map.Controller.get(controller);
|
||||||
|
}
|
||||||
|
|
||||||
if (controller == nil) {
|
if (controller == nil) {
|
||||||
me.controller = nil;
|
me.controller = nil;
|
||||||
} else {
|
} else {
|
||||||
|
@ -527,8 +529,8 @@ var Map = {
|
||||||
},
|
},
|
||||||
getLayer: func(type_arg) me.layers[type_arg],
|
getLayer: func(type_arg) me.layers[type_arg],
|
||||||
|
|
||||||
setRange: func(range) me.set("range",range),
|
setRange: func(range) { me.set("range",range); },
|
||||||
getRange: func me.get('range'),
|
setScreenRange: func(range) { me.set("screen-range",range); },
|
||||||
|
|
||||||
setPos: func(lat, lon, hdg=nil, range=nil, alt=nil)
|
setPos: func(lat, lon, hdg=nil, range=nil, alt=nil)
|
||||||
{
|
{
|
||||||
|
@ -555,6 +557,7 @@ var Map = {
|
||||||
getHdg: func me.get("hdg"),
|
getHdg: func me.get("hdg"),
|
||||||
getAlt: func me.get("altitude"),
|
getAlt: func me.get("altitude"),
|
||||||
getRange: func me.get("range"),
|
getRange: func me.get("range"),
|
||||||
|
getScreenRange: func me.get('screen-range'),
|
||||||
getLatLon: func [me.get("ref-lat"), me.get("ref-lon")],
|
getLatLon: func [me.get("ref-lat"), me.get("ref-lon")],
|
||||||
# N.B.: This always returns the same geo.Coord object,
|
# N.B.: This always returns the same geo.Coord object,
|
||||||
# so its values can and will change at any time (call
|
# so its values can and will change at any time (call
|
||||||
|
@ -626,7 +629,7 @@ var Text = {
|
||||||
{
|
{
|
||||||
die("updateText() requires enableUpdate() to be called first");
|
die("updateText() requires enableUpdate() to be called first");
|
||||||
},
|
},
|
||||||
|
|
||||||
# enable fast setprop-based text writing
|
# enable fast setprop-based text writing
|
||||||
enableFast: func ()
|
enableFast: func ()
|
||||||
{
|
{
|
||||||
|
@ -981,7 +984,7 @@ var Path = {
|
||||||
},
|
},
|
||||||
|
|
||||||
setColor: func me.setStroke(_getColor(arg)),
|
setColor: func me.setStroke(_getColor(arg)),
|
||||||
getColor: func me.getStroke(),
|
getColor: func me.getStroke(),
|
||||||
|
|
||||||
setColorFill: func me.setFill(_getColor(arg)),
|
setColorFill: func me.setFill(_getColor(arg)),
|
||||||
getColorFill: func me.getColorFill(),
|
getColorFill: func me.getColorFill(),
|
||||||
|
|
|
@ -243,7 +243,7 @@ LayeredMap.updateZoom = func {
|
||||||
z = math.max(0, math.min(z, size(me.ranges) - 1));
|
z = math.max(0, math.min(z, size(me.ranges) - 1));
|
||||||
me.zoom_property.setIntValue(z);
|
me.zoom_property.setIntValue(z);
|
||||||
var zoom = me.ranges[size(me.ranges) - 1 - z];
|
var zoom = me.ranges[size(me.ranges) - 1 - z];
|
||||||
# print("Setting zoom range to:", zoom);
|
print("Setting zoom range to: " ~ z ~ " " ~ zoom);
|
||||||
benchmark("Zooming map:"~zoom, func {
|
benchmark("Zooming map:"~zoom, func {
|
||||||
me._node.getNode("range", 1).setDoubleValue(zoom);
|
me._node.getNode("range", 1).setDoubleValue(zoom);
|
||||||
# TODO update center/limit translation to keep airport always visible
|
# TODO update center/limit translation to keep airport always visible
|
||||||
|
@ -274,7 +274,7 @@ LayeredMap.setupZoom = func(dialog) {
|
||||||
foreach(var r; ranges)
|
foreach(var r; ranges)
|
||||||
append(me.ranges, r.getValue() );
|
append(me.ranges, r.getValue() );
|
||||||
|
|
||||||
# print("Setting up Zoom Ranges:", size(ranges)-1);
|
print("Setting up Zoom Ranges:", size(ranges)-1);
|
||||||
me.listen(me.zoom_property, func me.updateZoom() );
|
me.listen(me.zoom_property, func me.updateZoom() );
|
||||||
me.updateZoom();
|
me.updateZoom();
|
||||||
me; #chainable
|
me; #chainable
|
||||||
|
@ -441,4 +441,3 @@ setlistener("/nasal/canvas/loaded", func {
|
||||||
# TODO: should be inside a separate subfolder, i.e. canvas/map/mfd
|
# TODO: should be inside a separate subfolder, i.e. canvas/map/mfd
|
||||||
load_modules( files_with('.mfd'), 'canvas' );
|
load_modules( files_with('.mfd'), 'canvas' );
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
37
Nasal/canvas/map/OSM.lcontroller
Normal file
37
Nasal/canvas/map/OSM.lcontroller
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# See: http://wiki.flightgear.org/MapStructure
|
||||||
|
# Class things:
|
||||||
|
var name = 'OSM';
|
||||||
|
var parents = [OverlayLayer.Controller];
|
||||||
|
var __self__ = caller(0)[0];
|
||||||
|
OverlayLayer.Controller.add(name, __self__);
|
||||||
|
TileLayer.add(name, {
|
||||||
|
parents: [TileLayer],
|
||||||
|
type: name, # Layer type
|
||||||
|
df_controller: __self__, # controller to use by default -- this one
|
||||||
|
});
|
||||||
|
|
||||||
|
var new = func(layer) {
|
||||||
|
var m = {
|
||||||
|
parents: [__self__],
|
||||||
|
layer: layer,
|
||||||
|
map: layer.map,
|
||||||
|
listeners: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
layer.makeURL = string.compileTemplate('https://maps.wikimedia.org/osm-intl/{z}/{x}/{y}.png');
|
||||||
|
layer.makePath = string.compileTemplate(layer.maps_base ~ '/osm-intl/{z}/{x}/{y}.png');
|
||||||
|
|
||||||
|
m.addVisibilityListener();
|
||||||
|
m.addRangeListener();
|
||||||
|
m.addScreenRangeListener();
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateLayer = func() {
|
||||||
|
}
|
||||||
|
|
||||||
|
var del = func() {
|
||||||
|
#print(name~".lcontroller.del()");
|
||||||
|
foreach (var l; me.listeners)
|
||||||
|
removelistener(l);
|
||||||
|
};
|
38
Nasal/canvas/map/OpenAIP.lcontroller
Normal file
38
Nasal/canvas/map/OpenAIP.lcontroller
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
# See: http://wiki.flightgear.org/MapStructure
|
||||||
|
# Class things:
|
||||||
|
var name = 'OpenAIP';
|
||||||
|
var parents = [OverlayLayer.Controller];
|
||||||
|
var __self__ = caller(0)[0];
|
||||||
|
OverlayLayer.Controller.add(name, __self__);
|
||||||
|
TileLayer.add(name, {
|
||||||
|
parents: [TileLayer],
|
||||||
|
type: name, # Layer type
|
||||||
|
df_controller: __self__, # controller to use by default -- this one
|
||||||
|
});
|
||||||
|
|
||||||
|
var new = func(layer) {
|
||||||
|
var m = {
|
||||||
|
parents: [__self__],
|
||||||
|
layer: layer,
|
||||||
|
map: layer.map,
|
||||||
|
listeners: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
# http://1.tile.maps.openaip.net/geowebcache/service/tms/1.0.0/openaip_basemap@EPSG%3A900913@png/6/30/43.png
|
||||||
|
layer.makeURL = string.compileTemplate('http://1.tile.maps.openaip.net/geowebcache/service/tms/1.0.0/openaip_basemap@EPSG%3A900913@png/{z}/{x}/{tms_y}.png');
|
||||||
|
layer.makePath = string.compileTemplate(layer.maps_base ~ '/openaip_basemap/{z}/{x}/{tms_y}.png');
|
||||||
|
|
||||||
|
m.addVisibilityListener();
|
||||||
|
m.addRangeListener();
|
||||||
|
m.addScreenRangeListener();
|
||||||
|
return m;
|
||||||
|
};
|
||||||
|
|
||||||
|
var updateLayer = func() {
|
||||||
|
}
|
||||||
|
|
||||||
|
var del = func() {
|
||||||
|
#print(name~".lcontroller.del()");
|
||||||
|
foreach (var l; me.listeners)
|
||||||
|
removelistener(l);
|
||||||
|
};
|
|
@ -164,12 +164,12 @@ var Coord = {
|
||||||
me._pupdate();
|
me._pupdate();
|
||||||
course *= D2R;
|
course *= D2R;
|
||||||
dist /= ERAD;
|
dist /= ERAD;
|
||||||
|
|
||||||
if (dist < 0.0) {
|
if (dist < 0.0) {
|
||||||
dist = abs(dist);
|
dist = abs(dist);
|
||||||
course = course - math.pi;
|
course = course - math.pi;
|
||||||
}
|
}
|
||||||
|
|
||||||
me._lat = math.asin(math.sin(me._lat) * math.cos(dist)
|
me._lat = math.asin(math.sin(me._lat) * math.cos(dist)
|
||||||
+ math.cos(me._lat) * math.sin(dist) * math.cos(course));
|
+ math.cos(me._lat) * math.sin(dist) * math.cos(course));
|
||||||
|
|
||||||
|
@ -398,6 +398,6 @@ var PositionedSearch = {
|
||||||
debug.benchmark('Toggle '~from~'nm/'~to~'nm', func {
|
debug.benchmark('Toggle '~from~'nm/'~to~'nm', func {
|
||||||
s.update();
|
s.update();
|
||||||
s.update( func positioned.findWithinRange(to, 'fix') );
|
s.update( func positioned.findWithinRange(to, 'fix') );
|
||||||
}); # ~ takes
|
}); # ~ takes
|
||||||
}, # of test
|
}, # of test
|
||||||
};
|
};
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setTransparency(0);
|
setTransparency(0);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
]]></open>
|
]]></open>
|
||||||
|
|
||||||
<close><![CDATA[
|
<close><![CDATA[
|
||||||
|
@ -213,6 +216,35 @@
|
||||||
</binding>
|
</binding>
|
||||||
</checkbox>
|
</checkbox>
|
||||||
|
|
||||||
|
<checkbox>
|
||||||
|
<label>OSM</label>
|
||||||
|
<halign>left</halign>
|
||||||
|
|
||||||
|
<property>/sim/gui/dialogs/map-canvas/draw-OSM</property>
|
||||||
|
<live>true</live>
|
||||||
|
<binding>
|
||||||
|
<command>dialog-apply</command>
|
||||||
|
</binding>
|
||||||
|
<binding>
|
||||||
|
<command>property-toggle</command>
|
||||||
|
</binding>
|
||||||
|
</checkbox>
|
||||||
|
|
||||||
|
<checkbox>
|
||||||
|
<label>OpenAIP</label>
|
||||||
|
<halign>left</halign>
|
||||||
|
|
||||||
|
<property>/sim/gui/dialogs/map-canvas/draw-OpenAIP</property>
|
||||||
|
<live>true</live>
|
||||||
|
<binding>
|
||||||
|
<command>dialog-apply</command>
|
||||||
|
</binding>
|
||||||
|
<binding>
|
||||||
|
<command>property-toggle</command>
|
||||||
|
</binding>
|
||||||
|
</checkbox>
|
||||||
|
|
||||||
|
|
||||||
<!-- layer only supported if tutorial system is active and targets specified-->
|
<!-- layer only supported if tutorial system is active and targets specified-->
|
||||||
<!--
|
<!--
|
||||||
<checkbox>
|
<checkbox>
|
||||||
|
@ -348,7 +380,9 @@
|
||||||
TestMap.setController("Aircraft position", "map-dialog"); # from aircraftpos.controller
|
TestMap.setController("Aircraft position", "map-dialog"); # from aircraftpos.controller
|
||||||
|
|
||||||
# Initialize a range:
|
# Initialize a range:
|
||||||
TestMap.setRange(20);
|
TestMap.setRange(60);
|
||||||
|
TestMap.setScreenRange(200);
|
||||||
|
|
||||||
var range_step = math.log10(TestMap.getRange());
|
var range_step = math.log10(TestMap.getRange());
|
||||||
# TODO: check if this is valid, IIRC DOM manipulation was fragile when done inside canvas/Nasal region (?)
|
# TODO: check if this is valid, IIRC DOM manipulation was fragile when done inside canvas/Nasal region (?)
|
||||||
gui.findElementByName(self, "zoomdisplay").setValue("property", TestMap._node.getNode("range").getPath());
|
gui.findElementByName(self, "zoomdisplay").setValue("property", TestMap._node.getNode("range").getPath());
|
||||||
|
@ -423,16 +457,25 @@
|
||||||
TestMap.getLayer(name).setVisible(n);
|
TestMap.getLayer(name).setVisible(n);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var SetProjection = func(projection) {
|
||||||
|
TestMap._node.setValue("projection", projection);
|
||||||
|
};
|
||||||
|
|
||||||
|
setlistener("/sim/gui/dialogs/map-canvas/projection",
|
||||||
|
func(n) SetProjection(n.getValue());
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
Styles.APS = {};
|
Styles.APS = {};
|
||||||
Styles.APS.scale_factor = 0.25;
|
Styles.APS.scale_factor = 0.25;
|
||||||
|
|
||||||
# TODO: introduce some meta NAV layer that handles both VORs and NDBs, can we instantiate those layers directly ?
|
# TODO: introduce some meta NAV layer that handles both VORs and NDBs, can we instantiate those layers directly ?
|
||||||
var r = func(name,vis=1,zindex=nil) return caller(0)[0];
|
var r = func(name,vis=1,zindex=nil) return caller(0)[0];
|
||||||
# TODO: we'll need some z-indexing here, right now it's just random
|
# TODO: we'll need some z-indexing here, right now it's just random
|
||||||
foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR',0),r('APS'), ] ) {
|
foreach(var type; [r('TFC',0),r('APT'),r('DME'),r('VOR'),r('NDB'),r('FIX',0),r('RTE'),r('WPT'),r('FLT'),r('WXR',0),r('APS')] ) {
|
||||||
if (1 and type.name != 'APS' and type.name != 'FLT') make_update_wrapper(type.name);
|
if (1 and type.name != 'APS' and type.name != 'FLT') make_update_wrapper(type.name);
|
||||||
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,
|
TestMap.addLayer(factory: canvas.SymbolLayer, type_arg: type.name,
|
||||||
visible: type.vis, priority: type.zindex,
|
visible: type.vis, priority: 4,
|
||||||
style: Styles.get(type.name),
|
style: Styles.get(type.name),
|
||||||
options: Options.get(type.name) );
|
options: Options.get(type.name) );
|
||||||
(func {
|
(func {
|
||||||
|
@ -445,6 +488,24 @@
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach(var type; [ r('OSM'), r('OpenAIP') ]) {
|
||||||
|
TestMap.addLayer(factory: canvas.OverlayLayer, type_arg: type.name,
|
||||||
|
visible: type.vis, priority: 1,
|
||||||
|
style: Styles.get(type.name),
|
||||||
|
options: Options.get(type.name) );
|
||||||
|
(func {
|
||||||
|
# Notify MapStructure about layer visibility changes:
|
||||||
|
var name = type.name;
|
||||||
|
props.globals.initNode("/sim/gui/dialogs/map-canvas/draw-"~name, type.vis, "BOOL");
|
||||||
|
append(listeners,
|
||||||
|
setlistener("/sim/gui/dialogs/map-canvas/draw-"~name,
|
||||||
|
func(n) SetLayerVisible(name,n.getValue()))
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
]]></load></nasal>
|
]]></load></nasal>
|
||||||
</canvas>
|
</canvas>
|
||||||
<layout>hbox</layout>
|
<layout>hbox</layout>
|
||||||
|
@ -498,7 +559,7 @@
|
||||||
</binding>
|
</binding>
|
||||||
</button>
|
</button>
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
</group>
|
</group>
|
||||||
</PropertyList>
|
</PropertyList>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue