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,