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
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
  // timto vyresen bug #8191 - TODO: znamena to, ze muzeme smazat volani updatePopup() ve funkcich, kde se nejdriv vola drawHeatmap() a pak updatePopup()?
253 e6097215 ballakt
  updatePopup()
254 1cf1413d ballakt
}
255 c892003d Martin Sebela
256 9a772066 Tomáš
const drawMapMarkers = (data) => {
257 c8718599 Martin Sebela
  if (markersLayer != null) {
258
    mymap.removeLayer(markersLayer)
259
  }
260
261
  markersLayer = L.layerGroup()
262
263
  Object.keys(data).forEach(key_ => {
264
    for (var key in data[key_]) {
265
      const { x, y, name } = data[key_][key]
266
      const pop =
267 9a772066 Tomáš
        prepareLayerPopUp(x, y, 1, `popup-${key_}`)
268
          .setContent(getPopupContent(datasetDictNameDisplayName[key_], name, 0, 0, 1, 1))
269 c8718599 Martin Sebela
      const newCircle =
270
        L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
271
          .bindPopup(pop)
272
      globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
273
      markersLayer.addLayer(
274
        newCircle
275
      )
276 815159f3 Tomáš Ballák
    }
277
  })
278 c8718599 Martin Sebela
279
  markersLayer.setZIndex(-1).addTo(mymap)
280 815159f3 Tomáš Ballák
}
281
282 c8718599 Martin Sebela
283
284
285
/* ------------ GUI ------------ */
286
287
const changeCurrentTime = (time = null) => {
288
  if (time !== null) {
289
    currentTime = time
290
  } else {
291
    currentTime = parseInt($('#dropdown-time input[type="radio"]:checked').val())
292 1cf1413d ballakt
  }
293
}
294 bb2d43b5 Martin Sebela
295 c8718599 Martin Sebela
const changeCurrentDate = (date = null) => {
296
  const dateInput = $('#date')
297
  currentDate = new Date(date ? date : dateInput.val())
298 bb2d43b5 Martin Sebela
299 c8718599 Martin Sebela
  dateInput.val(currentDateToString())
300
  $('#player-date span').html(`${currentDate.getDate()}. ${currentDate.getMonth() + 1}. ${currentDate.getFullYear()}`)
301 bb2d43b5 Martin Sebela
302 c8718599 Martin Sebela
  data = []
303 1cf1413d ballakt
}
304 bb2d43b5 Martin Sebela
305 c8718599 Martin Sebela
const toggleDayLock = () => {
306
  lockedDay = !lockedDay
307
  $('#player-date').toggleClass('lock')
308
}
309 72a438f3 vastja
310 3fc08f2d vastja
311 3ae59f75 vastja
312 c8718599 Martin Sebela
313 ab904bf5 Martin Sebela
/* ------------ POPUPS ------------ */
314 c8718599 Martin Sebela
315
const setGlobalPopupContent = (content) => {
316
  globalPopup._popup.setContent(content)
317
  globalPopup._popup.openOn(mymap)
318
}
319
320
const getPaginationButtonsInPopup = (currentPage, countPages) => ({
321 9a772066 Tomáš
  previousButton: '<button type="button" id="btn-popup-previous-page" onclick="js.setPreviousPageInPopup()"></button>',
322 6b7c4d1c Martin Sebela
  pagesList: `<p id="pages">${currentPage} z ${countPages}</p>`,
323 9a772066 Tomáš
  nextButton: '<button type="button" id="btn-popup-next-page" class="next" onclick="js.setNextPageInPopup()"></button>'
324 c8718599 Martin Sebela
})
325
326
const disablePopupPaginationButtons = () => {
327 6b7c4d1c Martin Sebela
  $('#btn-popup-previous-page').prop('disabled', true)
328
  $('#btn-popup-next-page').prop('disabled', true)
329
  $('.popup-pagination').hide()
330 c8718599 Martin Sebela
}
331
332
const generatePopupPaginationButtons = (controls) => {
333 6b7c4d1c Martin Sebela
  return `<div class="popup-pagination">${controls ? controls.reduce((sum, item) => sum + item, '') : ''}</div>`
334 c236b33a msebela
}
335 c892003d Martin Sebela
336 1a1d8f64 Martin Sebela
const getCountPagesInPopup = () => {
337 2f227a6c ballakt
  const infoKeys = Object.keys(info)
338 894e3339 Martin Sebela
339 2f227a6c ballakt
  if (infoKeys.length === 1) {
340
    // return number of records in one dataset (one dataset in area)
341
    return info[infoKeys[0]].items.length
342
  }
343
  // return number of datasets (agregation of all datasets in area)
344
  return infoKeys.length
345
}
346 c892003d Martin Sebela
347 894e3339 Martin Sebela
const getPopupDataOnPage = (pageInPopup) => {
348 2f227a6c ballakt
  const keys = Object.keys(info)
349 894e3339 Martin Sebela
  return info[keys[pageInPopup]]
350 2f227a6c ballakt
}
351 c892003d Martin Sebela
352 9a772066 Tomáš
const setPreviousPageInPopup = () => {
353 c8718599 Martin Sebela
  const countPagesInPopup = getCountPagesInPopup()
354
  const page = currentPageInPopup
355
356
  currentPageInPopup = (currentPageInPopup + countPagesInPopup - 1) % countPagesInPopup
357
  setPageContentInPopup(page)
358
}
359
360 9a772066 Tomáš
const setNextPageInPopup = () => {
361 c8718599 Martin Sebela
  const countPagesInPopup = getCountPagesInPopup()
362
  const page = currentPageInPopup
363
364
  currentPageInPopup = (currentPageInPopup + 1) % countPagesInPopup
365
  setPageContentInPopup(page)
366
}
367
368
const genMultipleDatasetsPopUp = (sum, currentPage, countPages, datasetName) => {
369
  const popupHeader = `<strong id="dataset-name">${datasetName}</strong>`
370
  const popupData = `<div id="place-intesity"><span id="current-number">${sum}</span></div>`
371
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
372
373
  return `
374
  ${popupHeader}
375
  ${popupData}
376
  ${generatePopupPaginationButtons([previousButton, pagesList, nextButton])}
377
  `
378
}
379
380
const prepareLayerPopUp = (lat, lng, num, className) => L.popup({
381
  autoPan: false,
382
  className: className
383
}).setLatLng([lat / num, lng / num])
384
385
const getPopupContent = (datasetName, placeName, currentCount, sum, currentPage, countPages) => {
386
  const popupHeader = `
387
    <strong>${datasetName}</strong>
388
    <div id="place-name">${placeName}</div>`
389
  const popupData = `
390
    <div id="place-intesity">
391
      <span id="current-number">${currentCount}</span>
392 6b7c4d1c Martin Sebela
      <span id="sum-number">${(sum && sum !== Number(currentCount)) ? '/' + sum : ''}</span>
393 c8718599 Martin Sebela
    </div>`
394
  const { previousButton, nextButton, pagesList } = getPaginationButtonsInPopup(currentPage, countPages)
395
396
  return `
397
  ${popupHeader}
398
  ${popupData}
399
  ${generatePopupPaginationButtons(countPages > 1 ? [previousButton, pagesList, nextButton] : null)}
400
  `
401
}
402
403 894e3339 Martin Sebela
const areMultipleDatasetsInRadius = () => {
404 2f227a6c ballakt
  return Object.keys(info).length > 1
405
}
406 c892003d Martin Sebela
407 894e3339 Martin Sebela
const setPopupDatasetClassName = (datasetName) => {
408 2c2cad8d Martin Sebela
  const popup = $('.leaflet-popup')
409
410
  popup.removeClass(function (index, css) {
411 894e3339 Martin Sebela
    return (css.match(/(^|\s)popup-\S+/g) || []).join(' ');
412 2c2cad8d Martin Sebela
  })
413 894e3339 Martin Sebela
  popup.addClass('popup-' + datasetName)
414 2c2cad8d Martin Sebela
}
415
416 ab904bf5 Martin Sebela
const showPopup = (e) => {
417 3ae59f75 vastja
  info = []
418 1a1d8f64 Martin Sebela
  currentPageInPopup = 0
419 3ae59f75 vastja
420
  // https://wiki.openstreetmap.org/wiki/Zoom_levels
421
  // Todo change to variable - it is used in heatmap init
422 2f227a6c ballakt
  const stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
423
  const radius = 25 * stile / 256
424
425
  let i = 0
426
  let lat = 0
427
  let lng = 0
428
429
  let total = 0
430
431
  const datasetsInRadius = {}
432 e6097215 ballakt
  const eventCoord = {
433
    lng: e.latlng.lng,
434
    lat: e.latlng.lat
435
  }
436 894e3339 Martin Sebela
437
  Object.keys(data[currentTime]).forEach(key => {
438 2f227a6c ballakt
    const namedData = data[currentTime][key]
439 894e3339 Martin Sebela
440 2f227a6c ballakt
    namedData.items.forEach(element => {
441
      if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
442
        lat += element.x
443
        lng += element.y
444
        info[i] = { place: element.place, number: element.number, datasetName: key }
445
        total += parseInt(element.number)
446
        i++
447
        datasetsInRadius[key] = true
448
      }
449
    })
450 8feb1753 ballakt
  })
451 1a1d8f64 Martin Sebela
452 2f227a6c ballakt
  // Process info for more then one dataset
453
  info = info.reduce((acc, item) => {
454
    if (!acc[item.datasetName]) {
455
      acc[item.datasetName] = {
456
        items: [],
457
        number: 0,
458
        datasetName: item.datasetName
459
      }
460
    }
461 c892003d Martin Sebela
462 2f227a6c ballakt
    acc[item.datasetName].items.push(item)
463
    acc[item.datasetName].number += Number(item.number)
464
    return acc
465
  }, {})
466
467 894e3339 Martin Sebela
  const countDatasets = Object.keys(datasetsInRadius).length
468 1a1d8f64 Martin Sebela
469 894e3339 Martin Sebela
  if (!countDatasets) {
470 e6097215 ballakt
    if (mymap._popup) {
471 6b7c4d1c Martin Sebela
      $('#sum-number').text('')
472 e6097215 ballakt
      $('#current-number').html(0)
473 c8718599 Martin Sebela
      disablePopupPaginationButtons()
474 e6097215 ballakt
    }
475
476 d5a88af0 Tomáš Ballák
    return
477
  }
478 1a1d8f64 Martin Sebela
479 894e3339 Martin Sebela
  if (countDatasets === 1) {
480
    const markersInRadius = getPopupDataOnPage(0)
481
    const popupPagesData = markersInRadius.items
482
    const { place, number } = popupPagesData[currentPageInPopup]
483 e6097215 ballakt
484 894e3339 Martin Sebela
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
485
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${markersInRadius.datasetName}`)
486 e6097215 ballakt
      globalPopup.coord = eventCoord
487
    }
488 2c2cad8d Martin Sebela
    else {
489 894e3339 Martin Sebela
      setPopupDatasetClassName(markersInRadius.datasetName)
490 2c2cad8d Martin Sebela
    }
491 e6097215 ballakt
492 894e3339 Martin Sebela
    setGlobalPopupContent(getPopupContent(datasetDictNameDisplayName[markersInRadius.datasetName], place, number, total, 1, popupPagesData.length))
493 c892003d Martin Sebela
494 894e3339 Martin Sebela
    if (popupPagesData.length === 1) {
495 c8718599 Martin Sebela
      disablePopupPaginationButtons()
496 3ae59f75 vastja
    }
497 1774c06d Tomáš Ballák
  } else {
498 894e3339 Martin Sebela
    const { datasetName, number } = getPopupDataOnPage(currentPageInPopup)
499 c892003d Martin Sebela
500 894e3339 Martin Sebela
    if (!globalPopup._popup || !areCoordsIdentical(globalPopup.coord, eventCoord)) {
501 e6097215 ballakt
      globalPopup._popup = prepareLayerPopUp(lat, lng, i, `popup-${datasetName}`)
502
      globalPopup.coord = eventCoord
503
    }
504 2c2cad8d Martin Sebela
    else {
505 894e3339 Martin Sebela
      setPopupDatasetClassName(datasetName)
506 2c2cad8d Martin Sebela
    }
507 e6097215 ballakt
508 1a1d8f64 Martin Sebela
    setGlobalPopupContent(genMultipleDatasetsPopUp(number, 1, getCountPagesInPopup(), datasetDictNameDisplayName[datasetName]))
509 3ae59f75 vastja
  }
510
}
511
512 9a772066 Tomáš
const setPageContentInPopup = (page) => {
513 894e3339 Martin Sebela
  const previousPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(page) : getPopupDataOnPage(0).items[page]
514
  const currentPageData = areMultipleDatasetsInRadius() ? getPopupDataOnPage(currentPageInPopup) : getPopupDataOnPage(0).items[currentPageInPopup]
515 1a1d8f64 Martin Sebela
  const datasetName = $('#dataset-name')
516 c892003d Martin Sebela
517 1a1d8f64 Martin Sebela
  if (datasetName) {
518
    datasetName.html(datasetDictNameDisplayName[currentPageData.datasetName])
519 883a423e Tomáš Ballák
  }
520 1774c06d Tomáš Ballák
521 1a1d8f64 Martin Sebela
  $('#place-name').html(currentPageData.place ? currentPageData.place : currentPageData.datasetName)
522
  $('#current-number').html(currentPageData.number)
523
  $('#pages').html(currentPageInPopup + 1 + ' z ' + getCountPagesInPopup())
524 c892003d Martin Sebela
525 1a1d8f64 Martin Sebela
  $('.leaflet-popup').removeClass(`popup-${previousPageData.datasetName}`).addClass(`popup-${currentPageData.datasetName}`)
526 3ae59f75 vastja
}
527 351696d5 Martin Sebela
528 c8718599 Martin Sebela
const updatePopup = () => {
529
  const { _popup } = mymap
530 1a1d8f64 Martin Sebela
531 c8718599 Martin Sebela
  if (_popup) {
532 ab904bf5 Martin Sebela
    showPopup({
533 c8718599 Martin Sebela
      latlng: _popup.getLatLng()
534
    })
535
  }
536 3fc08f2d vastja
}
537
538 c8718599 Martin Sebela
539
540
541
/* ------------ ANIMATION ------------ */
542
543 70a3df53 vastja
/**
544
 * Change animation start from playing to stopped or the other way round
545
 */
546 9a772066 Tomáš
const changeAnimationState = () => {
547 1a1d8f64 Martin Sebela
  const btnAnimate = $('#animate-btn')
548
549 a48642fb vastja
  isAnimationRunning = !isAnimationRunning
550 c892003d Martin Sebela
551 a48642fb vastja
  if (isAnimationRunning) {
552 1a1d8f64 Martin Sebela
    btnAnimate.removeClass('play').addClass('pause')
553 1774c06d Tomáš Ballák
    timer = setInterval(function () { next() }, 800)
554
  } else {
555 8feb1753 ballakt
    clearTimeout(timer)
556 1a1d8f64 Martin Sebela
    btnAnimate.removeClass('pause').addClass('play')
557 351696d5 Martin Sebela
  }
558
}
559
560 9a772066 Tomáš
const previous = async () => {
561 1774c06d Tomáš Ballák
  if (loading) {
562
    return
563
  }
564 ec5e3220 Martin Sebela
565 8feb1753 ballakt
  currentTime = (currentTime + 23) % 24
566 1774c06d Tomáš Ballák
  changeHour(currentTime)
567 9a772066 Tomáš
568 1a1d8f64 Martin Sebela
  if (!lockedDay && currentTime === 23) {
569 1774c06d Tomáš Ballák
    addDayToCurrentDate(-1)
570
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
571
  } else {
572
    drawHeatmap(data[currentTime])
573
  }
574 1a1d8f64 Martin Sebela
575 e6097215 ballakt
  updatePopup()
576 a48642fb vastja
}
577
578 9a772066 Tomáš
const next = async () => {
579 1774c06d Tomáš Ballák
  if (loading) {
580
    return
581
  }
582 ec5e3220 Martin Sebela
583 8feb1753 ballakt
  currentTime = (currentTime + 1) % 24
584 1774c06d Tomáš Ballák
  changeHour(currentTime)
585 1a1d8f64 Martin Sebela
586
  if (!lockedDay && currentTime === 0) {
587 1774c06d Tomáš Ballák
    addDayToCurrentDate(1)
588
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute)
589
  } else {
590
    drawHeatmap(data[currentTime])
591
  }
592 1a1d8f64 Martin Sebela
593 e6097215 ballakt
  updatePopup()
594 8b840eb7 vastja
}
595 c892003d Martin Sebela
596 1774c06d Tomáš Ballák
const onChangeHour = (hour) => {
597
  changeHour(hour)
598
  drawHeatmap(data[currentTime])
599
}
600 863ca316 Martin Sebela
601 1774c06d Tomáš Ballák
const changeHour = (hour) => {
602 c8718599 Martin Sebela
  $('#player-time').removeAttr('style')
603
  changeCurrentTime(hour)
604
  updateHeaderControls()
605
  setTimeline()
606
  changeUrlParameters()
607
  updatePopup()
608 3fc08f2d vastja
}
609
610 c8718599 Martin Sebela
const dragTimeline = () => {
611
  const hourElemWidthPx = 26
612 1774c06d Tomáš Ballák
613 c8718599 Martin Sebela
  const elem = $('#player-time')
614
  const offset = elem.offset().left - elem.parent().offset().left
615 c892003d Martin Sebela
616 c8718599 Martin Sebela
  if (offset >= 0 && offset <= elem.parent().width()) {
617
    const hour = Math.round(offset / hourElemWidthPx)
618 c892003d Martin Sebela
619 c8718599 Martin Sebela
    if (hour !== currentTime) {
620
      elem.attr('class', 'time hour-' + hour)
621
      $('#player-time span').html(formatTime(hour))
622 c892003d Martin Sebela
623 c8718599 Martin Sebela
      onChangeHour(hour)
624 a48642fb vastja
    }
625
  }
626 c8718599 Martin Sebela
}
627 ac154afa Martin Sebela
628 c8718599 Martin Sebela
const onArrowLeftRightKeysDownRegister = () => {
629
  $(document).keydown(function (e) {
630
    const { which } = e
631 9a772066 Tomáš
632 c8718599 Martin Sebela
    if (which === arrowKeyLEFT) {
633
      previous()
634
      e.preventDefault()
635
    } else if (which === arrowKeyRIGHT) {
636
      next()
637
      e.preventDefault()
638
    }
639
  })
640 03c02899 vastja
}
641 3fc08f2d vastja
642 70a3df53 vastja
/**
643 c8718599 Martin Sebela
 * Change browser url based on animation step.
644 70a3df53 vastja
 */
645 c8718599 Martin Sebela
const changeUrlParameters = () => {
646
  window.history.pushState(
647
    '',
648
    document.title,
649
    window.location.origin + window.location.pathname + `?date=${currentDateToString()}&time=${currentTime}${datasetSelected.reduce((acc, current) => acc + '&type[]=' + current, '')}`
650
  )
651 03c02899 vastja
}
652
653 c892003d Martin Sebela
654
655 dfe43218 vastja
656 c8718599 Martin Sebela
/* ------------ UTILS ------------ */
657
658
const formatTime = (hours, twoDigitsHours = false) => {
659
  return ((twoDigitsHours && hours < 10) ? '0' : '') + hours + ':00';
660 03c02899 vastja
}
661 0a828a5a Martin Sebela
662 c8718599 Martin Sebela
const formatDate = (date) => {
663 8feb1753 ballakt
  var day = String(date.getDate())
664
  var month = String(date.getMonth() + 1)
665 0a828a5a Martin Sebela
666
  if (day.length === 1) {
667 8feb1753 ballakt
    day = '0' + day
668 0a828a5a Martin Sebela
  }
669
670
  if (month.length === 1) {
671 8feb1753 ballakt
    month = '0' + month
672 0a828a5a Martin Sebela
  }
673
674 c8718599 Martin Sebela
  // return YYYY-MM-DD
675 8feb1753 ballakt
  return date.getFullYear() + '-' + month + '-' + day
676 0a828a5a Martin Sebela
}
677
678 c8718599 Martin Sebela
const currentDayToString = () => {
679
  const day = currentDate.getDate()
680
  return day > 9 ? `${day}` : `0${day}`
681
}
682
683
const currentMonthToString = () => {
684
  const month = currentDate.getMonth() + 1
685
  return month > 9 ? `${month}` : `0${month}`
686
}
687
688
const currentDateToString = () => `${currentDate.getFullYear()}-${currentMonthToString()}-${currentDayToString()}`
689
690
const addDayToCurrentDate = (day) => {
691
  currentDate.setDate(currentDate.getDate() + day)
692
  changeCurrentDate(currentDate)
693
}
694
695
const areCoordsIdentical = (first, second) => {
696
  return first.lat === second.lat && first.lng === second.lng
697
}
698
699
const debounce = (func, delay) => {
700
  let inDebounce
701
  return function () {
702
    const context = this
703
    const args = arguments
704
    clearTimeout(inDebounce)
705
    inDebounce = setTimeout(() => func.apply(context, args), delay)
706
  }
707
}
708
709
710
711
712
/* ------------ GUI ------------ */
713
714
const updateHeaderControls = () => {
715
  $(`#time_${currentTime}`).prop('checked', true)
716
  $('#dropdownMenuButtonTime').html(formatTime(currentTime, true))
717
}
718
719 bf634c97 Tomáš
const initDatepicker = async (availableDatesSource) => {
720
  const result = await genericFetch(availableDatesSource, 'GET')
721 ab904bf5 Martin Sebela
  const datesContainData = String(result).split(',')
722
723 bf634c97 Tomáš
  $('#date').datepicker({
724
    format: 'yyyy-mm-dd',
725
    language: 'cs',
726
    beforeShowDay: function (date) {
727 ab904bf5 Martin Sebela
      if (datesContainData.indexOf(formatDate(date)) < 0) {
728 bf634c97 Tomáš
        return { enabled: false, tooltip: 'Žádná data' }
729
      } else {
730
        return { enabled: true }
731
      }
732
    },
733
    autoclose: true
734 8feb1753 ballakt
  })
735
}
736 dd652e61 Martin Sebela
737 9a772066 Tomáš
const initLocationsMenu = () => {
738 1a1d8f64 Martin Sebela
  const elmLocationsList = $('.locations')
739
  const locationsDisplayClass = 'show'
740 dd652e61 Martin Sebela
741 81980e82 ballakt
  if ($(window).width() <= 480) {
742 1a1d8f64 Martin Sebela
    elmLocationsList.removeClass(locationsDisplayClass)
743 1774c06d Tomáš Ballák
  } else {
744 1a1d8f64 Martin Sebela
    elmLocationsList.addClass(locationsDisplayClass)
745 dd652e61 Martin Sebela
  }
746 4e003182 Martin Sebela
}
747
748 c8718599 Martin Sebela
const setTimeline = () => {
749
  $('#player-time > span').text(formatTime(currentTime))
750
  $('#player-time').attr('class', 'time hour-' + currentTime)
751
}
752
753
const onValueChangeRegister = () => {
754
  $('#date').change(function () {
755
    changeCurrentDate($(this).val())
756
    loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
757
    changeUrlParameters()
758 2f227a6c ballakt
  })
759 1cf1413d ballakt
760 c8718599 Martin Sebela
  $('#dropdown-time input[type="radio"]').each(function () {
761
    $(this).change(function () {
762
      changeHour(parseInt($(this).val()))
763
      drawHeatmap(data[currentTime])
764
    })
765
  })
766
767
  $('#dropdown-dataset input[type="checkbox"]').each(function () {
768
    $(this).change(
769
      debounce(() => onCheckboxClicked(this), 1000)
770
    )
771
  })
772
}
773
774
const onCheckboxClicked = async (checkbox) => {
775
  if ($(checkbox).prop('checked')) {
776
    await loadCurrentTimeHeatmap(dataSourceRoute, positionsSourceRoute, 0)
777
  } else {
778
    loadCheckboxDatasetNameData()
779
780
    data.forEach((item, index) => {
781
      Object.keys(item).forEach(datasetName => {
782
        if (datasetName === $(checkbox).val()) {
783
          delete data[index][datasetName]
784
        }
785
      })
786
787
      drawHeatmap(data[currentTime])
788
    })
789
  }
790
791
  updatePopup()
792
  changeUrlParameters()
793 1cf1413d ballakt
}
794 c892003d Martin Sebela
795 1cf1413d ballakt
const loadCheckboxDatasetNameData = () => {
796 2f227a6c ballakt
  datasetSelected = []
797 1a1d8f64 Martin Sebela
798 c892003d Martin Sebela
  $('#dropdown-dataset .dropdown-item').each(function () {
799 2f227a6c ballakt
    const input = $(this).find('input')
800
    const inputVal = input[0].value
801 c892003d Martin Sebela
802 2f227a6c ballakt
    if (input[0].checked) {
803
      datasetSelected.push(inputVal)
804
    }
805 c892003d Martin Sebela
806 883a423e Tomáš Ballák
    datasetDictNameDisplayName[inputVal] = $(input).data('dataset-display-name')
807 2f227a6c ballakt
  })
808
}
809 ec5e3220 Martin Sebela
810 9a772066 Tomáš
const updateAvailableDataSets = async (available) => {
811 c8718599 Martin Sebela
  let leastOneOptionEnabled = false
812 ec5e3220 Martin Sebela
813 c8718599 Martin Sebela
  $('#dropdown-dataset .dropdown-item').each(function () {
814
    const input = $(this).find('input')
815 ec5e3220 Martin Sebela
816 c8718599 Martin Sebela
    if (!(input[0].value in available)) {
817
      $(this).addClass('disabled')
818
      $(input).prop('checked', false)
819
    } else {
820
      leastOneOptionEnabled = true
821
      $(this).removeClass('disabled')
822
    }
823
  })
824 815159f3 Tomáš Ballák
825 c8718599 Martin Sebela
  $('#btn-update-heatmap').prop('disabled', !leastOneOptionEnabled)
826
}
827 ec5e3220 Martin Sebela
828 c8718599 Martin Sebela
829
830
831
/* ------------ GUI LOADING ------------ */
832
833
const loadingCallbackNested = (func, delay) => {
834
  setTimeout(() => {
835
    func(loading)
836
    if (loading) {
837
      loadingCallbackNested(func, delay)
838 fdba469a Martin Sebela
    }
839 c8718599 Martin Sebela
  }, delay)
840
}
841
842
const loadingY = (delay = defaultLoaderDelay) => {
843
  loading++
844
  // check after nms if there is something that is loading
845
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading), delay))
846
}
847
848
const loadingN = (delay = defaultLoaderDelay) => {
849
  loading--
850
  loadingCallbackNested(() => loadingCallbackNested((isLoading) => loadingTimeline(isLoading)), delay)
851
}
852
853
const loadingTimeline = (isLoading) => {
854
  if (isLoading) {
855
    loadingYTimeline()
856
  } else {
857
    loadingNTimeline()
858 ec5e3220 Martin Sebela
  }
859 815159f3 Tomáš Ballák
}
860 c8718599 Martin Sebela
861
const loadingYTimeline = () => {
862
  $('#player-time > .spinner-border').removeClass('d-none')
863
  $('#player-time > span').text('')
864
}
865
866
const loadingNTimeline = () => {
867
  $('#player-time > .spinner-border').addClass('d-none')
868
  setTimeline()
869 9a772066 Tomáš
}
870 ab904bf5 Martin Sebela
871 9a772066 Tomáš
module.exports = {
872
  initDatepicker,
873
  initLocationsMenu,
874
  initMap,
875
  onDocumentReady,
876
  checkDataSetsAvailability,
877
  loadCurrentTimeHeatmap,
878
  dragTimeline,
879
  setPreviousPageInPopup,
880
  setNextPageInPopup,
881
  previous,
882
  next,
883
  toggleDayLock,
884
  changeAnimationState,
885
  onChangeHour,
886
  setMapView
887 c8718599 Martin Sebela
}