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