Completed
Push — master ( 76beca...96dbbd )
by Yannick
34:24
created

leaflet-playback.js ➔ ... ➔ L.Control.extend.onAdd   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
c 0
b 0
f 0
nc 1
dl 0
loc 23
rs 9.0856
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
	L.Playback = L.Playback || {};
19
	L.Playback.Util = L.Class.extend({
20
		statics: {
21
			DateStr: function(time) {
22
				return new Date(time).toDateString();
23
			},
24
25
			TimeStr: function(time) {
26
				var d = new Date(time);
27
				var h = d.getHours();
28
				var m = d.getMinutes();
29
				var s = d.getSeconds();
30
				var tms = time / 1000;
31
				var dec = (tms - Math.floor(tms)).toFixed(2).slice(1);
32
				var mer = 'AM';
33
				if (h > 11) {
34
					h %= 12;
35
					mer = 'PM';
36
				} 
37
				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...
38
				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...
39
				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...
40
				return h + ':' + m + ':' + s + dec + ' ' + mer;
41
			},
42
43
			ParseGPX: function(gpx) {
44
				var geojson = {
45
				    type: 'Feature',
46
				    geometry: {
47
					type: 'MultiPoint',
48
					coordinates: []
49
				    },
50
				    properties: {
51
					time: [],
52
					speed: [],
53
					altitude: []
54
				    },
55
				    bbox: []
56
				};
57
				var xml = $.parseXML(gpx);
58
				var pts = $(xml).find('trkpt');
59
				for (var i=0, len=pts.length; i<len; i++) {
60
					var p = pts[i];
61
					var lat = parseFloat(p.getAttribute('lat'));
62
					var lng = parseFloat(p.getAttribute('lon'));
63
					var timeStr = $(p).find('time').text();
64
					var eleStr = $(p).find('ele').text();
65
					var t = new Date(timeStr).getTime();
66
					var ele = parseFloat(eleStr);
67
					var coords = geojson.geometry.coordinates;
68
					var props = geojson.properties;
69
					var time = props.time;
70
					var altitude = geojson.properties.altitude;
71
					coords.push([lng,lat]);
72
					time.push(t);
73
					altitude.push(ele);
74
				}
75
				return geojson;
76
			}
77
		}
78
	});
79
80
	L.Playback = L.Playback || {};
81
82
	L.Playback.MoveableMarker = L.Marker.extend({    
83
		initialize: function (startLatLng, options, feature) {
84
			var marker_options = options.marker || {};
85
86
			if (jQuery.isFunction(marker_options)) {
87
				marker_options = marker_options(feature);
88
			}
89
			L.Marker.prototype.initialize.call(this, startLatLng, marker_options);
90
			this.popupContent = '';
91
			this.feature = feature;
92
			if (marker_options.getPopup) {
93
				this.popupContent = marker_options.getPopup(feature);
94
			}
95
			if(options.popups) {
96
				this.bindPopup(this.getPopupContent() + startLatLng.toString());
97
			}
98
			if(options.labels) {
99
				if(this.bindLabel) {
100
					this.bindLabel(this.getPopupContent());
101
				} else {
102
					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...
103
				}
104
			}
105
		},
106
		getPopupContent: function() {
107
			if (this.popupContent !== '') {
108
				return '<b>' + this.popupContent + '</b><br/>';
109
			}
110
			return '';
111
		},
112
		move: function (latLng, transitionTime) {
113
			// Only if CSS3 transitions are supported
114
			if (L.DomUtil.TRANSITION) {
115
				if (this._icon) { 
116
					this._icon.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
117
					if (this._popup && this._popup._wrapper) {
118
						this._popup._wrapper.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
119
					}
120
				}
121
				if (this._shadow) { 
122
					this._shadow.style[L.DomUtil.TRANSITION] = 'all ' + transitionTime + 'ms linear'; 
123
				}
124
			}
125
			this.setLatLng(latLng);
126
			if (this._popup) {
127
				this._popup.setContent(this.getPopupContent() + this._latlng.toString());
128
			}
129
		}
130
	});
131
132
	L.Playback = L.Playback || {};
133
134
	L.Playback.Track = L.Class.extend({
135
		initialize : function (geoJSON, options) {
136
			options = options || {};
137
			var tickLen = options.tickLen || 250;
138
			this._staleTime = options.staleTime || 60*60*1000;
139
			this._fadeMarkersWhenStale = options.fadeMarkersWhenStale || true;
140
			this._beginTime = options.beginTime;
141
			this._finalTime = options.finalTime;
142
			this._geoJSON = geoJSON;
143
			this._tickLen = tickLen;
144
			this._ticks = [];
145
			this._marker = null;
146
			this._orientations = [];
147
148
			var sampleTimes = geoJSON.properties.time;
149
			this._orientIcon = options.orientIcons;
150
			var previousOrientation;
151
			var samples = geoJSON.geometry.coordinates;
152
			var currSample = samples[0];
153
			var nextSample = samples[1];
154
			var currSampleTime = sampleTimes[0];
155
			var t = currSampleTime;  // t is used to iterate through tick times
156
			var nextSampleTime = sampleTimes[1];
157
			var tmod = t % tickLen; // ms past a tick time
158
			var rem,
159
			    ratio;
160
161
			// handle edge case of only one t sample
162
			if (sampleTimes.length === 1) {
163
				if (tmod !== 0) {
164
					t += tickLen - tmod;
165
				}
166
				this._ticks[t] = samples[0];
167
				this._orientations[t] = 0;
168
				this._startTime = t;
169
				this._endTime = t;
170
				return;
171
			}
172
173
			// interpolate first tick if t not a tick time
174
			if (tmod !== 0) {
175
				rem = tickLen - tmod;
176
				ratio = rem / (nextSampleTime - currSampleTime);
177
				t += rem;
178
				this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
179
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
180
				previousOrientation = this._orientations[t];
181
			} else {
182
				this._ticks[t] = currSample;
183
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
184
				previousOrientation = this._orientations[t];
185
			}
186
187
			this._startTime = t;
188
			t += tickLen;
189
			while (t < nextSampleTime) {
190
				ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
191
				this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
192
				this._orientations[t] = this._directionOfPoint(currSample,nextSample);
193
				previousOrientation = this._orientations[t];
194
				t += tickLen;
195
			}
196
197
			// iterating through the rest of the samples
198
			for (var i = 1, len = samples.length; i < len; i++) {
199
				currSample = samples[i];
200
				nextSample = samples[i + 1];
201
				t = currSampleTime = sampleTimes[i];
202
				nextSampleTime = sampleTimes[i + 1];
203
204
				tmod = t % tickLen;
205
				if (tmod !== 0 && nextSampleTime) {
206
					rem = tickLen - tmod;
207
					ratio = rem / (nextSampleTime - currSampleTime);
208
					t += rem;
209
					this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
210
					if (nextSample) {
211
						this._orientations[t] = this._directionOfPoint(currSample,nextSample);
212
						previousOrientation = this._orientations[t];
213
					} else {
214
						this._orientations[t] = previousOrientation;
215
					}
216
				} else {
217
					this._ticks[t] = currSample;
218
					if (nextSample) {
219
						this._orientations[t] = this._directionOfPoint(currSample,nextSample);
220
						previousOrientation = this._orientations[t];
221
					} else {
222
						this._orientations[t] = previousOrientation;
223
					}
224
				}
225
				t += tickLen;
226
				while (t < nextSampleTime) {
227
					ratio = (t - currSampleTime) / (nextSampleTime - currSampleTime);
228
					if (nextSampleTime - currSampleTime > options.maxInterpolationTime) {
229
						this._ticks[t] = currSample;
230
						if (nextSample) {
231
							this._orientations[t] = this._directionOfPoint(currSample,nextSample);
232
							previousOrientation = this._orientations[t];
233
						} else {
234
							this._orientations[t] = previousOrientation;
235
						}
236
					} else {
237
						this._ticks[t] = this._interpolatePoint(currSample, nextSample, ratio);
238
						if (nextSample) {
239
							this._orientations[t] = this._directionOfPoint(currSample,nextSample);
240
							previousOrientation = this._orientations[t];
241
						} else {
242
							this._orientations[t] = previousOrientation;
243
						}
244
					}
245
					t += tickLen;
246
				}
247
			}
248
			// the last t in the while would be past bounds
249
			this._endTime = t - tickLen;
250
			this._lastTick = this._ticks[this._endTime];
251
		},
252
253
		_interpolatePoint : function (start, end, ratio) {
254
			try {
255
				var delta = [end[0] - start[0], end[1] - start[1]];
256
				var offset = [delta[0] * ratio, delta[1] * ratio];
257
				return [start[0] + offset[0], start[1] + offset[1]];
258
			} catch (e) {
259
				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...
260
				console.log(['start', start]);
261
				console.log(['end', end]);
262
				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...
263
			}
264
		},
265
266
		_directionOfPoint:function(start,end){
267
			return this._getBearing(start[1],start[0],end[1],end[0]);
268
		},
269
270
		_getBearing:function(startLat,startLong,endLat,endLong){
271
			startLat = this._radians(startLat);
272
			startLong = this._radians(startLong);
273
			endLat = this._radians(endLat);
274
			endLong = this._radians(endLong);
275
276
			var dLong = endLong - startLong;
277
			var dPhi = Math.log(Math.tan(endLat/2.0+Math.PI/4.0)/Math.tan(startLat/2.0+Math.PI/4.0));
278
			if (Math.abs(dLong) > Math.PI) {
279
				if (dLong > 0.0) {
280
					dLong = -(2.0 * Math.PI - dLong);
281
				} else {
282
					dLong = (2.0 * Math.PI + dLong);
283
				}
284
			}
285
			return (this._degrees(Math.atan2(dLong, dPhi)) + 360.0) % 360.0;
286
		},
287
		_radians:function(n) {
288
			return n * (Math.PI / 180);
289
		},
290
		_degrees:function(n) {
291
			return n * (180 / Math.PI);
292
		},
293
294
		getFirstTick : function () {
295
			return this._ticks[this._startTime];
296
		},
297
298
		getLastTick : function () {
299
			return this._ticks[this._endTime];
300
		},
301
302
		getStartTime : function () {
303
			return this._startTime;
304
		},
305
306
		getEndTime : function () {
307
			return this._endTime;
308
		},
309
310
		getTickMultiPoint : function () {
311
			var t = this.getStartTime();
312
			var endT = this.getEndTime();
313
			var coordinates = [];
314
			var time = [];
315
			while (t <= endT) {
316
				time.push(t);
317
				coordinates.push(this.tick(t));
318
				t += this._tickLen;
319
			}
320
			return {
321
			    type : 'Feature',
322
			    geometry : {
323
				type : 'MultiPoint',
324
				coordinates : coordinates
325
			    },
326
			    properties : {
327
				time : time
328
			    }
329
			};
330
		},
331
332
		trackPresentAtTick : function(timestamp)
333
		{
334
			return (timestamp >= this._startTime);
335
		},
336
		trackStaleAtTick : function(timestamp)
337
		{
338
			//if ((this._endTime + this._staleTime) <= timestamp) console.log('endtime: '+this._endTime+' - timestamp: '+timestamp+' => true !');
339
			//else console.log('endtime: '+this._endTime+' - timestamp: '+timestamp);
340
			return ((this._endTime + this._staleTime) <= timestamp);
341
		},
342
		tick : function (timestamp) {
343
			if (timestamp > this._endTime) {
344
				timestamp = this._endTime;
345
			}
346
			if (typeof this._finalTime != 'undefined' && timestamp > this._finalTime) {
347
				timestamp = this._finalTime;
348
			}
349
			if (timestamp < this._startTime) {
350
				timestamp = this._startTime;
351
			}
352
			return this._ticks[timestamp];
353
		},
354
		courseAtTime: function(timestamp)
355
		{
356
			//return 90;
357
			if (timestamp > this._endTime) {
358
				timestamp = this._endTime;
359
			}
360
			if (typeof this._finalTime != 'undefined' && timestamp > this._finalTime) {
361
				timestamp = this._finalTime;
362
			}
363
			if (timestamp < this._startTime) {
364
				timestamp = this._startTime;
365
			}
366
			return this._orientations[timestamp];
367
		},
368
		setMarker : function(timestamp, options){
369
			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...
370
			// if time stamp is not set, then get first tick
371
			if (timestamp) {
372
				lngLat = this.tick(timestamp);
373
			} else {
374
				lngLat = this.getFirstTick();
375
			}
376
			if (lngLat) {
377
				var latLng = new L.LatLng(lngLat[1], lngLat[0]);
378
				this._marker = new L.Playback.MoveableMarker(latLng, options, this._geoJSON);
379
				if (options.mouseOverCallback) {
380
					this._marker.on('mouseover',options.mouseOverCallback);
381
				}
382
				if (options.clickCallback) {
383
					this._marker.on('click',options.clickCallback);
384
				}
385
				//hide the marker if its not present yet and fadeMarkersWhenStale is true
386
				if (this._fadeMarkersWhenStale && !this.trackPresentAtTick(timestamp)) {
387
					this._marker.setOpacity(0);
388
				}
389
			}
390
			return this._marker;
391
		},
392
		moveMarker : function(latLng, transitionTime,timestamp) {
393
			if (this._marker) {
394
				if (this._fadeMarkersWhenStale) {
395
					//show the marker if its now present
396
					if (this.trackPresentAtTick(timestamp)) {
397
						this._marker.setOpacity(1);
398
					} else {
399
						this._marker.setOpacity(0);
400
					}
401
					if (this.trackStaleAtTick(timestamp)) {
402
						this._marker.setOpacity(0);
403
					}
404
				}
405
				if (this._orientIcon) {
406
					this._marker.setIconAngle(this.courseAtTime(timestamp));
407
				}
408
				this._marker.move(latLng, transitionTime);
409
			}
410
		},
411
		getMarker : function() {
412
			return this._marker;
413
		}
414
	});
415
416
	L.Playback = L.Playback || {};
417
	L.Playback.TrackController = L.Class.extend({
418
		initialize : function (map, tracks, options) {
419
			this.options = options || {};
420
			this._finalTime = options.finalTime;
421
			this._map = map;
422
			this._tracks = [];
423
			// initialize tick points
424
			this.setTracks(tracks);
425
		},
426
427
		clearTracks: function(){
428
			while (this._tracks.length > 0) {
429
				var track = this._tracks.pop();
430
				var marker = track.getMarker();
431
				if (marker) {
432
					this._map.removeLayer(marker);
433
				}
434
			}
435
		},
436
437
		setTracks : function (tracks) {
438
			// reset current tracks
439
			this.clearTracks();
440
			this.addTracks(tracks);
441
		},
442
		addTracks : function (tracks) {
443
			// return if nothing is set
444
			if (!tracks) {
445
				return;
446
			}
447
			if (tracks instanceof Array) {
448
				for (var i = 0, len = tracks.length; i < len; i++) {
449
					this.addTrack(tracks[i]);
450
				}
451
			} else {
452
				this.addTrack(tracks);
453
			}
454
		},
455
456
		// add single track
457
		addTrack : function (track, timestamp) {
458
			// return if nothing is set
459
			if (!track) {
460
				return;
461
			}
462
			var marker = track.setMarker(timestamp, this.options);
463
			if (marker) {
464
				marker.addTo(this._map);
465
				this._tracks.push(track);
466
			}
467
		},
468
469
		tock : function (timestamp, transitionTime) {
470
			for (var i = 0, len = this._tracks.length; i < len; i++) {
471
				var lngLat = this._tracks[i].tick(timestamp);
472
				var latLng = new L.LatLng(lngLat[1], lngLat[0]);
473
				this._tracks[i].moveMarker(latLng, transitionTime,timestamp);
474
			}
475
		},
476
477
		getStartTime : function () {
478
			var earliestTime = 0;
479
			if (this._tracks.length > 0) {
480
				earliestTime = this._tracks[0].getStartTime();
481
				for (var i = 1, len = this._tracks.length; i < len; i++) {
482
					var t = this._tracks[i].getStartTime();
483
					if (t < earliestTime) {
484
						earliestTime = t;
485
					}
486
				}
487
			}
488
			return earliestTime;
489
		},
490
491
		getEndTime : function () {
492
			var latestTime = 0;
493
			if (this._tracks.length > 0) {
494
				latestTime = this._tracks[0].getEndTime();
495
				for (var i = 1, len = this._tracks.length; i < len; i++) {
496
					var t = this._tracks[i].getEndTime();
497
					if (t > latestTime) {
498
						latestTime = t;
499
					}
500
				}
501
				if (typeof this._finalTime != 'undefined' && latestTime > this._finalTime) {
502
					latestTime = this._finalTime;
503
				}
504
505
			}
506
			return latestTime;
507
		},
508
509
		getTracks : function () {
510
			return this._tracks;
511
		}
512
	});
513
	
514
	L.Playback = L.Playback || {};
515
	L.Playback.Clock = L.Class.extend({
516
		initialize: function (trackController, callback, options) {
517
			this._trackController = trackController;
518
			this._callbacksArry = [];
519
			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...
520
			L.setOptions(this, options);
521
			this._speed = this.options.speed;
522
			this._tickLen = this.options.tickLen;
523
			this._cursor = trackController.getStartTime();
524
			this._transitionTime = this._tickLen / this._speed;
525
		},
526
527
		_tick: function (self) {
528
			self._trackController.tock(self._cursor, self._transitionTime);
529
			self._callbacks(self._cursor);
530
			if (self._cursor > self._trackController.getEndTime()) {
531
				clearInterval(self._intervalID);
532
				return;
533
			}
534
			self._cursor += self._tickLen;
535
		},
536
537
		_callbacks: function(cursor) {
538
			var arry = this._callbacksArry;
539
			for (var i=0, len=arry.length; i<len; i++) {
540
				arry[i](cursor);
541
			}
542
		},
543
544
		addCallback: function(fn) {
545
			this._callbacksArry.push(fn);
546
		},
547
548
		start: function () {
549
			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...
550
			this._intervalID = window.setInterval(
551
			    this._tick, 
552
			    this._transitionTime, 
553
			    this
554
			);
555
		},
556
557
		stop: function () {
558
			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...
559
			clearInterval(this._intervalID);
560
			this._intervalID = null;
561
		},
562
563
		getSpeed: function() {
564
			return this._speed;
565
		},
566
567
		isPlaying: function() {
568
			return this._intervalID ? true : false;
569
		},
570
571
		setSpeed: function (speed) {
572
			this._speed = speed;
573
			this._transitionTime = this._tickLen / speed;
574
			if (this._intervalID) {
575
				this.stop();
576
				this.start();
577
			}
578
		},
579
580
		setCursor: function (ms) {
581
			var time = parseInt(ms);
582
			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...
583
			var mod = time % this._tickLen;
584
			if (mod !== 0) {
585
				time += this._tickLen - mod;
586
			}
587
			this._cursor = time;
588
			this._trackController.tock(this._cursor, 0);
589
			this._callbacks(this._cursor);
590
		},
591
592
		getTime: function() {
593
			return this._cursor;
594
		},
595
596
		getStartTime: function() {
597
			return this._trackController.getStartTime();
598
		},
599
600
		getEndTime: function() {
601
			return this._trackController.getEndTime();
602
		},
603
604
		getTickLen: function() {
605
			return this._tickLen;
606
		}
607
	});
608
609
	// Simply shows all of the track points as circles.
610
	// TODO: Associate circle color with the marker color.
611
	L.Playback = L.Playback || {};
612
	L.Playback.TracksLayer = L.Class.extend({
613
		initialize : function (map, options) {
614
			var layer_options = options.layer || {};
615
			if (jQuery.isFunction(layer_options)) {
616
				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...
617
			}
618
			if (!layer_options.pointToLayer) {
619
				layer_options.pointToLayer = function (featureData, latlng) {
620
					return new L.CircleMarker(latlng, { radius : 5 });
621
				};
622
			}
623
			this.layer = new L.GeoJSON(null, layer_options);
624
			var overlayControl = {
625
			    'GPS Tracks' : this.layer
626
			};
627
			L.control.layers(null, overlayControl, {
628
			    collapsed : false
629
			}).addTo(map);
630
		},
631
632
		// clear all geoJSON layers
633
		clearLayer : function(){
634
			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...
635
				this.layer.removeLayer(this.layer._layers[i]);
636
			}
637
		},
638
639
		// add new geoJSON layer
640
		addLayer : function(geoJSON) {
641
			this.layer.addData(geoJSON);
642
		}
643
	});
644
645
	L.Playback = L.Playback || {};
646
	L.Playback.DateControl = L.Control.extend({
647
		options : {
648
		    position : 'bottomleft',
649
		    dateFormatFn: L.Playback.Util.DateStr,
650
    		    timeFormatFn: L.Playback.Util.TimeStr
651
		},
652
653
		initialize : function (playback, options) {
654
			L.setOptions(this, options);
655
			this.playback = playback;
656
		},
657
658
		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...
659
			this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
660
661
			var self = this;
662
			var playback = this.playback;
663
			var time = playback.getTime();
664
			var datetime = L.DomUtil.create('div', 'datetimeControl', this._container);
665
666
			// date time
667
			this._date = L.DomUtil.create('p', '', datetime);
668
			this._time = L.DomUtil.create('p', '', datetime);
669
670
			this._date.innerHTML = this.options.dateFormatFn(time);
671
			this._time.innerHTML = this.options.timeFormatFn(time);
672
673
			// setup callback
674
			playback.addCallback(function (ms) {
675
				self._date.innerHTML = self.options.dateFormatFn(ms);
676
				self._time.innerHTML = self.options.timeFormatFn(ms);
677
			});
678
679
			return this._container;
680
		}
681
	});
682
683
	L.Playback.PlayControl = L.Control.extend({
684
		options : {
685
		    position : 'bottomright'
686
		},
687
688
		initialize : function (playback) {
689
			this.playback = playback;
690
		},
691
692
		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...
693
			this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
694
			var self = this;
695
			var playback = this.playback;
696
			playback.setSpeed(100);
697
			var playControl = L.DomUtil.create('div', 'playControl', this._container);
698
			this._button = L.DomUtil.create('button', '', playControl);
699
			this._button.innerHTML = 'Play';
700
			var stop = L.DomEvent.stopPropagation;
701
			L.DomEvent
702
			    .on(this._button, 'click', stop)
703
			    .on(this._button, 'mousedown', stop)
704
			    .on(this._button, 'dblclick', stop)
705
			    .on(this._button, 'click', L.DomEvent.preventDefault)
706
			    .on(this._button, 'click', play, this);
707
708
			function play(){
709
				if (playback.isPlaying()) {
710
					playback.stop();
711
					self._button.innerHTML = 'Play';
712
				} else {
713
					playback.start();
714
					self._button.innerHTML = 'Stop';
715
				}
716
			}
717
			return this._container;
718
		}
719
	});
720
721
	L.Playback.SliderControl = L.Control.extend({
722
		options : {
723
			position : 'bottomleft'
724
		},
725
726
		initialize : function (playback) {
727
			this.playback = playback;
728
		},
729
730
		onAdd : function (map) {
731
			this._container = L.DomUtil.create('div', 'leaflet-control-layers leaflet-control-layers-expanded');
732
733
			var self = this;
734
			var playback = this.playback;
735
736
			// slider
737
			this._slider = L.DomUtil.create('input', 'slider', this._container);
738
			this._slider.type = 'range';
739
			this._slider.min = playback.getStartTime();
740
			this._slider.max = playback.getEndTime();
741
			this._slider.value = playback.getTime();
742
743
			var stop = L.DomEvent.stopPropagation;
744
745
			L.DomEvent
746
			    .on(this._slider, 'click', stop)
747
			    .on(this._slider, 'mousedown', stop)
748
			    .on(this._slider, 'dblclick', stop)
749
			    .on(this._slider, 'click', L.DomEvent.preventDefault)
750
			    //.on(this._slider, 'mousemove', L.DomEvent.preventDefault)
751
			    .on(this._slider, 'change', onSliderChange, this)
752
			    .on(this._slider, 'mousemove', onSliderChange, this);
753
754
			function onSliderChange(e) {
755
				var val = Number(e.target.value);
756
				playback.setCursor(val);
757
			}
758
759
			playback.addCallback(function (ms) {
760
				self._slider.value = ms;
761
			});
762
763
			map.on('playback:add_tracks', function() {
764
				self._slider.min = playback.getStartTime();
765
				self._slider.max = playback.getEndTime();
766
				self._slider.value = playback.getTime();
767
			});
768
			return this._container;
769
		}
770
	});
771
772
	L.Playback = L.Playback.Clock.extend({
773
		statics : {
774
		    MoveableMarker : L.Playback.MoveableMarker,
775
		    Track : L.Playback.Track,
776
		    TrackController : L.Playback.TrackController,
777
		    Clock : L.Playback.Clock,
778
		    Util : L.Playback.Util,
779
		    TracksLayer : L.Playback.TracksLayer,
780
		    PlayControl : L.Playback.PlayControl,
781
		    DateControl : L.Playback.DateControl,
782
		    SliderControl : L.Playback.SliderControl
783
		},
784
785
		options : {
786
		    tickLen: 250,
787
		    speed: 1,
788
		    maxInterpolationTime: 5*60*1000, // 5 minutes
789
		    tracksLayer : true,
790
		    playControl: false,
791
		    dateControl: false,
792
		    sliderControl: false,
793
		    // options
794
		    layer: {
795
			// pointToLayer(featureData, latlng)
796
		    },
797
		    marker : {
798
			// getPopup(feature)
799
		    }
800
		},
801
802
		initialize : function (map, geoJSON, callback, options) {
803
			L.setOptions(this, options);
804
			this._map = map;
805
			this._trackController = new L.Playback.TrackController(map, null, this.options);
806
			L.Playback.Clock.prototype.initialize.call(this, this._trackController, callback, this.options);
807
			if (this.options.tracksLayer) {
808
				this._tracksLayer = new L.Playback.TracksLayer(map, options);
809
			}
810
			this.setData(geoJSON);
811
			if (this.options.playControl) {
812
				this.playControl = new L.Playback.PlayControl(this);
813
				this.playControl.addTo(map);
814
			}
815
			if (this.options.sliderControl) {
816
				this.sliderControl = new L.Playback.SliderControl(this);
817
				this.sliderControl.addTo(map);
818
			}
819
			if (this.options.dateControl) {
820
				this.dateControl = new L.Playback.DateControl(this, options);
821
				this.dateControl.addTo(map);
822
			}
823
		},
824
825
		clearData : function(){
826
			this._trackController.clearTracks();
827
			if (this._tracksLayer) {
828
				this._tracksLayer.clearLayer();
829
			}
830
		},
831
832
		setData : function (geoJSON) {
833
			this.clearData();
834
			this.addData(geoJSON, this.getTime());
835
			this.setCursor(this.getStartTime());
836
		},
837
838
		// bad implementation
839
		addData : function (geoJSON, ms) {
840
			// return if data not set
841
			if (!geoJSON) {
842
				return;
843
			}
844
			if (geoJSON instanceof Array) {
845
				for (var i = 0, len = geoJSON.length; i < len; i++) {
846
					this._trackController.addTrack(new L.Playback.Track(geoJSON[i], this.options), ms);
847
				}
848
			} else {
849
				this._trackController.addTrack(new L.Playback.Track(geoJSON, this.options), ms);
850
			}
851
			this._map.fire('playback:set:data');
852
			if (this.options.tracksLayer) {
853
				this._tracksLayer.addLayer(geoJSON);
854
			}
855
		},
856
857
		destroy: function() {
858
			this.clearData();
859
			if (this.playControl) {
860
				this._map.removeControl(this.playControl);
861
			}
862
			if (this.sliderControl) {
863
				this._map.removeControl(this.sliderControl);
864
			}
865
			if (this.dateControl) {
866
				this._map.removeControl(this.dateControl);
867
			}
868
		}
869
	});
870
871
	L.Map.addInitHook(function () {
872
		if (this.options.playback) {
873
			this.playback = new L.Playback(this);
874
		}
875
	});
876
877
	L.playback = function (map, geoJSON, callback, options) {
878
		return new L.Playback(map, geoJSON, callback, options);
879
	};
880
	return L.Playback;
881
}));
882