# vim: set foldmethod=marker foldmarker={{{,}}} : var MapTiles = { # code from http://wiki.flightgear.org/Canvas_Snippets#A_simple_tile_map new : func (display) { var m = { parents: [MapTiles] }; m.display = display; m.group = m.display.createGroup(); m.tile_size = 256; m.zoom = 10; m.maps_base = getprop("/sim/fg-home") ~ '/cache/maps'; m.makeUrl = string.compileTemplate('https://{server}/{type}/{z}/{x}/{y}.png{apikey}'); m.makePath = string.compileTemplate(m.maps_base ~ '/{server}/{type}/{z}/{x}/{y}.png'); m.num_tiles = [ math.ceil( m.display.get('view[0]') / m.tile_size ) + 1, math.ceil( m.display.get('view[1]') / m.tile_size ) + 1 ]; m.center_tile_offset = [ (m.num_tiles[0] - 1) / 2, (m.num_tiles[1] - 1) / 2 ]; m.tiles = setsize([], m.num_tiles[0]); m.last_tile = [-1,-1]; m.last_type = data['tiles-type']; m.update_timer = maketimer(2, m, m.updateTiles); return m; }, # Simple user interface (Buttons for zoom and label for displaying it) changeZoom : func(d) { me.zoom = math.max(2, math.min(19, me.zoom + d)); call(me.updateTiles, [], me); }, # initialize the map by setting up a grid of raster images initialize_grid : func { for(var x = 0; x < me.num_tiles[0]; x += 1) { me.tiles[x] = setsize([], me.num_tiles[1]); for(var y = 0; y < me.num_tiles[1]; y += 1) me.tiles[x][y] = me.group.createChild("image", "map-tile"); } }, # this is the callback that will be regularly called by the timer to update the map updateTiles : func() { var lat = getprop('/position/latitude-deg'); var lon = getprop('/position/longitude-deg'); var n = math.pow(2, me.zoom); var offset = [ n * ((lon + 180) / 360) - me.center_tile_offset[0], (1 - math.ln(math.tan(lat * math.pi/180) + 1 / math.cos(lat * math.pi/180)) / math.pi) / 2 * n - me.center_tile_offset[1] ]; var tile_index = [int(offset[0]), int(offset[1])]; var ox = tile_index[0] - offset[0]; var oy = tile_index[1] - offset[1]; 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((ox + x) * me.tile_size + 0.5), int((oy + y) * me.tile_size + 0.5)); if( tile_index[0] != me.last_tile[0] or tile_index[1] != me.last_tile[1] or data['tiles-type'] != me.last_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(offset[0] + x), y: int(offset[1] + y), type: data['tiles-type'], server : data['tiles-server'], apikey: data['tiles-apikey'], }; (func { var img_path = me.makePath(pos); printlog('debug', 'img_path: ', img_path); 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); printlog('debug', 'requesting ' ~ img_url); http.save(img_url, img_path) .done(func {printlog('info', 'received image ' ~ img_path); tile.set("src", img_path);}) .fail(func (r) printlog('warn', 'Failed to get image ' ~ img_path ~ ' ' ~ r.status ~ ': ' ~ r.reason)); } else { # cached image found, reusing printlog('debug', 'loading ' ~ img_path); tile.set("src", img_path); } })(); } me.last_tile = tile_index; me.last_type = data['tiles-type']; } }, del : func() { me.update_timer.stop(); call(canvas.Window.del, [], me); }, }; var MapNavDisplay = { new : func (display) { var m = { parents: [MapNavDisplay] }; m.display = display; m.map = m.display.createGroup().createChild('map'); m.ctrl_ns = canvas.Map.Controller.get("Aircraft position"); m.ctrl_ns.SOURCES["map-dialog"] = { getPosition: func subvec(geo.aircraft_position().latlon(), 0, 2), getAltitude: func getprop('/position/altitude-ft'), getHeading: func { if (me.aircraft_heading) getprop('/orientation/heading-deg') else 0 }, aircraft_heading: 1, }; m.Styles = { get : func(type) return m.Styles[type], }; m.Options = { get : func(type) return m.Options[type], }; m.listeners = []; return m; }, showMap : func { me.setMap(); me.setStyles(); me.setOptions(); me.refresh(); }, setMap : func { var source = me.ctrl_ns.SOURCES["map-dialog"]; me.map.setController("Aircraft position", "map-dialog"); me.map.setRange(40); me.map.setTranslation( me.display.get("view[0]")/2, me.display.get("view[1]")/2 ); }, setStyles : func { ## set up a few keys supported by the DME.symbol file to customize appearance: {{{ me.Styles.DME = {}; me.Styles.DME.debug = 1; # HACK for benchmarking/debugging purposes me.Styles.DME.animation_test = 0; # for prototyping animated symbols me.Styles.DME.scale_factor = 0.4; # 40% (applied to whole group) me.Styles.DME.line_width = 3.0; me.Styles.DME.color_tuned = [0,1,0]; #rgb me.Styles.DME.color_default = [1,1,0]; #rgb me.Styles.APT = {}; me.Styles.APT.scale_factor = 0.4; # 40% (applied to whole group) me.Styles.APT.line_width = 3.0; me.Styles.APT.color_default = [0,0.6,0.85]; #rgb me.Styles.APT.label_font_color = me.Styles.APT.color_default; me.Styles.APT.label_font_size=16; me.Styles.TFC = {}; me.Styles.TFC.scale_factor = 0.4; # 40% (applied to whole group) me.Styles.WPT = {}; me.Styles.WPT.scale_factor = 0.5; # 50% (applied to whole group) me.Styles.RTE = {}; me.Styles.RTE.line_width = 2; me.Styles.FLT = {}; me.Styles.FLT.line_width = 3; me.Styles.FIX = {}; me.Styles.FIX.color = [1,0,0]; me.Styles.FIX.scale_factor = 0.4; # 40% me.Styles.VOR = {}; me.Styles.VOR.range_line_width = 2; me.Styles.VOR.radial_line_width = 1; me.Styles.VOR.scale_factor = 0.6; # 60% me.Styles.APS = {}; me.Styles.APS.scale_factor = 0.5; }, #}}} setOptions : func { me.Options.FLT = {}; }, make_update_wrapper : func(name) { if (!contains(me.Options, name)) me.Options[name] = {}; me.Options[name].update_wrapper = func(layer, fn) { fn(); } }, ToggleLayerVisible : func(name) { (var l = me.map.getLayer(name)).setVisible(l.getVisible()); }, SetLayerVisible : func(name,n=1) { me.map.getLayer(name).setVisible(n); }, refresh : func { 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 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') me.make_update_wrapper(type.name); me.map.addLayer(factory: canvas.SymbolLayer, type_arg: type.name, visible: type.vis, priority: type.zindex, style: me.Styles.get(type.name), options: me.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(me.listeners, setlistener("/sim/gui/dialogs/map-canvas/draw-"~name, func(n) me.SetLayerVisible(name,n.getValue()), 1, 2) ); })(); } }, };