Projekt

Obecné

Profil

Stáhnout (22 KB) Statistiky
| Větev: | Revize:
1
/* global L */
2
/* global $ */
3

    
4
var mymap
5
var heatmapLayer = null
6
var markersLayer = null
7

    
8
// values for arrow keys
9
const arrowKeyLEFT = 37
10
const arrowKeyRIGHT = 39
11

    
12
var startX = 49.7248
13
var startY = 13.3521
14
var startZoom = 17
15

    
16
var dataSourceRoute
17
let positionsSourceRoute
18

    
19
let currentTime
20
let currentDate
21

    
22
var timer
23
var isAnimationRunning = false
24
var data = []
25

    
26
//
27
// info = {
28
//  DATASETNAME: {
29
//    items: Array,
30
//    number: Number,
31
//    datasetName: String
32
// }
33
// }
34
//
35
var info = []
36
let currentPageInPopup = 0
37

    
38
// dictionary for names of datasets
39
const datasetDictNameDisplayName = {}
40
var datasetSelected = []
41

    
42
// animate data only in one day
43
let lockedDay = false
44

    
45
// loading information for async operations
46
let loading = 0
47

    
48
// default loader showup delay
49
const defaultLoaderDelay = 1000
50

    
51
// map markers for all datasets
52
const dataMapMarkers = {}
53

    
54
const globalMarkersHolder = {}
55
// all marker from which popup was removed
56
// contains: {key:[L.circle,L.pupup]}
57
// key: x and y, x + '' + y string
58
let globalMarkersChanged = {}
59

    
60
const globalPopup = {
61
  coord: {
62
    lat: 0,
63
    lng: 0
64
  },
65
  _popup: null
66
}
67

    
68

    
69

    
70

    
71
const onDocumentReady = () => {
72
  $('#dropdown-dataset').on('click', function (e) {
73
    e.stopPropagation()
74
  })
75

    
76
  $('#btn-update-heatmap').prop('name', '')
77
  changeCurrentTime()
78
  changeCurrentDate()
79
  onValueChangeRegister()
80
  onArrowLeftRightKeysDownRegister()
81
}
82

    
83

    
84

    
85

    
86
/* ------------ DATA FETCHERS ------------ */
87

    
88
const genericFetch = async (route, method) => {
89
  const headers = new Headers()
90
  const request = new Request(route, {
91
    method: method,
92
    headers: headers
93
  })
94
  const beforeJson = await fetch(request)
95

    
96
  return beforeJson.json()
97
}
98

    
99
const fetchDatasetDataByDatetime = async (route, datasetName, date, time) => {
100
  return await genericFetch(route + '/' + datasetName + '/' + date + '/' + time, 'GET')
101
}
102

    
103
const fetchDatasetMapMarkers = async (route, datasetName) => {
104
  return await genericFetch(route + '/' + datasetName, 'GET')
105
}
106

    
107
preload = async (time, timeShift, date) => {
108
  loadingY()
109

    
110
  for (let nTime = time + timeShift; nTime >= 0 && nTime <= 23; nTime = nTime + timeShift) {
111
    if (!data[nTime]) {
112
      data[nTime] = {}
113
    }
114

    
115
    datasetSelected.forEach(async (datasetName) => {
116
      if (!data[nTime][datasetName]) {
117
        data[nTime][datasetName] = await fetchDatasetDataByDatetime(dataSourceRoute, datasetName, date, nTime)
118
      }
119
    })
120
  }
121

    
122
  loadingN()
123
}
124

    
125
/**
126
 * Load and display heatmap layer for current data
127
 * 
128
 * @param {string} opendataRoute route to dataset source
129
 * @param {string} positionsRoute  route to dataset positions source
130
 */
131
const loadCurrentTimeHeatmap = async (opendataRoute, positionsRoute, loaderDelay = defaultLoaderDelay) => {
132
  loadCheckboxDatasetNameData()
133

    
134
  dataSourceRoute = opendataRoute
135
  positionsSourceRoute = positionsRoute
136
  const allPromises = []
137
  data[currentTime] = {}
138

    
139
  const dataSelectedHandler = async (datasetName) => {
140
    if (!(datasetName in dataMapMarkers)) {
141
      dataMapMarkers[datasetName] = await fetchDatasetMapMarkers(positionsRoute, datasetName)
142
    }
143

    
144
    const datasetData = await fetchDatasetDataByDatetime(dataSourceRoute, datasetName, currentDateToString(), currentTime)
145
    data[currentTime][datasetName] = datasetData
146
  }
147

    
148
  datasetSelected.forEach((datasetName) => {
149
    allPromises.push(dataSelectedHandler(datasetName))
150
  })
151

    
152
  loadingY(loaderDelay)
153

    
154
  await Promise.all(allPromises).then(
155
    () => {
156
      loadingN(0)
157
      drawMapMarkers(dataMapMarkers)
158
      drawHeatmap(data[currentTime])
159

    
160
      preload(currentTime, 1, currentDateToString())
161
      preload(currentTime, -1, currentDateToString())
162
    }
163
  )
164
}
165

    
166
/**
167
 * Checks dataset availibility
168
 * @param {string} route authority for datasets availibility checks
169
 */
170
const checkDataSetsAvailability = async (route) => {
171
  const result = await genericFetch(route + '/' + currentDateToString(), 'POST')
172
  updateAvailableDataSets(result)
173
}
174

    
175

    
176

    
177

    
178
/* ------------ MAP ------------ */
179

    
180
/**
181
 * Initialize leaflet map on start position which can be default or set based on user action
182
 */
183
const initMap = () => {
184
  startX = localStorage.getItem('lat') || startX
185
  startY = localStorage.getItem('lng') || startY
186
  startZoom = localStorage.getItem('zoom') || startZoom
187

    
188
  mymap = L.map('heatmap').setView([startX, startY], startZoom)
189

    
190
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
191
    attribution: '',
192
    maxZoom: 19
193
  }).addTo(mymap)
194

    
195
  mymap.on('click', function (e) { showPopup(e) })
196
}
197

    
198
const setMapView = (latitude, longitude, zoom) => {
199
  localStorage.setItem('lat', latitude)
200
  localStorage.setItem('lng', longitude)
201
  localStorage.setItem('zoom', zoom)
202

    
203
  mymap.setView([latitude, longitude], zoom)
204
}
205

    
206
const drawHeatmap = (dataRaw) => {
207
  const dataDict = dataRaw
208
  const mergedPoints = []
209
  let max = 0
210

    
211
  if (Object.keys(globalMarkersChanged).length) {
212
    Object.keys(globalMarkersChanged).forEach(function (key) {
213
      globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
214
    })
215
    globalMarkersChanged = {}
216
  }
217

    
218
  Object.keys(dataDict).forEach((key) => {
219
    const data = dataDict[key]
220
    max = Math.max(max, data.max)
221

    
222
    if (data != null) {
223
      // Bind back popups for markers (we dont know if there is any data for this marker or not)
224
      const points = data.items.map((point) => {
225
        const { x, y, number } = point
226
        const key = x + '' + y
227
        const holder = globalMarkersHolder[key]
228
        if (!globalMarkersChanged[key] && number) {
229
          // There is data for this marker => unbind popup with zero value
230
          holder[0] = holder[0].unbindPopup()
231
          globalMarkersChanged[key] = holder
232
        }
233

    
234
        return [x, y, number]
235
      })
236
      mergedPoints.push(...points)
237
    } else {
238
      if (heatmapLayer != null) {
239
        mymap.removeLayer(heatmapLayer)
240
      }
241
    }
242
  })
243

    
244
  if (heatmapLayer != null) {
245
    mymap.removeLayer(heatmapLayer)
246
  }
247

    
248
  if (mergedPoints.length) {
249
    heatmapLayer = L.heatLayer(mergedPoints, { max: max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
250
  }
251

    
252
  updatePopup()
253
}
254

    
255
const drawMapMarkers = (data) => {
256
  if (markersLayer != null) {
257
    mymap.removeLayer(markersLayer)
258
  }
259

    
260
  markersLayer = L.layerGroup()
261

    
262
  Object.keys(data).forEach(key_ => {
263
    for (var key in data[key_]) {
264
      const { x, y, name } = data[key_][key]
265
      const pop =
266
        prepareLayerPopUp(x, y, 1, `popup-${key_}`)
267
          .setContent(getPopupContent(datasetDictNameDisplayName[key_], name, 0, 0, 1, 1))
268
      const newCircle =
269
        L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
270
          .bindPopup(pop)
271
      globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
272
      markersLayer.addLayer(
273
        newCircle
274
      )
275
    }
276
  })
277

    
278
  markersLayer.setZIndex(-1).addTo(mymap)
279
}
280

    
281

    
282

    
283

    
284
/* ------------ GUI ------------ */
285

    
286
const changeCurrentTime = (time = null) => {
287
  if (time !== null) {
288
    currentTime = time
289
  } else {
290
    currentTime = parseInt($('#dropdown-time input[type="radio"]:checked').val())
291
  }
292
}
293

    
294
const changeCurrentDate = (date = null) => {
295
  const dateInput = $('#date')
296
  currentDate = new Date(date ? date : dateInput.val())
297

    
298
  dateInput.val(currentDateToString())
299
  $('#player-date span').html(`${currentDate.getDate()}. ${currentDate.getMonth() + 1}. ${currentDate.getFullYear()}`)
300

    
301
  data = []
302
}
303

    
304
const toggleDayLock = () => {
305
  lockedDay = !lockedDay
306
  $('#player-date').toggleClass('lock')
307
}
308

    
309

    
310

    
311

    
312
/* ------------ POPUPS ------------ */
313

    
314
const setGlobalPopupContent = (content) => {
315
  globalPopup._popup.setContent(content)
316
  globalPopup._popup.openOn(mymap)
317
}
318

    
319
const getPaginationButtonsInPopup = (currentPage, countPages) => ({
320
  previousButton: '<button type="button" id="btn-popup-previous-page" onclick="js.setPreviousPageInPopup()"></button>',
321
  pagesList: `<p id="pages">${currentPage} z ${countPages}</p>`,
322
  nextButton: '<button type="button" id="btn-popup-next-page" class="next" onclick="js.setNextPageInPopup()"></button>'
323
})
324

    
325
const disablePopupPaginationButtons = () => {
326
  $('#btn-popup-previous-page').prop('disabled', true)
327
  $('#btn-popup-next-page').prop('disabled', true)
328
  $('.popup-pagination').hide()
329
}
330

    
331
const generatePopupPaginationButtons = (controls) => {
332
  return `<div class="popup-pagination">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
333
}
334

    
335
const getCountPagesInPopup = () => {
336
  const infoKeys = Object.keys(info)
337

    
338
  if (infoKeys.length === 1) {
339
    // return number of records in one dataset (one dataset in area)
340
    return info[infoKeys[0]].items.length
341
  }
342
  // return number of datasets (agregation of all datasets in area)
343
  return infoKeys.length
344
}
345

    
346
const getPopupDataOnPage = (pageInPopup) => {
347
  const keys = Object.keys(info)
348
  return info[keys[pageInPopup]]
349
}
350

    
351
const setPreviousPageInPopup = () => {
352
  const countPagesInPopup = getCountPagesInPopup()
353
  const page = currentPageInPopup
354

    
355
  currentPageInPopup = (currentPageInPopup + countPagesInPopup - 1) % countPagesInPopup
356
  setPageContentInPopup(page)
357
}
358

    
359
const setNextPageInPopup = () => {
360
  const countPagesInPopup = getCountPagesInPopup()
361
  const page = currentPageInPopup
362

    
363
  currentPageInPopup = (currentPageInPopup + 1) % countPagesInPopup
364
  setPageContentInPopup(page)
365
}
366

    
367
const genMultipleDatasetsPopUp = (sum, currentPage, countPages, datasetName) => {
368
  const popupHeader = `<strong id="dataset-name">${datasetName}</strong>`
369
  const popupData = `<div id="place-intesity"><span id="current-number">${sum}</span></div>`
370
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
371

    
372
  return `
373
  ${popupHeader}
374
  ${popupData}
375
  ${generatePopupPaginationButtons([previousButton, pagesList, nextButton])}
376
  `
377
}
378

    
379
const prepareLayerPopUp = (lat, lng, num, className) => L.popup({
380
  autoPan: false,
381
  className: className
382
}).setLatLng([lat / num, lng / num])
383

    
384
const getPopupContent = (datasetName, placeName, currentCount, sum, currentPage, countPages) => {
385
  const popupHeader = `
386
    <strong>${datasetName}</strong>
387
    <div id="place-name">${placeName}</div>`
388
  const popupData = `
389
    <div id="place-intesity">
390
      <span id="current-number">${currentCount}</span>
391
      <span id="sum-number">${(sum && sum !== Number(currentCount)) ? '/' + sum : ''}</span>
392
    </div>`
393
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
394

    
395
  return `
396
  ${popupHeader}
397
  ${popupData}
398
  ${generatePopupPaginationButtons(countPages > 1 ? [previousButton, pagesList, nextButton] : null)}
399
  `
400
}
401

    
402
const areMultipleDatasetsInRadius = () => {
403
  return Object.keys(info).length > 1
404
}
405

    
406
const setPopupDatasetClassName = (datasetName) => {
407
  const popup = $('.leaflet-popup')
408

    
409
  popup.removeClass(function (index, css) {
410
    return (css.match(/(^|\s)popup-\S+/g) || []).join(' ');
411
  })
412
  popup.addClass('popup-' + datasetName)
413
}
414

    
415
const showPopup = (e) => {
416
  info = []
417
  currentPageInPopup = 0
418

    
419
  const stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
420
  const radius = 25 * stile / 256
421

    
422
  let i = 0
423
  let lat = 0
424
  let lng = 0
425

    
426
  let total = 0
427

    
428
  const datasetsInRadius = {}
429
  const eventCoord = {
430
    lng: e.latlng.lng,
431
    lat: e.latlng.lat
432
  }
433

    
434
  Object.keys(data[currentTime]).forEach(key => {
435
    const namedData = data[currentTime][key]
436

    
437
    namedData.items.forEach(element => {
438
      if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
439
        lat += element.x
440
        lng += element.y
441
        info[i] = { place: element.place, number: element.number, datasetName: key }
442
        total += parseInt(element.number)
443
        i++
444
        datasetsInRadius[key] = true
445
      }
446
    })
447
  })
448

    
449
  // Process info for more then one dataset
450
  info = info.reduce((acc, item) => {
451
    if (!acc[item.datasetName]) {
452
      acc[item.datasetName] = {
453
        items: [],
454
        number: 0,
455
        datasetName: item.datasetName
456
      }
457
    }
458

    
459
    acc[item.datasetName].items.push(item)
460
    acc[item.datasetName].number += Number(item.number)
461
    return acc
462
  }, {})
463

    
464
  const countDatasets = Object.keys(datasetsInRadius).length
465

    
466
  if (!countDatasets) {
467
    if (mymap._popup) {
468
      $('#sum-number').text('')
469
      $('#current-number').html(0)
470
      disablePopupPaginationButtons()
471
    }
472

    
473
    return
474
  }
475

    
476
  if (countDatasets === 1) {
477
    const markersInRadius = getPopupDataOnPage(0)
478
    const popupPagesData = markersInRadius.items
479
    const { place, number } = popupPagesData[currentPageInPopup]
480

    
481
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
482
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${markersInRadius.datasetName}`)
483
      globalPopup.coord = eventCoord
484
    }
485
    else {
486
      setPopupDatasetClassName(markersInRadius.datasetName)
487
    }
488

    
489
    setGlobalPopupContent(getPopupContent(datasetDictNameDisplayName[markersInRadius.datasetName], place, number, total, 1, popupPagesData.length))
490

    
491
    if (popupPagesData.length === 1) {
492
      disablePopupPaginationButtons()
493
    }
494
  } else {
495
    const { datasetName, number } = getPopupDataOnPage(currentPageInPopup)
496

    
497
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
498
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
499
      globalPopup.coord = eventCoord
500
    }
501
    else {
502
      setPopupDatasetClassName(datasetName)
503
    }
504

    
505
    setGlobalPopupContent(genMultipleDatasetsPopUp(number, 1, getCountPagesInPopup(), datasetDictNameDisplayName[datasetName]))
506
  }
507
}
508

    
509
const setPageContentInPopup = (page) => {
510
  const previousPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(page) : getPopupDataOnPage(0).items[page]
511
  const currentPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(currentPageInPopup) : getPopupDataOnPage(0).items[currentPageInPopup]
512
  const datasetName = $('#dataset-name')
513

    
514
  if (datasetName) {
515
    datasetName.html(datasetDictNameDisplayName[currentPageData.datasetName])
516
  }
517

    
518
  $('#place-name').html(currentPageData.place ? currentPageData.place : currentPageData.datasetName)
519
  $('#current-number').html(currentPageData.number)
520
  $('#pages').html(currentPageInPopup + 1 + ' z ' + getCountPagesInPopup())
521

    
522
  $('.leaflet-popup').removeClass(`popup-${previousPageData.datasetName}`).addClass(`popup-${currentPageData.datasetName}`)
523
}
524

    
525
const updatePopup = () => {
526
  const { _popup } = mymap
527

    
528
  if (_popup) {
529
    showPopup({
530
      latlng: _popup.getLatLng()
531
    })
532
  }
533
}
534

    
535

    
536

    
537

    
538
/* ------------ ANIMATION ------------ */
539

    
540
/**
541
 * Change animation start from playing to stopped or the other way round
542
 */
543
const changeAnimationState = () => {
544
  const btnAnimate = $('#animate-btn')
545

    
546
  isAnimationRunning = !isAnimationRunning
547

    
548
  if (isAnimationRunning) {
549
    btnAnimate.removeClass('play').addClass('pause')
550
    timer = setInterval(function () { next() }, 800)
551
  } else {
552
    clearTimeout(timer)
553
    btnAnimate.removeClass('pause').addClass('play')
554
  }
555
}
556

    
557
const previous = async () => {
558
  if (loading) {
559
    return
560
  }
561

    
562
  currentTime = (currentTime + 23) % 24
563
  changeHour(currentTime)
564

    
565
  if (!lockedDay && currentTime === 23) {
566
    addDayToCurrentDate(-1)
567
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
568
  } else {
569
    drawHeatmap(data[currentTime])
570
  }
571

    
572
  updatePopup()
573
}
574

    
575
const next = async () => {
576
  if (loading) {
577
    return
578
  }
579

    
580
  currentTime = (currentTime + 1) % 24
581
  changeHour(currentTime)
582

    
583
  if (!lockedDay && currentTime === 0) {
584
    addDayToCurrentDate(1)
585
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
586
  } else {
587
    drawHeatmap(data[currentTime])
588
  }
589

    
590
  updatePopup()
591
}
592

    
593
const onChangeHour = (hour) => {
594
  changeHour(hour)
595
  drawHeatmap(data[currentTime])
596
}
597

    
598
const changeHour = (hour) => {
599
  $('#player-time').removeAttr('style')
600
  changeCurrentTime(hour)
601
  updateHeaderControls()
602
  setTimeline()
603
  changeUrlParameters()
604
  updatePopup()
605
}
606

    
607
const dragTimeline = () => {
608
  const hourElemWidthPx = 26
609

    
610
  const elem = $('#player-time')
611
  const offset = elem.offset().left - elem.parent().offset().left
612

    
613
  if (offset >= 0 && offset <= elem.parent().width()) {
614
    const hour = Math.round(offset / hourElemWidthPx)
615

    
616
    if (hour !== currentTime) {
617
      elem.attr('class', 'time hour-' + hour)
618
      $('#player-time span').html(formatTime(hour))
619

    
620
      onChangeHour(hour)
621
    }
622
  }
623
}
624

    
625
const onArrowLeftRightKeysDownRegister = () => {
626
  $(document).keydown(function (e) {
627
    const { which } = e
628

    
629
    if (which === arrowKeyLEFT) {
630
      previous()
631
      e.preventDefault()
632
    } else if (which === arrowKeyRIGHT) {
633
      next()
634
      e.preventDefault()
635
    }
636
  })
637
}
638

    
639
/**
640
 * Change browser url based on animation step.
641
 */
642
const changeUrlParameters = () => {
643
  window.history.pushState(
644
    '',
645
    document.title,
646
    window.location.origin + window.location.pathname + `?date=${currentDateToString()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type[]=' + current, '')}`
647
  )
648
}
649

    
650

    
651

    
652

    
653
/* ------------ UTILS ------------ */
654

    
655
const formatTime = (hours, twoDigitsHours = false) => {
656
  return ((twoDigitsHours && hours < 10) ? '0' : '') + hours + ':00';
657
}
658

    
659
const formatDate = (date) => {
660
  var day = String(date.getDate())
661
  var month = String(date.getMonth() + 1)
662

    
663
  if (day.length === 1) {
664
    day = '0' + day
665
  }
666

    
667
  if (month.length === 1) {
668
    month = '0' + month
669
  }
670

    
671
  // return YYYY-MM-DD
672
  return date.getFullYear() + '-' + month + '-' + day
673
}
674

    
675
const currentDayToString = () => {
676
  const day = currentDate.getDate()
677
  return day > 9 ? `${day}` : `0${day}`
678
}
679

    
680
const currentMonthToString = () => {
681
  const month = currentDate.getMonth() + 1
682
  return month > 9 ? `${month}` : `0${month}`
683
}
684

    
685
const currentDateToString = () => `${currentDate.getFullYear()}-${currentMonthToString()}-${currentDayToString()}`
686

    
687
const addDayToCurrentDate = (day) => {
688
  currentDate.setDate(currentDate.getDate() + day)
689
  changeCurrentDate(currentDate)
690
}
691

    
692
const areCoordsIdentical = (first, second) => {
693
  return first.lat === second.lat && first.lng === second.lng
694
}
695

    
696
const debounce = (func, delay) => {
697
  let inDebounce
698
  return function () {
699
    const context = this
700
    const args = arguments
701
    clearTimeout(inDebounce)
702
    inDebounce = setTimeout(() => func.apply(context, args), delay)
703
  }
704
}
705

    
706

    
707

    
708

    
709
/* ------------ GUI ------------ */
710

    
711
const updateHeaderControls = () => {
712
  $(`#time_${currentTime}`).prop('checked', true)
713
  $('#dropdownMenuButtonTime').html(formatTime(currentTime, true))
714
}
715

    
716
const initDatepicker = async (availableDatesSource) => {
717
  const result = await genericFetch(availableDatesSource, 'GET')
718
  const datesContainData = String(result).split(',')
719

    
720
  $('#date').datepicker({
721
    format: 'yyyy-mm-dd',
722
    language: 'cs',
723
    beforeShowDay: function (date) {
724
      if (datesContainData.indexOf(formatDate(date)) < 0) {
725
        return { enabled: false, tooltip: 'Žádná data' }
726
      } else {
727
        return { enabled: true }
728
      }
729
    },
730
    autoclose: true
731
  })
732
}
733

    
734
const initLocationsMenu = () => {
735
  const elmLocationsList = $('.locations')
736
  const locationsDisplayClass = 'show'
737

    
738
  if ($(window).width() <= 480) {
739
    elmLocationsList.removeClass(locationsDisplayClass)
740
  } else {
741
    elmLocationsList.addClass(locationsDisplayClass)
742
  }
743
}
744

    
745
const setTimeline = () => {
746
  $('#player-time > span').text(formatTime(currentTime))
747
  $('#player-time').attr('class', 'time hour-' + currentTime)
748
}
749

    
750
const onValueChangeRegister = () => {
751
  $('#date').change(function () {
752
    changeCurrentDate($(this).val())
753
    loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
754
    changeUrlParameters()
755
  })
756

    
757
  $('#dropdown-time input[type="radio"]').each(function () {
758
    $(this).change(function () {
759
      changeHour(parseInt($(this).val()))
760
      drawHeatmap(data[currentTime])
761
    })
762
  })
763

    
764
  $('#dropdown-dataset input[type="checkbox"]').each(function () {
765
    $(this).change(
766
      debounce(() => onCheckboxClicked(this), 1000)
767
    )
768
  })
769
}
770

    
771
const onCheckboxClicked = async (checkbox) => {
772
  if ($(checkbox).prop('checked')) {
773
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
774
  } else {
775
    loadCheckboxDatasetNameData()
776

    
777
    data.forEach((item, index) => {
778
      Object.keys(item).forEach(datasetName => {
779
        if (datasetName === $(checkbox).val()) {
780
          delete data[index][datasetName]
781
        }
782
      })
783

    
784
      drawHeatmap(data[currentTime])
785
    })
786
  }
787

    
788
  updatePopup()
789
  changeUrlParameters()
790
}
791

    
792
const loadCheckboxDatasetNameData = () => {
793
  datasetSelected = []
794

    
795
  $('#dropdown-dataset .dropdown-item').each(function () {
796
    const input = $(this).find('input')
797
    const inputVal = input[0].value
798

    
799
    if (input[0].checked) {
800
      datasetSelected.push(inputVal)
801
    }
802

    
803
    datasetDictNameDisplayName[inputVal] = $(input).data('dataset-display-name')
804
  })
805
}
806

    
807
const updateAvailableDataSets = async (available) => {
808
  let leastOneOptionEnabled = false
809

    
810
  $('#dropdown-dataset .dropdown-item').each(function () {
811
    const input = $(this).find('input')
812

    
813
    if (!(input[0].value in available)) {
814
      $(this).addClass('disabled')
815
      $(input).prop('checked', false)
816
    } else {
817
      leastOneOptionEnabled = true
818
      $(this).removeClass('disabled')
819
    }
820
  })
821

    
822
  $('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled)
823
}
824

    
825

    
826

    
827

    
828
/* ------------ GUI LOADING ------------ */
829

    
830
const loadingCallbackNested = (func, delay) => {
831
  setTimeout(() => {
832
    func(loading)
833
    if (loading) {
834
      loadingCallbackNested(func, delay)
835
    }
836
  }, delay)
837
}
838

    
839
const loadingY = (delay = defaultLoaderDelay) => {
840
  loading++
841
  // check after nms if there is something that is loading
842
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading), delay))
843
}
844

    
845
const loadingN = (delay = defaultLoaderDelay) => {
846
  loading--
847
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading)), delay)
848
}
849

    
850
const loadingTimeline = (isLoading) => {
851
  if (isLoading) {
852
    loadingYTimeline()
853
  } else {
854
    loadingNTimeline()
855
  }
856
}
857

    
858
const loadingYTimeline = () => {
859
  $('#player-time > .spinner-border').removeClass('d-none')
860
  $('#player-time > span').text('')
861
}
862

    
863
const loadingNTimeline = () => {
864
  $('#player-time > .spinner-border').addClass('d-none')
865
  setTimeline()
866
}
867

    
868
module.exports = {
869
  initDatepicker,
870
  initLocationsMenu,
871
  initMap,
872
  onDocumentReady,
873
  checkDataSetsAvailability,
874
  loadCurrentTimeHeatmap,
875
  dragTimeline,
876
  setPreviousPageInPopup,
877
  setNextPageInPopup,
878
  previous,
879
  next,
880
  toggleDayLock,
881
  changeAnimationState,
882
  onChangeHour,
883
  setMapView
884
}
(2-2/4)