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