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