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