Projekt

Obecné

Profil

Stáhnout (10.7 KB) Statistiky
| Větev: | Revize:
1
/* global L */
2
/* global $ */
3
var mymap
4
var heatmapLayer = null
5
var marksLayer = null
6

    
7
var startX = 49.7248
8
var startY = 13.3521
9
var startZoom = 17
10

    
11
var dataSourceRoute
12
var currentTime
13
var name
14
var date
15

    
16
var timer
17
var isAnimationRunning = false
18
var data = []
19

    
20
var info = []
21
var currenInfo = 0
22

    
23
// holds all instances of markers for bind/unbind popup purpose
24
// contains: {key:[L.circle,L.pupup]}
25
// key: x and y, x + '' + y string
26
const globalMarkersHolder = {}
27

    
28
// all marker from which popup was removed
29
// contains: {key:[L.circle,L.pupup]}
30
// key: x and y, x + '' + y string
31
let globalMarkersChanged = {}
32

    
33
const genPopUpControls = (controls) => {
34
  return `<div class="popup-controls">${controls.reduce((sum, item) => sum + item, '')}</div>`
35
}
36
const genPopUp = (place, number, sum, currentPos, maxPos) => {
37
  const header = `<strong>Zařízení a počet:</strong><div id="place-info">${place}</div>`
38
  const currentNum = `<span id="digit-info">${number}</span>`
39
  // eslint-disable-next-line eqeqeq
40
  const sumNum = `<span id="total-info" style="font-size: large">${(sum && (sum != number)) ? '/' + sum : ''}</span>`
41
  const digitInfo = `<div id="number-info">${currentNum}${sumNum}</div>`
42
  let previousButton = '<button id="previous-info-btn" class="circle-button" onclick="previousInfo()"></button>'
43
  let nextButton = '<button id="next-info-btn" onclick="nextInfo()" class="circle-button next"></button>'
44
  let posInfo = `<div id="count-info">${currentPos} z ${maxPos}</div>`
45

    
46
  if (!sum) {
47
    previousButton = ''
48
    nextButton = ''
49
    posInfo = ''
50
  }
51
  return `
52
  ${header}
53
  ${digitInfo}
54
  ${genPopUpControls([previousButton, posInfo, nextButton])}
55
  `
56
}
57
/**
58
 * Initialize leaflet map on start position which can be default or set based on user action
59
 */
60
// eslint-disable-next-line no-unused-vars
61
function initMap () {
62
  startX = localStorage.getItem('lat') || startX
63
  startY = localStorage.getItem('lng') || startY
64
  startZoom = localStorage.getItem('zoom') || startZoom
65

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

    
68
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
69
    attribution: '',
70
    maxZoom: 19
71
  }).addTo(mymap)
72

    
73
  mymap.on('click', showInfo)
74
}
75

    
76
function showInfo (e) {
77
  info = []
78
  currenInfo = 0
79

    
80
  // https://wiki.openstreetmap.org/wiki/Zoom_levels
81
  // Todo change to variable - it is used in heatmap init
82
  var stile = 40075016.686 * Math.cos(startX) / Math.pow(2, mymap.getZoom())
83
  var radius = 25 * stile / 256
84

    
85
  var i = 0
86
  var lat = 0
87
  var lng = 0
88

    
89
  var total = 0
90
  data[currentTime].items.forEach(element => {
91
    if (e.latlng.distanceTo(new L.LatLng(element.x, element.y)) < radius) {
92
      lat += element.x
93
      lng += element.y
94
      info[i] = { place: element.place, number: element.number }
95
      total += parseInt(element.number)
96
      i++
97
    }
98
  })
99

    
100
  if (info.length > 0) {
101
    const { place, number } = info[currenInfo]
102
    L.popup({
103
      autoPan: false
104
    })
105
      .setLatLng([lat / i, lng / i])
106
      .setContent(genPopUp(place, number, total, currenInfo + 1, info.length))
107
      .openOn(mymap)
108

    
109
    if (info.length === 1) {
110
      $('#previous-info-btn').prop('disabled', true)
111
      $('#next-info-btn').prop('disabled', true)
112
      $('.popup-controls').hide()
113
    }
114
  }
115
}
116

    
117
// eslint-disable-next-line no-unused-vars
118
function previousInfo () {
119
  currenInfo = (currenInfo + info.length - 1) % info.length
120
  displayInfoText()
121
}
122

    
123
// eslint-disable-next-line no-unused-vars
124
function nextInfo () {
125
  currenInfo = (currenInfo + 1) % info.length
126
  displayInfoText()
127
}
128

    
129
function displayInfoText () {
130
  $('#place-info').html(info[currenInfo].place)
131
  $('#digit-info').html(info[currenInfo].number)
132
  $('#count-info').html(currenInfo + 1 + ' z ' + info.length)
133
}
134

    
135
// eslint-disable-next-line no-unused-vars
136
function setMapView (latitude, longitude, zoom) {
137
  localStorage.setItem('lat', latitude)
138
  localStorage.setItem('lng', longitude)
139
  localStorage.setItem('zoom', zoom)
140
  mymap.setView([latitude, longitude], zoom)
141
}
142

    
143
/**
144
 * Change animation start from playing to stopped or the other way round
145
 */
146
// eslint-disable-next-line no-unused-vars
147
function changeAnimationState () {
148
  isAnimationRunning = !isAnimationRunning
149
  if (isAnimationRunning) {
150
    $('#play-pause').attr('class', 'pause')
151
    timer = setInterval(
152
      function () {
153
        next()
154
      },
155
      800
156
    )
157
  } else {
158
    clearTimeout(timer)
159
    $('#play-pause').attr('class', 'play')
160
  }
161
}
162

    
163
// eslint-disable-next-line no-unused-vars
164
function previous () {
165
  currentTime = (currentTime + 23) % 24
166
  drawHeatmap(data[currentTime])
167
  setTimeline()
168
  mymap.closePopup()
169
  updateHeaderControls()
170
  changeUrl()
171
}
172

    
173
function next () {
174
  currentTime = (currentTime + 1) % 24
175
  drawHeatmap(data[currentTime])
176
  setTimeline()
177
  mymap.closePopup()
178
  updateHeaderControls()
179
  changeUrl()
180
}
181

    
182
/**
183
 * Change browser url based on animation step
184
 */
185
function changeUrl () {
186
  window.history.pushState(
187
    '',
188
    document.title,
189
    window.location.origin + window.location.pathname + `?data_set[date]=${$('#date').val()}&data_set[time]=${currentTime}&data_set[type]=${$('#type').children('option:selected').val()}`
190
  )
191
}
192

    
193
function updateHeaderControls () {
194
  document.getElementById('time').value = currentTime
195
}
196

    
197
function setTimeline () {
198
  $('#timeline').text(currentTime + ':00')
199
  $('#timeline').attr('class', 'time hour-' + currentTime)
200
}
201

    
202
/**
203
 * Load and display heatmap layer for current data
204
 * @param {string} opendataRoute route to dataset source
205
 * @param {string} positionsRoute  route to dataset postitions source
206
 */
207
// eslint-disable-next-line no-unused-vars
208
function loadCurrentTimeHeatmap (opendataRoute, positionsRoute) {
209
  dataSourceRoute = opendataRoute
210
  data = []
211

    
212
  name = $('#type').children('option:selected').val()
213
  date = $('#date').val()
214
  currentTime = parseInt($('#time').children('option:selected').val())
215
  setTimeline()
216
  $.ajax({
217
    type: 'POST',
218
    url: positionsRoute + '/' + name,
219
    success: function (result) {
220
      drawDataSourceMarks(result)
221
      $.ajax({
222
        type: 'POST',
223
        url: dataSourceRoute + '/' + name + '/' + date + '/' + currentTime,
224
        success: function (result) {
225
          data[currentTime] = result
226
          drawHeatmap(data[currentTime])
227
        }
228
      })
229
    }
230
  })
231

    
232
  preload(currentTime, 1)
233
  preload(currentTime, -1)
234
}
235

    
236
function drawDataSourceMarks (data) {
237
  if (marksLayer != null) {
238
    L.removeLayer(marksLayer)
239
  }
240
  marksLayer = L.layerGroup()
241
  for (var key in data) {
242
    const { x, y, name } = data[key]
243
    const pop =
244
      L.popup({ autoPan: false })
245
        .setLatLng([x, y])
246
        .setContent(genPopUp(name, 0, 0, 1, 1))
247
    const newCircle =
248
      L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
249
        .bindPopup(pop)
250
    globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
251
    marksLayer.addLayer(
252
      newCircle
253
    )
254
  }
255

    
256
  marksLayer.setZIndex(-1).addTo(mymap)
257
}
258

    
259
function preload (time, change) {
260
  var ntime = time + change
261
  if (ntime >= 0 && ntime <= 23) {
262
    $.ajax({
263
      type: 'POST',
264
      url: dataSourceRoute + '/' + name + '/' + date + '/' + ntime,
265
      success: function (result) {
266
        data[ntime] = result
267
        preload(ntime, change)
268
      }
269
    })
270
  }
271
}
272

    
273
function drawHeatmap (data) {
274
  // Todo still switched
275
  if (data.items != null) {
276
    // Bind back popups for markers (we dont know if there is any data for this marker or not)
277
    if (Object.keys(globalMarkersChanged).length) {
278
      Object.keys(globalMarkersChanged).forEach(function (key) {
279
        globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
280
      })
281
      globalMarkersChanged = {}
282
    }
283
    const points = data.items.map((point) => {
284
      const { x, y, number } = point
285
      const key = x + '' + y
286
      const holder = globalMarkersHolder[key]
287
      if (!globalMarkersChanged[key] && number) {
288
        // There is data for this marker => unbind popup with zero value
289
        holder[0] = holder[0].unbindPopup()
290
        globalMarkersChanged[key] = holder
291
      }
292
      return [x, y, number]
293
    })
294
    if (heatmapLayer != null) {
295
      mymap.removeLayer(heatmapLayer)
296
    }
297
    heatmapLayer = L.heatLayer(points, { max: data.max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
298
  } else {
299
    if (heatmapLayer != null) {
300
      mymap.removeLayer(heatmapLayer)
301
    }
302
  }
303

    
304
  // var heat_01 = ...
305
  // on background map.addLayer(heat_01) -> map.removeLayer(heat_01);
306
  // $(.leaflet-heatmap-layer).css('opacity', 'value');
307
}
308

    
309
/**
310
 * Checks dataset availibility
311
 * @param {string} route authority for datasets availibility checks
312
 */
313
// eslint-disable-next-line no-unused-vars
314
function checkDataSetsAvailability (route) {
315
  $.ajax({
316
    type: 'POST',
317
    // Todo it might be good idea to change db collections format
318
    url: route + '/' + $('#date').val(),
319
    success: function (result) {
320
      updateAvailableDataSets(result)
321
    }
322
  })
323
}
324

    
325
var allOptionsDisabled = false
326

    
327
function updateAvailableDataSets (available) {
328
  var isOptionEnabled = true
329
  $('#type > option').each(function () {
330
    if ((this.value in available) === false) {
331
      $(this).prop('disabled', true)
332
      $(this).prop('selected', false)
333
    } else {
334
      $(this).prop('disabled', false)
335
      if (allOptionsDisabled) {
336
        $(this).prop('selected', true)
337
        allOptionsDisabled = false
338
      }
339
      isOptionEnabled = false
340
    }
341
  })
342
  allOptionsDisabled = isOptionEnabled
343

    
344
  $('#submit-btn').prop('disabled', isOptionEnabled)
345
}
346

    
347
function formatDate (date) {
348
  var day = String(date.getDate())
349
  var month = String(date.getMonth() + 1)
350

    
351
  if (day.length === 1) {
352
    day = '0' + day
353
  }
354

    
355
  if (month.length === 1) {
356
    month = '0' + month
357
  }
358

    
359
  return date.getFullYear() + '-' + month + '-' + day
360
}
361

    
362
// eslint-disable-next-line no-unused-vars
363
function initDatepicker (availableDatesSource) {
364
  var availableDates = ''
365

    
366
  $.ajax({
367
    type: 'GET',
368
    url: availableDatesSource,
369
    success: function (result) {
370
      availableDates = String(result).split(',')
371
    }
372
  }).then(function () {
373
    $('#date').datepicker({
374
      format: 'yyyy-mm-dd',
375
      language: 'cs',
376
      beforeShowDay: function (date) {
377
        if (availableDates.indexOf(formatDate(date)) < 0) {
378
          return { enabled: false, tooltip: 'Žádná data' }
379
        } else {
380
          return { enabled: true }
381
        }
382
      },
383
      autoclose: true
384
    })
385
  })
386
}
387

    
388
function initLocationsMenu () {
389
  var locationsWrapper = '.locations'
390
  var locationsDisplayClass = 'show'
391

    
392
  if ($(window).width() <= 480) {
393
    $(locationsWrapper).removeClass(locationsDisplayClass)
394
  } else {
395
    $(locationsWrapper).addClass(locationsDisplayClass)
396
  }
397
}
398

    
399
function openDatepicker () {
400
  if ($(window).width() <= 990) {
401
    $('.navbar-collapse').collapse()
402
  }
403

    
404
  $('#date').datepicker('show')
405
}
    (1-1/1)