Projekt

Obecné

Profil

Stáhnout (21.5 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 currentPageInPopup = 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

    
69
const setGlobalPopupContent = (content) => {
70
  globalPopup._popup.setContent(content)
71
  globalPopup._popup.openOn(mymap)
72
}
73

    
74
const disablePopupControls = () => {
75
  $('#btn-previous-page').prop('disabled', true)
76
  $('#btn-next-page').prop('disabled', true)
77
  $('.popup-controls').hide()
78
}
79

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

    
84
const loadingCallbackNested = (func, delay) => {
85
  setTimeout(() => {
86
    func(loading)
87
    if (loading) {
88
      loadingCallbackNested(func, delay)
89
    }
90
  }, delay)
91
}
92

    
93
const loadingY = (delay = defaultLoaderDelay) => {
94
  loading++
95
  // check after nms if there is something that is loading
96
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading), delay))
97
}
98

    
99
const loadingN = (delay = defaultLoaderDelay) => {
100
  loading--
101
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading)), delay)
102
}
103

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

    
112
const changeCurrentDate = (date = null) => {
113
  const dateInput = $('#date')
114
  currentDate = new Date(date ? date : dateInput.val())
115

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

    
119
  data = []
120
}
121

    
122
const currentDayToString = () => {
123
  const day = currentDate.getDate()
124
  return day > 9 ? `${day}` : `0${day}`
125
}
126

    
127
const currentMonthToString = () => {
128
  const month = currentDate.getMonth() + 1
129
  return month > 9 ? `${month}` : `0${month}`
130
}
131

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

    
134
const addDayToCurrentDate = (day) => {
135
  currentDate.setDate(currentDate.getDate() + day)
136
  changeCurrentDate(currentDate)
137
}
138

    
139
const toggleDayLock = () => {
140
  lockedDay = !lockedDay
141
  $('#player-date').toggleClass('lock')
142
}
143

    
144
const fetchByNameDate = async (baseRoute, name, date, currentTime) => {
145
  const headers = new Headers()
146
  const myRequest = new Request(baseRoute + '/' + name + '/' + date + '/' + currentTime, {
147
    method: 'GET',
148
    headers: headers
149
  })
150
  const beforeJson = await fetch(myRequest)
151

    
152
  return beforeJson.json()
153
}
154

    
155
const fetchDataSourceMarks = async (positionRoute, datasetName) => {
156
  const headers = new Headers()
157
  const myRequest = new Request(positionRoute + '/' + datasetName, {
158
    method: 'GET',
159
    headers: headers
160
  })
161
  const beforeJson = await fetch(myRequest)
162

    
163
  return beforeJson.json()
164
}
165

    
166
const getPaginationButtonsInPopup = (currentPage, countPages) => ({
167
  previousButton: '<button id="btn-previous-page" class="circle-button" onclick="setPreviousPageInPopup()"></button>',
168
  pagesList: `<div id="pages">${currentPage} z ${countPages}</div>`,
169
  nextButton: '<button id="btn-next-page" class="circle-button next" onclick="setNextPageInPopup()"></button>'
170
})
171

    
172
const genPopUpControls = (controls) => {
173
  return `<div class="popup-controls">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
174
}
175

    
176
const genMultipleDatasetsPopUp = (sum, currentPage, countPages, datasetName) => {
177
  const popupHeader = `<strong id="dataset-name">${datasetName}</strong>`
178
  const popupData = `<div id="number-info"><span id="current-number">${sum}</span></div>`
179
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
180

    
181
  return `
182
  ${popupHeader}
183
  ${popupData}
184
  ${genPopUpControls([previousButton, pagesList, nextButton])}
185
  `
186
}
187

    
188
const prepareLayerPopUp = (lat, lng, num, className) => L.popup({
189
  autoPan: false,
190
  className: className
191
}).setLatLng([lat / num, lng / num])
192

    
193
const getPopupContent = (datasetName, placeName, currentCount, sum, currentPage, countPages) => {
194
  const popupHeader = `
195
    <strong>${datasetName}</strong>
196
    <div id="place-name">${placeName}</div>`
197
  const popupData = `
198
    <div id="number-info">
199
      <span id="current-number">${currentCount}</span>
200
      <span id="part-info">${(sum && sum !== Number(currentCount)) ? '/' + sum : ''}</span>
201
    </div>`
202
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
203

    
204
  return `
205
  ${popupHeader}
206
  ${popupData}
207
  ${genPopUpControls(countPages > 1 ? [previousButton, pagesList, nextButton] : null)}
208
  `
209
}
210

    
211
const onCheckboxClicked = async (checkbox) => {
212
  if ($(checkbox).prop('checked')) {
213
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
214
  } else {
215
    loadCheckboxDatasetNameData()
216

    
217
    data.forEach((item, index) => {
218
      Object.keys(item).forEach((datasetName) => {
219
        if (datasetName === $(checkbox).val()) {
220
          delete data[index][datasetName]
221
        }
222
      })
223

    
224
      drawHeatmap(data[currentTime])
225
    })
226
  }
227

    
228
  updatePopup()
229
  changeUrlParameters()
230
}
231

    
232
const onArrowLeftRightKeysDownRegister = () => {
233
  $(document).keydown(function (e) {
234
    const { which } = e
235
    
236
    if (which === arrowKeyLEFT) {
237
      previous()
238
      e.preventDefault()
239
    } else if (which === arrowKeyRIGHT) {
240
      next()
241
      e.preventDefault()
242
    }
243
  })
244
}
245

    
246
const debounce = (func, delay) => {
247
  let inDebounce
248
  return function () {
249
    const context = this
250
    const args = arguments
251
    clearTimeout(inDebounce)
252
    inDebounce = setTimeout(() => func.apply(context, args), delay)
253
  }
254
}
255

    
256
const onValueChangeRegister = () => {
257
  $('#date').change(function () {
258
    changeCurrentDate($(this).val())
259
    loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
260
    changeUrlParameters()
261
  })
262

    
263
  $('#dropdown-time input[type="radio"]').each(function () {
264
    $(this).change(function () {
265
      changeHour(parseInt($(this).val()))
266
      drawHeatmap(data[currentTime])
267
    })
268
  })
269

    
270
  $('#dropdown-dataset input[type="checkbox"]').each(function () {
271
    $(this).change(
272
      debounce(() => onCheckboxClicked(this), 1000)
273
    )
274
  })
275
}
276

    
277
/**
278
 * Initialize leaflet map on start position which can be default or set based on user action
279
 */
280
// eslint-disable-next-line no-unused-vars
281
function initMap () {
282
  startX = localStorage.getItem('lat') || startX
283
  startY = localStorage.getItem('lng') || startY
284
  startZoom = localStorage.getItem('zoom') || startZoom
285

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

    
288
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
289
    attribution: '',
290
    maxZoom: 19
291
  }).addTo(mymap)
292

    
293
  mymap.on('click', function (e) { showInfo(e) })
294
}
295

    
296
const getCountPagesInPopup = () => {
297
  const infoKeys = Object.keys(info)
298
  if (infoKeys.length === 1) {
299
    // return number of records in one dataset (one dataset in area)
300
    return info[infoKeys[0]].items.length
301
  }
302
  // return number of datasets (agregation of all datasets in area)
303
  return infoKeys.length
304
}
305

    
306
const getElFromObjectInfo = (position) => {
307
  const keys = Object.keys(info)
308
  return info[keys[position]]
309
}
310

    
311
const hasInfoMultipleDatasets = () => {
312
  return Object.keys(info).length > 1
313
}
314

    
315
function showInfo (e) {
316
  info = []
317
  currentPageInPopup = 0
318

    
319
  // https://wiki.openstreetmap.org/wiki/Zoom_levels
320
  // Todo change to variable - it is used in heatmap init
321
  const stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
322
  const radius = 25 * stile / 256
323

    
324
  let i = 0
325
  let lat = 0
326
  let lng = 0
327

    
328
  let total = 0
329

    
330
  const datasetsInRadius = {}
331
  const eventCoord = {
332
    lng: e.latlng.lng,
333
    lat: e.latlng.lat
334
  }
335
  Object.keys(data[currentTime]).forEach((key) => {
336
    const namedData = data[currentTime][key]
337
    namedData.items.forEach(element => {
338
      if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
339
        lat += element.x
340
        lng += element.y
341
        info[i] = { place: element.place, number: element.number, datasetName: key }
342
        total += parseInt(element.number)
343
        i++
344
        datasetsInRadius[key] = true
345
      }
346
    })
347
  })
348

    
349
  // Process info for more then one dataset
350
  info = info.reduce((acc, item) => {
351
    if (!acc[item.datasetName]) {
352
      acc[item.datasetName] = {
353
        items: [],
354
        number: 0,
355
        datasetName: item.datasetName
356
      }
357
    }
358

    
359
    acc[item.datasetName].items.push(item)
360
    acc[item.datasetName].number += Number(item.number)
361
    return acc
362
  }, {})
363
  // There is one dataset
364

    
365
  const numDatasets = Object.keys(datasetsInRadius).length
366

    
367
  if (!numDatasets) {
368
    if (mymap._popup) {
369
      $('#part-info').text('')
370
      $('#current-number').html(0)
371
      disablePopupControls()
372
    }
373

    
374
    return
375
  }
376

    
377
  if (numDatasets === 1) {
378
    const infoDict = getElFromObjectInfo(0)
379
    const info_ = infoDict.items
380
    const { place, number } = info_[currentPageInPopup]
381

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

    
387
    setGlobalPopupContent(getPopupContent(datasetDictNameDisplayName[infoDict.datasetName], place, number, total, 1, info_.length))
388

    
389
    if (info_.length === 1) {
390
      disablePopupControls()
391
    }
392
  } else {
393
    const { datasetName, number } = getElFromObjectInfo(currentPageInPopup)
394

    
395
    if (!globalPopup._popup || !areSameCoord(globalPopup.coord, eventCoord)) {
396
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
397
      globalPopup.coord = eventCoord
398
    }
399

    
400
    setGlobalPopupContent(genMultipleDatasetsPopUp(number, 1, getCountPagesInPopup(), datasetDictNameDisplayName[datasetName]))
401
  }
402
}
403

    
404
// eslint-disable-next-line no-unused-vars
405
function setPreviousPageInPopup () {
406
  const countPagesInPopup = getCountPagesInPopup()
407
  const newPage = currentPageInPopup
408

    
409
  currentPageInPopup = (currentPageInPopup + countPagesInPopup - 1) % countPagesInPopup
410
  setPageContentInPopup(newPage)
411
}
412

    
413
// eslint-disable-next-line no-unused-vars
414
function setNextPageInPopup () {
415
  const countPagesInPopup = getCountPagesInPopup()
416
  const newPage = currentPageInPopup
417

    
418
  currentPageInPopup = (currentPageInPopup + 1) % countPagesInPopup
419
  setPageContentInPopup(newPage)
420
}
421

    
422
function setPageContentInPopup (page) {
423
  const previousPageData = hasInfoMultipleDatasets() ? getElFromObjectInfo(page) : getElFromObjectInfo(0).items[page]
424
  const currentPageData = hasInfoMultipleDatasets() ? getElFromObjectInfo(currentPageInPopup) : getElFromObjectInfo(0).items[currentPageInPopup]
425
  const datasetName = $('#dataset-name')
426

    
427
  if (datasetName) {
428
    datasetName.html(datasetDictNameDisplayName[currentPageData.datasetName])
429
  }
430

    
431
  $('#place-name').html(currentPageData.place ? currentPageData.place : currentPageData.datasetName)
432
  $('#current-number').html(currentPageData.number)
433
  $('#pages').html(currentPageInPopup + 1 + ' z ' + getCountPagesInPopup())
434

    
435
  $('.leaflet-popup').removeClass(`popup-${previousPageData.datasetName}`).addClass(`popup-${currentPageData.datasetName}`)
436
}
437

    
438
// eslint-disable-next-line no-unused-vars
439
function setMapView (latitude, longitude, zoom) {
440
  localStorage.setItem('lat', latitude)
441
  localStorage.setItem('lng', longitude)
442
  localStorage.setItem('zoom', zoom)
443

    
444
  mymap.setView([latitude, longitude], zoom)
445
}
446

    
447
/**
448
 * Change animation start from playing to stopped or the other way round
449
 */
450
// eslint-disable-next-line no-unused-vars
451
function changeAnimationState () {
452
  const btnAnimate = $('#animate-btn')
453

    
454
  isAnimationRunning = !isAnimationRunning
455

    
456
  if (isAnimationRunning) {
457
    btnAnimate.removeClass('play').addClass('pause')
458
    timer = setInterval(function () { next() }, 800)
459
  } else {
460
    clearTimeout(timer)
461
    btnAnimate.removeClass('pause').addClass('play')
462
  }
463
}
464

    
465
// eslint-disable-next-line no-unused-vars
466
async function previous () {
467
  if (loading) {
468
    return
469
  }
470

    
471
  currentTime = (currentTime + 23) % 24
472
  changeHour(currentTime)
473
  
474
  if (!lockedDay && currentTime === 23) {
475
    addDayToCurrentDate(-1)
476
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
477
  } else {
478
    drawHeatmap(data[currentTime])
479
  }
480

    
481
  updatePopup()
482
}
483

    
484
async function next () {
485
  if (loading) {
486
    return
487
  }
488

    
489
  currentTime = (currentTime + 1) % 24
490
  changeHour(currentTime)
491

    
492
  if (!lockedDay && currentTime === 0) {
493
    addDayToCurrentDate(1)
494
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
495
  } else {
496
    drawHeatmap(data[currentTime])
497
  }
498

    
499
  updatePopup()
500
}
501

    
502
/**
503
 * Change browser url based on animation step.
504
 */
505
const changeUrlParameters = () => {
506
  window.history.pushState(
507
    '',
508
    document.title,
509
    window.location.origin + window.location.pathname + `?date=${currentDateToString()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type=' + current, '')}`
510
  )
511
}
512

    
513
const updateHeaderControls = () => {
514
  $(`#time_${currentTime}`).prop('checked', true)
515
  $('#dropdownMenuButtonTime').html((currentTime < 10 ? '0' : '') + `${currentTime}:00`)
516
}
517

    
518
const setTimeline = () => {
519
  $('#player-time > span').text(currentTime + ':00')
520
  $('#player-time').attr('class', 'time hour-' + currentTime)
521
}
522

    
523
const loadingTimeline = (isLoading) => {
524
  if (isLoading) {
525
    loadingYTimeline()
526
  } else {
527
    loadingNTimeline()
528
  }
529
}
530

    
531
const loadingYTimeline = () => {
532
  $('#player-time > .spinner-border').removeClass('d-none')
533
  $('#player-time > span').text('')
534
}
535

    
536
const loadingNTimeline = () => {
537
  $('#player-time > .spinner-border').addClass('d-none')
538
  setTimeline()
539
}
540

    
541
const onChangeHour = (hour) => {
542
  changeHour(hour)
543
  drawHeatmap(data[currentTime])
544
}
545

    
546
const changeHour = (hour) => {
547
  $('#player-time').removeAttr('style')
548
  changeCurrentTime(hour)
549
  updateHeaderControls()
550
  setTimeline()
551
  changeUrlParameters()
552
  updatePopup()
553
}
554

    
555
const updatePopup = () => {
556
  const { _popup } = mymap
557

    
558
  if (_popup) {
559
    showInfo({
560
      latlng: _popup.getLatLng()
561
    })
562
  }
563
}
564

    
565
/**
566
 * Load and display heatmap layer for current data
567
 * @param {string} opendataRoute route to dataset source
568
 * @param {string} positionsRoute  route to dataset postitions source
569
 */
570
// eslint-disable-next-line no-unused-vars
571
async function loadCurrentTimeHeatmap (opendataRoute, positionsRoute, loaderDelay = defaultLoaderDelay) {
572
  loadCheckboxDatasetNameData()
573

    
574
  dataSourceRoute = opendataRoute
575
  positionsSourceRoute = positionsRoute
576
  const allPromises = []
577
  data[currentTime] = {}
578

    
579
  const dataSelectedHandler = async (datasetName) => {
580
    if (!(datasetName in dataSourceMarks)) {
581
      dataSourceMarks[datasetName] = await fetchDataSourceMarks(positionsRoute, datasetName)
582
    }
583

    
584
    const datasetData = await fetchByNameDate(dataSourceRoute, datasetName, currentDateToString(), currentTime)
585
    data[currentTime][datasetName] = datasetData
586
  }
587
  datasetSelected.forEach((datasetName) => {
588
    allPromises.push(dataSelectedHandler(datasetName))
589
  })
590

    
591
  loadingY(loaderDelay)
592

    
593
  await Promise.all(allPromises).then(
594
    () => {
595
      loadingN(0)
596
      drawDataSourceMarks(dataSourceMarks)
597
      drawHeatmap(data[currentTime])
598
      preload(currentTime, 1, currentDateToString())
599
      preload(currentTime, -1, currentDateToString())
600
    }
601
  )
602
}
603

    
604
function drawDataSourceMarks (data) {
605
  if (marksLayer != null) {
606
    mymap.removeLayer(marksLayer)
607
  }
608

    
609
  marksLayer = L.layerGroup()
610

    
611
  Object.keys(data).forEach((key_) => {
612
    for (var key in data[key_]) {
613
      const { x, y, name } = data[key_][key]
614
      const pop =
615
          prepareLayerPopUp(x, y, 1, `popup-${key_}`)
616
            .setContent(getPopupContent(datasetDictNameDisplayName[key_], name, 0, 0, 1, 1))
617
      const newCircle =
618
        L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
619
          .bindPopup(pop)
620
      globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
621
      marksLayer.addLayer(
622
        newCircle
623
      )
624
    }
625
  })
626

    
627
  marksLayer.setZIndex(-1).addTo(mymap)
628
}
629

    
630
async function preload (time, change, date) {
631
  loadingY()
632

    
633
  for (let nTime = time + change; nTime >= 0 && nTime <= 23; nTime = nTime + change) {
634
    if (!data[nTime]) {
635
      data[nTime] = {}
636
    }
637

    
638
    datasetSelected.forEach(async (datasetName) => {
639
      if (!data[nTime][datasetName]) {
640
        data[nTime][datasetName] = await fetchByNameDate(dataSourceRoute, datasetName, date, nTime)
641
      }
642
    })
643
  }
644

    
645
  loadingN()
646
}
647

    
648
function drawHeatmap (dataRaw) {
649
  // Todo still switched
650
  const dataDict = dataRaw
651
  const mergedPoints = []
652
  let max = 0
653

    
654
  if (Object.keys(globalMarkersChanged).length) {
655
    Object.keys(globalMarkersChanged).forEach(function (key) {
656
      globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
657
    })
658
    globalMarkersChanged = {}
659
  }
660

    
661
  Object.keys(dataDict).forEach((key) => {
662
    const data = dataDict[key]
663
    max = Math.max(max, data.max)
664

    
665
    if (data != null) {
666
    // Bind back popups for markers (we dont know if there is any data for this marker or not)
667
      const points = data.items.map((point) => {
668
        const { x, y, number } = point
669
        const key = x + '' + y
670
        const holder = globalMarkersHolder[key]
671
        if (!globalMarkersChanged[key] && number) {
672
          // There is data for this marker => unbind popup with zero value
673
          holder[0] = holder[0].unbindPopup()
674
          globalMarkersChanged[key] = holder
675
        }
676

    
677
        return [x, y, number]
678
      })
679
      mergedPoints.push(...points)
680
    } else {
681
      if (heatmapLayer != null) {
682
        mymap.removeLayer(heatmapLayer)
683
      }
684
    }
685
  })
686

    
687
  if (heatmapLayer != null) {
688
    mymap.removeLayer(heatmapLayer)
689
  }
690

    
691
  if (mergedPoints.length) {
692
    heatmapLayer = L.heatLayer(mergedPoints, { max: max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
693
  }
694
}
695

    
696
/**
697
 * Checks dataset availibility
698
 * @param {string} route authority for datasets availibility checks
699
 */
700
// eslint-disable-next-line no-unused-vars
701
function checkDataSetsAvailability (route) {
702
  $.ajax({
703
    type: 'POST',
704
    // Todo it might be good idea to change db collections format
705
    url: route + '/' + currentDateToString(),
706
    success: function (result) {
707
      updateAvailableDataSets(result)
708
    }
709
  })
710
}
711

    
712
function updateAvailableDataSets (available) {
713
  let leastOneOptionEnabled = false
714

    
715
  $('#dropdown-dataset .dropdown-item').each(function () {
716
    const input = $(this).find('input')
717
    const inputVal = input[0].value
718

    
719
    if (!(inputVal in available)) {
720
      $(this).addClass('disabled')
721
      $(input).prop('checked', false)
722
    } else {
723
      leastOneOptionEnabled = true
724
      $(this).removeClass('disabled')
725
    }
726
  })
727

    
728
  $('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled)
729
}
730

    
731
function formatDate (date) {
732
  var day = String(date.getDate())
733
  var month = String(date.getMonth() + 1)
734

    
735
  if (day.length === 1) {
736
    day = '0' + day
737
  }
738

    
739
  if (month.length === 1) {
740
    month = '0' + month
741
  }
742

    
743
  return date.getFullYear() + '-' + month + '-' + day
744
}
745

    
746
// eslint-disable-next-line no-unused-vars
747
function initDatepicker (availableDatesSource) {
748
  var availableDates = ''
749

    
750
  $.ajax({
751
    type: 'GET',
752
    url: availableDatesSource,
753
    success: function (result) {
754
      availableDates = String(result).split(',')
755
    }
756
  }).then(function () {
757
    $('#date').datepicker({
758
      format: 'yyyy-mm-dd',
759
      language: 'cs',
760
      beforeShowDay: function (date) {
761
        if (availableDates.indexOf(formatDate(date)) < 0) {
762
          return { enabled: false, tooltip: 'Žádná data' }
763
        } else {
764
          return { enabled: true }
765
        }
766
      },
767
      autoclose: true
768
    })
769
  })
770
}
771

    
772
function initLocationsMenu () {
773
  const elmLocationsList = $('.locations')
774
  const locationsDisplayClass = 'show'
775

    
776
  if ($(window).width() <= 480) {
777
    elmLocationsList.removeClass(locationsDisplayClass)
778
  } else {
779
    elmLocationsList.addClass(locationsDisplayClass)
780
  }
781
}
782

    
783
function onDocumentReady () {
784
  $('#dropdown-dataset').on('click', function (e) {
785
    e.stopPropagation()
786
  })
787

    
788
  $('#btn-update-heatmap').prop('name', '')
789
  changeCurrentTime()
790
  changeCurrentDate()
791
  onValueChangeRegister()
792
  onArrowLeftRightKeysDownRegister()
793
}
794

    
795
const loadCheckboxDatasetNameData = () => {
796
  datasetSelected = []
797

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

    
802
    if (input[0].checked) {
803
      datasetSelected.push(inputVal)
804
    }
805

    
806
    datasetDictNameDisplayName[inputVal] = $(input).data('dataset-display-name')
807
  })
808
}
809

    
810
const dragTimeline = () => {
811
  const hourElemWidthPx = 26
812

    
813
  const elem = $('#player-time')
814
  const offset = elem.offset().left - elem.parent().offset().left
815

    
816
  if (offset >= 0 && offset <= elem.parent().width()) {
817
    const hour = Math.round(offset / hourElemWidthPx)
818

    
819
    if (hour !== currentTime) {
820
      elem.attr('class', 'time hour-' + hour)
821
      $('#player-time span').html(hour + ':00')
822

    
823
      onChangeHour(hour)
824
    }
825
  }
826
}
(2-2/2)