Projekt

Obecné

Profil

Stáhnout (9.69 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
// holds all instances of markers for bind/unbind popup purpose
21
// contains: {key:[L.circle,L.pupup]}
22
// key: x and y, x + '' + y string
23
const globalMarkersHolder = {}
24

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

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

    
42
  if (!sum) {
43
    previousButton = ''
44
    nextButton = ''
45
    posInfo = ''
46
  }
47
  return `
48
  ${header}
49
  ${digitInfo}
50
  ${genPopUpControls([previousButton, posInfo, nextButton])}
51
  `
52
}
53
// eslint-disable-next-line no-unused-vars
54
function initMap () {
55

    
56
  startX = localStorage.getItem('lat') || startX;
57
  startY = localStorage.getItem('lng') || startY;
58
  startZoom = localStorage.getItem('zoom') || startZoom;
59

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

    
62
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
63
    attribution: '',
64
    maxZoom: 19
65
  }).addTo(mymap)
66

    
67
  mymap.on('click', showInfo)
68
}
69

    
70
var info = []
71
var currenInfo = 0
72

    
73
function showInfo (e) {
74
  info = []
75
  currenInfo = 0
76

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

    
82
  var i = 0
83
  var lat = 0
84
  var lng = 0
85

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

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

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

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

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

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

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

    
140
// eslint-disable-next-line no-unused-vars
141
function changeAnimationState () {
142
  isAnimationRunning = !isAnimationRunning
143
  if (isAnimationRunning) {
144
    $('#play-pause').attr('class', 'pause')
145
    timer = setInterval(
146
      function () {
147
        next()
148
      },
149
      800
150
    )
151
  } else {
152
    clearTimeout(timer)
153
    $('#play-pause').attr('class', 'play')
154
  }
155
}
156

    
157
// eslint-disable-next-line no-unused-vars
158
function previous () {
159
  currentTime = (currentTime + 23) % 24
160
  drawHeatmap(data[currentTime])
161
  setTimeline()
162
  mymap.closePopup()
163
  updateHeaderControls()
164
  changeUrl()
165
}
166

    
167
function next () {
168
  currentTime = (currentTime + 1) % 24
169
  drawHeatmap(data[currentTime])
170
  setTimeline()
171
  mymap.closePopup()
172
  updateHeaderControls()
173
  changeUrl()
174
}
175

    
176
function changeUrl () {
177
  window.history.pushState(
178
    '',
179
    document.title,
180
    window.location.origin + window.location.pathname + `?data_set[date]=${$('#date').val()}&data_set[time]=${currentTime}&data_set[type]=${$('#type').children('option:selected').val()}`
181
  )
182
}
183

    
184
function updateHeaderControls () {
185
  document.getElementById('time').value = currentTime
186
}
187

    
188
function setTimeline () {
189
  $('#timeline').text(currentTime + ':00')
190
  $('#timeline').attr('class', 'time hour-' + currentTime)
191
}
192

    
193
// eslint-disable-next-line no-unused-vars
194
function loadCurrentTimeHeatmap (opendataRoute, positionsRoute) {
195
  dataSourceRoute = opendataRoute
196
  data = []
197

    
198
  name = $('#type').children('option:selected').val()
199
  date = $('#date').val()
200
  currentTime = parseInt($('#time').children('option:selected').val())
201
  setTimeline()
202
  $.ajax({
203
    type: 'POST',
204
    url: positionsRoute + '/' + name,
205
    success: function (result) {
206
      drawDataSourceMarks(result)
207
      $.ajax({
208
        type: 'POST',
209
        url: dataSourceRoute + '/' + name + '/' + date + '/' + currentTime,
210
        success: function (result) {
211
          data[currentTime] = result
212
          drawHeatmap(data[currentTime])
213
        }
214
      })
215
    }
216
  })
217

    
218
  preload(currentTime, 1)
219
  preload(currentTime, -1)
220
}
221

    
222
function drawDataSourceMarks (data) {
223
  if (marksLayer != null) {
224
    L.removeLayer(marksLayer)
225
  }
226
  marksLayer = L.layerGroup()
227
  for (var key in data) {
228
    const { x, y, name } = data[key]
229
    const pop =
230
      L.popup({ autoPan: false })
231
        .setLatLng([x, y])
232
        .setContent(genPopUp(name, 0, 0, 1, 1))
233
    const newCircle =
234
      L.circle([x, y], { radius: 2, fillOpacity: 0.8, color: '#004fb3', fillColor: '#004fb3', bubblingMouseEvents: true })
235
        .bindPopup(pop)
236
    globalMarkersHolder[x + '' + y] = [newCircle, pop] // add new marker to global holders
237
    marksLayer.addLayer(
238
      newCircle
239
    )
240
  }
241

    
242
  marksLayer.setZIndex(-1).addTo(mymap)
243
}
244

    
245
function preload (time, change) {
246
  var ntime = time + change
247
  if (ntime >= 0 && ntime <= 23) {
248
    $.ajax({
249
      type: 'POST',
250
      url: dataSourceRoute + '/' + name + '/' + date + '/' + ntime,
251
      success: function (result) {
252
        data[ntime] = result
253
        preload(ntime, change)
254
      }
255
    })
256
  }
257
}
258

    
259
function drawHeatmap (data) {
260
  // Todo still switched
261
  if (data.items != null) {
262
    // Bind back popups for markers (we dont know if there is any data for this marker or not)
263
    if (Object.keys(globalMarkersChanged).length) {
264
      Object.keys(globalMarkersChanged).forEach(function (key) {
265
        globalMarkersChanged[key][0].bindPopup(globalMarkersChanged[key][1])
266
      })
267
      globalMarkersChanged = {}
268
    }
269
    const points = data.items.map((point) => {
270
      const { x, y, number } = point
271
      const key = x + '' + y
272
      const holder = globalMarkersHolder[key]
273
      if (!globalMarkersChanged[key] && number) {
274
        // There is data for this marker => unbind popup with zero value
275
        holder[0] = holder[0].unbindPopup()
276
        globalMarkersChanged[key] = holder
277
      }
278
      return [x, y, number]
279
    })
280
    if (heatmapLayer != null) {
281
      mymap.removeLayer(heatmapLayer)
282
    }
283
    heatmapLayer = L.heatLayer(points, { max: data.max, minOpacity: 0.5, radius: 35, blur: 30 }).addTo(mymap)
284
  } else {
285
    if (heatmapLayer != null) {
286
      mymap.removeLayer(heatmapLayer)
287
    }
288
  }
289

    
290
  // var heat_01 = ...
291
  // on background map.addLayer(heat_01) -> map.removeLayer(heat_01);
292
  // $(.leaflet-heatmap-layer).css('opacity', 'value');
293
}
294

    
295
// eslint-disable-next-line no-unused-vars
296
function checkDataSetsAvailability (route) {
297
  $.ajax({
298
    type: 'POST',
299
    // Todo it might be good idea to change db collections format
300
    url: route + '/' + $('#date').val(),
301
    success: function (result) {
302
      updateAvailableDataSets(result)
303
    }
304
  })
305
}
306

    
307
var allOptionsDisabled = false
308

    
309
function updateAvailableDataSets (available) {
310
  var isOptionEnabled = true
311
  $('#type > option').each(function () {
312
    if ((this.value in available) === false) {
313
      $(this).prop('disabled', true)
314
      $(this).prop('selected', false)
315
    } else {
316
      $(this).prop('disabled', false)
317
      if (allOptionsDisabled) {
318
        $(this).prop('selected', true)
319
        allOptionsDisabled = false
320
      }
321
      isOptionEnabled = false
322
    }
323
  })
324
  allOptionsDisabled = isOptionEnabled
325

    
326
  $('#submit-btn').prop('disabled', isOptionEnabled)
327
}
328

    
329
function formatDate (date) {
330
  var day = String(date.getDate())
331
  var month = String(date.getMonth() + 1)
332

    
333
  if (day.length === 1) {
334
    day = '0' + day
335
  }
336

    
337
  if (month.length === 1) {
338
    month = '0' + month
339
  }
340

    
341
  return date.getFullYear() + '-' + month + '-' + day
342
}
343

    
344
// eslint-disable-next-line no-unused-vars
345
function initDatepicker (availableDatesSource) {
346
  var availableDates = ''
347

    
348
  $.ajax({
349
    type: 'GET',
350
    url: availableDatesSource,
351
    success: function (result) {
352
      availableDates = String(result).split(',')
353
    }
354
  }).then(function () {
355
    $('#date').datepicker({
356
      format: 'yyyy-mm-dd',
357
      language: 'cs',
358
      beforeShowDay: function (date) {
359
        if (availableDates.indexOf(formatDate(date)) < 0) {
360
          return { enabled: false, tooltip: 'Žádná data' }
361
        } else {
362
          return { enabled: true }
363
        }
364
      },
365
      autoclose: true
366
    })
367
  })
368
}
    (1-1/1)