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