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