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