diff --git a/webgui/topics/Map.js b/webgui/topics/Map.js index 14804016f..1d4f90a50 100644 --- a/webgui/topics/Map.js +++ b/webgui/topics/Map.js @@ -1,5 +1,5 @@ define([ - 'knockout', 'text!./Map.html', './Map/NavdbLayer' + 'knockout', 'text!./Map.html', './Map/NavdbLayer', './Map/AILayer' ], function(ko, htmlString, NavdbLayer ) { function ViewModel(params) { @@ -78,6 +78,7 @@ define([ "Track" : trackLayer, "NavDB": L.navdbLayer(), + "AI": L.aiLayer(), "VFRMap.com Sectionals (US)" : new L.TileLayer('http://vfrmap.com/20140918/tiles/vfrc/{z}/{y}/{x}.jpg', { maxZoom : 12, diff --git a/webgui/topics/Map/AILayer.js b/webgui/topics/Map/AILayer.js new file mode 100644 index 000000000..96c009110 --- /dev/null +++ b/webgui/topics/Map/AILayer.js @@ -0,0 +1,186 @@ +(function(factory) { + if (typeof define === "function" && define.amd) { + // AMD. Register as an anonymous module. + define([ + 'leaflet', './MapIcons' + ], factory); + } else { + // Browser globals + factory(); + } +}(function(leaflet, MAP_ICON) { + + var SGPropertyNode = function(json) { + this.json = json; + }; + + SGPropertyNode.prototype.getValue = function() { + return this.json.value; + } + + SGPropertyNode.prototype.getName = function() { + return this.json.name; + } + + SGPropertyNode.prototype.getPath = function() { + return this.json.path; + } + + SGPropertyNode.prototype.getIndex = function() { + return this.json.index; + } + + SGPropertyNode.prototype.getChildren = function(name) { + var reply = []; + this.json.children.forEach(function(child) { + if (name && child.name == name) + reply.push(new SGPropertyNode(child)); + }); + return reply; + } + + SGPropertyNode.prototype.getNode = function(name, index) { + if (!index) + index = 0; + for (var i = 0; i < this.json.children.length; i++) { + var child = this.json.children[i]; + if (child.name == name && child.index == index) + return new SGPropertyNode(child); + } + } + + leaflet.AILayer = leaflet.GeoJSON.extend({ + options : { + pointToLayer : function(feature, latlng) { + var options = { + title : feature.properties.callsign, + alt : feature.properties.callsign, + riseOnHover : true, + }; + + if (feature.properties.type == "aircraft" || feature.properties.type == "multiplayer") { + options.angle = feature.properties.heading; + options.icon = MAP_ICON["aircraft"]; + } + return new leaflet.RotatedMarker(latlng, options); + }, + + onEachFeature : function(feature, layer) { + if (feature.properties) { + var popupString = ''; + layer.bindPopup(popupString, { + maxHeight : 200 + }); + } + }, + + }, + onAdd : function(map) { + leaflet.GeoJSON.prototype.onAdd.call(this, map); + this.update(++this.updateId); + }, + + onRemove : function(map) { + this.updateId++; + leaflet.GeoJSON.prototype.onRemove.call(this, map); + }, + + updateId : 0, + update : function(id) { + var self = this; + + if (self.updateId != id) + return; + + var url = "/json/ai/models?d=99"; + var jqxhr = $.get(url).done(function(data) { + self.clearLayers(); + self.addData(self.aiPropsToGeoJson(data, [ + "aircraft", "multiplayer", "carrier" + ])); + }).fail(function(a, b) { + self.updateId++; + console.log(a, b); + alert('failed to load AI data'); + }).always(function() { + }); + + if (self.updateId == id) { + setTimeout(function() { + self.update(id) + }, 10000); + } + }, + + aiPropsToGeoJson : function(props, types) { + var geoJSON = { + type : "FeatureCollection", + features : [], + }; + + var root = new SGPropertyNode(props); + types.forEach(function(type) { + root.getChildren(type).forEach(function(child) { + + if (!child.getNode("valid").getValue()) + return; + + var position = child.getNode("position"); + var orientation = child.getNode("orientation"); + var velocities = child.getNode("velocities"); + var lon = position.getNode("longitude-deg").getValue(); + var lat = position.getNode("latitude-deg").getValue(); + var alt = position.getNode("altitude-ft") * 0.3048; + var heading = orientation.getNode("true-heading-deg").getValue(); + var id = child.getNode("id").getValue(); + var callsign = ""; + var name = ""; + var speed = 0; + if (type == "multiplayer") { + name = child.getNode("sim").getNode("model").getNode("path").getValue(); + } + if (type == "carrier") { + callsign = child.getNode("sign").getValue(); + name = child.getNode("name").getValue(); + speed = velocities.getNode("speed-kts").getValue(); + } else { + callsign = child.getNode("callsign").getValue(); + speed = velocities.getNode("true-airspeed-kt").getValue(); + } + + geoJSON.features.push({ + "type" : "Feature", + "geometry" : { + "type" : "Point", + "coordinates" : [ + lon, lat, alt.toFixed(0) + ], + }, + "id" : id, + "properties" : { + "type" : type, + "heading" : heading.toFixed(0), + "speed" : speed.toFixed(0), + "callsign" : callsign, + "name" : name, + }, + }); + + }); + }); + + return geoJSON; + }, + + }); + + leaflet.aiLayer = function(options) { + return new leaflet.AILayer(null, options); + } + +})); diff --git a/webgui/topics/Map/MapIcons.js b/webgui/topics/Map/MapIcons.js new file mode 100644 index 000000000..2d41fa356 --- /dev/null +++ b/webgui/topics/Map/MapIcons.js @@ -0,0 +1,40 @@ +(function(factory) { + if (typeof define === "function" && define.amd) { + // AMD. Register as an anonymous module. + define([ + 'leaflet' + ], factory); + } else { + // Browser globals + factory(L); + } +}(function(leaflet) { + + function SquareIcon(w, url) { + return leaflet.icon({ + iconSize : [ + w, w + ], + iconAnchor : [ + w / 2, w / 2 + ], + popupAnchor : [ + 0, w / 2 - 2 + ], + iconUrl : url, + }) + } + + var MAP_ICON = {}; + MAP_ICON["VOR"] = SquareIcon(30, 'images/vor.svg'); + MAP_ICON["NDB"] = SquareIcon(30, 'images/ndb.svg'); + MAP_ICON["dme"] = SquareIcon(30, 'images/dme.svg'); + MAP_ICON["airport-paved"] = SquareIcon(30, 'images/airport-paved.svg'); + MAP_ICON["airport-unpaved"] = SquareIcon(30, 'images/airport-unpaved.svg'); + MAP_ICON["airport-unknown"] = SquareIcon(30, 'images/airport-unknown.svg'); + MAP_ICON["arp"] = SquareIcon(30, 'images/arp.svg'); + MAP_ICON["aircraft"] = SquareIcon(20, 'images/aircraft.svg'); + + return MAP_ICON; +})); + diff --git a/webgui/topics/Map/NavdbLayer.js b/webgui/topics/Map/NavdbLayer.js index 6539c03f6..d9cd6c058 100644 --- a/webgui/topics/Map/NavdbLayer.js +++ b/webgui/topics/Map/NavdbLayer.js @@ -2,40 +2,15 @@ if (typeof define === "function" && define.amd) { // AMD. Register as an anonymous module. define([ - 'leaflet' + 'leaflet','./MapIcons' ], factory); } else { // Browser globals factory(); } -}(function() { +}(function(leaflet,MAP_ICON) { - function SquareIcon(w, url) { - return L.icon({ - iconSize : [ - w, w - ], - iconAnchor : [ - w / 2, w / 2 - ], - popupAnchor : [ - 0, w / 2 - 2 - ], - iconUrl : url, - }) - } - - var MAP_ICON = {}; - MAP_ICON["VOR"] = SquareIcon(30, 'images/vor.svg'); - MAP_ICON["NDB"] = SquareIcon(30, 'images/ndb.svg'); - MAP_ICON["dme"] = SquareIcon(30, 'images/dme.svg'); - MAP_ICON["airport-paved"] = SquareIcon(30, 'images/airport-paved.svg'); - MAP_ICON["airport-unpaved"] = SquareIcon(30, 'images/airport-unpaved.svg'); - MAP_ICON["airport-unknown"] = SquareIcon(30, 'images/airport-unknown.svg'); - MAP_ICON["arp"] = SquareIcon(30, 'images/arp.svg'); - MAP_ICON["aircraft"] = SquareIcon(20, 'images/aircraft.svg'); - - L.NavdbLayer = L.GeoJSON.extend({ + leaflet.NavdbLayer = leaflet.GeoJSON.extend({ options : { pointToLayer : function(feature, latlng) { var options = { @@ -68,7 +43,7 @@ } } - return new L.RotatedMarker(latlng, options); + return new leaflet.RotatedMarker(latlng, options); }, onEachFeature : function(feature, layer) { @@ -82,6 +57,8 @@ layer.bindPopup(popupString, { maxHeight : 200 }); + if( feature.properties.metar ) { + } } }, @@ -125,14 +102,14 @@ }, onAdd : function(map) { - L.GeoJSON.prototype.onAdd.call(this, map); + leaflet.GeoJSON.prototype.onAdd.call(this, map); this.dirty = true; this.update(++this.updateId); }, onRemove : function(map) { this.updateId++; - L.GeoJSON.prototype.onRemove.call(this, map); + leaflet.GeoJSON.prototype.onRemove.call(this, map); }, invalidate : function() { @@ -190,7 +167,7 @@ }); - L.navdbLayer = function(options) { - return new L.NavdbLayer(null, options); + leaflet.navdbLayer = function(options) { + return new leaflet.NavdbLayer(null, options); } })); diff --git a/webgui/widgets/map.js b/webgui/widgets/map.js index 862ca9386..c534d01de 100644 --- a/webgui/widgets/map.js +++ b/webgui/widgets/map.js @@ -51,6 +51,14 @@ define( 53.5, 10.0 ], MapOptions.zoom || 13); + if( params && params.on ) { + for ( var p in params.on ) { + var h = params.on[p]; + if( typeof(h) === 'function' ) + self.map.on(p,h); + } + } + var baseLayers = { "OpenStreetMaps" : new leaflet.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom : 18,