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