Phi: add a scale to map
[fg:fgdata.git] / webgui / widgets / map.js
1 define(
2         [
3                 'knockout', 'jquery', 'leaflet', 'text!./map.html'
4         ],
5         function(ko, jquery, leaflet, htmlString) {
6
7             function ViewModel(params, componentInfo) {
8                 var self = this;
9
10                 self.element = componentInfo.element;
11                 self.followAircraft = ko.observable(true);
12
13                 self.toggleFollowAircraft = function(a) {
14                     self.followAircraft(!self.followAircraft());
15                 }
16
17                 self.altitude = ko.observable(0).extend({
18                     fgprop : 'altitude'
19                 });
20
21                 self.tas = ko.observable(0).extend({
22                     fgprop : 'groundspeed'
23                 });
24
25                 self.heading = ko.observable(0).extend({
26                     fgprop : 'heading'
27                 });
28
29                 if (params && params.css) {
30                     for ( var p in params.css) {
31                         $(self.element).css(p, params.css[p]);
32                     }
33                 }
34                 if ($(self.element).height() < 1) {
35                     $(self.element).css("min-height", $(self.element).width());
36                 }
37
38                 var MapOptions = {
39                     attributionControl : false,
40                     dragging: false,
41                 };
42
43                 if (params && params.map) {
44                     for ( var p in params.map) {
45                         MapOptions[p] = params.map[p];
46                     }
47                     MapOptions = params.map;
48                 }
49
50                 self.map = leaflet.map(self.element, MapOptions).setView([
51                         53.5, 10.0
52                 ], MapOptions.zoom || 13);
53
54                 var baseLayers = {
55                     "OpenStreetMaps" : new leaflet.TileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
56                         maxZoom : 18,
57                         attribution : 'Map data &copy; <a target="_blank" href="http://openstreetmap.org">OpenStreetMap</a> contributors'
58                     })
59                 }
60                 self.map.addLayer(baseLayers["OpenStreetMaps"]);
61
62                 if (params && params.hasFollowAircraft ) {
63                     self.map.on('dragstart', function(e) {
64                         self.followAircraft(false);
65                     });
66
67                     var followAircraftControl = L.control();
68
69                     followAircraftControl.onAdd = function(map) {
70                         this._div = L.DomUtil.create('div', 'followAircraft');
71                         this._div.innerHTML = '<img src="images/followAircraft.svg" title="Center Map on Aircraft Position" data-bind="click: toggleFollowAircraft"/>';
72                         return this._div;
73                     }
74                     followAircraftControl.addTo(self.map);
75                 }
76
77                 if (params && params.overlays) {
78                     L.control.layers(baseLayers, params.overlays).addTo(self.map);
79                 }
80
81                 if (params && params.scale) {
82                   L.control.scale(params.scale).addTo(self.map);
83                 }
84
85                 L.RotatedMarker = L.Marker.extend({
86                     options : {
87                         angle : 0
88                     },
89
90                     _setPos : function(pos) {
91                         L.Marker.prototype._setPos.call(this, pos);
92                         this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)';
93                     }
94                 });
95
96                 L.AircraftMarker = L.RotatedMarker
97                         .extend({
98                             options : {
99                                 angle : 0,
100                                 clickable : false,
101                                 keyboard : false,
102                                 icon : L
103                                         .divIcon({
104                                             iconSize : [
105                                                     60, 60
106                                             ],
107                                             iconAnchor : [
108                                                     30, 30
109                                             ],
110                                             className : 'aircraft-marker-icon',
111                                             html : '<svg xmlns="http://www.w3.org/2000/svg" height="100%" width="100%" viewBox="0 0 500 500" preserveAspectRatio="xMinYMin meet"><path d="M250.2,59.002c11.001,0,20.176,9.165,20.176,20.777v122.24l171.12,95.954v42.779l-171.12-49.501v89.227l40.337,29.946v35.446l-60.52-20.18-60.502,20.166v-35.45l40.341-29.946v-89.227l-171.14,49.51v-42.779l171.14-95.954v-122.24c0-11.612,9.15-20.777,20.16-20.777z" fill="#808080" stroke="black" stroke-width="5"/></svg>',
112                                         }),
113                                 zIndexOffset : 10000,
114                                 updateInterval : 100,
115                             },
116
117                             initialize : function(latlng, options) {
118                                 L.RotatedMarker.prototype.initialize(latlng, options);
119                                 L.Util.setOptions(this, options);
120                             },
121
122                             onAdd : function(map) {
123                                 L.RotatedMarker.prototype.onAdd.call(this, map);
124                                 this.popup = L.popup({
125                                     autoPan : false,
126                                     keepInView : false,
127                                     closeButton : false,
128                                     className : 'aircraft-marker-popup',
129                                     closeOnClick : false,
130                                     maxWidth : 200,
131                                     minWidth : 120,
132                                     offset : [
133                                             30, 30
134                                     ],
135                                 }, this);
136                                 this.popup
137                                         .setContent('<div class="aircraft-marker aircraft-marker-altitude"><span data-bind="text: altitude().toFixed(0)"></span>ft</div>'
138                                                 + '<div class="aircraft-marker aircraft-marker-heading"><span data-bind="text: heading().toFixed(0)"></span>&deg</div>'
139                                                 + '<div class="aircraft-marker aircraft-marker-tas"><span data-bind="text: tas().toFixed(0)"></span>kt</div><div style="clear: both"/>');
140                                 this.bindPopup(this.popup);
141                                 this.addTo(this._map);
142                                 this.openPopup();
143                             },
144
145                             onRemove : function(map) {
146                                 if (this.timeoutid != null)
147                                     clearTimeout(this.timeoutid);
148                                 L.RotatedMarker.prototype.onRemove.call(this, map);
149                             },
150
151                         });
152
153                 L.aircraftMarker = function(latlng, options) {
154                     return new L.AircraftMarker(latlng, options);
155                 }
156
157                 var aircraftMarker = L.aircraftMarker(self.map.getCenter());
158
159                 aircraftMarker.addTo(self.map);
160
161                 var aircraftTrack = L.polyline([], {
162                     color : 'red'
163                 }).addTo(self.map);
164
165                 self.latitude = ko.observable(0).extend({
166                     fgprop : 'latitude'
167                 });
168
169                 self.longitude = ko.observable(0).extend({
170                     fgprop : 'longitude'
171                 });
172
173                 self.heading = ko.observable(0).extend({
174                     fgprop : 'true-heading'
175                 });
176
177                 self.position = ko.pureComputed(function() {
178                     return leaflet.latLng(self.latitude(), self.longitude());
179                 }).extend({
180                     rateLimit : 200
181                 });
182
183                 self.position.subscribe(function(newValue) {
184                     aircraftMarker.setLatLng(newValue);
185                 });
186
187                 self.heading.subscribe(function(newValue) {
188                     aircraftMarker.options.angle = newValue;
189                 });
190
191                 self.mapCenter = ko.pureComputed(function() {
192                     return leaflet.latLng(self.latitude(), self.longitude());
193                 }).extend({
194                     rateLimit : 2000
195                 });
196
197                 self.aircraftTrailLength = 60;
198
199                 self.mapCenter.subscribe(function(newValue) {
200                     if (self.followAircraft()) {
201                         self.map.setView(newValue);
202                     }
203
204                     var trail = aircraftTrack.getLatLngs();
205                     while (trail.length > self.aircraftTrailLength)
206                         trail.shift();
207                     trail.push(newValue);
208                     aircraftTrack.setLatLngs(trail);
209                 });
210
211                 var center = leaflet.latLng(self.latitude(), self.longitude());
212                 self.map.setView( center );
213                 aircraftMarker.options.angle = self.heading();
214                 aircraftMarker.setLatLng(center);
215             }
216
217             // Return component definition
218             return {
219                 viewModel : {
220                     createViewModel : function(params, componentInfo) {
221                         return new ViewModel(params, componentInfo);
222                     },
223                 },
224                 template : htmlString,
225             };
226         });