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