Completed
Push — master ( 994fb6...2e5736 )
by Yannick
06:03
created

js/leaflet-playback.js   F

Complexity

Total Complexity 174
Complexity/F 2.35

Size

Lines of Code 1018
Function Count 74

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 0
wmc 174
c 1
b 0
f 0
nc 1048576
mnd 4
bc 167
fnc 74
dl 0
loc 1018
rs 2.2253
bpm 2.2567
cpm 2.3513
noi 25

1 Function

Rating   Name   Duplication   Size   Complexity  
B leaflet-playback.js ➔ ?!? 0 1003 1

How to fix   Complexity   

Complexity

Complex classes like js/leaflet-playback.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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
        
96
        L.Marker.prototype.initialize.call(this, startLatLng, marker_options);
97
        
98
        this.popupContent = '';
99
        this.feature = feature;
100
		
101
        if (marker_options.getPopup){
102
            this.popupContent = marker_options.getPopup(feature);            
103
        }
104
        
105
        if(options.popups)
106
        {
107
            this.bindPopup(this.getPopupContent() + startLatLng.toString());
108
        }
109
        	
110
        if(options.labels)
111
        {
112
            if(this.bindLabel)
113
            {
114
                this.bindLabel(this.getPopupContent());
115
            }
116
            else
117
            {
118
                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...
119
            }
120
        }
121
    },
122
    
123
    getPopupContent: function() {
124
        if (this.popupContent !== ''){
125
            return '<b>' + this.popupContent + '</b><br/>';
126
        }
127
        
128
        return '';
129
    },
130
131
    move: function (latLng, transitionTime) {
132
        // Only if CSS3 transitions are supported
133
        if (L.DomUtil.TRANSITION) {
134
            if (this._icon) { 
135
                this._icon.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
136
                if (this._popup && this._popup._wrapper)
137
                    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...
138
            }
139
            if (this._shadow) { 
140
                this._shadow.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
141
            }
142
        }
143
        this.setLatLng(latLng);
144
        if (this._popup) {
145
            this._popup.setContent(this.getPopupContent() + this._latlng.toString());
146
        }    
147
    },
148
    
149
    // modify leaflet markers to add our roration code
150
    /*
151
     * Based on comments by @runanet and @coomsie 
152
     * https://github.com/CloudMade/Leaflet/issues/386
153
     *
154
     * Wrapping function is needed to preserve L.Marker.update function
155
     */
156
    _old__setPos:L.Marker.prototype._setPos,
157
    
158
    _updateImg: function (i, a, s) {
159
        a = L.point(s).divideBy(2)._subtract(L.point(a));
160
        var transform = '';
161
        transform += ' translate(' + -a.x + 'px, ' + -a.y + 'px)';
162
        transform += ' rotate(' + this.options.iconAngle + 'deg)';
163
        transform += ' translate(' + a.x + 'px, ' + a.y + 'px)';
164
        i.style[L.DomUtil.TRANSFORM] += transform;
165
    },
166
    setIconAngle: function (iconAngle) {
167
        this.options.iconAngle = iconAngle;
168
        if (this._map)
169
            this.update();
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...
170
    },
171
    _setPos: function (pos) {
172
        if (this._icon) {
173
            this._icon.style[L.DomUtil.TRANSFORM] = "";
174
        }
175
        if (this._shadow) {
176
            this._shadow.style[L.DomUtil.TRANSFORM] = "";
177
        }
178
179
        this._old__setPos.apply(this, [pos]);
180
        if (this.options.iconAngle) {
181
            var a = this.options.icon.options.iconAnchor;
182
            var s = this.options.icon.options.iconSize;
183
            var i;
184
            if (this._icon) {
185
                i = this._icon;
186
                this._updateImg(i, a, s);
187
            }
188
189
            if (this._shadow) {
190
                // Rotate around the icons anchor.
191
                s = this.options.icon.options.shadowSize;
192
                i = this._shadow;
193
                this._updateImg(i, a, s);
194
            }
195
196
        }
197
    }
198
});
199
200
L.Playback = L.Playback || {};
201
202
203
        
204
L.Playback.Track = L.Class.extend({
205
206
        initialize : function (geoJSON, options) {
207
            options = options || {};
208
            var tickLen = options.tickLen || 250;
209
            this._staleTime = options.staleTime || 60*60*1000;
210
            this._fadeMarkersWhenStale = options.fadeMarkersWhenStale || false;
211
            
212
            this._geoJSON = geoJSON;
213
            this._tickLen = tickLen;
214
            this._ticks = [];
215
            this._marker = null;
216
			this._orientations = [];
217
			
218
            var sampleTimes = geoJSON.properties.time;
219
			
220
            this._orientIcon = options.orientIcons;
221
            var previousOrientation;
222
			
223
            var samples = geoJSON.geometry.coordinates;
224
            var currSample = samples[0];
225
            var nextSample = samples[1];
226
			
227
            var currSampleTime = sampleTimes[0];
228
            var t = currSampleTime;  // t is used to iterate through tick times
229
            var nextSampleTime = sampleTimes[1];
230
            var tmod = t % tickLen; // ms past a tick time
231
            var rem,
232
            ratio;
233
234
            // handle edge case of only one t sample
235
            if (sampleTimes.length === 1) {
236
                if (tmod !== 0)
237
                    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...
238
                this._ticks[t] = samples[0];
239
				this._orientations[t] = 0;
240
                this._startTime = t;
241
                this._endTime = t;
242
                return;
243
            }
244
245
            // interpolate first tick if t not a tick time
246
            if (tmod !== 0) {
247
                rem = tickLen - tmod;
248
                ratio = rem / (nextSampleTime - currSampleTime);
249
                t += rem;
250
                this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
251
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
252
                previousOrientation = this._orientations[t];
253
            } else {
254
                this._ticks[t] = currSample;
255
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
256
                previousOrientation = this._orientations[t];
257
            }
258
259
            this._startTime = t;
260
            t += tickLen;
261
            while (t < nextSampleTime) {
262
                ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
263
                this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
264
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
265
                previousOrientation = this._orientations[t];
266
                t += tickLen;
267
            }
268
269
            // iterating through the rest of the samples
270
            for (var i = 1, len = samples.length; i < len; i++) {
271
                currSample = samples[i];
272
                nextSample = samples[i + 1];
273
                t = currSampleTime = sampleTimes[i];
274
                nextSampleTime = sampleTimes[i + 1];
275
276
                tmod = t % tickLen;
277
                if (tmod !== 0 && nextSampleTime) {
278
                    rem = tickLen - tmod;
279
                    ratio = rem / (nextSampleTime - currSampleTime);
280
                    t += rem;
281
                    this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
282
					if(nextSample){
283
                        this._orientations[t] = this._directionOfPoint(currSample,nextSample);
284
                        previousOrientation = this._orientations[t];
285
                    } else {
286
                        this._orientations[t] = previousOrientation;    
287
                    }
288
                } else {
289
                    this._ticks[t] = currSample;
290
                    if(nextSample){
291
                        this._orientations[t] = this._directionOfPoint(currSample,nextSample);
292
                        previousOrientation = this._orientations[t];
293
                    } else {
294
                        this._orientations[t] = previousOrientation;    
295
                    }
296
                }
297
298
                t += tickLen;
299
                while (t < nextSampleTime) {
300
                    ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
301
                    
302
                    if (nextSampleTime - currSampleTime > options.maxInterpolationTime){
303
                        this._ticks[t] = currSample;
304
                        
305
						if(nextSample){
306
                            this._orientations[t] = this._directionOfPoint(currSample,nextSample);
307
                            previousOrientation = this._orientations[t];
308
                        } else {
309
                            this._orientations[t] = previousOrientation;    
310
                        }
311
                    }
312
                    else {
313
                        this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
314
						if(nextSample) {
315
                            this._orientations[t] = this._directionOfPoint(currSample,nextSample);
316
                            previousOrientation = this._orientations[t];
317
                        } else {
318
                            this._orientations[t] = previousOrientation;    
319
                        }
320
                    }
321
                    
322
                    t += tickLen;
323
                }
324
            }
325
326
            // the last t in the while would be past bounds
327
            this._endTime = t - tickLen;
328
            this._lastTick = this._ticks[this._endTime];
329
330
        },
331
332
        _interpolatePoint : function (start, end, ratio) {
333
            try {
334
                var delta = [end[0] - start[0], end[1] - start[1]];
335
                var offset = [delta[0] * ratio, delta[1] * ratio];
336
                return [start[0] + offset[0], start[1] + offset[1]];
337
            } catch (e) {
338
                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...
339
                console.log(['start', start]);
340
                console.log(['end', end]);
341
                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...
342
            }
343
        },
344
        
345
        _directionOfPoint:function(start,end){
346
            return this._getBearing(start[1],start[0],end[1],end[0]);
347
        },
348
        
349
        _getBearing:function(startLat,startLong,endLat,endLong){
350
              startLat = this._radians(startLat);
351
              startLong = this._radians(startLong);
352
              endLat = this._radians(endLat);
353
              endLong = this._radians(endLong);
354
355
              var dLong = endLong - startLong;
356
357
              var dPhi = Math.log(Math.tan(endLat/2.0+Math.PI/4.0)/Math.tan(startLat/2.0+Math.PI/4.0));
358
              if (Math.abs(dLong) > Math.PI){
359
                if (dLong > 0.0)
360
                   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...
361
                else
362
                   dLong = (2.0 * Math.PI + dLong);
363
              }
364
365
              return (this._degrees(Math.atan2(dLong, dPhi)) + 360.0) % 360.0;
366
        },
367
        
368
        _radians:function(n) {
369
          return n * (Math.PI / 180);
370
        },
371
        _degrees:function(n) {
372
          return n * (180 / Math.PI);
373
        },
374
375
        getFirstTick : function () {
376
            return this._ticks[this._startTime];
377
        },
378
379
        getLastTick : function () {
380
            return this._ticks[this._endTime];
381
        },
382
383
        getStartTime : function () {
384
            return this._startTime;
385
        },
386
387
        getEndTime : function () {
388
            return this._endTime;
389
        },
390
391
        getTickMultiPoint : function () {
392
            var t = this.getStartTime();
393
            var endT = this.getEndTime();
394
            var coordinates = [];
395
            var time = [];
396
            while (t <= endT) {
397
                time.push(t);
398
                coordinates.push(this.tick(t));
399
                t += this._tickLen;
400
            }
401
402
            return {
403
                type : 'Feature',
404
                geometry : {
405
                    type : 'MultiPoint',
406
                    coordinates : coordinates
407
                },
408
                properties : {
409
                    time : time
410
                }
411
            };
412
        },
413
		
414
        trackPresentAtTick : function(timestamp)
415
        {
416
            return (timestamp >= this._startTime);
417
        },
418
        
419
        trackStaleAtTick : function(timestamp)
420
        {
421
            return ((this._endTime + this._staleTime) <= timestamp);
422
        },
423
424
        tick : function (timestamp) {
425
            if (timestamp > this._endTime)
426
                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...
427
            if (timestamp < this._startTime)
428
                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...
429
            return this._ticks[timestamp];
430
        },
431
		
432
        courseAtTime: function(timestamp)
433
        {
434
            //return 90;
435
            if (timestamp > this._endTime)
436
               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...
437
            if (timestamp < this._startTime)
438
                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...
439
            return this._orientations[timestamp];
440
        },
441
        
442
        setMarker : function(timestamp, options){
443
            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...
444
            
445
            // if time stamp is not set, then get first tick
446
            if (timestamp) {
447
                lngLat = this.tick(timestamp);
448
            }
449
            else {
450
                lngLat = this.getFirstTick();
451
            }        
452
        
453
            if (lngLat) {
454
                var latLng = new L.LatLng(lngLat[1], lngLat[0]);
455
                this._marker = new L.Playback.MoveableMarker(latLng, options, this._geoJSON);     
456
				if(options.mouseOverCallback) {
457
                    this._marker.on('mouseover',options.mouseOverCallback);
458
                }
459
				if(options.clickCallback) {
460
                    this._marker.on('click',options.clickCallback);
461
                }
462
				
463
				//hide the marker if its not present yet and fadeMarkersWhenStale is true
464
				if(this._fadeMarkersWhenStale && !this.trackPresentAtTick(timestamp))
465
				{
466
					this._marker.setOpacity(0);
467
				}
468
            }
469
            
470
            return this._marker;
471
        },
472
        
473
        moveMarker : function(latLng, transitionTime,timestamp) {
474
            if (this._marker) {
475
                if(this._fadeMarkersWhenStale) {
476
                    //show the marker if its now present
477
                    if(this.trackPresentAtTick(timestamp)) {
478
                        this._marker.setOpacity(1);
479
                    } else {
480
                        this._marker.setOpacity(0);
481
                    }
482
                    
483
                    if(this.trackStaleAtTick(timestamp)) {
484
                        this._marker.setOpacity(0.25);
485
                    }
486
                }
487
				
488
                if(this._orientIcon){
489
                    this._marker.setIconAngle(this.courseAtTime(timestamp));
490
                }
491
				
492
                this._marker.move(latLng, transitionTime);
493
            }
494
        },
495
        
496
        getMarker : function() {
497
            return this._marker;
498
        }
499
500
    });
501
502
L.Playback = L.Playback || {};
503
504
L.Playback.TrackController = L.Class.extend({
505
506
    initialize : function (map, tracks, options) {
507
        this.options = options || {};
508
    
509
        this._map = map;
510
511
        this._tracks = [];
512
513
        // initialize tick points
514
        this.setTracks(tracks);
515
    },
516
    
517
    clearTracks: function(){
518
        while (this._tracks.length > 0) {
519
            var track = this._tracks.pop();
520
            var marker = track.getMarker();
521
            
522
            if (marker){
523
                this._map.removeLayer(marker);
524
            }
525
        }            
526
    },
527
528
    setTracks : function (tracks) {
529
        // reset current tracks
530
        this.clearTracks();
531
        
532
        this.addTracks(tracks);
533
    },
534
    
535
    addTracks : function (tracks) {
536
        // return if nothing is set
537
        if (!tracks) {
538
            return;
539
        }
540
        
541
        if (tracks instanceof Array) {            
542
            for (var i = 0, len = tracks.length; i < len; i++) {
543
                this.addTrack(tracks[i]);
544
            }
545
        } else {
546
            this.addTrack(tracks);
547
        }            
548
    },
549
    
550
    // add single track
551
    addTrack : function (track, timestamp) {
552
        // return if nothing is set
553
        if (!track) {
554
            return;
555
        }
556
557
        var marker = track.setMarker(timestamp, this.options);
558
559
        if (marker) {
560
            marker.addTo(this._map);
561
            
562
            this._tracks.push(track);
563
        }            
564
    },
565
566
    tock : function (timestamp, transitionTime) {
567
        for (var i = 0, len = this._tracks.length; i < len; i++) {
568
            var lngLat = this._tracks[i].tick(timestamp);
569
            var latLng = new L.LatLng(lngLat[1], lngLat[0]);
570
            this._tracks[i].moveMarker(latLng, transitionTime,timestamp);
571
        }
572
    },
573
574
    getStartTime : function () {
575
        var earliestTime = 0;
576
577
        if (this._tracks.length > 0) {
578
            earliestTime = this._tracks[0].getStartTime();
579
            for (var i = 1, len = this._tracks.length; i < len; i++) {
580
                var t = this._tracks[i].getStartTime();
581
                if (t < earliestTime) {
582
                    earliestTime = t;
583
                }
584
            }
585
        }
586
        
587
        return earliestTime;
588
    },
589
590
    getEndTime : function () {
591
        var latestTime = 0;
592
    
593
        if (this._tracks.length > 0){
594
            latestTime = this._tracks[0].getEndTime();
595
            for (var i = 1, len = this._tracks.length; i < len; i++) {
596
                var t = this._tracks[i].getEndTime();
597
                if (t > latestTime) {
598
                    latestTime = t;
599
                }
600
            }
601
        }
602
    
603
        return latestTime;
604
    },
605
606
    getTracks : function () {
607
        return this._tracks;
608
    }
609
});
610
L.Playback = L.Playback || {};
611
612
L.Playback.Clock = L.Class.extend({
613
614
  initialize: function (trackController, callback, options) {
615
    this._trackController = trackController;
616
    this._callbacksArry = [];
617
    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...
618
    L.setOptions(this, options);
619
    this._speed = this.options.speed;
620
    this._tickLen = this.options.tickLen;
621
    this._cursor = trackController.getStartTime();
622
    this._transitionTime = this._tickLen / this._speed;
623
  },
624
625
  _tick: function (self) {
626
    if (self._cursor > self._trackController.getEndTime()) {
627
      clearInterval(self._intervalID);
628
      return;
629
    }
630
    self._trackController.tock(self._cursor, self._transitionTime);
631
    self._callbacks(self._cursor);
632
    self._cursor += self._tickLen;
633
  },
634
635
  _callbacks: function(cursor) {
636
    var arry = this._callbacksArry;
637
    for (var i=0, len=arry.length; i<len; i++) {
638
      arry[i](cursor);
639
    }
640
  },
641
642
  addCallback: function(fn) {
643
    this._callbacksArry.push(fn);
644
  },
645
646
  start: function () {
647
    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...
648
    this._intervalID = window.setInterval(
649
      this._tick, 
650
      this._transitionTime, 
651
      this);
652
  },
653
654
  stop: function () {
655
    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...
656
    clearInterval(this._intervalID);
657
    this._intervalID = null;
658
  },
659
660
  getSpeed: function() {
661
    return this._speed;
662
  },
663
664
  isPlaying: function() {
665
    return this._intervalID ? true : false;
666
  },
667
668
  setSpeed: function (speed) {
669
    this._speed = speed;
670
    this._transitionTime = this._tickLen / speed;
671
    if (this._intervalID) {
672
      this.stop();
673
      this.start();
674
    }
675
  },
676
677
  setCursor: function (ms) {
678
    var time = parseInt(ms);
679
    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...
680
    var mod = time % this._tickLen;
681
    if (mod !== 0) {
682
      time += this._tickLen - mod;
683
    }
684
    this._cursor = time;
685
    this._trackController.tock(this._cursor, 0);
686
    this._callbacks(this._cursor);
687
  },
688
689
  getTime: function() {
690
    return this._cursor;
691
  },
692
693
  getStartTime: function() {
694
    return this._trackController.getStartTime();
695
  },
696
697
  getEndTime: function() {
698
    return this._trackController.getEndTime();
699
  },
700
701
  getTickLen: function() {
702
    return this._tickLen;
703
  }
704
705
});
706
707
// Simply shows all of the track points as circles.
708
// TODO: Associate circle color with the marker color.
709
710
L.Playback = L.Playback || {};
711
712
L.Playback.TracksLayer = L.Class.extend({
713
    initialize : function (map, options) {
714
        var layer_options = options.layer || {};
715
        
716
        if (jQuery.isFunction(layer_options)){
717
            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...
718
        }
719
        
720
        if (!layer_options.pointToLayer) {
721
            layer_options.pointToLayer = function (featureData, latlng) {
722
                return new L.CircleMarker(latlng, { radius : 5 });
723
            };
724
        }
725
    
726
        this.layer = new L.GeoJSON(null, layer_options);
727
728
        var overlayControl = {
729
            'GPS Tracks' : this.layer
730
        };
731
732
        L.control.layers(null, overlayControl, {
733
            collapsed : false
734
        }).addTo(map);
735
    },
736
737
    // clear all geoJSON layers
738
    clearLayer : function(){
739
        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...
740
            this.layer.removeLayer(this.layer._layers[i]);            
741
        }
742
    },
743
744
    // add new geoJSON layer
745
    addLayer : function(geoJSON) {
746
        this.layer.addData(geoJSON);
747
    }
748
});
749
L.Playback = L.Playback || {};
750
751
L.Playback.DateControl = L.Control.extend({
752
    options : {
753
        position : 'bottomleft',
754
        dateFormatFn: L.Playback.Util.DateStr,
755
        timeFormatFn: L.Playback.Util.TimeStr
756
    },
757
758
    initialize : function (playback, options) {
759
        L.setOptions(this, options);
760
        this.playback = playback;
761
    },
762
763
    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...
764
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
765
766
        var self = this;
767
        var playback = this.playback;
768
        var time = playback.getTime();
769
770
        var datetime = L.DomUtil.create('div', 'datetimeControl', this._container);
771
772
        // date time
773
        this._date = L.DomUtil.create('p', '', datetime);
774
        this._time = L.DomUtil.create('p', '', datetime);
775
776
        this._date.innerHTML = this.options.dateFormatFn(time);
777
        this._time.innerHTML = this.options.timeFormatFn(time);
778
779
        // setup callback
780
        playback.addCallback(function (ms) {
781
            self._date.innerHTML = self.options.dateFormatFn(ms);
782
            self._time.innerHTML = self.options.timeFormatFn(ms);
783
        });
784
785
        return this._container;
786
    }
787
});
788
    
789
L.Playback.PlayControl = L.Control.extend({
790
    options : {
791
        position : 'bottomright'
792
    },
793
794
    initialize : function (playback) {
795
        this.playback = playback;
796
    },
797
798
    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...
799
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
800
801
        var self = this;
802
        var playback = this.playback;
803
        playback.setSpeed(100);
804
805
        var playControl = L.DomUtil.create('div', 'playControl', this._container);
806
807
808
        this._button = L.DomUtil.create('button', '', playControl);
809
        this._button.innerHTML = 'Play';
810
811
812
        var stop = L.DomEvent.stopPropagation;
813
814
        L.DomEvent
815
        .on(this._button, 'click', stop)
816
        .on(this._button, 'mousedown', stop)
817
        .on(this._button, 'dblclick', stop)
818
        .on(this._button, 'click', L.DomEvent.preventDefault)
819
        .on(this._button, 'click', play, this);
820
        
821
        function play(){
822
            if (playback.isPlaying()) {
823
                playback.stop();
824
                self._button.innerHTML = 'Play';
825
            }
826
            else {
827
                playback.start();
828
                self._button.innerHTML = 'Stop';
829
            }                
830
        }
831
832
        return this._container;
833
    }
834
});    
835
    
836
L.Playback.SliderControl = L.Control.extend({
837
    options : {
838
        position : 'bottomleft'
839
    },
840
841
    initialize : function (playback) {
842
        this.playback = playback;
843
    },
844
845
    onAdd : function (map) {
846
        this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
847
848
        var self = this;
849
        var playback = this.playback;
850
851
        // slider
852
        this._slider = L.DomUtil.create('input', 'slider', this._container);
853
        this._slider.type = 'range';
854
        this._slider.min = playback.getStartTime();
855
        this._slider.max = playback.getEndTime();
856
        this._slider.value = playback.getTime();
857
858
        var stop = L.DomEvent.stopPropagation;
859
860
        L.DomEvent
861
        .on(this._slider, 'click', stop)
862
        .on(this._slider, 'mousedown', stop)
863
        .on(this._slider, 'dblclick', stop)
864
        .on(this._slider, 'click', L.DomEvent.preventDefault)
865
        //.on(this._slider, 'mousemove', L.DomEvent.preventDefault)
866
        .on(this._slider, 'change', onSliderChange, this)
867
        .on(this._slider, 'mousemove', onSliderChange, this);           
868
869
870
        function onSliderChange(e) {
871
            var val = Number(e.target.value);
872
            playback.setCursor(val);
873
        }
874
875
        playback.addCallback(function (ms) {
876
            self._slider.value = ms;
877
        });
878
        
879
        
880
        map.on('playback:add_tracks', function() {
881
            self._slider.min = playback.getStartTime();
882
            self._slider.max = playback.getEndTime();
883
            self._slider.value = playback.getTime();
884
        });
885
886
        return this._container;
887
    }
888
});      
889
890
L.Playback = L.Playback.Clock.extend({
891
        statics : {
892
            MoveableMarker : L.Playback.MoveableMarker,
893
            Track : L.Playback.Track,
894
            TrackController : L.Playback.TrackController,
895
            Clock : L.Playback.Clock,
896
            Util : L.Playback.Util,
897
            
898
            TracksLayer : L.Playback.TracksLayer,
899
            PlayControl : L.Playback.PlayControl,
900
            DateControl : L.Playback.DateControl,
901
            SliderControl : L.Playback.SliderControl
902
        },
903
904
        options : {
905
            tickLen: 250,
906
            speed: 1,
907
            maxInterpolationTime: 5*60*1000, // 5 minutes
908
909
            tracksLayer : true,
910
            
911
            playControl: false,
912
            dateControl: false,
913
            sliderControl: false,
914
            
915
            // options
916
            layer: {
917
                // pointToLayer(featureData, latlng)
918
            },
919
            
920
            marker : {
921
                // getPopup(feature)
922
            }
923
        },
924
925
        initialize : function (map, geoJSON, callback, options) {
926
            L.setOptions(this, options);
927
            
928
            this._map = map;
929
            this._trackController = new L.Playback.TrackController(map, null, this.options);
930
            L.Playback.Clock.prototype.initialize.call(this, this._trackController, callback, this.options);
931
            
932
            if (this.options.tracksLayer) {
933
                this._tracksLayer = new L.Playback.TracksLayer(map, options);
934
            }
935
936
            this.setData(geoJSON);            
937
            
938
939
            if (this.options.playControl) {
940
                this.playControl = new L.Playback.PlayControl(this);
941
                this.playControl.addTo(map);
942
            }
943
944
            if (this.options.sliderControl) {
945
                this.sliderControl = new L.Playback.SliderControl(this);
946
                this.sliderControl.addTo(map);
947
            }
948
949
            if (this.options.dateControl) {
950
                this.dateControl = new L.Playback.DateControl(this, options);
951
                this.dateControl.addTo(map);
952
            }
953
954
        },
955
        
956
        clearData : function(){
957
            this._trackController.clearTracks();
958
            
959
            if (this._tracksLayer) {
960
                this._tracksLayer.clearLayer();
961
            }
962
        },
963
        
964
        setData : function (geoJSON) {
965
            this.clearData();
966
        
967
            this.addData(geoJSON, this.getTime());
968
            
969
            this.setCursor(this.getStartTime());
970
        },
971
972
        // bad implementation
973
        addData : function (geoJSON, ms) {
974
            // return if data not set
975
            if (!geoJSON) {
976
                return;
977
            }
978
        
979
            if (geoJSON instanceof Array) {
980
                for (var i = 0, len = geoJSON.length; i < len; i++) {
981
                    this._trackController.addTrack(new L.Playback.Track(geoJSON[i], this.options), ms);
982
                }
983
            } else {
984
                this._trackController.addTrack(new L.Playback.Track(geoJSON, this.options), ms);
985
            }
986
987
            this._map.fire('playback:set:data');
988
            
989
            if (this.options.tracksLayer) {
990
                this._tracksLayer.addLayer(geoJSON);
991
            }                  
992
        },
993
994
        destroy: function() {
995
            this.clearData();
996
            if (this.playControl) {
997
                this._map.removeControl(this.playControl);
998
            }
999
            if (this.sliderControl) {
1000
                this._map.removeControl(this.sliderControl);
1001
            }
1002
            if (this.dateControl) {
1003
                this._map.removeControl(this.dateControl);
1004
            }
1005
        }
1006
    });
1007
1008
L.Map.addInitHook(function () {
1009
    if (this.options.playback) {
1010
        this.playback = new L.Playback(this);
1011
    }
1012
});
1013
1014
L.playback = function (map, geoJSON, callback, options) {
1015
    return new L.Playback(map, geoJSON, callback, options);
1016
};
1017
return L.Playback;
1018
1019
}));
1020