Completed
Push — master ( 7a8794...5ef0a0 )
by Yannick
31:30
created

➔ L.Class.extend.courseAtTime   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
c 0
b 0
f 0
nc 8
dl 0
loc 11
rs 8.8571
nop 1
1
// UMD initialization to work with CommonJS, AMD and basic browser script include
2
(function (factory) {
3
	var L;
4
	if (typeof define === 'function' && define.amd) {
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
5
		// AMD
6
		define(['leaflet'], factory);
7
	} else if (typeof module === 'object' && typeof module.exports === "object") {
8
		// Node/CommonJS
9
		L = require('leaflet');
10
		module.exports = factory(L);
11
	} else {
12
		// Browser globals
13
		if (typeof window.L === 'undefined')
14
			throw 'Leaflet must be loaded first';
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
15
		factory(window.L);
16
	}
17
}(function (L) {
18
19
L.Playback = L.Playback || {};
20
21
L.Playback.Util = L.Class.extend({
22
  statics: {
23
24
    DateStr: function(time) {
25
      return new Date(time).toDateString();
26
    },
27
28
    TimeStr: function(time) {
29
      var d = new Date(time);
30
      var h = d.getHours();
31
      var m = d.getMinutes();
32
      var s = d.getSeconds();
33
      var tms = time / 1000;
34
      var dec = (tms - Math.floor(tms)).toFixed(2).slice(1);
35
      var mer = 'AM';
36
      if (h > 11) {
37
        h %= 12;
38
        mer = 'PM';
39
      } 
40
      if (h === 0) h = 12;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
41
      if (m < 10) m = '0' + m;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
42
      if (s < 10) s = '0' + s;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
43
      return h + ':' + m + ':' + s + dec + ' ' + mer;
44
    },
45
46
    ParseGPX: function(gpx) {
47
      var geojson = {
48
        type: 'Feature',
49
        geometry: {
50
          type: 'MultiPoint',
51
          coordinates: []
52
        },
53
        properties: {
54
          time: [],
55
          speed: [],
56
          altitude: []
57
        },
58
        bbox: []
59
      };
60
      var xml = $.parseXML(gpx);
61
      var pts = $(xml).find('trkpt');
62
      for (var i=0, len=pts.length; i<len; i++) {
63
        var p = pts[i];
64
        var lat = parseFloat(p.getAttribute('lat'));
65
        var lng = parseFloat(p.getAttribute('lon'));
66
        var timeStr = $(p).find('time').text();
67
        var eleStr = $(p).find('ele').text();
68
        var t = new Date(timeStr).getTime();
69
        var ele = parseFloat(eleStr);
70
71
        var coords = geojson.geometry.coordinates;
72
        var props = geojson.properties;
73
        var time = props.time;
74
        var altitude = geojson.properties.altitude;
75
76
        coords.push([lng,lat]);
77
        time.push(t);
78
        altitude.push(ele);
79
      }
80
      return geojson;
81
    }
82
  }
83
84
});
85
86
L.Playback = L.Playback || {};
87
88
L.Playback.MoveableMarker = L.Marker.extend({    
89
    initialize: function (startLatLng, options, feature) {
90
        var marker_options = options.marker || {};
91
92
        if (jQuery.isFunction(marker_options)){
93
            marker_options = marker_options(feature);
94
        }
95
        L.Marker.prototype.initialize.call(this, startLatLng, marker_options);
96
        this.popupContent = '';
97
        this.feature = feature;
98
        if (marker_options.getPopup){
99
            this.popupContent = marker_options.getPopup(feature);
100
        }
101
        if(options.popups)
102
        {
103
            this.bindPopup(this.getPopupContent() + startLatLng.toString());
104
        }
105
        if(options.labels)
106
        {
107
            if(this.bindLabel)
108
            {
109
                this.bindLabel(this.getPopupContent());
110
            }
111
            else
112
            {
113
                console.log("Label binding requires leaflet-label (https://github.com/Leaflet/Leaflet.label)");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
114
            }
115
        }
116
    },
117
    getPopupContent: function() {
118
        if (this.popupContent !== ''){
119
            return '<b>' + this.popupContent + '</b><br/>';
120
        }
121
        return '';
122
    },
123
    move: function (latLng, transitionTime) {
124
        // Only if CSS3 transitions are supported
125
        if (L.DomUtil.TRANSITION) {
126
            if (this._icon) { 
127
                this._icon.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
128
                if (this._popup && this._popup._wrapper)
129
                    this._popup._wrapper.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
130
            }
131
            if (this._shadow) { 
132
                this._shadow.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
133
            }
134
        }
135
        this.setLatLng(latLng);
136
        if (this._popup) {
137
            this._popup.setContent(this.getPopupContent() + this._latlng.toString());
138
        }
139
    }
140
});
141
142
L.Playback = L.Playback || {};
143
144
L.Playback.Track = L.Class.extend({
145
146
        initialize : function (geoJSON, options) {
147
            options = options || {};
148
            var tickLen = options.tickLen || 250;
149
            this._staleTime = options.staleTime || 60*60*1000;
150
            this._fadeMarkersWhenStale = options.fadeMarkersWhenStale || true;
151
            this._beginTime = options.beginTime;
152
            this._finalTime = options.finalTime;
153
154
            this._geoJSON = geoJSON;
155
            this._tickLen = tickLen;
156
            this._ticks = [];
157
            this._marker = null;
158
	    this._orientations = [];
159
160
            var sampleTimes = geoJSON.properties.time;
161
162
            this._orientIcon = options.orientIcons;
163
            var previousOrientation;
164
165
            var samples = geoJSON.geometry.coordinates;
166
            var currSample = samples[0];
167
            var nextSample = samples[1];
168
169
            var currSampleTime = sampleTimes[0];
170
            var t = currSampleTime;  // t is used to iterate through tick times
171
            var nextSampleTime = sampleTimes[1];
172
            var tmod = t % tickLen; // ms past a tick time
173
            var rem,
174
            ratio;
175
176
            // handle edge case of only one t sample
177
            if (sampleTimes.length === 1) {
178
                if (tmod !== 0)
179
                    t += tickLen - tmod;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
180
                this._ticks[t] = samples[0];
181
		this._orientations[t] = 0;
182
                this._startTime = t;
183
                this._endTime = t;
184
                return;
185
            }
186
187
            // interpolate first tick if t not a tick time
188
            if (tmod !== 0) {
189
                rem = tickLen - tmod;
190
                ratio = rem / (nextSampleTime - currSampleTime);
191
                t += rem;
192
                this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
193
		this._orientations[t] = this._directionOfPoint(currSample,nextSample);
194
                previousOrientation = this._orientations[t];
195
            } else {
196
                this._ticks[t] = currSample;
197
		this._orientations[t] = this._directionOfPoint(currSample,nextSample);
198
                previousOrientation = this._orientations[t];
199
            }
200
201
            this._startTime = t;
202
            t += tickLen;
203
            while (t < nextSampleTime) {
204
                ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
205
                this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
206
		this._orientations[t] = this._directionOfPoint(currSample,nextSample);
207
                previousOrientation = this._orientations[t];
208
                t += tickLen;
209
            }
210
211
            // iterating through the rest of the samples
212
            for (var i = 1, len = samples.length; i < len; i++) {
213
                currSample = samples[i];
214
                nextSample = samples[i + 1];
215
                t = currSampleTime = sampleTimes[i];
216
                nextSampleTime = sampleTimes[i + 1];
217
218
                tmod = t % tickLen;
219
                if (tmod !== 0 && nextSampleTime) {
220
                    rem = tickLen - tmod;
221
                    ratio = rem / (nextSampleTime - currSampleTime);
222
                    t += rem;
223
                    this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
224
		    if(nextSample){
225
                        this._orientations[t] = this._directionOfPoint(currSample,nextSample);
226
                        previousOrientation = this._orientations[t];
227
                    } else {
228
                        this._orientations[t] = previousOrientation;
229
                    }
230
                } else {
231
                    this._ticks[t] = currSample;
232
                    if(nextSample){
233
                        this._orientations[t] = this._directionOfPoint(currSample,nextSample);
234
                        previousOrientation = this._orientations[t];
235
                    } else {
236
                        this._orientations[t] = previousOrientation;
237
                    }
238
                }
239
240
                t += tickLen;
241
                while (t < nextSampleTime) {
242
                    ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
243
244
                    if (nextSampleTime - currSampleTime > options.maxInterpolationTime){
245
                        this._ticks[t] = currSample;
246
247
			if(nextSample){
248
                            this._orientations[t] = this._directionOfPoint(currSample,nextSample);
249
                            previousOrientation = this._orientations[t];
250
                        } else {
251
                            this._orientations[t] = previousOrientation;
252
                        }
253
                    }
254
                    else {
255
                        this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
256
			if(nextSample) {
257
                            this._orientations[t] = this._directionOfPoint(currSample,nextSample);
258
                            previousOrientation = this._orientations[t];
259
                        } else {
260
                            this._orientations[t] = previousOrientation;
261
                        }
262
                    }
263
                    t += tickLen;
264
                }
265
            }
266
267
            // the last t in the while would be past bounds
268
            this._endTime = t - tickLen;
269
            this._lastTick = this._ticks[this._endTime];
270
271
        },
272
273
        _interpolatePoint : function (start, end, ratio) {
274
            try {
275
                var delta = [end[0] - start[0], end[1] - start[1]];
276
                var offset = [delta[0] * ratio, delta[1] * ratio];
277
                return [start[0] + offset[0], start[1] + offset[1]];
278
            } catch (e) {
279
                console.log('err: cant interpolate a point');
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
280
                console.log(['start', start]);
281
                console.log(['end', end]);
282
                console.log(['ratio', ratio]);
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
283
            }
284
        },
285
286
        _directionOfPoint:function(start,end){
287
            return this._getBearing(start[1],start[0],end[1],end[0]);
288
        },
289
290
        _getBearing:function(startLat,startLong,endLat,endLong){
291
              startLat = this._radians(startLat);
292
              startLong = this._radians(startLong);
293
              endLat = this._radians(endLat);
294
              endLong = this._radians(endLong);
295
296
              var dLong = endLong - startLong;
297
              var dPhi = Math.log(Math.tan(endLat/2.0+Math.PI/4.0)/Math.tan(startLat/2.0+Math.PI/4.0));
298
              if (Math.abs(dLong) > Math.PI){
299
                if (dLong > 0.0)
300
                   dLong = -(2.0 * Math.PI - dLong);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
301
                else
302
                   dLong = (2.0 * Math.PI + dLong);
303
              }
304
              return (this._degrees(Math.atan2(dLong, dPhi)) + 360.0) % 360.0;
305
        },
306
        _radians:function(n) {
307
          return n * (Math.PI / 180);
308
        },
309
        _degrees:function(n) {
310
          return n * (180 / Math.PI);
311
        },
312
313
        getFirstTick : function () {
314
            return this._ticks[this._startTime];
315
        },
316
317
        getLastTick : function () {
318
            return this._ticks[this._endTime];
319
        },
320
321
        getStartTime : function () {
322
            return this._startTime;
323
        },
324
325
        getEndTime : function () {
326
            return this._endTime;
327
        },
328
329
        getTickMultiPoint : function () {
330
            var t = this.getStartTime();
331
            var endT = this.getEndTime();
332
            var coordinates = [];
333
            var time = [];
334
            while (t <= endT) {
335
                time.push(t);
336
                coordinates.push(this.tick(t));
337
                t += this._tickLen;
338
            }
339
340
            return {
341
                type : 'Feature',
342
                geometry : {
343
                    type : 'MultiPoint',
344
                    coordinates : coordinates
345
                },
346
                properties : {
347
                    time : time
348
                }
349
            };
350
        },
351
352
        trackPresentAtTick : function(timestamp)
353
        {
354
            return (timestamp >= this._startTime);
355
        },
356
        trackStaleAtTick : function(timestamp)
357
        {
358
	    //if ((this._endTime + this._staleTime) <= timestamp) console.log('endtime: '+this._endTime+' - timestamp: '+timestamp+' => true !');
359
	    //else console.log('endtime: '+this._endTime+' - timestamp: '+timestamp);
360
	    return ((this._endTime + this._staleTime) <= timestamp);
361
        },
362
        tick : function (timestamp) {
363
            if (timestamp > this._endTime)
364
                timestamp = this._endTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
365
            if (typeof this._finalTime != 'undefined' && timestamp > this._finalTime)
366
                timestamp = this._finalTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
367
            if (timestamp < this._startTime)
368
                timestamp = this._startTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
369
            return this._ticks[timestamp];
370
        },
371
        courseAtTime: function(timestamp)
372
        {
373
            //return 90;
374
            if (timestamp > this._endTime)
375
               timestamp = this._endTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
376
            if (typeof this._finalTime != 'undefined' && timestamp > this._finalTime)
377
                timestamp = this._finalTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
378
            if (timestamp < this._startTime)
379
                timestamp = this._startTime;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
380
            return this._orientations[timestamp];
381
        },
382
        setMarker : function(timestamp, options){
383
            var lngLat = null;
0 ignored issues
show
Unused Code introduced by
The assignment to lngLat seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
384
            // if time stamp is not set, then get first tick
385
            if (timestamp) {
386
                lngLat = this.tick(timestamp);
387
            }
388
            else {
389
                lngLat = this.getFirstTick();
390
            }
391
            if (lngLat) {
392
                var latLng = new L.LatLng(lngLat[1], lngLat[0]);
393
                this._marker = new L.Playback.MoveableMarker(latLng, options, this._geoJSON);
394
		if(options.mouseOverCallback) {
395
                    this._marker.on('mouseover',options.mouseOverCallback);
396
                }
397
		if(options.clickCallback) {
398
                    this._marker.on('click',options.clickCallback);
399
                }
400
		//hide the marker if its not present yet and fadeMarkersWhenStale is true
401
		if(this._fadeMarkersWhenStale && !this.trackPresentAtTick(timestamp))
402
		{
403
			this._marker.setOpacity(0);
404
		}
405
            }
406
	    return this._marker;
407
        },
408
        moveMarker : function(latLng, transitionTime,timestamp) {
409
            if (this._marker) {
410
                if(this._fadeMarkersWhenStale) {
411
                    //show the marker if its now present
412
                    if(this.trackPresentAtTick(timestamp)) {
413
                        this._marker.setOpacity(1);
414
                    } else {
415
                        this._marker.setOpacity(0);
416
                    }
417
                    if(this.trackStaleAtTick(timestamp)) {
418
                        this._marker.setOpacity(0);
419
                    }
420
                }
421
                if(this._orientIcon){
422
                    this._marker.setIconAngle(this.courseAtTime(timestamp));
423
                }
424
                this._marker.move(latLng, transitionTime);
425
            }
426
        },
427
        getMarker : function() {
428
            return this._marker;
429
        }
430
431
    });
432
433
L.Playback = L.Playback || {};
434
435
L.Playback.TrackController = L.Class.extend({
436
437
    initialize : function (map, tracks, options) {
438
        this.options = options || {};
439
        this._map = map;
440
        this._tracks = [];
441
        // initialize tick points
442
        this.setTracks(tracks);
443
    },
444
445
    clearTracks: function(){
446
        while (this._tracks.length > 0) {
447
            var track = this._tracks.pop();
448
            var marker = track.getMarker();
449
            if (marker){
450
                this._map.removeLayer(marker);
451
            }
452
        }
453
    },
454
455
    setTracks : function (tracks) {
456
        // reset current tracks
457
        this.clearTracks();
458
        this.addTracks(tracks);
459
    },
460
    addTracks : function (tracks) {
461
        // return if nothing is set
462
        if (!tracks) {
463
            return;
464
        }
465
        if (tracks instanceof Array) {
466
            for (var i = 0, len = tracks.length; i < len; i++) {
467
                this.addTrack(tracks[i]);
468
            }
469
        } else {
470
            this.addTrack(tracks);
471
        }
472
    },
473
474
    // add single track
475
    addTrack : function (track, timestamp) {
476
        // return if nothing is set
477
        if (!track) {
478
            return;
479
        }
480
        var marker = track.setMarker(timestamp, this.options);
481
        if (marker) {
482
            marker.addTo(this._map);
483
            this._tracks.push(track);
484
        }
485
    },
486
487
    tock : function (timestamp, transitionTime) {
488
        for (var i = 0, len = this._tracks.length; i < len; i++) {
489
            var lngLat = this._tracks[i].tick(timestamp);
490
            var latLng = new L.LatLng(lngLat[1], lngLat[0]);
491
            this._tracks[i].moveMarker(latLng, transitionTime,timestamp);
492
        }
493
    },
494
495
    getStartTime : function () {
496
        var earliestTime = 0;
497
498
        if (this._tracks.length > 0) {
499
            earliestTime = this._tracks[0].getStartTime();
500
            for (var i = 1, len = this._tracks.length; i < len; i++) {
501
                var t = this._tracks[i].getStartTime();
502
                if (t < earliestTime) {
503
                    earliestTime = t;
504
                }
505
            }
506
        }
507
        return earliestTime;
508
    },
509
510
    getEndTime : function () {
511
        var latestTime = 0;
512
        if (this._tracks.length > 0){
513
            latestTime = this._tracks[0].getEndTime();
514
            for (var i = 1, len = this._tracks.length; i < len; i++) {
515
                var t = this._tracks[i].getEndTime();
516
                if (t > latestTime) {
517
                    latestTime = t;
518
                }
519
            }
520
        }
521
        return latestTime;
522
    },
523
524
    getTracks : function () {
525
        return this._tracks;
526
    }
527
});
528
L.Playback = L.Playback || {};
529
530
L.Playback.Clock = L.Class.extend({
531
    initialize: function (trackController, callback, options) {
532
	this._trackController = trackController;
533
	this._callbacksArry = [];
534
	if (callback) this.addCallback(callback);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
535
	L.setOptions(this, options);
536
	this._speed = this.options.speed;
537
	this._tickLen = this.options.tickLen;
538
	this._cursor = trackController.getStartTime();
539
	this._transitionTime = this._tickLen / this._speed;
540
    },
541
542
    _tick: function (self) {
543
	self._trackController.tock(self._cursor, self._transitionTime);
544
	self._callbacks(self._cursor);
545
	if (self._cursor > self._trackController.getEndTime()) {
546
	    clearInterval(self._intervalID);
547
	    return;
548
	}
549
	self._cursor += self._tickLen;
550
    },
551
552
    _callbacks: function(cursor) {
553
	var arry = this._callbacksArry;
554
	for (var i=0, len=arry.length; i<len; i++) {
555
	    arry[i](cursor);
556
	}
557
    },
558
559
    addCallback: function(fn) {
560
	this._callbacksArry.push(fn);
561
    },
562
563
    start: function () {
564
	if (this._intervalID) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
565
	this._intervalID = window.setInterval(
566
	    this._tick, 
567
	    this._transitionTime, 
568
	    this
569
	);
570
    },
571
572
    stop: function () {
573
	if (!this._intervalID) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
574
	clearInterval(this._intervalID);
575
	this._intervalID = null;
576
    },
577
578
    getSpeed: function() {
579
	return this._speed;
580
    },
581
582
    isPlaying: function() {
583
	return this._intervalID ? true : false;
584
    },
585
586
    setSpeed: function (speed) {
587
	this._speed = speed;
588
	this._transitionTime = this._tickLen / speed;
589
	if (this._intervalID) {
590
	    this.stop();
591
	    this.start();
592
	}
593
    },
594
595
    setCursor: function (ms) {
596
	var time = parseInt(ms);
597
	if (!time) return;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
598
	var mod = time % this._tickLen;
599
	if (mod !== 0) {
600
	    time += this._tickLen - mod;
601
	}
602
	this._cursor = time;
603
	this._trackController.tock(this._cursor, 0);
604
	this._callbacks(this._cursor);
605
    },
606
607
    getTime: function() {
608
	return this._cursor;
609
    },
610
611
    getStartTime: function() {
612
	return this._trackController.getStartTime();
613
    },
614
615
    getEndTime: function() {
616
	return this._trackController.getEndTime();
617
    },
618
619
    getTickLen: function() {
620
	return this._tickLen;
621
    }
622
623
});
624
625
// Simply shows all of the track points as circles.
626
// TODO: Associate circle color with the marker color.
627
628
L.Playback = L.Playback || {};
629
630
L.Playback.TracksLayer = L.Class.extend({
631
    initialize : function (map, options) {
632
        var layer_options = options.layer || {};
633
        if (jQuery.isFunction(layer_options)){
634
            layer_options = layer_options(feature);
0 ignored issues
show
Bug introduced by
The variable feature seems to be never declared. If this is a global, consider adding a /** global: feature */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
635
        }
636
        if (!layer_options.pointToLayer) {
637
            layer_options.pointToLayer = function (featureData, latlng) {
638
                return new L.CircleMarker(latlng, { radius : 5 });
639
            };
640
        }
641
        this.layer = new L.GeoJSON(null, layer_options);
642
        var overlayControl = {
643
            'GPS Tracks' : this.layer
644
        };
645
        L.control.layers(null, overlayControl, {
646
            collapsed : false
647
        }).addTo(map);
648
    },
649
650
    // clear all geoJSON layers
651
    clearLayer : function(){
652
        for (var i in this.layer._layers) {
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
653
            this.layer.removeLayer(this.layer._layers[i]);
654
        }
655
    },
656
657
    // add new geoJSON layer
658
    addLayer : function(geoJSON) {
659
        this.layer.addData(geoJSON);
660
    }
661
});
662
L.Playback = L.Playback || {};
663
664
L.Playback.DateControl = L.Control.extend({
665
    options : {
666
        position : 'bottomleft',
667
        dateFormatFn: L.Playback.Util.DateStr,
668
        timeFormatFn: L.Playback.Util.TimeStr
669
    },
670
671
    initialize : function (playback, options) {
672
        L.setOptions(this, options);
673
        this.playback = playback;
674
    },
675
676
    onAdd : function (map) {
0 ignored issues
show
Unused Code introduced by
The parameter map is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
677
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
678
679
        var self = this;
680
        var playback = this.playback;
681
        var time = playback.getTime();
682
683
        var datetime = L.DomUtil.create('div', 'datetimeControl', this._container);
684
685
        // date time
686
        this._date = L.DomUtil.create('p', '', datetime);
687
        this._time = L.DomUtil.create('p', '', datetime);
688
689
        this._date.innerHTML = this.options.dateFormatFn(time);
690
        this._time.innerHTML = this.options.timeFormatFn(time);
691
692
        // setup callback
693
        playback.addCallback(function (ms) {
694
            self._date.innerHTML = self.options.dateFormatFn(ms);
695
            self._time.innerHTML = self.options.timeFormatFn(ms);
696
        });
697
698
        return this._container;
699
    }
700
});
701
702
L.Playback.PlayControl = L.Control.extend({
703
    options : {
704
        position : 'bottomright'
705
    },
706
707
    initialize : function (playback) {
708
        this.playback = playback;
709
    },
710
711
    onAdd : function (map) {
0 ignored issues
show
Unused Code introduced by
The parameter map is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
712
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
713
714
        var self = this;
715
        var playback = this.playback;
716
        playback.setSpeed(100);
717
718
        var playControl = L.DomUtil.create('div', 'playControl', this._container);
719
720
721
        this._button = L.DomUtil.create('button', '', playControl);
722
        this._button.innerHTML = 'Play';
723
724
725
        var stop = L.DomEvent.stopPropagation;
726
727
        L.DomEvent
728
        .on(this._button, 'click', stop)
729
        .on(this._button, 'mousedown', stop)
730
        .on(this._button, 'dblclick', stop)
731
        .on(this._button, 'click', L.DomEvent.preventDefault)
732
        .on(this._button, 'click', play, this);
733
734
        function play(){
735
            if (playback.isPlaying()) {
736
                playback.stop();
737
                self._button.innerHTML = 'Play';
738
            }
739
            else {
740
                playback.start();
741
                self._button.innerHTML = 'Stop';
742
            }
743
        }
744
745
        return this._container;
746
    }
747
});
748
749
L.Playback.SliderControl = L.Control.extend({
750
    options : {
751
        position : 'bottomleft'
752
    },
753
754
    initialize : function (playback) {
755
        this.playback = playback;
756
    },
757
758
    onAdd : function (map) {
759
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
760
761
        var self = this;
762
        var playback = this.playback;
763
764
        // slider
765
        this._slider = L.DomUtil.create('input', 'slider', this._container);
766
        this._slider.type = 'range';
767
        this._slider.min = playback.getStartTime();
768
        this._slider.max = playback.getEndTime();
769
        this._slider.value = playback.getTime();
770
771
        var stop = L.DomEvent.stopPropagation;
772
773
        L.DomEvent
774
        .on(this._slider, 'click', stop)
775
        .on(this._slider, 'mousedown', stop)
776
        .on(this._slider, 'dblclick', stop)
777
        .on(this._slider, 'click', L.DomEvent.preventDefault)
778
        //.on(this._slider, 'mousemove', L.DomEvent.preventDefault)
779
        .on(this._slider, 'change', onSliderChange, this)
780
        .on(this._slider, 'mousemove', onSliderChange, this);           
781
782
783
        function onSliderChange(e) {
784
            var val = Number(e.target.value);
785
            playback.setCursor(val);
786
        }
787
788
        playback.addCallback(function (ms) {
789
            self._slider.value = ms;
790
        });
791
792
        map.on('playback:add_tracks', function() {
793
            self._slider.min = playback.getStartTime();
794
            self._slider.max = playback.getEndTime();
795
            self._slider.value = playback.getTime();
796
        });
797
        return this._container;
798
    }
799
});
800
801
L.Playback = L.Playback.Clock.extend({
802
        statics : {
803
            MoveableMarker : L.Playback.MoveableMarker,
804
            Track : L.Playback.Track,
805
            TrackController : L.Playback.TrackController,
806
            Clock : L.Playback.Clock,
807
            Util : L.Playback.Util,
808
            TracksLayer : L.Playback.TracksLayer,
809
            PlayControl : L.Playback.PlayControl,
810
            DateControl : L.Playback.DateControl,
811
            SliderControl : L.Playback.SliderControl
812
        },
813
814
        options : {
815
            tickLen: 250,
816
            speed: 1,
817
            maxInterpolationTime: 5*60*1000, // 5 minutes
818
            tracksLayer : true,
819
            playControl: false,
820
            dateControl: false,
821
            sliderControl: false,
822
            // options
823
            layer: {
824
                // pointToLayer(featureData, latlng)
825
            },
826
            marker : {
827
                // getPopup(feature)
828
            }
829
        },
830
831
        initialize : function (map, geoJSON, callback, options) {
832
            L.setOptions(this, options);
833
            this._map = map;
834
            this._trackController = new L.Playback.TrackController(map, null, this.options);
835
            L.Playback.Clock.prototype.initialize.call(this, this._trackController, callback, this.options);
836
            if (this.options.tracksLayer) {
837
                this._tracksLayer = new L.Playback.TracksLayer(map, options);
838
            }
839
840
            this.setData(geoJSON);
841
842
            if (this.options.playControl) {
843
                this.playControl = new L.Playback.PlayControl(this);
844
                this.playControl.addTo(map);
845
            }
846
847
            if (this.options.sliderControl) {
848
                this.sliderControl = new L.Playback.SliderControl(this);
849
                this.sliderControl.addTo(map);
850
            }
851
852
            if (this.options.dateControl) {
853
                this.dateControl = new L.Playback.DateControl(this, options);
854
                this.dateControl.addTo(map);
855
            }
856
857
        },
858
        clearData : function(){
859
            this._trackController.clearTracks();
860
            if (this._tracksLayer) {
861
                this._tracksLayer.clearLayer();
862
            }
863
        },
864
865
        setData : function (geoJSON) {
866
            this.clearData();
867
            this.addData(geoJSON, this.getTime());
868
            this.setCursor(this.getStartTime());
869
        },
870
871
        // bad implementation
872
        addData : function (geoJSON, ms) {
873
            // return if data not set
874
            if (!geoJSON) {
875
                return;
876
            }
877
            if (geoJSON instanceof Array) {
878
                for (var i = 0, len = geoJSON.length; i < len; i++) {
879
                    this._trackController.addTrack(new L.Playback.Track(geoJSON[i], this.options), ms);
880
                }
881
            } else {
882
                this._trackController.addTrack(new L.Playback.Track(geoJSON, this.options), ms);
883
            }
884
885
            this._map.fire('playback:set:data');
886
            if (this.options.tracksLayer) {
887
                this._tracksLayer.addLayer(geoJSON);
888
            }
889
        },
890
891
        destroy: function() {
892
            this.clearData();
893
            if (this.playControl) {
894
                this._map.removeControl(this.playControl);
895
            }
896
            if (this.sliderControl) {
897
                this._map.removeControl(this.sliderControl);
898
            }
899
            if (this.dateControl) {
900
                this._map.removeControl(this.dateControl);
901
            }
902
        }
903
    });
904
905
L.Map.addInitHook(function () {
906
    if (this.options.playback) {
907
        this.playback = new L.Playback(this);
908
    }
909
});
910
911
L.playback = function (map, geoJSON, callback, options) {
912
    return new L.Playback(map, geoJSON, callback, options);
913
};
914
return L.Playback;
915
916
}));
917