Projekt

Obecné

Profil

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