Projekt

Obecné

Profil

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

    
4
var mymap
5
var heatmapLayer = null
6
var marksLayer = 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

    
21
let currentDate
22

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

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

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

    
43
// data only for one day
44
let lockedDay = false
45

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

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

    
52
// marks for all datasets
53
const dataSourceMarks = {}
54

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

    
61
const globalPopup = {
62
  coord: {
63
    lat: 0,
64
    lng: 0
65
  },
66
  _popup: null
67
}
68
const setGlobalPopupContent = (content) => {
69
  globalPopup._popup.setContent(content)
70
  globalPopup._popup.openOn(mymap)
71
}
72
const disablePopupControls = () => {
73
  $('#previous-info-btn').prop('disabled', true)
74
  $('#next-info-btn').prop('disabled', true)
75
  $('.popup-controls').hide()
76
}
77

    
78
const areSameCoord = (first, second) => {
79
  return first.lat === second.lat && first.lng === second.lng
80
}
81

    
82
const loadingCallbackNested = (func, delay) => {
83
  setTimeout(() => {
84
    func(loading)
85
    if (loading) {
86
      loadingCallbackNested(func, delay)
87
    }
88
  }, delay)
89
}
90
const loadingY = (delay = defaultLoaderDelay) => {
91
  loading++
92
  // check after nms if there is something that is loading
93
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading), delay))
94
}
95
const loadingN = (delay = defaultLoaderDelay) => {
96
  loading--
97
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading)), delay)
98
}
99

    
100
const changeCurrentTime = (time = null) => {
101
  if (time !== null) {
102
    currentTime = time
103
  } else {
104
    currentTime = parseInt($('#dropdown-time input[type="radio"]:checked').val())
105
  }
106
}
107

    
108
const changeCurrentDate = (date = null) => {
109
  if (date) {
110
    currentDate = new Date(date)
111
  } else {
112
    currentDate = new Date($('#date').val())
113
  }
114

    
115
  $('#date').val(currentDateToString())
116
  $('#player-date span').html(`${currentDate.getDate()}. ${currentDate.getMonth() + 1}. ${currentDate.getFullYear()}`)
117

    
118
  data = []
119
}
120
const currentDayToString = () => {
121
  const day = currentDate.getDate()
122
  return day > 9 ? `${day}` : `0${day}`
123
}
124
const currentMonthToString = () => {
125
  const month = currentDate.getMonth() + 1
126
  return month > 9 ? `${month}` : `0${month}`
127
}
128
const currentDateToString = () => `${currentDate.getFullYear()}-${currentMonthToString()}-${currentDayToString()}`
129
const addDayToCurrentDate = (day) => {
130
  currentDate.setDate(currentDate.getDate() + day)
131
  changeCurrentDate(currentDate)
132
}
133
const toggleDayLock = () => {
134
  lockedDay = !lockedDay
135
  $('#player-date').toggleClass('lock')
136
}
137

    
138
const fetchByNameDate = async (baseRoute, name, date, currentTime) => {
139
  const headers = new Headers()
140
  const myRequest = new Request(baseRoute + '/' + name + '/' + date + '/' + currentTime, {
141
    method: 'GET',
142
    headers: headers
143
  })
144
  const beforeJson = await fetch(myRequest)
145
  return beforeJson.json()
146
}
147

    
148
const fetchDataSourceMarks = async (positionRoute, datasetName) => {
149
  const headers = new Headers()
150
  const myRequest = new Request(positionRoute + '/' + datasetName, {
151
    method: 'GET',
152
    headers: headers
153
  })
154
  const beforeJson = await fetch(myRequest)
155
  return beforeJson.json()
156
}
157

    
158
const genPopUpControlButtons = (currentPage, numPages, onNextClick, onPreviousClick) => ({
159
  previousButton: '<button id="previous-info-btn" class="circle-button" onclick="previousInfo()"></button>',
160
  nextButton: '<button id="next-info-btn" class="circle-button next" onclick="nextInfo()"></button>',
161
  posInfo: `<div id="pages">${currentPage} z ${numPages}</div>`
162
})
163

    
164
const genPopUpControls = (controls) => {
165
  return `<div class="popup-controls">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
166
}
167

    
168
const genMultipleDatasetsPopUp = (sum, currentPos, maxPos, datasetName) => {
169
  const popupHeader = `<strong id="dataset-info">${datasetName}</strong>`
170
  const popupData = `<div id="number-info"><span id="current-number">${sum}</span></div>`
171
  const { previousButton, nextButton, posInfo } = genPopUpControlButtons(currentPos, maxPos)
172

    
173
  return `
174
  ${popupHeader}
175
  ${popupData}
176
  ${genPopUpControls([previousButton, posInfo, nextButton])}
177
  `
178
}
179

    
180
const prepareLayerPopUp = (lat, lng, num, className) => L.popup({
181
  autoPan: false,
182
  className: className
183
}).setLatLng([lat / num, lng / num])
184

    
185
const genPopUp = (datasetName, place, currentCount, sum, currentPos, maxPos) => {
186
  const popupHeader = `
187
    <strong>${datasetName}</strong>
188
    <div id="place-info">${place}</div>`
189
  const popupData = `
190
    <div id="number-info">
191
      <span id="current-number">${currentCount}</span>
192
      <span id="part-info">${(sum && (sum !== Number(currentCount))) ? '/' + sum : ''}</span>
193
    </div>`
194
  const { previousButton, nextButton, posInfo } = genPopUpControlButtons(currentPos, maxPos)
195

    
196
  return `
197
  ${popupHeader}
198
  ${popupData}
199
  ${genPopUpControls(maxPos > 1 ? [previousButton, posInfo, nextButton] : null)}
200
  `
201
}
202

    
203
const onCheckboxClicked = async (checkbox) => {
204
  if ($(checkbox).prop('checked')) {
205
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
206
  } else {
207
    loadCheckboxDatasetNameData()
208

    
209
    data.forEach((item, index) => {
210
      Object.keys(item).forEach((datasetName) => {
211
        if (datasetName === $(checkbox).val()) {
212
          delete data[index][datasetName]
213
        }
214
      })
215
      drawHeatmap(data[currentTime])
216
    })
217
  }
218
  updatePopup()
219
  changeUrl()
220
}
221

    
222
const onArrowLeftRightKeysDownRegister = () => {
223
  $(document).keydown(function (e) {
224
    const { which } = e
225
    
226
    if (which === arrowKeyLEFT) {
227
      previous()
228
      e.preventDefault()
229
    } else if (which === arrowKeyRIGHT) {
230
      next()
231
      e.preventDefault()
232
    }
233
  })
234
}
235

    
236
const debounce = (func, delay) => {
237
  let inDebounce
238
  return function () {
239
    const context = this
240
    const args = arguments
241
    clearTimeout(inDebounce)
242
    inDebounce = setTimeout(() => func.apply(context, args), delay)
243
  }
244
}
245

    
246
const onValueChangeRegister = () => {
247
  $('#date').change(function () {
248
    changeCurrentDate($(this).val())
249
    loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
250
    changeUrl()
251
  })
252

    
253
  $('#dropdown-time input[type="radio"]').each(function () {
254
    $(this).change(function () {
255
      changeHour(parseInt($(this).val()))
256
      drawHeatmap(data[currentTime])
257
    })
258
  })
259

    
260
  $('#dropdown-dataset input[type="checkbox"]').each(function () {
261
    $(this).change(
262
      debounce(() => onCheckboxClicked(this), 1000)
263
    )
264
  })
265
}
266

    
267
/**
268
 * Initialize leaflet map on start position which can be default or set based on user action
269
 */
270
// eslint-disable-next-line no-unused-vars
271
function initMap () {
272
  startX = localStorage.getItem('lat') || startX
273
  startY = localStorage.getItem('lng') || startY
274
  startZoom = localStorage.getItem('zoom') || startZoom
275

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

    
278
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
279
    attribution: '',
280
    maxZoom: 19
281
  }).addTo(mymap)
282

    
283
  mymap.on('click', function (e) { showInfo(e) })
284
}
285

    
286
const getInfoLength = () => {
287
  const infoKeys = Object.keys(info)
288
  if (infoKeys.length === 1) {
289
    // return number of records in one dataset (one dataset in area)
290
    return info[infoKeys[0]].items.length
291
  }
292
  // return number of datasets (agregation of all datasets in area)
293
  return infoKeys.length
294
}
295

    
296
const getElFromObjectInfo = (position) => {
297
  const keys = Object.keys(info)
298
  return info[keys[position]]
299
}
300

    
301
const hasInfoMultipleDatasets = () => {
302
  return Object.keys(info).length > 1
303
}
304

    
305
function showInfo (e) {
306
  info = []
307
  currentInfo = 0
308
  const currentPos = 1
309

    
310
  // https://wiki.openstreetmap.org/wiki/Zoom_levels
311
  // Todo change to variable - it is used in heatmap init
312
  const stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
313
  const radius = 25 * stile / 256
314

    
315
  let i = 0
316
  let lat = 0
317
  let lng = 0
318

    
319
  let total = 0
320

    
321
  const datasetsInRadius = {}
322
  const eventCoord = {
323
    lng: e.latlng.lng,
324
    lat: e.latlng.lat
325
  }
326
  Object.keys(data[currentTime]).forEach((key) => {
327
    const namedData = data[currentTime][key]
328
    namedData.items.forEach(element => {
329
      if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
330
        lat += element.x
331
        lng += element.y
332
        info[i] = { place: element.place, number: element.number, datasetName: key }
333
        total += parseInt(element.number)
334
        i++
335
        datasetsInRadius[key] = true
336
      }
337
    })
338
  })
339
  // Process info for more then one dataset
340
  info = info.reduce((acc, item) => {
341
    if (!acc[item.datasetName]) {
342
      acc[item.datasetName] = {
343
        items: [],
344
        number: 0,
345
        datasetName: item.datasetName
346
      }
347
    }
348

    
349
    acc[item.datasetName].items.push(item)
350
    acc[item.datasetName].number += Number(item.number)
351
    return acc
352
  }, {})
353
  // There is one dataset
354

    
355
  const numDatasets = Object.keys(datasetsInRadius).length
356
  if (!numDatasets) {
357
    if (mymap._popup) {
358
      $('#part-info').text('')
359
      $('#current-number').html(0)
360
      disablePopupControls()
361
    }
362

    
363
    return
364
  }
365
  if (numDatasets === 1) {
366
    const infoDict = getElFromObjectInfo(0)
367
    const info_ = infoDict.items
368
    const { place, number } = info_[currentInfo]
369

    
370
    if (!globalPopup._popup || !areSameCoord(globalPopup.coord, eventCoord)) {
371
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${infoDict.datasetName}`)
372
      globalPopup.coord = eventCoord
373
    }
374

    
375
    setGlobalPopupContent(genPopUp(datasetDictNameDisplayName[infoDict.datasetName], place, number, total, currentPos, info_.length))
376

    
377
    if (info_.length === 1) {
378
      disablePopupControls()
379
    }
380
  } else {
381
    const { datasetName, number } = getElFromObjectInfo(currentInfo)
382

    
383
    if (!globalPopup._popup || !areSameCoord(globalPopup.coord, eventCoord)) {
384
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
385
      globalPopup.coord = eventCoord
386
    }
387

    
388
    setGlobalPopupContent(genMultipleDatasetsPopUp(number, currentPos, getInfoLength(), datasetDictNameDisplayName[datasetName]))
389
  }
390
}
391

    
392
// eslint-disable-next-line no-unused-vars
393
function previousInfo () {
394
  const infoLength = getInfoLength()
395
  const previousCurrentInfo = currentInfo
396

    
397
  currentInfo = (currentInfo + infoLength - 1) % infoLength
398
  displayInfoText(previousCurrentInfo)
399
}
400

    
401
// eslint-disable-next-line no-unused-vars
402
function nextInfo () {
403
  const infoLength = getInfoLength()
404
  const previousCurrentInfo = currentInfo
405

    
406
  currentInfo = (currentInfo + 1) % infoLength
407
  displayInfoText(previousCurrentInfo)
408
}
409

    
410
function displayInfoText (previousInfoNum) {
411
  const previousInfo = hasInfoMultipleDatasets() ? getElFromObjectInfo(previousInfoNum) : getElFromObjectInfo(0).items[previousInfoNum]
412
  const info_ = hasInfoMultipleDatasets() ? getElFromObjectInfo(currentInfo) : getElFromObjectInfo(0).items[currentInfo]
413
  const infoLength = getInfoLength()
414
  const datasetInfo = $('#dataset-info')
415

    
416
  if (datasetInfo) {
417
    $(datasetInfo).html(datasetDictNameDisplayName[info_.datasetName])
418
  }
419

    
420
  $('#place-info').html(info_.place ? info_.place : info_.datasetName)
421
  $('#current-number').html(info_.number)
422
  $('#pages').html(currentInfo + 1 + ' z ' + infoLength)
423

    
424
  $('.leaflet-popup').removeClass(`popup-${previousInfo.datasetName}`)
425
  $('.leaflet-popup').addClass(`popup-${info_.datasetName}`)
426
}
427

    
428
// eslint-disable-next-line no-unused-vars
429
function setMapView (latitude, longitude, zoom) {
430
  localStorage.setItem('lat', latitude)
431
  localStorage.setItem('lng', longitude)
432
  localStorage.setItem('zoom', zoom)
433
  mymap.setView([latitude, longitude], zoom)
434
}
435

    
436
/**
437
 * Change animation start from playing to stopped or the other way round
438
 */
439
// eslint-disable-next-line no-unused-vars
440
function changeAnimationState () {
441
  isAnimationRunning = !isAnimationRunning
442

    
443
  if (isAnimationRunning) {
444
    $('#animate-btn').removeClass('play').addClass('pause')
445
    timer = setInterval(function () { next() }, 800)
446
  } else {
447
    clearTimeout(timer)
448
    $('#animate-btn').removeClass('pause').addClass('play')
449
  }
450
}
451

    
452
// eslint-disable-next-line no-unused-vars
453
async function previous () {
454
  if (loading) {
455
    return
456
  }
457

    
458
  currentTime = (currentTime + 23) % 24
459
  changeHour(currentTime)
460
  // mymap.closePopup()
461
  if (!lockedDay && (currentTime === 23)) {
462
    addDayToCurrentDate(-1)
463
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
464
  } else {
465
    drawHeatmap(data[currentTime])
466
  }
467
  updatePopup()
468
}
469

    
470
async function next () {
471
  if (loading) {
472
    return
473
  }
474

    
475
  currentTime = (currentTime + 1) % 24
476
  changeHour(currentTime)
477
  if (!lockedDay && (currentTime === 0)) {
478
    addDayToCurrentDate(1)
479
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
480
  } else {
481
    drawHeatmap(data[currentTime])
482
  }
483
  updatePopup()
484
}
485

    
486
/**
487
 * Change browser url based on animation step.
488
 */
489
function changeUrl () {
490
  window.history.pushState(
491
    '',
492
    document.title,
493
    window.location.origin + window.location.pathname + `?date=${currentDateToString()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type=' + current, '')}`
494
  )
495
}
496

    
497
function updateHeaderControls () {
498
  $(`#time_${currentTime}`).prop('checked', true)
499
  $('#dropdownMenuButtonTime').html((currentTime < 10 ? '0' : '') + `${currentTime}:00`)
500
}
501

    
502
function setTimeline () {
503
  $('#player-time > span').text(currentTime + ':00')
504
  $('#player-time').attr('class', 'time hour-' + currentTime)
505
}
506

    
507
const loadingTimeline = (isLoading) => {
508
  if (isLoading) {
509
    loadingYTimeline()
510
  } else {
511
    loadingNTimeline()
512
  }
513
}
514
const loadingYTimeline = () => {
515
  $('#player-time > .spinner-border').removeClass('d-none')
516
  $('#player-time > span').text('')
517
}
518
const loadingNTimeline = () => {
519
  $('#player-time > .spinner-border').addClass('d-none')
520
  setTimeline()
521
}
522
const onChangeHour = (hour) => {
523
  changeHour(hour)
524
  drawHeatmap(data[currentTime])
525
}
526

    
527
const changeHour = (hour) => {
528
  $('#player-time').removeAttr('style')
529
  changeCurrentTime(hour)
530
  updateHeaderControls()
531
  setTimeline()
532
  changeUrl()
533
  updatePopup()
534
}
535

    
536
const updatePopup = () => {
537
  const { _popup } = mymap
538
  if (_popup) {
539
    showInfo({
540
      latlng: _popup.getLatLng()
541
    }
542
    )
543
  }
544
}
545
/**
546
 * Load and display heatmap layer for current data
547
 * @param {string} opendataRoute route to dataset source
548
 * @param {string} positionsRoute  route to dataset postitions source
549
 */
550
// eslint-disable-next-line no-unused-vars
551
async function loadCurrentTimeHeatmap (opendataRoute, positionsRoute, loaderDelay = defaultLoaderDelay) {
552
  loadCheckboxDatasetNameData()
553

    
554
  dataSourceRoute = opendataRoute
555
  positionsSourceRoute = positionsRoute
556
  const allPromises = []
557
  data[currentTime] = {}
558

    
559
  const dataSelectedHandler = async (datasetName) => {
560
    if (!(datasetName in dataSourceMarks)) {
561
      dataSourceMarks[datasetName] = await fetchDataSourceMarks(positionsRoute, datasetName)
562
    }
563
    const datasetData = await fetchByNameDate(dataSourceRoute, datasetName, currentDateToString(), currentTime)
564
    data[currentTime][datasetName] = datasetData
565
  }
566
  datasetSelected.forEach((datasetName) => {
567
    allPromises.push(dataSelectedHandler(datasetName))
568
  })
569

    
570
  loadingY(loaderDelay)
571
  await Promise.all(allPromises).then(
572
    () => {
573
      loadingN(0)
574
      drawDataSourceMarks(dataSourceMarks)
575
      drawHeatmap(data[currentTime])
576
      preload(currentTime, 1, currentDateToString())
577
      preload(currentTime, -1, currentDateToString())
578
    }
579
  )
580
}
581

    
582
function drawDataSourceMarks (data) {
583
  if (marksLayer != null) {
584
    mymap.removeLayer(marksLayer)
585
  }
586

    
587
  marksLayer = L.layerGroup()
588

    
589
  Object.keys(data).forEach((key_) => {
590
    for (var key in data[key_]) {
591
      const { x, y, name } = data[key_][key]
592
      const pop =
593
          prepareLayerPopUp(x, y, 1, `popup-${key_}`)
594
            .setContent(genPopUp(datasetDictNameDisplayName[key_], name, 0, 0, 1, 1))
595
      const newCircle =
596
        L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
597
          .bindPopup(pop)
598
      globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
599
      marksLayer.addLayer(
600
        newCircle
601
      )
602
    }
603
  })
604

    
605
  marksLayer.setZIndex(-1).addTo(mymap)
606
}
607

    
608
async function preload (time, change, date) {
609
  loadingY()
610
  for (let nTime = time + change; nTime >= 0 && nTime <= 23; nTime = nTime + change) {
611
    if (!data[nTime]) {
612
      data[nTime] = {}
613
    }
614

    
615
    datasetSelected.forEach(async (datasetName) => {
616
      if (!data[nTime][datasetName]) {
617
        data[nTime][datasetName] = await fetchByNameDate(dataSourceRoute, datasetName, date, nTime)
618
      }
619
    })
620
  }
621
  loadingN()
622
}
623

    
624
function drawHeatmap (dataRaw) {
625
  // Todo still switched
626
  const dataDict = dataRaw
627
  const mergedPoints = []
628
  let max = 0
629

    
630
  if (Object.keys(globalMarkersChanged).length) {
631
    Object.keys(globalMarkersChanged).forEach(function (key) {
632
      globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
633
    })
634
    globalMarkersChanged = {}
635
  }
636

    
637
  Object.keys(dataDict).forEach((key) => {
638
    const data = dataDict[key]
639
    max = Math.max(max, data.max)
640

    
641
    if (data != null) {
642
    // Bind back popups for markers (we dont know if there is any data for this marker or not)
643
      const points = data.items.map((point) => {
644
        const { x, y, number } = point
645
        const key = x + '' + y
646
        const holder = globalMarkersHolder[key]
647
        if (!globalMarkersChanged[key] && number) {
648
          // There is data for this marker => unbind popup with zero value
649
          holder[0] = holder[0].unbindPopup()
650
          globalMarkersChanged[key] = holder
651
        }
652

    
653
        return [x, y, number]
654
      })
655
      mergedPoints.push(...points)
656
    } else {
657
      if (heatmapLayer != null) {
658
        mymap.removeLayer(heatmapLayer)
659
      }
660
    }
661
  })
662

    
663
  if (heatmapLayer != null) {
664
    mymap.removeLayer(heatmapLayer)
665
  }
666

    
667
  if (mergedPoints.length) {
668
    heatmapLayer = L.heatLayer(mergedPoints, { max: max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
669
  }
670
}
671

    
672
/**
673
 * Checks dataset availibility
674
 * @param {string} route authority for datasets availibility checks
675
 */
676
// eslint-disable-next-line no-unused-vars
677
function checkDataSetsAvailability (route) {
678
  $.ajax({
679
    type: 'POST',
680
    // Todo it might be good idea to change db collections format
681
    url: route + '/' + currentDateToString(),
682
    success: function (result) {
683
      updateAvailableDataSets(result)
684
    }
685
  })
686
}
687

    
688
function updateAvailableDataSets (available) {
689
  let leastOneOptionEnabled = false
690

    
691
  $('#dropdown-dataset .dropdown-item').each(function () {
692
    const input = $(this).find('input')
693
    const inputVal = input[0].value
694

    
695
    if (!(inputVal in available)) {
696
      $(this).addClass('disabled')
697
      $(input).prop('checked', false)
698
    } else {
699
      leastOneOptionEnabled = true
700
      $(this).removeClass('disabled')
701
    }
702
  })
703

    
704
  $('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled)
705
}
706

    
707
function formatDate (date) {
708
  var day = String(date.getDate())
709
  var month = String(date.getMonth() + 1)
710

    
711
  if (day.length === 1) {
712
    day = '0' + day
713
  }
714

    
715
  if (month.length === 1) {
716
    month = '0' + month
717
  }
718

    
719
  return date.getFullYear() + '-' + month + '-' + day
720
}
721

    
722
// eslint-disable-next-line no-unused-vars
723
function initDatepicker (availableDatesSource) {
724
  var availableDates = ''
725

    
726
  $.ajax({
727
    type: 'GET',
728
    url: availableDatesSource,
729
    success: function (result) {
730
      availableDates = String(result).split(',')
731
    }
732
  }).then(function () {
733
    $('#date').datepicker({
734
      format: 'yyyy-mm-dd',
735
      language: 'cs',
736
      beforeShowDay: function (date) {
737
        if (availableDates.indexOf(formatDate(date)) < 0) {
738
          return { enabled: false, tooltip: 'Žádná data' }
739
        } else {
740
          return { enabled: true }
741
        }
742
      },
743
      autoclose: true
744
    })
745
  })
746
}
747

    
748
function initLocationsMenu () {
749
  var locationsWrapper = '.locations'
750
  var locationsDisplayClass = 'show'
751

    
752
  if ($(window).width() <= 480) {
753
    $(locationsWrapper).removeClass(locationsDisplayClass)
754
  } else {
755
    $(locationsWrapper).addClass(locationsDisplayClass)
756
  }
757
}
758

    
759
function onDocumentReady () {
760
  $('#dropdown-dataset').on('click', function (e) {
761
    e.stopPropagation()
762
  })
763

    
764
  $('#btn-update-heatmap').prop('name', '')
765
  changeCurrentTime()
766
  changeCurrentDate()
767
  onValueChangeRegister()
768
  onArrowLeftRightKeysDownRegister()
769
}
770

    
771
const loadCheckboxDatasetNameData = () => {
772
  datasetSelected = []
773
  $('#dropdown-dataset .dropdown-item').each(function () {
774
    const input = $(this).find('input')
775
    const inputVal = input[0].value
776

    
777
    if (input[0].checked) {
778
      datasetSelected.push(inputVal)
779
    }
780

    
781
    datasetDictNameDisplayName[inputVal] = $(input).data('dataset-display-name')
782
  })
783
}
784

    
785
function dragTimeline () {
786
  const hourElemWidthPx = 26
787

    
788
  const elem = $('#player-time')
789
  const offset = elem.offset().left - elem.parent().offset().left
790

    
791
  if (offset >= 0 && offset <= elem.parent().width()) {
792
    const hour = Math.round(offset / hourElemWidthPx)
793

    
794
    if (hour != currentTime) {
795
      elem.attr('class', 'time hour-' + hour)
796
      $('#player-time span').html(hour + ':00')
797

    
798
      onChangeHour(hour)
799
    }
800
  }
801
}
(2-2/2)