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