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