Improved www.ttss.krakow.pl
Jacek Kowalski
2019-03-12 b1d3d61482c0084f2d181d5eb6849b36376f06f2
commit | author | age
f4a54f 1 "use strict";
JK 2
f36d1f 3 var ttss_refresh = 10000; // 10 seconds
a8a6d1 4 var ttss_position_type = 'CORRECTED';
57b8d3 5
a4d011 6 var geolocation = null;
JK 7 var geolocation_set = 0;
8 var geolocation_button = null;
9 var geolocation_feature = null;
10 var geolocation_accuracy = null;
11 var geolocation_source = null;
12 var geolocation_layer = null;
13
eafc1c 14 var trams_xhr = null;
JK 15 var trams_timer = null;
16 var trams_last_update = 0;
17 var trams_source = null;
18 var trams_layer = null;
19
20 var buses_xhr = null;
21 var buses_timer = null;
22 var buses_last_update = 0;
23 var buses_source = null;
24 var buses_layer = null;
25
f0bae0 26 var vehicles_info = {};
57b8d3 27
JK 28 var stops_xhr = null;
b1d3d6 29 var stops_ignored = ['131'];
f4a54f 30 var stops_buses_source = null;
JK 31 var stops_buses_layer = null;
32 var stops_trams_source = null;
33 var stops_trams_layer = null;
34 var stop_points_buses_source = null;
35 var stop_points_buses_layer = null;
36 var stop_points_trams_source = null;
37 var stop_points_trams_layer = null;
38
39 var stop_selected_source = null;
40 var stop_selected_layer = null;
57b8d3 41
1d4785 42 var feature_clicked = null;
8b6250 43 var feature_xhr = null;
JK 44 var feature_timer = null;
1d4785 45
JK 46 var route_source = null;
47 var route_layer = null;
07c714 48
57b8d3 49 var map = null;
d29c06 50
JK 51 var panel = null;
52
57b8d3 53 var fail_element = document.getElementById('fail');
a4d011 54 var fail_text = document.querySelector('#fail span');
57b8d3 55
7ca6a1 56 var ignore_hashchange = false;
JK 57
d29c06 58
JK 59 function Panel(element) {
60     this._element = element;
61     this._element.classList.add('panel');
62     
63     this._hide = addParaWithText(this._element, '▶');
64     this._hide.title = lang.action_collapse;
65     this._hide.className = 'hide';
66     this._hide.addEventListener('click', this.toggleExpanded.bind(this));
67     
68     this._close = addParaWithText(this._element, '×');
69     this._close.title = lang.action_close;
70     this._close.className = 'close';
71     this._close.addEventListener('click', this.close.bind(this));
72     
73     this._content = document.createElement('div');
74     this._element.appendChild(this._content);
75 };
76 Panel.prototype = {
77     _element: null,
78     _hide: null,
79     _close: null,
80     _content: null,
81     
82     _closeCallback: null,
83     _runCallback: function() {
84         var callback = this.closeCallback;
85         this.closeCallback = null;
86         if(callback) callback();
87     },
88     
89     expand: function() {
90         this._element.classList.add('expanded');
91         setText(this._hide, '▶');
92         this._hide.title = lang.action_collapse;
93     },
94     collapse: function() {
95         this._element.classList.remove('expanded');
96         setText(this._hide, '◀');
97         this._hide.title = lang.action_expand;
98     },
99     toggleExpanded: function() {
100         if(this._element.classList.contains('expanded')) {
101             this.collapse();
102         } else {
103             this.expand();
104         }
105     },
106     fail: function(message) {
107         addParaWithText(this._content, message).className = 'error';
108     },
109     show: function(contents, closeCallback) {
110         this._runCallback();
111         this.closeCallback = closeCallback;
112         
113         deleteChildren(this._content);
114         
115         this._content.appendChild(contents);
116         this._element.classList.add('enabled');
117         setTimeout(this.expand.bind(this), 1);
118     },
119     close: function() {
120         this._runCallback();
121         this._element.classList.remove('expanded');
122         this._element.classList.remove('enabled');
123     },
124 };
125
57b8d3 126 function fail(msg) {
a4d011 127     setText(fail_text, msg);
57b8d3 128     fail_element.style.top = '0.5em';
8b6250 129 }
JK 130
131 function fail_ajax_generic(data, fnc) {
57b8d3 132     // abort() is not a failure
JK 133     if(data.readyState == 0 && data.statusText == 'abort') return;
134     
135     if(data.status == 0) {
8b6250 136         fnc(lang.error_request_failed_connectivity, data);
57b8d3 137     } else if (data.statusText) {
8b6250 138         fnc(lang.error_request_failed_status.replace('$status', data.statusText), data);
57b8d3 139     } else {
8b6250 140         fnc(lang.error_request_failed, data);
57b8d3 141     }
8b6250 142 }
JK 143
144 function fail_ajax(data) {
145     fail_ajax_generic(data, fail);
146 }
147
148 function fail_ajax_popup(data) {
d29c06 149     fail_ajax_generic(data, panel.fail.bind(panel));
57b8d3 150 }
JK 151
152 function getGeometry(object) {
07c714 153     return new ol.geom.Point(ol.proj.fromLonLat([object.longitude / 3600000.0, object.latitude / 3600000.0]));
57b8d3 154 }
JK 155
1d4785 156 function styleVehicle(vehicle, selected) {
JK 157     var color_type = 'black';
158     if(vehicle.get('vehicle_type')) {
159         switch(vehicle.get('vehicle_type').low) {
125626 160             case 0:
1d4785 161                 color_type = 'orange';
JK 162             break;
125626 163             case 1:
JK 164             case 2:
1d4785 165                 color_type = 'green';
JK 166             break;
167         }
168     }
169     
3d2caa 170     var fill = '#B70';
6cb525 171     if(vehicle.getId().startsWith('b')) {
JK 172         fill = '#05B';
173     }
174     if(selected) {
175         fill = '#292';
176     }
1d4785 177     
3d2caa 178     var image = '<svg xmlns="http://www.w3.org/2000/svg" height="30" width="20"><polygon points="10,0 20,23 0,23" style="fill:'+fill+';stroke:'+color_type+';stroke-width:3" /></svg>';
1d4785 179     
f4a54f 180     vehicle.setStyle(new ol.style.Style({
1d4785 181         image: new ol.style.Icon({
JK 182             src: 'data:image/svg+xml;base64,' + btoa(image),
a8a6d1 183             rotation: Math.PI * parseFloat(vehicle.get('heading') ? vehicle.get('heading') : 0) / 180.0,
1d4785 184         }),
JK 185         text: new ol.style.Text({
186             font: 'bold 10px sans-serif',
187             text: vehicle.get('line'),
188             fill: new ol.style.Fill({color: 'white'}),
189         }),
f4a54f 190     }));
1d4785 191 }
JK 192
f4a54f 193 function markStops(stops, type, routeStyle) {
JK 194     stop_selected_source.clear();
ba6e87 195     
f4a54f 196     var style = stops_trams_layer.getStyle();
JK 197     if(type == 'b') {
198         style = stops_buses_layer.getStyle();
199     }
200     style = style.clone();
201     
202     if(routeStyle) {
203         style.getImage().setRadius(5);
204     } else {
205         style.getImage().getStroke().setWidth(2);
206         style.getImage().getStroke().setColor('#F00');
207         style.getImage().setRadius(5);
ba6e87 208     }
1d4785 209     
f4a54f 210     stop_selected_layer.setStyle(style);
JK 211     
212     var feature = null;
213     var prefix = null;
214     for(var i = 0; i < stops.length; i++) {
215         feature = null;
216         if(stops[i].getId) {
217             feature = stops[i];
218         } else {
219             prefix = stops[i].substr(0,2);
220             feature = null;
221             if(prefix == 'sb') {
222                 feature = stops_buses_source.getFeatureById(stops[i]);
223             } else if(prefix == 'st') {
224                 feature = stops_trams_source.getFeatureById(stops[i]);
225             } else if(prefix == 'pb') {
226                 feature = stop_points_buses_source.getFeatureById(stops[i]);
227             } else if(prefix == 'pt') {
228                 feature = stop_points_trams_source.getFeatureById(stops[i]);
229             }
230         }
231         if(feature) {
232             stop_selected_source.addFeature(feature);
233         }
1d4785 234     }
JK 235     
f4a54f 236     stop_selected_layer.setVisible(true);
1d4785 237 }
JK 238
239 function unstyleSelectedFeatures() {
f4a54f 240     stop_selected_source.clear();
JK 241     route_source.clear();
242     if(feature_clicked && (feature_clicked.getId().startsWith('t') || feature_clicked.getId().startsWith('b'))) {
243         styleVehicle(feature_clicked);
1d4785 244     }
JK 245 }
246
eafc1c 247 function updateTrams() {
JK 248     if(trams_timer) clearTimeout(trams_timer);
249     if(trams_xhr) trams_xhr.abort();
250     trams_xhr = $.get(
251         ttss_trams_base + '/geoserviceDispatcher/services/vehicleinfo/vehicles'
a8a6d1 252             + '?positionType=' + ttss_position_type
57b8d3 253             + '&colorType=ROUTE_BASED'
eafc1c 254             + '&lastUpdate=' + encodeURIComponent(trams_last_update)
57b8d3 255     ).done(function(data) {
eafc1c 256         trams_last_update = data.lastUpdate;
57b8d3 257         
JK 258         for(var i = 0; i < data.vehicles.length; i++) {
259             var vehicle = data.vehicles[i];
260             
eafc1c 261             var vehicle_feature = trams_source.getFeatureById('t' + vehicle.id);
57b8d3 262             if(vehicle.isDeleted) {
JK 263                 if(vehicle_feature) {
eafc1c 264                     trams_source.removeFeature(vehicle_feature);
745cfd 265                     if(feature_clicked && feature_clicked.getId() === vehicle_feature.getId()) {
07c714 266                         featureClicked();
57b8d3 267                     }
JK 268                 }
269                 continue;
270             }
271             
272             var vehicle_name_space = vehicle.name.indexOf(' ');
273             vehicle.line = vehicle.name.substr(0, vehicle_name_space);
274             vehicle.direction = vehicle.name.substr(vehicle_name_space+1);
275             if(special_directions[vehicle.direction]) {
276                 vehicle.line = special_directions[vehicle.direction];
277             }
278             
279             vehicle.geometry = getGeometry(vehicle);
3d2caa 280             vehicle.vehicle_type = parseVehicle('t' + vehicle.id);
57b8d3 281             
JK 282             if(!vehicle_feature) {
283                 vehicle_feature = new ol.Feature(vehicle);
eafc1c 284                 vehicle_feature.setId('t' + vehicle.id);
57b8d3 285                 
f4a54f 286                 styleVehicle(vehicle_feature);
eafc1c 287                 trams_source.addFeature(vehicle_feature);
57b8d3 288             } else {
JK 289                 vehicle_feature.setProperties(vehicle);
a8a6d1 290                 vehicle_feature.getStyle().getImage().setRotation(Math.PI * parseFloat(vehicle.heading ? vehicle.heading : 0) / 180.0);
57b8d3 291             }
JK 292         }
293         
eafc1c 294         trams_timer = setTimeout(function() {
JK 295             updateTrams();
57b8d3 296         }, ttss_refresh);
JK 297     }).fail(fail_ajax);
7ca6a1 298     
eafc1c 299     return trams_xhr;
JK 300 }
301
302 function updateBuses() {
303     if(buses_timer) clearTimeout(buses_timer);
304     if(buses_xhr) buses_xhr.abort();
305     
306     buses_xhr = $.get(
307         ttss_buses_base + '/geoserviceDispatcher/services/vehicleinfo/vehicles'
8a0cf8 308             + '?positionType=RAW'
eafc1c 309             + '&colorType=ROUTE_BASED'
JK 310             + '&lastUpdate=' + encodeURIComponent(buses_last_update)
311     ).done(function(data) {
312         buses_last_update = data.lastUpdate;
313         
314         for(var i = 0; i < data.vehicles.length; i++) {
315             var vehicle = data.vehicles[i];
316             
317             var vehicle_feature = buses_source.getFeatureById('b' + vehicle.id);
326ad3 318             if(vehicle.isDeleted || !vehicle.latitude || !vehicle.longitude) {
eafc1c 319                 if(vehicle_feature) {
JK 320                     buses_source.removeFeature(vehicle_feature);
321                     if(feature_clicked && feature_clicked.getId() === vehicle_feature.getId()) {
322                         featureClicked();
323                     }
324                 }
325                 continue;
326             }
327             
328             var vehicle_name_space = vehicle.name.indexOf(' ');
329             vehicle.line = vehicle.name.substr(0, vehicle_name_space);
330             vehicle.direction = vehicle.name.substr(vehicle_name_space+1);
331             if(special_directions[vehicle.direction]) {
332                 vehicle.line = special_directions[vehicle.direction];
333             }
334             
335             vehicle.geometry = getGeometry(vehicle);
3d2caa 336             vehicle.vehicle_type = parseVehicle('b' + vehicle.id);
eafc1c 337             
JK 338             if(!vehicle_feature) {
339                 vehicle_feature = new ol.Feature(vehicle);
340                 vehicle_feature.setId('b' + vehicle.id);
341                 
f4a54f 342                 styleVehicle(vehicle_feature);
eafc1c 343                 buses_source.addFeature(vehicle_feature);
JK 344             } else {
345                 vehicle_feature.setProperties(vehicle);
346                 vehicle_feature.getStyle().getImage().setRotation(Math.PI * parseFloat(vehicle.heading) / 180.0);
347             }
348         }
349         
350         buses_timer = setTimeout(function() {
351             updateBuses();
352         }, ttss_refresh);
353     }).fail(fail_ajax);
354     
355     return buses_xhr;
57b8d3 356 }
JK 357
358 function updateStopSource(stops, prefix, source) {
359     for(var i = 0; i < stops.length; i++) {
360         var stop = stops[i];
e61357 361         
JK 362         if(stop.category == 'other') continue;
b1d3d6 363         if(stops_ignored.indexOf(stop.shortName) >= 0) continue;
e61357 364         
57b8d3 365         stop.geometry = getGeometry(stop);
JK 366         var stop_feature = new ol.Feature(stop);
367         
368         stop_feature.setId(prefix + stop.id);
369         
370         source.addFeature(stop_feature);
371     }
372 }
373
f4a54f 374 function updateStops(base, suffix, source) {
7ca6a1 375     return $.get(
eafc1c 376         base + '/geoserviceDispatcher/services/stopinfo/stops'
57b8d3 377             + '?left=-648000000'
JK 378             + '&bottom=-324000000'
379             + '&right=648000000'
380             + '&top=324000000'
381     ).done(function(data) {
f4a54f 382         updateStopSource(data.stops, 's' + suffix, source);
57b8d3 383     }).fail(fail_ajax);
JK 384 }
385
f4a54f 386 function updateStopPoints(base, suffix, source) {
7ca6a1 387     return $.get(
eafc1c 388         base + '/geoserviceDispatcher/services/stopinfo/stopPoints'
57b8d3 389             + '?left=-648000000'
JK 390             + '&bottom=-324000000'
391             + '&right=648000000'
392             + '&top=324000000'
393     ).done(function(data) {
f4a54f 394         updateStopSource(data.stopPoints, 'p' + suffix, source);
57b8d3 395     }).fail(fail_ajax);
7ca6a1 396 }
JK 397
eafc1c 398 function vehicleTable(tripId, table, featureId) {
8b6250 399     if(feature_xhr) feature_xhr.abort();
JK 400     if(feature_timer) clearTimeout(feature_timer);
401     
eafc1c 402     var url = ttss_trams_base;
JK 403     if(featureId.startsWith('b')) {
404         url = ttss_buses_base;
405     }
406     
407     var vehicleId = featureId.substr(1);
408     
8b6250 409     feature_xhr = $.get(
eafc1c 410         url + '/services/tripInfo/tripPassages'
8b6250 411             + '?tripId=' + encodeURIComponent(tripId)
JK 412             + '&mode=departure'
413     ).done(function(data) {
98ba34 414         if(!data.routeName || !data.directionText) {
8b6250 415             return;
JK 416         }
417         
418         deleteChildren(table);
419         
420         for(var i = 0, il = data.old.length; i < il; i++) {
421             var tr = document.createElement('tr');
422             addCellWithText(tr, data.old[i].actualTime || data.old[i].plannedTime);
423             addCellWithText(tr, data.old[i].stop_seq_num + '. ' + data.old[i].stop.name);
424             
425             tr.className = 'active';
426             table.appendChild(tr);
427         }
428         
f4a54f 429         var stopsToMark = [];
1d4785 430         
8b6250 431         for(var i = 0, il = data.actual.length; i < il; i++) {
JK 432             var tr = document.createElement('tr');
433             addCellWithText(tr, data.actual[i].actualTime || data.actual[i].plannedTime);
434             addCellWithText(tr, data.actual[i].stop_seq_num + '. ' + data.actual[i].stop.name);
1d4785 435             
f4a54f 436             stopsToMark.push('s' + featureId.substr(0,1) + data.actual[i].stop.id);
8b6250 437             
JK 438             if(data.actual[i].status == 'STOPPING') {
439                 tr.className = 'success';
440             }
441             table.appendChild(tr);
442         }
f4a54f 443         
JK 444         markStops(stopsToMark, featureId.substr(0,1), true);
8b6250 445         
eafc1c 446         feature_timer = setTimeout(function() { vehicleTable(tripId, table, featureId); }, ttss_refresh);
1d4785 447         
JK 448         if(!vehicleId) return;
449            
450         feature_xhr = $.get(
eafc1c 451             url + '/geoserviceDispatcher/services/pathinfo/vehicle'
1d4785 452                 + '?id=' + encodeURIComponent(vehicleId)
JK 453         ).done(function(data) {
454             if(!data || !data.paths || !data.paths[0] || !data.paths[0].wayPoints) return;
455             
456             var point = null;
457             var points = [];
458             for(var i = 0; i < data.paths[0].wayPoints.length; i++) {
459                 point = data.paths[0].wayPoints[i];
460                 points.push(ol.proj.fromLonLat([
461                     point.lon / 3600000.0,
462                     point.lat / 3600000.0,
463                 ]));
464             }
465             
466             route_source.addFeature(new ol.Feature({
467                 geometry: new ol.geom.LineString(points)
468             }));
f4a54f 469             route_layer.setVisible(true);
1d4785 470         });
8b6250 471     }).fail(fail_ajax_popup);
JK 472 }
473
eafc1c 474 function stopTable(stopType, stopId, table, featureId) {
8b6250 475     if(feature_xhr) feature_xhr.abort();
JK 476     if(feature_timer) clearTimeout(feature_timer);
477     
eafc1c 478     var url = ttss_trams_base;
JK 479     if(featureId.substr(1,1) == 'b') {
480         url = ttss_buses_base;
481     }
482     
8b6250 483     feature_xhr = $.get(
eafc1c 484         url + '/services/passageInfo/stopPassages/' + stopType
8b6250 485             + '?' + stopType + '=' + encodeURIComponent(stopId)
JK 486             + '&mode=departure'
487     ).done(function(data) {
488         deleteChildren(table);
489         
490         for(var i = 0, il = data.old.length; i < il; i++) {
491             var tr = document.createElement('tr');
492             addCellWithText(tr, data.old[i].patternText);
493             var dir_cell = addCellWithText(tr, data.old[i].direction);
494             var vehicle = parseVehicle(data.old[i].vehicleId);
495             dir_cell.appendChild(displayVehicle(vehicle));
496             var status = parseStatus(data.old[i]);
497             addCellWithText(tr, status);
498             addCellWithText(tr, '');
499             
500             tr.className = 'active';
501             table.appendChild(tr);
502         }
503         
504         for(var i = 0, il = data.actual.length; i < il; i++) {
505             var tr = document.createElement('tr');
506             addCellWithText(tr, data.actual[i].patternText);
507             var dir_cell = addCellWithText(tr, data.actual[i].direction);
508             var vehicle = parseVehicle(data.actual[i].vehicleId);
509             dir_cell.appendChild(displayVehicle(vehicle));
510             var status = parseStatus(data.actual[i]);
511             var status_cell = addCellWithText(tr, status);
512             var delay = parseDelay(data.actual[i]);
513             var delay_cell = addCellWithText(tr, delay);
514             
515             if(status == lang.boarding_sign) {
516                 tr.className = 'success';
517                 status_cell.className = 'status-boarding';
518             } else if(parseInt(delay) > 9) {
519                 tr.className = 'danger';
520                 delay_cell.className = 'status-delayed';
521             } else if(parseInt(delay) > 3) {
522                 tr.className = 'warning';
523             }
524             
525             table.appendChild(tr);
526         }
527         
eafc1c 528         feature_timer = setTimeout(function() { stopTable(stopType, stopId, table, featureId); }, ttss_refresh);
8b6250 529     }).fail(fail_ajax_popup);
JK 530 }
531
7ca6a1 532 function featureClicked(feature) {
1d4785 533     if(feature && !feature.getId()) return;
JK 534     
535     unstyleSelectedFeatures();
536     
7ca6a1 537     if(!feature) {
d29c06 538         panel.close();
7ca6a1 539         return;
JK 540     }
541     
542     var coordinates = feature.getGeometry().getCoordinates();
543     
9f0f6a 544     var div = document.createElement('div');
8b6250 545     
07c714 546     var type;
JK 547     var name = feature.get('name');
548     var additional;
8b6250 549     var table = document.createElement('table');
JK 550     var thead = document.createElement('thead');
551     var tbody = document.createElement('tbody');
552     table.appendChild(thead);
553     table.appendChild(tbody);
07c714 554     
a4d011 555     var tabular_data = true;
JK 556     
7ca6a1 557     switch(feature.getId().substr(0, 1)) {
a4d011 558         case 'l':
JK 559             tabular_data = false;
560             type = '';
561             name = lang.type_location;
562         break;
eafc1c 563         case 't':
JK 564         case 'b':
a83099 565             type = lang.type_tram;
JK 566             if(feature.getId().startsWith('b')) {
567                 type = lang.type_bus;
568             }
07c714 569             
JK 570             var span = displayVehicle(feature.get('vehicle_type'));
571             
572             additional = document.createElement('p');
439d60 573             if(span.title) {
JK 574                 setText(additional, span.title);
f0bae0 575             } else {
JK 576                 setText(additional, feature.getId());
439d60 577             }
07c714 578             additional.insertBefore(span, additional.firstChild);
8b6250 579             
JK 580             addElementWithText(thead, 'th', lang.header_time);
581             addElementWithText(thead, 'th', lang.header_stop);
582             
eafc1c 583             vehicleTable(feature.get('tripId'), tbody, feature.getId());
f4a54f 584             
JK 585             styleVehicle(feature, true);
7ca6a1 586         break;
07c714 587         case 's':
a83099 588             type = lang.type_stop_tram;
JK 589             if(feature.getId().startsWith('sb')) {
590                 type = lang.type_stop_bus;
591             }
8b6250 592             
JK 593             addElementWithText(thead, 'th', lang.header_line);
594             addElementWithText(thead, 'th', lang.header_direction);
595             addElementWithText(thead, 'th', lang.header_time);
596             addElementWithText(thead, 'th', lang.header_delay);
597             
eafc1c 598             stopTable('stop', feature.get('shortName'), tbody, feature.getId());
f4a54f 599             markStops([feature], feature.getId().substr(1,1));
07c714 600         break;
JK 601         case 'p':
a83099 602             type = lang.type_stoppoint_tram;
JK 603             if(feature.getId().startsWith('pb')) {
604                 type = lang.type_stoppoint_bus;
605             }
8b6250 606             
JK 607             additional = document.createElement('p');
608             additional.className = 'small';
609             addElementWithText(additional, 'a', lang.departures_for_stop).addEventListener(
610                 'click',
611                 function() {
f7f4ce 612                     var stops_source = stops_trams_source;
JK 613                     if(feature.getId().startsWith('pb')) {
614                         stops_source = stops_buses_source;
615                     }
8b6250 616                     featureClicked(stops_source.forEachFeature(function(stop_feature) {
a83099 617                         if(stop_feature.get('shortName') == feature.get('shortName') && stop_feature.getId().substr(1,1) == feature.getId().substr(1,1)) {
8b6250 618                             return stop_feature;
JK 619                         }
620                     }));
621                 }
622             );
623             
624             addElementWithText(thead, 'th', lang.header_line);
625             addElementWithText(thead, 'th', lang.header_direction);
626             addElementWithText(thead, 'th', lang.header_time);
627             addElementWithText(thead, 'th', lang.header_delay);
628             
eafc1c 629             stopTable('stopPoint', feature.get('stopPoint'), tbody, feature.getId());
f4a54f 630             markStops([feature], feature.getId().substr(1,1));
07c714 631         break;
JK 632     }
8b6250 633     
JK 634     var loader = addElementWithText(tbody, 'td', lang.loading);
635     loader.className = 'active';
ee4e7c 636     loader.colSpan = thead.childNodes.length;
07c714 637     
9f0f6a 638     addParaWithText(div, type).className = 'type';
JK 639     addParaWithText(div, name).className = 'name';
07c714 640     
JK 641     if(additional) {
9f0f6a 642         div.appendChild(additional);
7ca6a1 643     }
JK 644     
a4d011 645     if(tabular_data) {
JK 646         div.appendChild(table);
647         ignore_hashchange = true;
648         window.location.hash = '#!' + feature.getId();
649     }
7ca6a1 650     
1d4785 651     setTimeout(function () {map.getView().animate({
07c714 652         center: feature.getGeometry().getCoordinates(),
1d4785 653     }) }, 10);
07c714 654     
9f0f6a 655     
d29c06 656     panel.show(div, function() {
9f0f6a 657         if(!ignore_hashchange) {
JK 658             ignore_hashchange = true;
659             window.location.hash = '';
660             
661             unstyleSelectedFeatures();
d29c06 662             feature_clicked = null;
9f0f6a 663             
JK 664             if(feature_xhr) feature_xhr.abort();
665             if(feature_timer) clearTimeout(feature_timer);
666         }
667     });
07c714 668     
1d4785 669     feature_clicked = feature;
a4d011 670 }
JK 671
672 function mapClicked(e) {
673     var point = e.coordinate;
674     var features = [];
675     map.forEachFeatureAtPixel(e.pixel, function(feature, layer) {
676         if(layer == stop_selected_layer) return;
677         if(feature.getId()) features.push(feature);
678     });
679     
680     if(features.length > 1) {
681         featureClicked();
682         
683         var div = document.createElement('div');
684         
685         addParaWithText(div, lang.select_feature);
686         
687         for(var i = 0; i < features.length; i++) {
688             var feature = features[i];
689             
690             var p = document.createElement('p');
691             var a = document.createElement('a');
692             p.appendChild(a);
693             a.addEventListener('click', function(feature) { return function() {
694                 featureClicked(feature);
695             }}(feature));
696             
697             var type = '';
698             switch(feature.getId().substr(0, 1)) {
699                 case 'l':
700                     type = '';
701                     name = lang.type_location;
702                 break;
703                 case 't':
704                 case 'b':
705                     type = lang.type_tram;
706                     if(feature.getId().startsWith('b')) {
707                         type = lang.type_bus;
708                     }
709                     if(feature.get('vehicle_type').num) {
710                         type += ' ' + feature.get('vehicle_type').num;
711                     }
712                 break;
713                 case 's':
714                     type = lang.type_stop_tram;
715                     if(feature.getId().startsWith('sb')) {
716                         type = lang.type_stop_bus;
717                     }
718                 break;
719                 case 'p':
720                     type = lang.type_stoppoint_tram;
721                     if(feature.getId().startsWith('pb')) {
722                         type = lang.type_stoppoint_bus;
723                     }
724                 break;
725             }
726             
727             addElementWithText(a, 'span', type).className = 'small';
728             a.appendChild(document.createTextNode(' '));
729             addElementWithText(a, 'span', feature.get('name'));
730             
731             div.appendChild(p);
732         }
733         
d29c06 734         panel.show(div);
a4d011 735         
JK 736         return;
737     }
738     
739     var feature = features[0];
740     if(!feature) {
741         if(stops_buses_layer.getVisible()) {
742             feature = returnClosest(point, feature, stops_buses_source.getClosestFeatureToCoordinate(point));
743         }
744         if(stops_trams_layer.getVisible()) {
745             feature = returnClosest(point, feature, stops_trams_source.getClosestFeatureToCoordinate(point));
746         }
747         if(stop_points_buses_layer.getVisible()) {
748             feature = returnClosest(point, feature, stop_points_buses_source.getClosestFeatureToCoordinate(point));
749         }
750         if(stop_points_trams_layer.getVisible()) {
751             feature = returnClosest(point, feature, stop_points_trams_source.getClosestFeatureToCoordinate(point));
752         }
753         if(trams_layer.getVisible()) {
754             feature = returnClosest(point, feature, trams_source.getClosestFeatureToCoordinate(point));
755         }
756         if(buses_layer.getVisible()) {
757             feature = returnClosest(point, feature, buses_source.getClosestFeatureToCoordinate(point));
758         }
759         
760         if(getDistance(point, feature) > map.getView().getResolution() * 20) {
761             feature = null;
762         }
763     }
764     
765     featureClicked(feature);
766 }
767
768 function trackingStop() {
d29c06 769     geolocation_button.classList.remove('clicked');
a4d011 770     geolocation.setTracking(false);
JK 771     
772     geolocation_source.clear();
773 }
774 function trackingStart() {
775     geolocation_set = 0;
d29c06 776     geolocation_button.classList.add('clicked');
a4d011 777     geolocation_feature.setGeometry(new ol.geom.Point(map.getView().getCenter()));
JK 778     geolocation_accuracy.setGeometry(new ol.geom.Circle(map.getView().getCenter(), 100000));
779     
780     geolocation_source.addFeature(geolocation_feature);
781     geolocation_source.addFeature(geolocation_accuracy);
782     
783     geolocation.setTracking(true);
784 }
785 function trackingToggle() {
786     if(geolocation.getTracking()) {
787         trackingStop();
788     } else {
789         trackingStart();
790     }
7ca6a1 791 }
JK 792
793 function hash() {
794     if(ignore_hashchange) {
795         ignore_hashchange = false;
796         return;
797     }
798     
799     var feature = null;
f4a54f 800     var vehicleId = null;
JK 801     var stopId = null;
7ca6a1 802     
JK 803     if(window.location.hash.match(/^#!t[0-9]{3}$/)) {
f4a54f 804         vehicleId = depotIdToVehicleId(window.location.hash.substr(3), 't');
JK 805     } else if(window.location.hash.match(/^#!b[0-9]{3}$/)) {
806         vehicleId = depotIdToVehicleId(window.location.hash.substr(3), 'b');
7ca6a1 807     } else if(window.location.hash.match(/^#![A-Za-z]{2}[0-9]{3}$/)) {
f4a54f 808         vehicleId = depotIdToVehicleId(window.location.hash.substr(2));
439d60 809     } else if(window.location.hash.match(/^#!v-?[0-9]+$/)) {
f4a54f 810         vehicleId = 't' + window.location.hash.substr(3);
JK 811     } else if(window.location.hash.match(/^#![tb]-?[0-9]+$/)) {
812         vehicleId = window.location.hash.substr(2);
813     } else if(window.location.hash.match(/^#![sp]-?[0-9]+$/)) {
814         stopId = window.location.hash.substr(2,1) + 't' + window.location.hash.substr(3);
815     } else if(window.location.hash.match(/^#![sp][tb]-?[0-9]+$/)) {
816         stopId = window.location.hash.substr(2);
a8a6d1 817     } else if(window.location.hash == '#!RAW') {
JK 818         ttss_position_type = 'RAW';
7ca6a1 819     }
JK 820     
f4a54f 821     if(vehicleId) {
JK 822         if(vehicleId.startsWith('b')) {
823             feature = buses_source.getFeatureById(vehicleId);
824         } else {
825             feature = trams_source.getFeatureById(vehicleId);
826         }
7ca6a1 827     } else if(stopId) {
f4a54f 828         if(stopId.startsWith('st')) {
JK 829             feature = stops_trams_source.getFeatureById(stopId);
830         } else if(stopId.startsWith('sb')) {
831             feature = stops_buses_source.getFeatureById(stopId);
832         } else if(stopId.startsWith('pt')) {
833             feature = stop_points_trams_source.getFeatureById(stopId);
834         } else if(stopId.startsWith('pb')) {
835             feature = stop_points_buses_source.getFeatureById(stopId);
836         }
7ca6a1 837     }
JK 838     
839     featureClicked(feature);
57b8d3 840 }
JK 841
0e60d1 842 function getDistance(c1, c2) {
JK 843     if(c1.getGeometry) {
844         c1 = c1.getGeometry().getCoordinates();
845     }
846     if(c2.getGeometry) {
847         c2 = c2.getGeometry().getCoordinates();
848     }
849     
850     var c1 = ol.proj.transform(c1, 'EPSG:3857', 'EPSG:4326');
851     var c2 = ol.proj.transform(c2, 'EPSG:3857', 'EPSG:4326');
a8a6d1 852     return ol.sphere.getDistance(c1, c2);
0e60d1 853 }
JK 854
855 function returnClosest(point, f1, f2) {
856     if(!f1) return f2;
857     if(!f2) return f1;
858     
859     return (getDistance(point, f1) < getDistance(point, f2)) ? f1 : f2;
860 }
861
57b8d3 862 function init() {
JK 863     if(!window.jQuery) {
864         fail(lang.jquery_not_loaded);
865         return;
866     }
867     
868     $.ajaxSetup({
869         dataType: 'json',
870         timeout: 10000,
871     });
d29c06 872     
JK 873     panel = new Panel(document.getElementById('panel'));
57b8d3 874     
f4a54f 875     stops_buses_source = new ol.source.Vector({
57b8d3 876         features: [],
JK 877     });
f4a54f 878     stops_buses_layer = new ol.layer.Vector({
JK 879         source: stops_buses_source,
880         renderMode: 'image',
881         style: new ol.style.Style({
882             image: new ol.style.Circle({
883                 fill: new ol.style.Fill({color: '#07F'}),
884                 stroke: new ol.style.Stroke({color: '#05B', width: 1}),
885                 radius: 3,
886             }),
887         }),
57b8d3 888     });
JK 889     
f4a54f 890     stops_trams_source = new ol.source.Vector({
57b8d3 891         features: [],
JK 892     });
f4a54f 893     stops_trams_layer = new ol.layer.Vector({
JK 894         source: stops_trams_source,
895         renderMode: 'image',
896         style: new ol.style.Style({
897             image: new ol.style.Circle({
898                 fill: new ol.style.Fill({color: '#FA0'}),
899                 stroke: new ol.style.Stroke({color: '#B70', width: 1}),
900                 radius: 3,
901             }),
902         }),
903     });
904     
905     stop_points_buses_source = new ol.source.Vector({
906         features: [],
907     });
908     stop_points_buses_layer = new ol.layer.Vector({
909         source: stop_points_buses_source,
910         renderMode: 'image',
911         visible: false,
912         style: new ol.style.Style({
913             image: new ol.style.Circle({
914                 fill: new ol.style.Fill({color: '#07F'}),
915                 stroke: new ol.style.Stroke({color: '#05B', width: 2}),
916                 radius: 3,
917             }),
918         }),
919     });
920     
921     stop_points_trams_source = new ol.source.Vector({
922         features: [],
923     });
924     stop_points_trams_layer = new ol.layer.Vector({
925         source: stop_points_trams_source,
926         renderMode: 'image',
927         visible: false,
928         style: new ol.style.Style({
929             image: new ol.style.Circle({
930                 fill: new ol.style.Fill({color: '#FA0'}),
931                 stroke: new ol.style.Stroke({color: '#B70', width: 2}),
932                 radius: 3,
933             }),
934         }),
935     });
936     
937     stop_selected_source = new ol.source.Vector({
938         features: [],
939     });
940     stop_selected_layer = new ol.layer.Vector({
941         source: stop_selected_source,
57b8d3 942         visible: false,
JK 943     });
944     
eafc1c 945     trams_source = new ol.source.Vector({
57b8d3 946         features: [],
JK 947     });
eafc1c 948     trams_layer = new ol.layer.Vector({
JK 949         source: trams_source,
950     });
951     
952     buses_source = new ol.source.Vector({
953         features: [],
954     });
955     buses_layer = new ol.layer.Vector({
956         source: buses_source,
57b8d3 957     });
JK 958     
1d4785 959     route_source = new ol.source.Vector({
JK 960         features: [],
961     });
962     route_layer = new ol.layer.Vector({
963         source: route_source,
964         style: new ol.style.Style({
965             stroke: new ol.style.Stroke({ color: [255, 153, 0, .8], width: 5 })
966         }),
967     });
968     
1c0616 969     ol.style.IconImageCache.shared.setSize(512);
JK 970     
a4d011 971     geolocation_feature = new ol.Feature({
JK 972         name: '',
973         style: new ol.style.Style({
974             image: new ol.style.Circle({
975                 fill: new ol.style.Fill({color: '#39C'}),
976                 stroke: new ol.style.Stroke({color: '#FFF', width: 2}),
977                 radius: 5,
978             }),
979         }),
980     });
981     geolocation_feature.setId('location_point');
982     geolocation_accuracy = new ol.Feature();
983     geolocation_source = new ol.source.Vector({
984         features: [],
985     });
986     geolocation_layer = new ol.layer.Vector({
987         source: geolocation_source,
988     });
989     geolocation_button = document.querySelector('#track button');
990     if(!navigator.geolocation) {
d29c06 991         geolocation_button.classList.add('hidden');
a4d011 992     }
JK 993     
376c6e 994     geolocation = new ol.Geolocation({projection: 'EPSG:3857'});
a4d011 995     geolocation.on('change:position', function() {
JK 996         var coordinates = geolocation.getPosition();
997         geolocation_feature.setGeometry(coordinates ? new ol.geom.Point(coordinates) : null);
998         if(geolocation_set < 1) {
999             geolocation_set = 1;
1000             map.getView().animate({
1001                 center: coordinates,
1002             })
1003         }
1004     });
1005     geolocation.on('change:accuracyGeometry', function() {
1006         var accuracy = geolocation.getAccuracyGeometry();
1007         geolocation_accuracy.setGeometry(accuracy);
1008         if(geolocation_set < 2) {
1009             geolocation_set = 2;
1010             map.getView().fit(accuracy);
1011         }
1012     });
1013     geolocation.on('error', function(error) {
1014         fail(lang.error_location + ' ' + error.message);
1015         trackingStop();
d29c06 1016         geolocation_button.classList.add('hidden');
a4d011 1017     });
JK 1018     geolocation_button.addEventListener('click', trackingToggle);
1019     
57b8d3 1020     map = new ol.Map({
JK 1021         target: 'map',
1022         layers: [
1023             new ol.layer.Tile({
1024                 source: new ol.source.OSM()
1025             }),
1d4785 1026             route_layer,
a4d011 1027             geolocation_layer,
f4a54f 1028             stops_buses_layer,
JK 1029             stops_trams_layer,
1030             stop_points_buses_layer,
1031             stop_points_trams_layer,
1032             stop_selected_layer,
eafc1c 1033             buses_layer,
JK 1034             trams_layer,
57b8d3 1035         ],
JK 1036         view: new ol.View({
1037             center: ol.proj.fromLonLat([19.94, 50.06]),
a4d011 1038             zoom: 14,
JK 1039             maxZoom: 19,
57b8d3 1040         }),
JK 1041         controls: ol.control.defaults({
1042             attributionOptions: ({
1043                 collapsible: false,
1044             })
1045         }).extend([
1046             new ol.control.Control({
1047                 element: document.getElementById('title'),
1048             }),
1049             new ol.control.Control({
1050                 element: fail_element,
a4d011 1051             }),
JK 1052             new ol.control.Control({
1053                 element: document.getElementById('track'),
1054             }),
57b8d3 1055         ]),
f4a54f 1056         loadTilesWhileAnimating: false,
57b8d3 1057     });
JK 1058     
1059     // Display popup on click
a4d011 1060     map.on('singleclick', mapClicked);
9f0f6a 1061     
JK 1062     fail_element.addEventListener('click', function() {
1063         fail_element.style.top = '-10em';
1064     });
f0bae0 1065     
57b8d3 1066     // Change mouse cursor when over marker
JK 1067     map.on('pointermove', function(e) {
1068         var hit = map.hasFeatureAtPixel(e.pixel);
1069         var target = map.getTargetElement();
1070         target.style.cursor = hit ? 'pointer' : '';
1071     });
1072     
1073     // Change layer visibility on zoom
1074     map.getView().on('change:resolution', function(e) {
f4a54f 1075         stop_points_buses_layer.setVisible(map.getView().getZoom() >= 16);
JK 1076         stop_points_trams_layer.setVisible(map.getView().getZoom() >= 16);
57b8d3 1077     });
JK 1078     
7ca6a1 1079     $.when(
3d2caa 1080         updateVehicleInfo(),
eafc1c 1081         updateTrams(),
JK 1082         updateBuses(),
f4a54f 1083         updateStops(ttss_trams_base, 't', stops_trams_source),
JK 1084         updateStops(ttss_buses_base, 'b', stops_buses_source),
1085         updateStopPoints(ttss_trams_base, 't', stop_points_trams_source),
1086         updateStopPoints(ttss_buses_base, 'b', stop_points_buses_source),
7ca6a1 1087     ).done(function() {
JK 1088         hash();
1089     });
1090     
1091     window.addEventListener('hashchange', hash);
57b8d3 1092     
JK 1093     setTimeout(function() {
eafc1c 1094         if(trams_xhr) trams_xhr.abort();
JK 1095         if(trams_timer) clearTimeout(trams_timer);
1096         if(buses_xhr) buses_xhr.abort();
1097         if(buses_timer) clearTimeout(buses_timer);
57b8d3 1098           
JK 1099         fail(lang.error_refresh);
1100     }, 1800000);
1101 }
1102
1103 init();