Projekt

Obecné

Profil

Stáhnout (22 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
let currentDate
21 a48642fb vastja
22 8feb1753 ballakt
var timer
23
var isAnimationRunning = false
24
var data = []
25 a48642fb vastja
26 e6097215 ballakt
//
27
// info = {
28
//  DATASETNAME: {
29
//    items: Array,
30
//    number: Number,
31
//    datasetName: String
32
// }
33
// }
34
//
35 70a3df53 vastja
var info = []
36 1a1d8f64 Martin Sebela
let currentPageInPopup = 0
37 2f227a6c ballakt
38 d51166e8 ballakt
// dictionary for names of datasets
39 883a423e Tomáš Ballák
const datasetDictNameDisplayName = {}
40 2f227a6c ballakt
var datasetSelected = []
41 70a3df53 vastja
42 ab904bf5 Martin Sebela
// animate data only in one day
43 1774c06d Tomáš Ballák
let lockedDay = false
44
45 d51166e8 ballakt
// loading information for async operations
46 1774c06d Tomáš Ballák
let loading = 0
47
48 d51166e8 ballakt
// default loader showup delay
49
const defaultLoaderDelay = 1000
50
51 ab904bf5 Martin Sebela
// map markers for all datasets
52
const dataMapMarkers = {}
53 1774c06d Tomáš Ballák
54 8feb1753 ballakt
const globalMarkersHolder = {}
55 d5a88af0 Tomáš Ballák
// 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 084a5972 ballakt
60 e6097215 ballakt
const globalPopup = {
61
  coord: {
62
    lat: 0,
63
    lng: 0
64
  },
65
  _popup: null
66
}
67 1a1d8f64 Martin Sebela
68 e6097215 ballakt
69
70 1774c06d Tomáš Ballák
71 bf634c97 Tomáš
const onDocumentReady = () => {
72 c8718599 Martin Sebela
  $('#dropdown-dataset').on('click', function (e) {
73
    e.stopPropagation()
74
  })
75 1a1d8f64 Martin Sebela
76 c8718599 Martin Sebela
  $('#btn-update-heatmap').prop('name', '')
77
  changeCurrentTime()
78
  changeCurrentDate()
79
  onValueChangeRegister()
80
  onArrowLeftRightKeysDownRegister()
81 1774c06d Tomáš Ballák
}
82 1a1d8f64 Martin Sebela
83
84
85
86 c8718599 Martin Sebela
/* ------------ DATA FETCHERS ------------ */
87 ab904bf5 Martin Sebela
88 bf634c97 Tomáš
const genericFetch = async (route, method) => {
89 2f227a6c ballakt
  const headers = new Headers()
90 ab904bf5 Martin Sebela
  const request = new Request(route, {
91 bf634c97 Tomáš
    method: method,
92 2f227a6c ballakt
    headers: headers
93
  })
94 ab904bf5 Martin Sebela
  const beforeJson = await fetch(request)
95 1a1d8f64 Martin Sebela
96 2f227a6c ballakt
  return beforeJson.json()
97
}
98 ab904bf5 Martin Sebela
99
const fetchDatasetDataByDatetime = async (route, datasetName, date, time) => {
100
  return await genericFetch(route + '/' + datasetName + '/' + date + '/' + time, 'GET')
101 bf634c97 Tomáš
}
102 c892003d Martin Sebela
103 ab904bf5 Martin Sebela
const fetchDatasetMapMarkers = async (route, datasetName) => {
104
  return await genericFetch(route + '/' + datasetName, 'GET')
105 2f227a6c ballakt
}
106 084a5972 ballakt
107 ab904bf5 Martin Sebela
preload = async (time, timeShift, date) => {
108 c8718599 Martin Sebela
  loadingY()
109 c892003d Martin Sebela
110 ab904bf5 Martin Sebela
  for (let nTime = time + timeShift; nTime >= 0 && nTime <= 23; nTime = nTime + timeShift) {
111 c8718599 Martin Sebela
    if (!data[nTime]) {
112
      data[nTime] = {}
113
    }
114
115
    datasetSelected.forEach(async (datasetName) => {
116
      if (!data[nTime][datasetName]) {
117 ab904bf5 Martin Sebela
        data[nTime][datasetName] = await fetchDatasetDataByDatetime(dataSourceRoute, datasetName, date, nTime)
118 c8718599 Martin Sebela
      }
119
    })
120
  }
121
122
  loadingN()
123 084a5972 ballakt
}
124 c892003d Martin Sebela
125 c8718599 Martin Sebela
/**
126
 * Load and display heatmap layer for current data
127
 * 
128
 * @param {string} opendataRoute route to dataset source
129 ab904bf5 Martin Sebela
 * @param {string} positionsRoute  route to dataset positions source
130 c8718599 Martin Sebela
 */
131 9a772066 Tomáš
const loadCurrentTimeHeatmap = async (opendataRoute, positionsRoute, loaderDelay = defaultLoaderDelay) => {
132 c8718599 Martin Sebela
  loadCheckboxDatasetNameData()
133 c892003d Martin Sebela
134 c8718599 Martin Sebela
  dataSourceRoute = opendataRoute
135
  positionsSourceRoute = positionsRoute
136
  const allPromises = []
137
  data[currentTime] = {}
138
139
  const dataSelectedHandler = async (datasetName) => {
140 ab904bf5 Martin Sebela
    if (!(datasetName in dataMapMarkers)) {
141
      dataMapMarkers[datasetName] = await fetchDatasetMapMarkers(positionsRoute, datasetName)
142 c8718599 Martin Sebela
    }
143
144 ab904bf5 Martin Sebela
    const datasetData = await fetchDatasetDataByDatetime(dataSourceRoute, datasetName, currentDateToString(), currentTime)
145 c8718599 Martin Sebela
    data[currentTime][datasetName] = datasetData
146
  }
147 ab904bf5 Martin Sebela
148 c8718599 Martin Sebela
  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 ab904bf5 Martin Sebela
      drawMapMarkers(dataMapMarkers)
158 c8718599 Martin Sebela
      drawHeatmap(data[currentTime])
159 9a772066 Tomáš
160 c8718599 Martin Sebela
      preload(currentTime, 1, currentDateToString())
161
      preload(currentTime, -1, currentDateToString())
162
    }
163
  )
164 2f227a6c ballakt
}
165 c892003d Martin Sebela
166 c8718599 Martin Sebela
/**
167
 * Checks dataset availibility
168
 * @param {string} route authority for datasets availibility checks
169
 */
170 bf634c97 Tomáš
const checkDataSetsAvailability = async (route) => {
171
  const result = await genericFetch(route + '/' + currentDateToString(), 'POST')
172
  updateAvailableDataSets(result)
173 c8718599 Martin Sebela
}
174 2f227a6c ballakt
175 bb2d43b5 Martin Sebela
176 c8718599 Martin Sebela
177
178
/* ------------ MAP ------------ */
179
180
/**
181
 * Initialize leaflet map on start position which can be default or set based on user action
182
 */
183 9a772066 Tomáš
const initMap = () => {
184 c8718599 Martin Sebela
  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 ab904bf5 Martin Sebela
  mymap.on('click', function (e) { showPopup(e) })
196 084a5972 ballakt
}
197 c892003d Martin Sebela
198 bf634c97 Tomáš
const setMapView = (latitude, longitude, zoom) => {
199 c8718599 Martin Sebela
  localStorage.setItem('lat', latitude)
200
  localStorage.setItem('lng', longitude)
201
  localStorage.setItem('zoom', zoom)
202 c892003d Martin Sebela
203 c8718599 Martin Sebela
  mymap.setView([latitude, longitude], zoom)
204
}
205
206 9a772066 Tomáš
const drawHeatmap = (dataRaw) => {
207 c8718599 Martin Sebela
  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 9a772066 Tomáš
      // Bind back popups for markers (we dont know if there is any data for this marker or not)
224 c8718599 Martin Sebela
      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 1cf1413d ballakt
        }
233 c8718599 Martin Sebela
234
        return [x, y, number]
235 1cf1413d ballakt
      })
236 c8718599 Martin Sebela
      mergedPoints.push(...points)
237
    } else {
238
      if (heatmapLayer != null) {
239
        mymap.removeLayer(heatmapLayer)
240
      }
241
    }
242
  })
243 1a1d8f64 Martin Sebela
244 c8718599 Martin Sebela
  if (heatmapLayer != null) {
245
    mymap.removeLayer(heatmapLayer)
246 1cf1413d ballakt
  }
247 1a1d8f64 Martin Sebela
248 c8718599 Martin Sebela
  if (mergedPoints.length) {
249
    heatmapLayer = L.heatLayer(mergedPoints, { max: max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
250
  }
251
252 e6097215 ballakt
  updatePopup()
253 1cf1413d ballakt
}
254 c892003d Martin Sebela
255 9a772066 Tomáš
const drawMapMarkers = (data) => {
256 c8718599 Martin Sebela
  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 9a772066 Tomáš
        prepareLayerPopUp(x, y, 1, `popup-${key_}`)
267
          .setContent(getPopupContent(datasetDictNameDisplayName[key_], name, 0, 0, 1, 1))
268 c8718599 Martin Sebela
      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 815159f3 Tomáš Ballák
    }
276
  })
277 c8718599 Martin Sebela
278
  markersLayer.setZIndex(-1).addTo(mymap)
279 815159f3 Tomáš Ballák
}
280
281 c8718599 Martin Sebela
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 1cf1413d ballakt
  }
292
}
293 bb2d43b5 Martin Sebela
294 c8718599 Martin Sebela
const changeCurrentDate = (date = null) => {
295
  const dateInput = $('#date')
296
  currentDate = new Date(date ? date : dateInput.val())
297 bb2d43b5 Martin Sebela
298 c8718599 Martin Sebela
  dateInput.val(currentDateToString())
299
  $('#player-date span').html(`${currentDate.getDate()}. ${currentDate.getMonth() + 1}. ${currentDate.getFullYear()}`)
300 bb2d43b5 Martin Sebela
301 c8718599 Martin Sebela
  data = []
302 1cf1413d ballakt
}
303 bb2d43b5 Martin Sebela
304 c8718599 Martin Sebela
const toggleDayLock = () => {
305
  lockedDay = !lockedDay
306
  $('#player-date').toggleClass('lock')
307
}
308 72a438f3 vastja
309 3fc08f2d vastja
310 3ae59f75 vastja
311 c8718599 Martin Sebela
312 ab904bf5 Martin Sebela
/* ------------ POPUPS ------------ */
313 c8718599 Martin Sebela
314
const setGlobalPopupContent = (content) => {
315
  globalPopup._popup.setContent(content)
316
  globalPopup._popup.openOn(mymap)
317
}
318
319
const getPaginationButtonsInPopup = (currentPage, countPages) => ({
320 9a772066 Tomáš
  previousButton: '<button type="button" id="btn-popup-previous-page" onclick="js.setPreviousPageInPopup()"></button>',
321 6b7c4d1c Martin Sebela
  pagesList: `<p id="pages">${currentPage} z ${countPages}</p>`,
322 9a772066 Tomáš
  nextButton: '<button type="button" id="btn-popup-next-page" class="next" onclick="js.setNextPageInPopup()"></button>'
323 c8718599 Martin Sebela
})
324
325
const disablePopupPaginationButtons = () => {
326 6b7c4d1c Martin Sebela
  $('#btn-popup-previous-page').prop('disabled', true)
327
  $('#btn-popup-next-page').prop('disabled', true)
328
  $('.popup-pagination').hide()
329 c8718599 Martin Sebela
}
330
331
const generatePopupPaginationButtons = (controls) => {
332 6b7c4d1c Martin Sebela
  return `<div class="popup-pagination">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
333 c236b33a msebela
}
334 c892003d Martin Sebela
335 1a1d8f64 Martin Sebela
const getCountPagesInPopup = () => {
336 2f227a6c ballakt
  const infoKeys = Object.keys(info)
337 894e3339 Martin Sebela
338 2f227a6c ballakt
  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 c892003d Martin Sebela
346 894e3339 Martin Sebela
const getPopupDataOnPage = (pageInPopup) => {
347 2f227a6c ballakt
  const keys = Object.keys(info)
348 894e3339 Martin Sebela
  return info[keys[pageInPopup]]
349 2f227a6c ballakt
}
350 c892003d Martin Sebela
351 9a772066 Tomáš
const setPreviousPageInPopup = () => {
352 c8718599 Martin Sebela
  const countPagesInPopup = getCountPagesInPopup()
353
  const page = currentPageInPopup
354
355
  currentPageInPopup = (currentPageInPopup + countPagesInPopup - 1) % countPagesInPopup
356
  setPageContentInPopup(page)
357
}
358
359 9a772066 Tomáš
const setNextPageInPopup = () => {
360 c8718599 Martin Sebela
  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 6b7c4d1c Martin Sebela
      <span id="sum-number">${(sum && sum !== Number(currentCount)) ? '/' + sum : ''}</span>
392 c8718599 Martin Sebela
    </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 894e3339 Martin Sebela
const areMultipleDatasetsInRadius = () => {
403 2f227a6c ballakt
  return Object.keys(info).length > 1
404
}
405 c892003d Martin Sebela
406 894e3339 Martin Sebela
const setPopupDatasetClassName = (datasetName) => {
407 2c2cad8d Martin Sebela
  const popup = $('.leaflet-popup')
408
409
  popup.removeClass(function (index, css) {
410 894e3339 Martin Sebela
    return (css.match(/(^|\s)popup-\S+/g) || []).join(' ');
411 2c2cad8d Martin Sebela
  })
412 894e3339 Martin Sebela
  popup.addClass('popup-' + datasetName)
413 2c2cad8d Martin Sebela
}
414
415 ab904bf5 Martin Sebela
const showPopup = (e) => {
416 3ae59f75 vastja
  info = []
417 1a1d8f64 Martin Sebela
  currentPageInPopup = 0
418 3ae59f75 vastja
419 2f227a6c ballakt
  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 e6097215 ballakt
  const eventCoord = {
430
    lng: e.latlng.lng,
431
    lat: e.latlng.lat
432
  }
433 894e3339 Martin Sebela
434
  Object.keys(data[currentTime]).forEach(key => {
435 2f227a6c ballakt
    const namedData = data[currentTime][key]
436 894e3339 Martin Sebela
437 2f227a6c ballakt
    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 8feb1753 ballakt
  })
448 1a1d8f64 Martin Sebela
449 2f227a6c ballakt
  // 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 c892003d Martin Sebela
459 2f227a6c ballakt
    acc[item.datasetName].items.push(item)
460
    acc[item.datasetName].number += Number(item.number)
461
    return acc
462
  }, {})
463
464 894e3339 Martin Sebela
  const countDatasets = Object.keys(datasetsInRadius).length
465 1a1d8f64 Martin Sebela
466 894e3339 Martin Sebela
  if (!countDatasets) {
467 e6097215 ballakt
    if (mymap._popup) {
468 6b7c4d1c Martin Sebela
      $('#sum-number').text('')
469 e6097215 ballakt
      $('#current-number').html(0)
470 c8718599 Martin Sebela
      disablePopupPaginationButtons()
471 e6097215 ballakt
    }
472
473 d5a88af0 Tomáš Ballák
    return
474
  }
475 1a1d8f64 Martin Sebela
476 894e3339 Martin Sebela
  if (countDatasets === 1) {
477
    const markersInRadius = getPopupDataOnPage(0)
478
    const popupPagesData = markersInRadius.items
479
    const { place, number } = popupPagesData[currentPageInPopup]
480 e6097215 ballakt
481 894e3339 Martin Sebela
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
482
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${markersInRadius.datasetName}`)
483 e6097215 ballakt
      globalPopup.coord = eventCoord
484
    }
485 2c2cad8d Martin Sebela
    else {
486 894e3339 Martin Sebela
      setPopupDatasetClassName(markersInRadius.datasetName)
487 2c2cad8d Martin Sebela
    }
488 e6097215 ballakt
489 894e3339 Martin Sebela
    setGlobalPopupContent(getPopupContent(datasetDictNameDisplayName[markersInRadius.datasetName], place, number, total, 1, popupPagesData.length))
490 c892003d Martin Sebela
491 894e3339 Martin Sebela
    if (popupPagesData.length === 1) {
492 c8718599 Martin Sebela
      disablePopupPaginationButtons()
493 3ae59f75 vastja
    }
494 1774c06d Tomáš Ballák
  } else {
495 894e3339 Martin Sebela
    const { datasetName, number } = getPopupDataOnPage(currentPageInPopup)
496 c892003d Martin Sebela
497 894e3339 Martin Sebela
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
498 e6097215 ballakt
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
499
      globalPopup.coord = eventCoord
500
    }
501 2c2cad8d Martin Sebela
    else {
502 894e3339 Martin Sebela
      setPopupDatasetClassName(datasetName)
503 2c2cad8d Martin Sebela
    }
504 e6097215 ballakt
505 1a1d8f64 Martin Sebela
    setGlobalPopupContent(genMultipleDatasetsPopUp(number, 1, getCountPagesInPopup(), datasetDictNameDisplayName[datasetName]))
506 3ae59f75 vastja
  }
507
}
508
509 9a772066 Tomáš
const setPageContentInPopup = (page) => {
510 894e3339 Martin Sebela
  const previousPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(page) : getPopupDataOnPage(0).items[page]
511
  const currentPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(currentPageInPopup) : getPopupDataOnPage(0).items[currentPageInPopup]
512 1a1d8f64 Martin Sebela
  const datasetName = $('#dataset-name')
513 c892003d Martin Sebela
514 1a1d8f64 Martin Sebela
  if (datasetName) {
515
    datasetName.html(datasetDictNameDisplayName[currentPageData.datasetName])
516 883a423e Tomáš Ballák
  }
517 1774c06d Tomáš Ballák
518 1a1d8f64 Martin Sebela
  $('#place-name').html(currentPageData.place ? currentPageData.place : currentPageData.datasetName)
519
  $('#current-number').html(currentPageData.number)
520
  $('#pages').html(currentPageInPopup + 1 + ' z ' + getCountPagesInPopup())
521 c892003d Martin Sebela
522 1a1d8f64 Martin Sebela
  $('.leaflet-popup').removeClass(`popup-${previousPageData.datasetName}`).addClass(`popup-${currentPageData.datasetName}`)
523 3ae59f75 vastja
}
524 351696d5 Martin Sebela
525 c8718599 Martin Sebela
const updatePopup = () => {
526
  const { _popup } = mymap
527 1a1d8f64 Martin Sebela
528 c8718599 Martin Sebela
  if (_popup) {
529 ab904bf5 Martin Sebela
    showPopup({
530 c8718599 Martin Sebela
      latlng: _popup.getLatLng()
531
    })
532
  }
533 3fc08f2d vastja
}
534
535 c8718599 Martin Sebela
536
537
538
/* ------------ ANIMATION ------------ */
539
540 70a3df53 vastja
/**
541
 * Change animation start from playing to stopped or the other way round
542
 */
543 9a772066 Tomáš
const changeAnimationState = () => {
544 1a1d8f64 Martin Sebela
  const btnAnimate = $('#animate-btn')
545
546 a48642fb vastja
  isAnimationRunning = !isAnimationRunning
547 c892003d Martin Sebela
548 a48642fb vastja
  if (isAnimationRunning) {
549 1a1d8f64 Martin Sebela
    btnAnimate.removeClass('play').addClass('pause')
550 1774c06d Tomáš Ballák
    timer = setInterval(function () { next() }, 800)
551
  } else {
552 8feb1753 ballakt
    clearTimeout(timer)
553 1a1d8f64 Martin Sebela
    btnAnimate.removeClass('pause').addClass('play')
554 351696d5 Martin Sebela
  }
555
}
556
557 9a772066 Tomáš
const previous = async () => {
558 1774c06d Tomáš Ballák
  if (loading) {
559
    return
560
  }
561 ec5e3220 Martin Sebela
562 8feb1753 ballakt
  currentTime = (currentTime + 23) % 24
563 1774c06d Tomáš Ballák
  changeHour(currentTime)
564 9a772066 Tomáš
565 1a1d8f64 Martin Sebela
  if (!lockedDay && currentTime === 23) {
566 1774c06d Tomáš Ballák
    addDayToCurrentDate(-1)
567
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
568
  } else {
569
    drawHeatmap(data[currentTime])
570
  }
571 1a1d8f64 Martin Sebela
572 e6097215 ballakt
  updatePopup()
573 a48642fb vastja
}
574
575 9a772066 Tomáš
const next = async () => {
576 1774c06d Tomáš Ballák
  if (loading) {
577
    return
578
  }
579 ec5e3220 Martin Sebela
580 8feb1753 ballakt
  currentTime = (currentTime + 1) % 24
581 1774c06d Tomáš Ballák
  changeHour(currentTime)
582 1a1d8f64 Martin Sebela
583
  if (!lockedDay && currentTime === 0) {
584 1774c06d Tomáš Ballák
    addDayToCurrentDate(1)
585
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
586
  } else {
587
    drawHeatmap(data[currentTime])
588
  }
589 1a1d8f64 Martin Sebela
590 e6097215 ballakt
  updatePopup()
591 8b840eb7 vastja
}
592 c892003d Martin Sebela
593 1774c06d Tomáš Ballák
const onChangeHour = (hour) => {
594
  changeHour(hour)
595
  drawHeatmap(data[currentTime])
596
}
597 863ca316 Martin Sebela
598 1774c06d Tomáš Ballák
const changeHour = (hour) => {
599 c8718599 Martin Sebela
  $('#player-time').removeAttr('style')
600
  changeCurrentTime(hour)
601
  updateHeaderControls()
602
  setTimeline()
603
  changeUrlParameters()
604
  updatePopup()
605 3fc08f2d vastja
}
606
607 c8718599 Martin Sebela
const dragTimeline = () => {
608
  const hourElemWidthPx = 26
609 1774c06d Tomáš Ballák
610 c8718599 Martin Sebela
  const elem = $('#player-time')
611
  const offset = elem.offset().left - elem.parent().offset().left
612 c892003d Martin Sebela
613 c8718599 Martin Sebela
  if (offset >= 0 && offset <= elem.parent().width()) {
614
    const hour = Math.round(offset / hourElemWidthPx)
615 c892003d Martin Sebela
616 c8718599 Martin Sebela
    if (hour !== currentTime) {
617
      elem.attr('class', 'time hour-' + hour)
618
      $('#player-time span').html(formatTime(hour))
619 c892003d Martin Sebela
620 c8718599 Martin Sebela
      onChangeHour(hour)
621 a48642fb vastja
    }
622
  }
623 c8718599 Martin Sebela
}
624 ac154afa Martin Sebela
625 c8718599 Martin Sebela
const onArrowLeftRightKeysDownRegister = () => {
626
  $(document).keydown(function (e) {
627
    const { which } = e
628 9a772066 Tomáš
629 c8718599 Martin Sebela
    if (which === arrowKeyLEFT) {
630
      previous()
631
      e.preventDefault()
632
    } else if (which === arrowKeyRIGHT) {
633
      next()
634
      e.preventDefault()
635
    }
636
  })
637 03c02899 vastja
}
638 3fc08f2d vastja
639 70a3df53 vastja
/**
640 c8718599 Martin Sebela
 * Change browser url based on animation step.
641 70a3df53 vastja
 */
642 c8718599 Martin Sebela
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 03c02899 vastja
}
649
650 c892003d Martin Sebela
651
652 dfe43218 vastja
653 c8718599 Martin Sebela
/* ------------ UTILS ------------ */
654
655
const formatTime = (hours, twoDigitsHours = false) => {
656
  return ((twoDigitsHours && hours < 10) ? '0' : '') + hours + ':00';
657 03c02899 vastja
}
658 0a828a5a Martin Sebela
659 c8718599 Martin Sebela
const formatDate = (date) => {
660 8feb1753 ballakt
  var day = String(date.getDate())
661
  var month = String(date.getMonth() + 1)
662 0a828a5a Martin Sebela
663
  if (day.length === 1) {
664 8feb1753 ballakt
    day = '0' + day
665 0a828a5a Martin Sebela
  }
666
667
  if (month.length === 1) {
668 8feb1753 ballakt
    month = '0' + month
669 0a828a5a Martin Sebela
  }
670
671 c8718599 Martin Sebela
  // return YYYY-MM-DD
672 8feb1753 ballakt
  return date.getFullYear() + '-' + month + '-' + day
673 0a828a5a Martin Sebela
}
674
675 c8718599 Martin Sebela
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 bf634c97 Tomáš
const initDatepicker = async (availableDatesSource) => {
717
  const result = await genericFetch(availableDatesSource, 'GET')
718 ab904bf5 Martin Sebela
  const datesContainData = String(result).split(',')
719
720 bf634c97 Tomáš
  $('#date').datepicker({
721
    format: 'yyyy-mm-dd',
722
    language: 'cs',
723
    beforeShowDay: function (date) {
724 ab904bf5 Martin Sebela
      if (datesContainData.indexOf(formatDate(date)) < 0) {
725 bf634c97 Tomáš
        return { enabled: false, tooltip: 'Žádná data' }
726
      } else {
727
        return { enabled: true }
728
      }
729
    },
730
    autoclose: true
731 8feb1753 ballakt
  })
732
}
733 dd652e61 Martin Sebela
734 9a772066 Tomáš
const initLocationsMenu = () => {
735 1a1d8f64 Martin Sebela
  const elmLocationsList = $('.locations')
736
  const locationsDisplayClass = 'show'
737 dd652e61 Martin Sebela
738 81980e82 ballakt
  if ($(window).width() <= 480) {
739 1a1d8f64 Martin Sebela
    elmLocationsList.removeClass(locationsDisplayClass)
740 1774c06d Tomáš Ballák
  } else {
741 1a1d8f64 Martin Sebela
    elmLocationsList.addClass(locationsDisplayClass)
742 dd652e61 Martin Sebela
  }
743 4e003182 Martin Sebela
}
744
745 c8718599 Martin Sebela
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 2f227a6c ballakt
  })
756 1cf1413d ballakt
757 c8718599 Martin Sebela
  $('#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 1cf1413d ballakt
}
791 c892003d Martin Sebela
792 1cf1413d ballakt
const loadCheckboxDatasetNameData = () => {
793 2f227a6c ballakt
  datasetSelected = []
794 1a1d8f64 Martin Sebela
795 c892003d Martin Sebela
  $('#dropdown-dataset .dropdown-item').each(function () {
796 2f227a6c ballakt
    const input = $(this).find('input')
797
    const inputVal = input[0].value
798 c892003d Martin Sebela
799 2f227a6c ballakt
    if (input[0].checked) {
800
      datasetSelected.push(inputVal)
801
    }
802 c892003d Martin Sebela
803 883a423e Tomáš Ballák
    datasetDictNameDisplayName[inputVal] = $(input).data('dataset-display-name')
804 2f227a6c ballakt
  })
805
}
806 ec5e3220 Martin Sebela
807 9a772066 Tomáš
const updateAvailableDataSets = async (available) => {
808 c8718599 Martin Sebela
  let leastOneOptionEnabled = false
809 ec5e3220 Martin Sebela
810 c8718599 Martin Sebela
  $('#dropdown-dataset .dropdown-item').each(function () {
811
    const input = $(this).find('input')
812 ec5e3220 Martin Sebela
813 c8718599 Martin Sebela
    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 815159f3 Tomáš Ballák
822 c8718599 Martin Sebela
  $('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled)
823
}
824 ec5e3220 Martin Sebela
825 c8718599 Martin Sebela
826
827
828
/* ------------ GUI LOADING ------------ */
829
830
const loadingCallbackNested = (func, delay) => {
831
  setTimeout(() => {
832
    func(loading)
833
    if (loading) {
834
      loadingCallbackNested(func, delay)
835 fdba469a Martin Sebela
    }
836 c8718599 Martin Sebela
  }, 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 ec5e3220 Martin Sebela
  }
856 815159f3 Tomáš Ballák
}
857 c8718599 Martin Sebela
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 9a772066 Tomáš
}
867 ab904bf5 Martin Sebela
868 9a772066 Tomáš
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
}