1
|
let app = angular.module('pvpk', ['ngRoute', 'ngResource', 'ngSanitize']);
|
2
|
|
3
|
app.constant('config', {
|
4
|
APP_NAME: 'PVPK',
|
5
|
APP_VERSION: 1.0,
|
6
|
API_URL: API_URL,
|
7
|
API_TOKEN: API_TOKEN,
|
8
|
DEFAULT_POSITION: {LAT: 49.53, LNG: 13.3},
|
9
|
DEFAULT_ZOOM: 10
|
10
|
});
|
11
|
|
12
|
//PRIPRAVA PRO REFAKTORING
|
13
|
// app.config(function($stateProvider, $locationProvider) {
|
14
|
// // $stateProvider
|
15
|
// // .state('report',{
|
16
|
// // views: {
|
17
|
// // 'search': {
|
18
|
// // templateUrl: 'report-filters.html',
|
19
|
// // controller: searchController
|
20
|
// // },
|
21
|
// // 'graph': {
|
22
|
// // templateUrl: 'report-table.html',
|
23
|
// // controller: graphController
|
24
|
// // },
|
25
|
// // 'map': {
|
26
|
// // templateUrl: 'report-graph.html',
|
27
|
// // controller: mapController
|
28
|
// // }
|
29
|
// // }
|
30
|
// // });
|
31
|
// $locationProvider.html5Mode(true);
|
32
|
// });
|
33
|
|
34
|
|
35
|
app.controller('mainController', function ($rootScope, $scope, $location, $window) {
|
36
|
|
37
|
this.$onInit = function () {
|
38
|
|
39
|
};
|
40
|
|
41
|
$window.onload = function () {
|
42
|
let params = $location.search();
|
43
|
if (params.deviceId) {
|
44
|
$rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
|
45
|
$rootScope.$emit('activeMarker', {id: params.deviceId});
|
46
|
}
|
47
|
|
48
|
$scope.showLoadingScreen = false;
|
49
|
};
|
50
|
|
51
|
$rootScope.$on('$locationChangeSuccess', function (event, newUrl, oldUrl) {
|
52
|
|
53
|
if (newUrl !== oldUrl && $scope.historyUrl) {
|
54
|
let params = $location.search();
|
55
|
|
56
|
if ($scope.historyUrl.q !== $scope.historyUrl.q || $scope.historyUrl.isDirection != params.isDirection) {
|
57
|
$rootScope.$emit('setSearchFromUrl', null);
|
58
|
}
|
59
|
|
60
|
if ($scope.historyUrl.fromDate !== params.fromDate || $scope.historyUrl.toDate !== params.toDate ||
|
61
|
$scope.historyUrl.fromTime !== params.fromTime || $scope.historyUrl.toTime !== params.toTime) {
|
62
|
$rootScope.$emit('setRangeFromUrl', null);
|
63
|
if (params.deviceId) {
|
64
|
$rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
|
65
|
}
|
66
|
} else if (params.deviceId && ($scope.historyUrl.deviceId !== params.deviceId || $scope.historyUrl.direction !== params.direction)) {
|
67
|
$rootScope.$emit('infoLocation', {id: params.deviceId, direction: params.direction});
|
68
|
$rootScope.$emit('activeMarker', {id: params.deviceId});
|
69
|
}
|
70
|
}
|
71
|
|
72
|
$scope.historyUrl = $location.search();
|
73
|
});
|
74
|
|
75
|
$rootScope.handleErrorResponse = function (response) {
|
76
|
|
77
|
let modalError = jQuery('#modalError');
|
78
|
switch (response.status) {
|
79
|
case 400:
|
80
|
console.log('API ERROR 400');
|
81
|
$scope.modalError = {
|
82
|
title: 'Neplatný požadavek',
|
83
|
body: 'Požadavek nemůže být vyřízen, poněvadž byl syntakticky nesprávně zapsán.',
|
84
|
button: 'OK'
|
85
|
};
|
86
|
modalError.modal('show');
|
87
|
break;
|
88
|
case 401:
|
89
|
$scope.modalError = {
|
90
|
title: 'Platnost webové aplikace vypršela',
|
91
|
body: 'Pro obnovení platnosti stačí stisknout tlačítko <strong>Obnovit</strong>.',
|
92
|
button: 'Obnovit',
|
93
|
clickButton: $scope.reloadApp
|
94
|
};
|
95
|
modalError.modal({backdrop: 'static', keyboard: false});
|
96
|
break;
|
97
|
case 404:
|
98
|
console.log('API ERROR 404');
|
99
|
$scope.modalError = {title: 'Nenalezen', body: 'Záznam nebyl nalezen.', button: 'OK'};
|
100
|
modalError.modal('show');
|
101
|
break;
|
102
|
case 500:
|
103
|
console.log('API ERROR 500');
|
104
|
$scope.modalError = {title: 'Chyba', body: 'Chyba serveru. Zopakujte akci později.', button: 'OK'};
|
105
|
modalError.modal('show');
|
106
|
break;
|
107
|
case -1:
|
108
|
console.log('API NOT CONNECTED');
|
109
|
$scope.modalError = {
|
110
|
title: 'Připojení k internetu',
|
111
|
body: 'Nejste připojeni k internetu. Zkontrolujte připojení.',
|
112
|
button: 'OK'
|
113
|
};
|
114
|
modalError.modal('show');
|
115
|
break;
|
116
|
default:
|
117
|
console.log('API UNKNOWN ERROR');
|
118
|
$scope.modalError = {title: 'Neočekávaná chyba', body: 'Nastala neočekávaná chyba.', button: 'OK'};
|
119
|
modalError.modal('show');
|
120
|
break;
|
121
|
}
|
122
|
};
|
123
|
|
124
|
$scope.reloadApp = function () {
|
125
|
$window.location.reload();
|
126
|
}
|
127
|
});
|
128
|
|
129
|
|
130
|
app.controller('searchController', function ($rootScope, $scope, $location, config, Device) {
|
131
|
|
132
|
this.$onInit = function () {
|
133
|
$scope.locations = [];
|
134
|
$scope.showSearchLoading = false;
|
135
|
|
136
|
$rootScope.$emit('setSearchFromUrl', null);
|
137
|
};
|
138
|
|
139
|
$scope.searchLocations = function () {
|
140
|
if (!$scope.search.q || $scope.search.q.length <= 1) {
|
141
|
$scope.locations = [];
|
142
|
return;
|
143
|
}
|
144
|
|
145
|
$scope.showSearchLoading = true;
|
146
|
|
147
|
let params = $location.search();
|
148
|
params.q = $scope.search.q;
|
149
|
params.isDirection = $scope.search.isDirection ? 1 : 0;
|
150
|
$location.search(params);
|
151
|
|
152
|
Device.query({
|
153
|
address: $scope.search.q,
|
154
|
showDirection: $scope.search.isDirection ? 1 : 0
|
155
|
}, function (data) {
|
156
|
$scope.locations = data;
|
157
|
$scope.showSearchLoading = false;
|
158
|
}, function (response) {
|
159
|
$scope.showSearchLoading = false;
|
160
|
console.log('Error api all Devices');
|
161
|
$rootScope.handleErrorResponse(response);
|
162
|
});
|
163
|
};
|
164
|
|
165
|
$rootScope.$on('setSearchFromUrl', function (event, args) {
|
166
|
let params = $location.search();
|
167
|
|
168
|
$scope.search = {
|
169
|
q: params.q,
|
170
|
isDirection: params.isDirection ? !!+params.isDirection : false
|
171
|
};
|
172
|
$scope.searchLocations();
|
173
|
});
|
174
|
|
175
|
$scope.selectDevice = function (id, direction) {
|
176
|
$rootScope.$emit('activeMarker', {id: id});
|
177
|
$rootScope.$emit('infoLocation', {id: id, direction: direction});
|
178
|
};
|
179
|
|
180
|
});
|
181
|
|
182
|
|
183
|
app.controller('infoController', function ($rootScope, $scope, $location, config, Device, Vehicle) {
|
184
|
|
185
|
this.$onInit = function () {
|
186
|
$rootScope.selectDevice = null;
|
187
|
$scope.showInfoLoading = false;
|
188
|
$scope.vehicles = [];
|
189
|
$scope.typeVehicle = null;
|
190
|
$scope.filterVehicles = [];
|
191
|
|
192
|
Vehicle.query(null, function (data) {
|
193
|
$scope.vehicles = data;
|
194
|
}, function (response) {
|
195
|
$rootScope.graphShow = false;
|
196
|
console.log('Error api all Vehicles');
|
197
|
$rootScope.handleErrorResponse(response);
|
198
|
});
|
199
|
|
200
|
$rootScope.$emit('setRangeFromUrl', null);
|
201
|
};
|
202
|
|
203
|
$rootScope.$on('setRangeFromUrl', function (event, args) {
|
204
|
let params = $location.search();
|
205
|
let defaultRange = $scope.defaultRange();
|
206
|
|
207
|
$scope.range = {
|
208
|
fromDate: moment(params.fromDate, 'YYYY-MM-DD').isValid() ? moment(params.fromDate).toDate() : defaultRange.fromDate.toDate(),
|
209
|
toDate: moment(params.toDate, 'YYYY-MM-DD').isValid() ? moment(params.toDate).toDate() : defaultRange.toDate.toDate(),
|
210
|
fromTime: moment(params.fromTime, 'HH:mm').isValid() ? moment(params.fromTime, 'HH:mm').toDate() : defaultRange.fromTime.toDate(),
|
211
|
toTime: moment(params.toTime, 'HH:mm').isValid() ? moment(params.toTime, 'HH:mm').toDate() : defaultRange.toTime.toDate()
|
212
|
};
|
213
|
|
214
|
});
|
215
|
|
216
|
$rootScope.$on('infoLocation', function (event, args) {
|
217
|
$scope.showInfoLoading = true;
|
218
|
|
219
|
let params = $location.search();
|
220
|
params.deviceId = args.id;
|
221
|
params.direction = args.direction;
|
222
|
$location.search(params);
|
223
|
|
224
|
let range = $scope.getRange();
|
225
|
|
226
|
Device.get({
|
227
|
id: args.id,
|
228
|
direction: args.direction,
|
229
|
dateFrom: range.fromDate.format('YYYY-MM-DD'),
|
230
|
dateTo: range.toDate.format('YYYY-MM-DD'),
|
231
|
timeFrom: range.fromTime.format('HH:mm'),
|
232
|
timeTo: range.toTime.format('HH:mm'),
|
233
|
}, function (data) {
|
234
|
$rootScope.selectDevice = data;
|
235
|
|
236
|
$scope.typeVehicle = null;
|
237
|
$scope.renderGraphAverageSpeed();
|
238
|
$scope.renderGraphNumberVehicles();
|
239
|
|
240
|
$scope.showInfoLoading = false;
|
241
|
}, function (response) {
|
242
|
$rootScope.selectDevice = null;
|
243
|
$scope.showInfoLoading = false;
|
244
|
console.log('Error api get Devices');
|
245
|
$rootScope.handleErrorResponse(response);
|
246
|
});
|
247
|
|
248
|
});
|
249
|
|
250
|
$scope.changeRange = function () {
|
251
|
if ($scope.range.fromDate >= $scope.range.toDate || $scope.range.fromTime >= $scope.range.toTime) {
|
252
|
$rootScope.selectDevice.traffics = [];
|
253
|
return;
|
254
|
}
|
255
|
|
256
|
let range = $scope.getRange();
|
257
|
|
258
|
let params = $location.search();
|
259
|
params.fromDate = range.fromDate.format('YYYY-MM-DD');
|
260
|
params.toDate = range.toDate.format('YYYY-MM-DD');
|
261
|
params.fromTime = range.fromTime.format('HH:mm');
|
262
|
params.toTime = range.toTime.format('HH:mm');
|
263
|
$location.search(params);
|
264
|
|
265
|
if ($rootScope.selectDevice)
|
266
|
$rootScope.$emit('infoLocation', {
|
267
|
id: $rootScope.selectDevice.id,
|
268
|
direction: $rootScope.selectDevice.direction
|
269
|
});
|
270
|
};
|
271
|
|
272
|
$scope.getRange = function () {
|
273
|
let defaultRange = $scope.defaultRange();
|
274
|
|
275
|
return {
|
276
|
fromDate: moment($scope.range.fromDate).isValid() ? moment($scope.range.fromDate) : defaultRange.fromDate,
|
277
|
toDate: moment($scope.range.toDate).isValid() ? moment($scope.range.toDate) : defaultRange.toDate,
|
278
|
fromTime: moment($scope.range.fromTime).isValid() ? moment($scope.range.fromTime) : defaultRange.fromTime,
|
279
|
toTime: moment($scope.range.toTime).isValid() ? moment($scope.range.toTime) : defaultRange.toTime
|
280
|
};
|
281
|
};
|
282
|
|
283
|
$scope.defaultRange = function () {
|
284
|
return {
|
285
|
fromDate: moment().day(-30),
|
286
|
toDate: moment().day(-1),
|
287
|
fromTime: moment({hour: 7}),
|
288
|
toTime: moment({hour: 16})
|
289
|
};
|
290
|
};
|
291
|
|
292
|
|
293
|
$scope.renderGraphAverageSpeed = function () {
|
294
|
|
295
|
let t = $rootScope.selectDevice.traffics.reduce(function (l, r) {
|
296
|
let key = r.timeFrom;
|
297
|
if (typeof l[key] === 'undefined') {
|
298
|
l[key] = {
|
299
|
numberVehicle: 0,
|
300
|
speedSum: 0
|
301
|
};
|
302
|
}
|
303
|
|
304
|
if (r.speedAverage > 0) {
|
305
|
l[key].numberVehicle += r.numberVehicle;
|
306
|
l[key].speedSum += r.speedAverage * r.numberVehicle;
|
307
|
}
|
308
|
return l;
|
309
|
}, {});
|
310
|
|
311
|
let labels = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
|
312
|
return d.timeFrom;
|
313
|
}));
|
314
|
let data = Object.values(t).map(function (d) {
|
315
|
return Math.round(d.speedSum / d.numberVehicle);
|
316
|
});
|
317
|
|
318
|
|
319
|
let canvasGraphAverageSpeed = document.getElementById('graphAverageSpeed').getContext('2d');
|
320
|
|
321
|
if ($scope.graphAverageSpeed)
|
322
|
$scope.graphAverageSpeed.destroy();
|
323
|
|
324
|
$scope.graphAverageSpeed = new Chart(canvasGraphAverageSpeed, {
|
325
|
type: 'line',
|
326
|
data: {
|
327
|
labels: labels,
|
328
|
datasets: [{
|
329
|
data: data,
|
330
|
borderWidth: 2,
|
331
|
label: "Rychlost",
|
332
|
fill: 'start',
|
333
|
backgroundColor: 'rgba(0, 123, 255, 0.3)',
|
334
|
borderColor: 'rgba(0, 123, 255,1)',
|
335
|
cubicInterpolationMode: 'monotone',
|
336
|
radius: 0
|
337
|
}]
|
338
|
},
|
339
|
options: {
|
340
|
responsive: true,
|
341
|
pointDot: false,
|
342
|
scales: {
|
343
|
xAxes: [{
|
344
|
ticks: {
|
345
|
autoSkip: true,
|
346
|
maxTicksLimit: 15
|
347
|
}
|
348
|
}],
|
349
|
yAxes: [{
|
350
|
scaleLabel: {
|
351
|
display: true,
|
352
|
labelString: 'km/h'
|
353
|
},
|
354
|
ticks: {
|
355
|
beginAtZero: true
|
356
|
}
|
357
|
}]
|
358
|
},
|
359
|
tooltips: {
|
360
|
enabled: true,
|
361
|
mode: 'single',
|
362
|
callbacks: {
|
363
|
label: function (tooltipItems) {
|
364
|
return tooltipItems.yLabel + ' km/h';
|
365
|
}
|
366
|
}
|
367
|
}
|
368
|
}
|
369
|
});
|
370
|
};
|
371
|
|
372
|
|
373
|
$scope.renderGraphNumberVehicles = function () {
|
374
|
let color = ['rgba(158, 158, 158, #alpha)', 'rgba(213, 0, 0, #alpha)', 'rgba(0, 123, 255, #alpha)', 'rgba(170, 0, 255, #alpha)',
|
375
|
'rgba(0, 200, 83, #alpha)', 'rgba(255, 214, 0, #alpha)', 'rgba(255, 109, 0, #alpha)',
|
376
|
'rgba(174, 234, 0, #alpha)', 'rgba(98, 0, 234, #alpha)', 'rgba(255, 171, 0, #alpha)', 'rgba(100, 221, 23, #alpha)', 'rgba(0, 184, 212, #alpha)'];
|
377
|
|
378
|
|
379
|
let labels = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
|
380
|
return d.timeFrom;
|
381
|
}));
|
382
|
|
383
|
let useVehiclesIds = jQuery.unique($rootScope.selectDevice.traffics.map(function (d) {
|
384
|
return d.typeVehicleId;
|
385
|
}));
|
386
|
|
387
|
$scope.filterVehicles = jQuery.grep($scope.vehicles, function (n) {
|
388
|
return useVehiclesIds.indexOf(n.id) >= 0;
|
389
|
});
|
390
|
|
391
|
let datasets = [];
|
392
|
for (let i = 0, vehicle; vehicle = $scope.filterVehicles[i]; i++) {
|
393
|
if ($scope.typeVehicle == null || $scope.typeVehicle === vehicle.id) {
|
394
|
let dataset = {
|
395
|
label: vehicle.name,
|
396
|
backgroundColor: color[vehicle.id].replace("#alpha", "0.3"),
|
397
|
borderColor: color[vehicle.id].replace("#alpha", "1"),
|
398
|
borderWidth: 2,
|
399
|
data: []
|
400
|
};
|
401
|
|
402
|
let l = 0;
|
403
|
for (let j = 0, traffic; traffic = $rootScope.selectDevice.traffics[j]; j++) {
|
404
|
if (labels[l] !== traffic.timeFrom) {
|
405
|
l++;
|
406
|
if (dataset.data.length < l) {
|
407
|
dataset.data.push(0);
|
408
|
}
|
409
|
}
|
410
|
if (traffic.typeVehicleId === vehicle.id) {
|
411
|
dataset.data.push(traffic.numberVehicleAverage);
|
412
|
}
|
413
|
}
|
414
|
datasets.push(dataset);
|
415
|
}
|
416
|
}
|
417
|
|
418
|
let canvasGraphNumberVehicles = document.getElementById('graphNumberVehicles').getContext('2d');
|
419
|
|
420
|
if ($scope.graphNumberVehicles)
|
421
|
$scope.graphNumberVehicles.destroy();
|
422
|
|
423
|
$scope.graphNumberVehicles = new Chart(canvasGraphNumberVehicles, {
|
424
|
type: 'bar',
|
425
|
data: {
|
426
|
labels: labels,
|
427
|
datasets: datasets
|
428
|
},
|
429
|
options: {
|
430
|
tooltips: {
|
431
|
mode: 'index',
|
432
|
intersect: false
|
433
|
},
|
434
|
responsive: true,
|
435
|
scales: {
|
436
|
xAxes: [{
|
437
|
stacked: true,
|
438
|
ticks: {
|
439
|
autoSkip: true,
|
440
|
maxTicksLimit: 15
|
441
|
}
|
442
|
}],
|
443
|
yAxes: [{
|
444
|
scaleLabel: {
|
445
|
display: true,
|
446
|
labelString: "počet vozidel"
|
447
|
},
|
448
|
stacked: true
|
449
|
}]
|
450
|
}
|
451
|
}
|
452
|
});
|
453
|
};
|
454
|
|
455
|
|
456
|
$scope.infoClose = function () {
|
457
|
$rootScope.selectDevice = null;
|
458
|
|
459
|
let params = $location.search();
|
460
|
params.deviceId = null;
|
461
|
params.direction = null;
|
462
|
$location.search(params);
|
463
|
|
464
|
$rootScope.$emit('setDefaultMap', null);
|
465
|
};
|
466
|
});
|
467
|
|
468
|
|
469
|
app.controller('mapController', function ($rootScope, $scope, config, Device) {
|
470
|
|
471
|
this.$onInit = function () {
|
472
|
|
473
|
$scope.map = new GMaps({
|
474
|
div: '#map',
|
475
|
zoomControl: true,
|
476
|
mapTypeControl: false,
|
477
|
scaleControl: false,
|
478
|
streetViewControl: false,
|
479
|
rotateControl: false,
|
480
|
fullscreenControl: false,
|
481
|
mapTypeId: 'roadmap',
|
482
|
zoom: config.DEFAULT_ZOOM,
|
483
|
lat: config.DEFAULT_POSITION.LAT,
|
484
|
lng: config.DEFAULT_POSITION.LNG,
|
485
|
// styles: [
|
486
|
// {
|
487
|
// featureType: "poi",
|
488
|
// elementType: "labels",
|
489
|
// stylers: [{ visibility: "off" }]
|
490
|
// }
|
491
|
// ]
|
492
|
});
|
493
|
|
494
|
Device.query({showDirection: 0}, function (data) {
|
495
|
for (let i = 0, lctn; lctn = data[i]; i++) {
|
496
|
$scope.createMarker(lctn);
|
497
|
}
|
498
|
}, function (response) {
|
499
|
console.log('Error api all Devices');
|
500
|
$rootScope.handleErrorResponse(response);
|
501
|
});
|
502
|
};
|
503
|
|
504
|
|
505
|
$scope.createMarker = function (lctn) {
|
506
|
if (lctn.lat && lctn.lng) {
|
507
|
$scope.map.addMarker({
|
508
|
lat: lctn.lat,
|
509
|
lng: lctn.lng,
|
510
|
title: lctn.name,
|
511
|
click: function () {
|
512
|
$rootScope.$emit('infoLocation', {id: lctn.id});
|
513
|
},
|
514
|
infoWindow: {
|
515
|
content: '<h6 class="mb-1">' + lctn.name + '</h6>'
|
516
|
+ '<address>' + lctn.street + ', ' + lctn.town + '</address>'
|
517
|
},
|
518
|
id: lctn.id
|
519
|
});
|
520
|
}
|
521
|
};
|
522
|
|
523
|
$rootScope.$on('activeMarker', function (event, args) {
|
524
|
let id = args.id;
|
525
|
for (let i = 0, marker; marker = $scope.map.markers[i]; i++) {
|
526
|
if (marker.id && marker.id === id && marker.infoWindow) {
|
527
|
$scope.map.setCenter(marker.position.lat(), marker.position.lng());
|
528
|
$scope.map.setZoom(12);
|
529
|
$scope.map.hideInfoWindows();
|
530
|
marker.infoWindow.open($scope.map, marker);
|
531
|
return;
|
532
|
}
|
533
|
}
|
534
|
});
|
535
|
|
536
|
$rootScope.$on('setDefaultMap', function (event, args) {
|
537
|
$scope.map.setCenter(config.DEFAULT_POSITION.LAT, config.DEFAULT_POSITION.LNG);
|
538
|
$scope.map.setZoom(config.DEFAULT_ZOOM);
|
539
|
$scope.map.hideInfoWindows();
|
540
|
});
|
541
|
});
|
542
|
|
543
|
|
544
|
app.factory('Device', function ($resource, config) {
|
545
|
return $resource(config.API_URL + '/devices/:id', {id: '@id'}, {
|
546
|
'get': {
|
547
|
url: config.API_URL + '/devices/:id/time-period',
|
548
|
method: 'GET',
|
549
|
headers: {
|
550
|
'Content-Type': 'application/json',
|
551
|
'Accept': 'application/json',
|
552
|
'jwt': config.API_TOKEN
|
553
|
}
|
554
|
},
|
555
|
'query': {
|
556
|
url: config.API_URL + '/devices',
|
557
|
method: 'GET',
|
558
|
isArray: true,
|
559
|
headers: {
|
560
|
'Content-Type': 'application/json',
|
561
|
'Accept': 'application/json',
|
562
|
'jwt': config.API_TOKEN
|
563
|
}
|
564
|
}
|
565
|
});
|
566
|
});
|
567
|
|
568
|
app.factory('Vehicle', function ($resource, config) {
|
569
|
return $resource(config.API_URL + '/vehicles', null, {
|
570
|
'query': {
|
571
|
url: config.API_URL + '/vehicles',
|
572
|
method: 'GET',
|
573
|
isArray: true,
|
574
|
headers: {
|
575
|
'Content-Type': 'application/json',
|
576
|
'Accept': 'application/json',
|
577
|
'jwt': config.API_TOKEN
|
578
|
}
|
579
|
}
|
580
|
});
|
581
|
});
|