Projekt

Obecné

Profil

Stáhnout (22.3 KB) Statistiky
| Větev: | Revize:
1 8feb1753 ballakt
/* global L */
2
/* global $ */
3 2f227a6c ballakt
4 8feb1753 ballakt
var mymap
5
var heatmapLayer = null
6 c8718599 Martin Sebela
var markersLayer = null
7 03c02899 vastja
8 815159f3 Tomáš Ballák
// values for arrow keys
9
const arrowKeyLEFT = 37
10
const arrowKeyRIGHT = 39
11
12 8feb1753 ballakt
var startX = 49.7248
13
var startY = 13.3521
14
var startZoom = 17
15 3fc08f2d vastja
16 8feb1753 ballakt
var dataSourceRoute
17 1cf1413d ballakt
let positionsSourceRoute
18 1774c06d Tomáš Ballák
19
let currentTime
20
21
let currentDate
22 a48642fb vastja
23 8feb1753 ballakt
var timer
24
var isAnimationRunning = false
25
var data = []
26 a48642fb vastja
27 e6097215 ballakt
//
28
// info = {
29
//  DATASETNAME: {
30
//    items: Array,
31
//    number: Number,
32
//    datasetName: String
33
// }
34
// }
35
//
36 70a3df53 vastja
var info = []
37 1a1d8f64 Martin Sebela
let currentPageInPopup = 0
38 2f227a6c ballakt
39 d51166e8 ballakt
// dictionary for names of datasets
40 883a423e Tomáš Ballák
const datasetDictNameDisplayName = {}
41 2f227a6c ballakt
var datasetSelected = []
42 70a3df53 vastja
43 1774c06d Tomáš Ballák
// data only for one day
44
let lockedDay = false
45
46 d51166e8 ballakt
// loading information for async operations
47 1774c06d Tomáš Ballák
let loading = 0
48
49 d51166e8 ballakt
// default loader showup delay
50
const defaultLoaderDelay = 1000
51
52 1774c06d Tomáš Ballák
// marks for all datasets
53
const dataSourceMarks = {}
54
55 8feb1753 ballakt
const globalMarkersHolder = {}
56 d5a88af0 Tomáš Ballák
// 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 084a5972 ballakt
61 e6097215 ballakt
const globalPopup = {
62
  coord: {
63
    lat: 0,
64
    lng: 0
65
  },
66
  _popup: null
67
}
68 1a1d8f64 Martin Sebela
69 e6097215 ballakt
70
71 1774c06d Tomáš Ballák
72 bf634c97 Tomáš
const onDocumentReady = () => {
73 c8718599 Martin Sebela
  $('#dropdown-dataset').on('click', function (e) {
74
    e.stopPropagation()
75
  })
76 1a1d8f64 Martin Sebela
77 c8718599 Martin Sebela
  $('#btn-update-heatmap').prop('name', '')
78
  changeCurrentTime()
79
  changeCurrentDate()
80
  onValueChangeRegister()
81
  onArrowLeftRightKeysDownRegister()
82 1774c06d Tomáš Ballák
}
83 1a1d8f64 Martin Sebela
84
85
86
87 c8718599 Martin Sebela
/* ------------ DATA FETCHERS ------------ */
88 bf634c97 Tomáš
const genericFetch = async (route, method) => {
89 2f227a6c ballakt
  const headers = new Headers()
90 bf634c97 Tomáš
  const myRequest = new Request(route, {
91
    method: method,
92 2f227a6c ballakt
    headers: headers
93
  })
94
  const beforeJson = await fetch(myRequest)
95 1a1d8f64 Martin Sebela
96 2f227a6c ballakt
  return beforeJson.json()
97
}
98 bf634c97 Tomáš
const fetchByNameDate = async (baseRoute, name, date, currentTime) => {
99
  return await genericFetch(baseRoute + '/' + name + '/' + date + '/' + currentTime, 'GET')
100
}
101 c892003d Martin Sebela
102 2f227a6c ballakt
const fetchDataSourceMarks = async (positionRoute, datasetName) => {
103 bf634c97 Tomáš
    return await genericFetch(positionRoute + '/' + datasetName, 'GET')
104 2f227a6c ballakt
}
105 084a5972 ballakt
106 bf634c97 Tomáš
preload = async (time, change, date) => {
107 c8718599 Martin Sebela
  loadingY()
108 c892003d Martin Sebela
109 c8718599 Martin Sebela
  for (let nTime = time + change; nTime >= 0 && nTime <= 23; nTime = nTime + change) {
110
    if (!data[nTime]) {
111
      data[nTime] = {}
112
    }
113
114
    datasetSelected.forEach(async (datasetName) => {
115
      if (!data[nTime][datasetName]) {
116
        data[nTime][datasetName] = await fetchByNameDate(dataSourceRoute, datasetName, date, nTime)
117
      }
118
    })
119
  }
120
121
  loadingN()
122 084a5972 ballakt
}
123 c892003d Martin Sebela
124 c8718599 Martin Sebela
/**
125
 * Load and display heatmap layer for current data
126
 * 
127
 * @param {string} opendataRoute route to dataset source
128
 * @param {string} positionsRoute  route to dataset postitions source
129
 */
130 9a772066 Tomáš
const loadCurrentTimeHeatmap = async (opendataRoute, positionsRoute, loaderDelay = defaultLoaderDelay) => {
131 c8718599 Martin Sebela
  loadCheckboxDatasetNameData()
132 c892003d Martin Sebela
133 c8718599 Martin Sebela
  dataSourceRoute = opendataRoute
134
  positionsSourceRoute = positionsRoute
135
  const allPromises = []
136
  data[currentTime] = {}
137
138
  const dataSelectedHandler = async (datasetName) => {
139
    if (!(datasetName in dataSourceMarks)) {
140
      dataSourceMarks[datasetName] = await fetchDataSourceMarks(positionsRoute, datasetName)
141
    }
142
143
    const datasetData = await fetchByNameDate(dataSourceRoute, datasetName, currentDateToString(), currentTime)
144
    data[currentTime][datasetName] = datasetData
145
  }
146
  datasetSelected.forEach((datasetName) => {
147
    allPromises.push(dataSelectedHandler(datasetName))
148
  })
149
150
  loadingY(loaderDelay)
151
152
  await Promise.all(allPromises).then(
153
    () => {
154
      loadingN(0)
155
      drawMapMarkers(dataSourceMarks)
156
      drawHeatmap(data[currentTime])
157 9a772066 Tomáš
158 c8718599 Martin Sebela
      preload(currentTime, 1, currentDateToString())
159
      preload(currentTime, -1, currentDateToString())
160
    }
161
  )
162 2f227a6c ballakt
}
163 c892003d Martin Sebela
164 c8718599 Martin Sebela
/**
165
 * Checks dataset availibility
166
 * @param {string} route authority for datasets availibility checks
167
 */
168 bf634c97 Tomáš
const checkDataSetsAvailability = async (route) => {
169
  const result = await genericFetch(route + '/' + currentDateToString(), 'POST')
170
  updateAvailableDataSets(result)
171 c8718599 Martin Sebela
}
172 2f227a6c ballakt
173 bb2d43b5 Martin Sebela
174 c8718599 Martin Sebela
175
176
/* ------------ MAP ------------ */
177
178
/**
179
 * Initialize leaflet map on start position which can be default or set based on user action
180
 */
181 9a772066 Tomáš
const initMap = () => {
182 c8718599 Martin Sebela
  startX = localStorage.getItem('lat') || startX
183
  startY = localStorage.getItem('lng') || startY
184
  startZoom = localStorage.getItem('zoom') || startZoom
185
186
  mymap = L.map('heatmap').setView([startX, startY], startZoom)
187
188
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
189
    attribution: '',
190
    maxZoom: 19
191
  }).addTo(mymap)
192
193
  mymap.on('click', function (e) { showInfo(e) })
194 084a5972 ballakt
}
195 c892003d Martin Sebela
196 bf634c97 Tomáš
const setMapView = (latitude, longitude, zoom) => {
197 c8718599 Martin Sebela
  localStorage.setItem('lat', latitude)
198
  localStorage.setItem('lng', longitude)
199
  localStorage.setItem('zoom', zoom)
200 c892003d Martin Sebela
201 c8718599 Martin Sebela
  mymap.setView([latitude, longitude], zoom)
202
}
203
204 9a772066 Tomáš
const drawHeatmap = (dataRaw) => {
205 c8718599 Martin Sebela
  const dataDict = dataRaw
206
  const mergedPoints = []
207
  let max = 0
208
209
  if (Object.keys(globalMarkersChanged).length) {
210
    Object.keys(globalMarkersChanged).forEach(function (key) {
211
      globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
212
    })
213
    globalMarkersChanged = {}
214
  }
215
216
  Object.keys(dataDict).forEach((key) => {
217
    const data = dataDict[key]
218
    max = Math.max(max, data.max)
219
220
    if (data != null) {
221 9a772066 Tomáš
      // Bind back popups for markers (we dont know if there is any data for this marker or not)
222 c8718599 Martin Sebela
      const points = data.items.map((point) => {
223
        const { x, y, number } = point
224
        const key = x + '' + y
225
        const holder = globalMarkersHolder[key]
226
        if (!globalMarkersChanged[key] && number) {
227
          // There is data for this marker => unbind popup with zero value
228
          holder[0] = holder[0].unbindPopup()
229
          globalMarkersChanged[key] = holder
230 1cf1413d ballakt
        }
231 c8718599 Martin Sebela
232
        return [x, y, number]
233 1cf1413d ballakt
      })
234 c8718599 Martin Sebela
      mergedPoints.push(...points)
235
    } else {
236
      if (heatmapLayer != null) {
237
        mymap.removeLayer(heatmapLayer)
238
      }
239
    }
240
  })
241 1a1d8f64 Martin Sebela
242 c8718599 Martin Sebela
  if (heatmapLayer != null) {
243
    mymap.removeLayer(heatmapLayer)
244 1cf1413d ballakt
  }
245 1a1d8f64 Martin Sebela
246 c8718599 Martin Sebela
  if (mergedPoints.length) {
247
    heatmapLayer = L.heatLayer(mergedPoints, { max: max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
248
  }
249
250
  // timto vyresen bug #8191 - TODO: znamena to, ze muzeme smazat volani updatePopup() ve funkcich, kde se nejdriv vola drawHeatmap() a pak updatePopup()?
251 e6097215 ballakt
  updatePopup()
252 1cf1413d ballakt
}
253 c892003d Martin Sebela
254 9a772066 Tomáš
const drawMapMarkers = (data) => {
255 c8718599 Martin Sebela
  if (markersLayer != null) {
256
    mymap.removeLayer(markersLayer)
257
  }
258
259
  markersLayer = L.layerGroup()
260
261
  Object.keys(data).forEach(key_ => {
262
    for (var key in data[key_]) {
263
      const { x, y, name } = data[key_][key]
264
      const pop =
265 9a772066 Tomáš
        prepareLayerPopUp(x, y, 1, `popup-${key_}`)
266
          .setContent(getPopupContent(datasetDictNameDisplayName[key_], name, 0, 0, 1, 1))
267 c8718599 Martin Sebela
      const newCircle =
268
        L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
269
          .bindPopup(pop)
270
      globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
271
      markersLayer.addLayer(
272
        newCircle
273
      )
274 815159f3 Tomáš Ballák
    }
275
  })
276 c8718599 Martin Sebela
277
  markersLayer.setZIndex(-1).addTo(mymap)
278 815159f3 Tomáš Ballák
}
279
280 c8718599 Martin Sebela
281
282
283
/* ------------ GUI ------------ */
284
285
const changeCurrentTime = (time = null) => {
286
  if (time !== null) {
287
    currentTime = time
288
  } else {
289
    currentTime = parseInt($('#dropdown-time input[type="radio"]:checked').val())
290 1cf1413d ballakt
  }
291
}
292 bb2d43b5 Martin Sebela
293 c8718599 Martin Sebela
const changeCurrentDate = (date = null) => {
294
  const dateInput = $('#date')
295
  currentDate = new Date(date ? date : dateInput.val())
296 bb2d43b5 Martin Sebela
297 c8718599 Martin Sebela
  dateInput.val(currentDateToString())
298
  $('#player-date span').html(`${currentDate.getDate()}. ${currentDate.getMonth() + 1}. ${currentDate.getFullYear()}`)
299 bb2d43b5 Martin Sebela
300 c8718599 Martin Sebela
  data = []
301 1cf1413d ballakt
}
302 bb2d43b5 Martin Sebela
303 c8718599 Martin Sebela
const toggleDayLock = () => {
304
  lockedDay = !lockedDay
305
  $('#player-date').toggleClass('lock')
306
}
307 72a438f3 vastja
308 3fc08f2d vastja
309 3ae59f75 vastja
310 c8718599 Martin Sebela
311
/* ------------ POPUPs ------------ */
312
313
const setGlobalPopupContent = (content) => {
314
  globalPopup._popup.setContent(content)
315
  globalPopup._popup.openOn(mymap)
316
}
317
318
const getPaginationButtonsInPopup = (currentPage, countPages) => ({
319 9a772066 Tomáš
  previousButton: '<button type="button" id="btn-popup-previous-page" onclick="js.setPreviousPageInPopup()"></button>',
320 6b7c4d1c Martin Sebela
  pagesList: `<p id="pages">${currentPage} z ${countPages}</p>`,
321 9a772066 Tomáš
  nextButton: '<button type="button" id="btn-popup-next-page" class="next" onclick="js.setNextPageInPopup()"></button>'
322 c8718599 Martin Sebela
})
323
324
const disablePopupPaginationButtons = () => {
325 6b7c4d1c Martin Sebela
  $('#btn-popup-previous-page').prop('disabled', true)
326
  $('#btn-popup-next-page').prop('disabled', true)
327
  $('.popup-pagination').hide()
328 c8718599 Martin Sebela
}
329
330
const generatePopupPaginationButtons = (controls) => {
331 6b7c4d1c Martin Sebela
  return `<div class="popup-pagination">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
332 c236b33a msebela
}
333 c892003d Martin Sebela
334 1a1d8f64 Martin Sebela
const getCountPagesInPopup = () => {
335 2f227a6c ballakt
  const infoKeys = Object.keys(info)
336 894e3339 Martin Sebela
337 2f227a6c ballakt
  if (infoKeys.length === 1) {
338
    // return number of records in one dataset (one dataset in area)
339
    return info[infoKeys[0]].items.length
340
  }
341
  // return number of datasets (agregation of all datasets in area)
342
  return infoKeys.length
343
}
344 c892003d Martin Sebela
345 894e3339 Martin Sebela
const getPopupDataOnPage = (pageInPopup) => {
346 2f227a6c ballakt
  const keys = Object.keys(info)
347 894e3339 Martin Sebela
  return info[keys[pageInPopup]]
348 2f227a6c ballakt
}
349 c892003d Martin Sebela
350 9a772066 Tomáš
const setPreviousPageInPopup = () => {
351 c8718599 Martin Sebela
  const countPagesInPopup = getCountPagesInPopup()
352
  const page = currentPageInPopup
353
354
  currentPageInPopup = (currentPageInPopup + countPagesInPopup - 1) % countPagesInPopup
355
  setPageContentInPopup(page)
356
}
357
358 9a772066 Tomáš
const setNextPageInPopup = () => {
359 c8718599 Martin Sebela
  const countPagesInPopup = getCountPagesInPopup()
360
  const page = currentPageInPopup
361
362
  currentPageInPopup = (currentPageInPopup + 1) % countPagesInPopup
363
  setPageContentInPopup(page)
364
}
365
366
const genMultipleDatasetsPopUp = (sum, currentPage, countPages, datasetName) => {
367
  const popupHeader = `<strong id="dataset-name">${datasetName}</strong>`
368
  const popupData = `<div id="place-intesity"><span id="current-number">${sum}</span></div>`
369
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
370
371
  return `
372
  ${popupHeader}
373
  ${popupData}
374
  ${generatePopupPaginationButtons([previousButton, pagesList, nextButton])}
375
  `
376
}
377
378
const prepareLayerPopUp = (lat, lng, num, className) => L.popup({
379
  autoPan: false,
380
  className: className
381
}).setLatLng([lat / num, lng / num])
382
383
const getPopupContent = (datasetName, placeName, currentCount, sum, currentPage, countPages) => {
384
  const popupHeader = `
385
    <strong>${datasetName}</strong>
386
    <div id="place-name">${placeName}</div>`
387
  const popupData = `
388
    <div id="place-intesity">
389
      <span id="current-number">${currentCount}</span>
390 6b7c4d1c Martin Sebela
      <span id="sum-number">${(sum && sum !== Number(currentCount)) ? '/' + sum : ''}</span>
391 c8718599 Martin Sebela
    </div>`
392
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
393
394
  return `
395
  ${popupHeader}
396
  ${popupData}
397
  ${generatePopupPaginationButtons(countPages > 1 ? [previousButton, pagesList, nextButton] : null)}
398
  `
399
}
400
401 894e3339 Martin Sebela
const areMultipleDatasetsInRadius = () => {
402 2f227a6c ballakt
  return Object.keys(info).length > 1
403
}
404 c892003d Martin Sebela
405 894e3339 Martin Sebela
const setPopupDatasetClassName = (datasetName) => {
406 2c2cad8d Martin Sebela
  const popup = $('.leaflet-popup')
407
408
  popup.removeClass(function (index, css) {
409 894e3339 Martin Sebela
    return (css.match(/(^|\s)popup-\S+/g) || []).join(' ');
410 2c2cad8d Martin Sebela
  })
411 894e3339 Martin Sebela
  popup.addClass('popup-' + datasetName)
412 2c2cad8d Martin Sebela
}
413
414 9a772066 Tomáš
const showInfo = (e) => {
415 3ae59f75 vastja
  info = []
416 1a1d8f64 Martin Sebela
  currentPageInPopup = 0
417 3ae59f75 vastja
418
  // https://wiki.openstreetmap.org/wiki/Zoom_levels
419
  // Todo change to variable - it is used in heatmap init
420 2f227a6c ballakt
  const stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
421
  const radius = 25 * stile / 256
422
423
  let i = 0
424
  let lat = 0
425
  let lng = 0
426
427
  let total = 0
428
429
  const datasetsInRadius = {}
430 e6097215 ballakt
  const eventCoord = {
431
    lng: e.latlng.lng,
432
    lat: e.latlng.lat
433
  }
434 894e3339 Martin Sebela
435
  Object.keys(data[currentTime]).forEach(key => {
436 2f227a6c ballakt
    const namedData = data[currentTime][key]
437 894e3339 Martin Sebela
438 2f227a6c ballakt
    namedData.items.forEach(element => {
439
      if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
440
        lat += element.x
441
        lng += element.y
442
        info[i] = { place: element.place, number: element.number, datasetName: key }
443
        total += parseInt(element.number)
444
        i++
445
        datasetsInRadius[key] = true
446
      }
447
    })
448 8feb1753 ballakt
  })
449 1a1d8f64 Martin Sebela
450 2f227a6c ballakt
  // Process info for more then one dataset
451
  info = info.reduce((acc, item) => {
452
    if (!acc[item.datasetName]) {
453
      acc[item.datasetName] = {
454
        items: [],
455
        number: 0,
456
        datasetName: item.datasetName
457
      }
458
    }
459 c892003d Martin Sebela
460 2f227a6c ballakt
    acc[item.datasetName].items.push(item)
461
    acc[item.datasetName].number += Number(item.number)
462
    return acc
463
  }, {})
464
465 894e3339 Martin Sebela
  const countDatasets = Object.keys(datasetsInRadius).length
466 1a1d8f64 Martin Sebela
467 894e3339 Martin Sebela
  if (!countDatasets) {
468 e6097215 ballakt
    if (mymap._popup) {
469 6b7c4d1c Martin Sebela
      $('#sum-number').text('')
470 e6097215 ballakt
      $('#current-number').html(0)
471 c8718599 Martin Sebela
      disablePopupPaginationButtons()
472 e6097215 ballakt
    }
473
474 d5a88af0 Tomáš Ballák
    return
475
  }
476 1a1d8f64 Martin Sebela
477 894e3339 Martin Sebela
  if (countDatasets === 1) {
478
    const markersInRadius = getPopupDataOnPage(0)
479
    const popupPagesData = markersInRadius.items
480
    const { place, number } = popupPagesData[currentPageInPopup]
481 e6097215 ballakt
482 894e3339 Martin Sebela
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
483
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${markersInRadius.datasetName}`)
484 e6097215 ballakt
      globalPopup.coord = eventCoord
485
    }
486 2c2cad8d Martin Sebela
    else {
487 894e3339 Martin Sebela
      setPopupDatasetClassName(markersInRadius.datasetName)
488 2c2cad8d Martin Sebela
    }
489 e6097215 ballakt
490 894e3339 Martin Sebela
    setGlobalPopupContent(getPopupContent(datasetDictNameDisplayName[markersInRadius.datasetName], place, number, total, 1, popupPagesData.length))
491 c892003d Martin Sebela
492 894e3339 Martin Sebela
    if (popupPagesData.length === 1) {
493 c8718599 Martin Sebela
      disablePopupPaginationButtons()
494 3ae59f75 vastja
    }
495 1774c06d Tomáš Ballák
  } else {
496 894e3339 Martin Sebela
    const { datasetName, number } = getPopupDataOnPage(currentPageInPopup)
497 c892003d Martin Sebela
498 894e3339 Martin Sebela
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
499 e6097215 ballakt
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
500
      globalPopup.coord = eventCoord
501
    }
502 2c2cad8d Martin Sebela
    else {
503 894e3339 Martin Sebela
      setPopupDatasetClassName(datasetName)
504 2c2cad8d Martin Sebela
    }
505 e6097215 ballakt
506 1a1d8f64 Martin Sebela
    setGlobalPopupContent(genMultipleDatasetsPopUp(number, 1, getCountPagesInPopup(), datasetDictNameDisplayName[datasetName]))
507 3ae59f75 vastja
  }
508
}
509
510 9a772066 Tomáš
const setPageContentInPopup = (page) => {
511 894e3339 Martin Sebela
  const previousPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(page) : getPopupDataOnPage(0).items[page]
512
  const currentPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(currentPageInPopup) : getPopupDataOnPage(0).items[currentPageInPopup]
513 1a1d8f64 Martin Sebela
  const datasetName = $('#dataset-name')
514 c892003d Martin Sebela
515 1a1d8f64 Martin Sebela
  if (datasetName) {
516
    datasetName.html(datasetDictNameDisplayName[currentPageData.datasetName])
517 883a423e Tomáš Ballák
  }
518 1774c06d Tomáš Ballák
519 1a1d8f64 Martin Sebela
  $('#place-name').html(currentPageData.place ? currentPageData.place : currentPageData.datasetName)
520
  $('#current-number').html(currentPageData.number)
521
  $('#pages').html(currentPageInPopup + 1 + ' z ' + getCountPagesInPopup())
522 c892003d Martin Sebela
523 1a1d8f64 Martin Sebela
  $('.leaflet-popup').removeClass(`popup-${previousPageData.datasetName}`).addClass(`popup-${currentPageData.datasetName}`)
524 3ae59f75 vastja
}
525 351696d5 Martin Sebela
526 c8718599 Martin Sebela
const updatePopup = () => {
527
  const { _popup } = mymap
528 1a1d8f64 Martin Sebela
529 c8718599 Martin Sebela
  if (_popup) {
530
    showInfo({
531
      latlng: _popup.getLatLng()
532
    })
533
  }
534 3fc08f2d vastja
}
535
536 c8718599 Martin Sebela
537
538
539
/* ------------ ANIMATION ------------ */
540
541 70a3df53 vastja
/**
542
 * Change animation start from playing to stopped or the other way round
543
 */
544 9a772066 Tomáš
const changeAnimationState = () => {
545 1a1d8f64 Martin Sebela
  const btnAnimate = $('#animate-btn')
546
547 a48642fb vastja
  isAnimationRunning = !isAnimationRunning
548 c892003d Martin Sebela
549 a48642fb vastja
  if (isAnimationRunning) {
550 1a1d8f64 Martin Sebela
    btnAnimate.removeClass('play').addClass('pause')
551 1774c06d Tomáš Ballák
    timer = setInterval(function () { next() }, 800)
552
  } else {
553 8feb1753 ballakt
    clearTimeout(timer)
554 1a1d8f64 Martin Sebela
    btnAnimate.removeClass('pause').addClass('play')
555 351696d5 Martin Sebela
  }
556
}
557
558 9a772066 Tomáš
const previous = async () => {
559 1774c06d Tomáš Ballák
  if (loading) {
560
    return
561
  }
562 ec5e3220 Martin Sebela
563 8feb1753 ballakt
  currentTime = (currentTime + 23) % 24
564 1774c06d Tomáš Ballák
  changeHour(currentTime)
565 9a772066 Tomáš
566 1a1d8f64 Martin Sebela
  if (!lockedDay && currentTime === 23) {
567 1774c06d Tomáš Ballák
    addDayToCurrentDate(-1)
568
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
569
  } else {
570
    drawHeatmap(data[currentTime])
571
  }
572 1a1d8f64 Martin Sebela
573 e6097215 ballakt
  updatePopup()
574 a48642fb vastja
}
575
576 9a772066 Tomáš
const next = async () => {
577 1774c06d Tomáš Ballák
  if (loading) {
578
    return
579
  }
580 ec5e3220 Martin Sebela
581 8feb1753 ballakt
  currentTime = (currentTime + 1) % 24
582 1774c06d Tomáš Ballák
  changeHour(currentTime)
583 1a1d8f64 Martin Sebela
584
  if (!lockedDay && currentTime === 0) {
585 1774c06d Tomáš Ballák
    addDayToCurrentDate(1)
586
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
587
  } else {
588
    drawHeatmap(data[currentTime])
589
  }
590 1a1d8f64 Martin Sebela
591 e6097215 ballakt
  updatePopup()
592 8b840eb7 vastja
}
593 c892003d Martin Sebela
594 1774c06d Tomáš Ballák
const onChangeHour = (hour) => {
595
  changeHour(hour)
596
  drawHeatmap(data[currentTime])
597
}
598 863ca316 Martin Sebela
599 1774c06d Tomáš Ballák
const changeHour = (hour) => {
600 c8718599 Martin Sebela
  $('#player-time').removeAttr('style')
601
  changeCurrentTime(hour)
602
  updateHeaderControls()
603
  setTimeline()
604
  changeUrlParameters()
605
  updatePopup()
606 3fc08f2d vastja
}
607
608 c8718599 Martin Sebela
const dragTimeline = () => {
609
  const hourElemWidthPx = 26
610 1774c06d Tomáš Ballák
611 c8718599 Martin Sebela
  const elem = $('#player-time')
612
  const offset = elem.offset().left - elem.parent().offset().left
613 c892003d Martin Sebela
614 c8718599 Martin Sebela
  if (offset >= 0 && offset <= elem.parent().width()) {
615
    const hour = Math.round(offset / hourElemWidthPx)
616 c892003d Martin Sebela
617 c8718599 Martin Sebela
    if (hour !== currentTime) {
618
      elem.attr('class', 'time hour-' + hour)
619
      $('#player-time span').html(formatTime(hour))
620 c892003d Martin Sebela
621 c8718599 Martin Sebela
      onChangeHour(hour)
622 a48642fb vastja
    }
623
  }
624 c8718599 Martin Sebela
}
625 ac154afa Martin Sebela
626 c8718599 Martin Sebela
const onArrowLeftRightKeysDownRegister = () => {
627
  $(document).keydown(function (e) {
628
    const { which } = e
629 9a772066 Tomáš
630 c8718599 Martin Sebela
    if (which === arrowKeyLEFT) {
631
      previous()
632
      e.preventDefault()
633
    } else if (which === arrowKeyRIGHT) {
634
      next()
635
      e.preventDefault()
636
    }
637
  })
638 03c02899 vastja
}
639 3fc08f2d vastja
640 70a3df53 vastja
/**
641 c8718599 Martin Sebela
 * Change browser url based on animation step.
642 70a3df53 vastja
 */
643 c8718599 Martin Sebela
const changeUrlParameters = () => {
644
  window.history.pushState(
645
    '',
646
    document.title,
647
    window.location.origin + window.location.pathname + `?date=${currentDateToString()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type[]=' + current, '')}`
648
  )
649 03c02899 vastja
}
650
651 c892003d Martin Sebela
652
653 dfe43218 vastja
654 c8718599 Martin Sebela
/* ------------ UTILS ------------ */
655
656
const formatTime = (hours, twoDigitsHours = false) => {
657
  return ((twoDigitsHours && hours < 10) ? '0' : '') + hours + ':00';
658 03c02899 vastja
}
659 0a828a5a Martin Sebela
660 c8718599 Martin Sebela
const formatDate = (date) => {
661 8feb1753 ballakt
  var day = String(date.getDate())
662
  var month = String(date.getMonth() + 1)
663 0a828a5a Martin Sebela
664
  if (day.length === 1) {
665 8feb1753 ballakt
    day = '0' + day
666 0a828a5a Martin Sebela
  }
667
668
  if (month.length === 1) {
669 8feb1753 ballakt
    month = '0' + month
670 0a828a5a Martin Sebela
  }
671
672 c8718599 Martin Sebela
  // return YYYY-MM-DD
673 8feb1753 ballakt
  return date.getFullYear() + '-' + month + '-' + day
674 0a828a5a Martin Sebela
}
675
676 c8718599 Martin Sebela
const currentDayToString = () => {
677
  const day = currentDate.getDate()
678
  return day > 9 ? `${day}` : `0${day}`
679
}
680
681
const currentMonthToString = () => {
682
  const month = currentDate.getMonth() + 1
683
  return month > 9 ? `${month}` : `0${month}`
684
}
685
686
const currentDateToString = () => `${currentDate.getFullYear()}-${currentMonthToString()}-${currentDayToString()}`
687
688
const addDayToCurrentDate = (day) => {
689
  currentDate.setDate(currentDate.getDate() + day)
690
  changeCurrentDate(currentDate)
691
}
692
693
const areCoordsIdentical = (first, second) => {
694
  return first.lat === second.lat && first.lng === second.lng
695
}
696
697
const debounce = (func, delay) => {
698
  let inDebounce
699
  return function () {
700
    const context = this
701
    const args = arguments
702
    clearTimeout(inDebounce)
703
    inDebounce = setTimeout(() => func.apply(context, args), delay)
704
  }
705
}
706
707
708
709
710
/* ------------ GUI ------------ */
711
712
const updateHeaderControls = () => {
713
  $(`#time_${currentTime}`).prop('checked', true)
714
  $('#dropdownMenuButtonTime').html(formatTime(currentTime, true))
715
}
716
717 bf634c97 Tomáš
const initDatepicker = async (availableDatesSource) => {
718 8feb1753 ballakt
  var availableDates = ''
719 bf634c97 Tomáš
  const result = await genericFetch(availableDatesSource, 'GET')
720
  availableDates = String(result).split(',')
721
  $('#date').datepicker({
722
    format: 'yyyy-mm-dd',
723
    language: 'cs',
724
    beforeShowDay: function (date) {
725
      if (availableDates.indexOf(formatDate(date)) < 0) {
726
        return { enabled: false, tooltip: 'Žádná data' }
727
      } else {
728
        return { enabled: true }
729
      }
730
    },
731
    autoclose: true
732 8feb1753 ballakt
  })
733
}
734 dd652e61 Martin Sebela
735 9a772066 Tomáš
const initLocationsMenu = () => {
736 1a1d8f64 Martin Sebela
  const elmLocationsList = $('.locations')
737
  const locationsDisplayClass = 'show'
738 dd652e61 Martin Sebela
739 81980e82 ballakt
  if ($(window).width() <= 480) {
740 1a1d8f64 Martin Sebela
    elmLocationsList.removeClass(locationsDisplayClass)
741 1774c06d Tomáš Ballák
  } else {
742 1a1d8f64 Martin Sebela
    elmLocationsList.addClass(locationsDisplayClass)
743 dd652e61 Martin Sebela
  }
744 4e003182 Martin Sebela
}
745
746 c8718599 Martin Sebela
const setTimeline = () => {
747
  $('#player-time > span').text(formatTime(currentTime))
748
  $('#player-time').attr('class', 'time hour-' + currentTime)
749
}
750
751
const onValueChangeRegister = () => {
752
  $('#date').change(function () {
753
    changeCurrentDate($(this).val())
754
    loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
755
    changeUrlParameters()
756 2f227a6c ballakt
  })
757 1cf1413d ballakt
758 c8718599 Martin Sebela
  $('#dropdown-time input[type="radio"]').each(function () {
759
    $(this).change(function () {
760
      changeHour(parseInt($(this).val()))
761
      drawHeatmap(data[currentTime])
762
    })
763
  })
764
765
  $('#dropdown-dataset input[type="checkbox"]').each(function () {
766
    $(this).change(
767
      debounce(() => onCheckboxClicked(this), 1000)
768
    )
769
  })
770
}
771
772
const onCheckboxClicked = async (checkbox) => {
773
  if ($(checkbox).prop('checked')) {
774
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
775
  } else {
776
    loadCheckboxDatasetNameData()
777
778
    data.forEach((item, index) => {
779
      Object.keys(item).forEach(datasetName => {
780
        if (datasetName === $(checkbox).val()) {
781
          delete data[index][datasetName]
782
        }
783
      })
784
785
      drawHeatmap(data[currentTime])
786
    })
787
  }
788
789
  updatePopup()
790
  changeUrlParameters()
791 1cf1413d ballakt
}
792 c892003d Martin Sebela
793 1cf1413d ballakt
const loadCheckboxDatasetNameData = () => {
794 2f227a6c ballakt
  datasetSelected = []
795 1a1d8f64 Martin Sebela
796 c892003d Martin Sebela
  $('#dropdown-dataset .dropdown-item').each(function () {
797 2f227a6c ballakt
    const input = $(this).find('input')
798
    const inputVal = input[0].value
799 c892003d Martin Sebela
800 2f227a6c ballakt
    if (input[0].checked) {
801
      datasetSelected.push(inputVal)
802
    }
803 c892003d Martin Sebela
804 883a423e Tomáš Ballák
    datasetDictNameDisplayName[inputVal] = $(input).data('dataset-display-name')
805 2f227a6c ballakt
  })
806
}
807 ec5e3220 Martin Sebela
808 9a772066 Tomáš
const updateAvailableDataSets = async (available) => {
809 c8718599 Martin Sebela
  let leastOneOptionEnabled = false
810 ec5e3220 Martin Sebela
811 c8718599 Martin Sebela
  $('#dropdown-dataset .dropdown-item').each(function () {
812
    const input = $(this).find('input')
813 ec5e3220 Martin Sebela
814 c8718599 Martin Sebela
    if (!(input[0].value in available)) {
815
      $(this).addClass('disabled')
816
      $(input).prop('checked', false)
817
    } else {
818
      leastOneOptionEnabled = true
819
      $(this).removeClass('disabled')
820
    }
821
  })
822 815159f3 Tomáš Ballák
823 c8718599 Martin Sebela
  $('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled)
824
}
825 ec5e3220 Martin Sebela
826 c8718599 Martin Sebela
827
828
829
/* ------------ GUI LOADING ------------ */
830
831
const loadingCallbackNested = (func, delay) => {
832
  setTimeout(() => {
833
    func(loading)
834
    if (loading) {
835
      loadingCallbackNested(func, delay)
836 fdba469a Martin Sebela
    }
837 c8718599 Martin Sebela
  }, delay)
838
}
839
840
const loadingY = (delay = defaultLoaderDelay) => {
841
  loading++
842
  // check after nms if there is something that is loading
843
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading), delay))
844
}
845
846
const loadingN = (delay = defaultLoaderDelay) => {
847
  loading--
848
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading)), delay)
849
}
850
851
const loadingTimeline = (isLoading) => {
852
  if (isLoading) {
853
    loadingYTimeline()
854
  } else {
855
    loadingNTimeline()
856 ec5e3220 Martin Sebela
  }
857 815159f3 Tomáš Ballák
}
858 c8718599 Martin Sebela
859
const loadingYTimeline = () => {
860
  $('#player-time > .spinner-border').removeClass('d-none')
861
  $('#player-time > span').text('')
862
}
863
864
const loadingNTimeline = () => {
865
  $('#player-time > .spinner-border').addClass('d-none')
866
  setTimeline()
867 9a772066 Tomáš
}
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 c8718599 Martin Sebela
}