Improved www.ttss.krakow.pl
Jacek Kowalski
2019-06-17 ae320718c65e75b727724729140e1546d566417c
commit | author | age
7e7221 1 'use strict';
f4a54f 2
f36d1f 3 var ttss_refresh = 10000; // 10 seconds
4bfa36 4 var ttss_position_type = 'RAW';
57b8d3 5
a4d011 6 var geolocation = null;
JK 7 var geolocation_set = 0;
8 var geolocation_button = null;
9 var geolocation_feature = null;
10 var geolocation_accuracy = null;
11 var geolocation_source = null;
12 var geolocation_layer = null;
13
4bfa36 14 var vehicles_xhr = {};
JK 15 var vehicles_timer = {};
16 var vehicles_last_update = {};
17 var vehicles_source = {};
18 var vehicles_layer = {};
eafc1c 19
f0bae0 20 var vehicles_info = {};
57b8d3 21
JK 22 var stops_xhr = null;
6063e6 23 var stops_ignored = ['131', '744', '1263'];
88a24c 24 var stops_style = {
JK 25     'sb': new ol.style.Style({
26         image: new ol.style.Circle({
27             fill: new ol.style.Fill({color: '#07F'}),
28             stroke: new ol.style.Stroke({color: '#05B', width: 2}),
29             radius: 3,
30         }),
31     }),
32     'st': new ol.style.Style({
33         image: new ol.style.Circle({
34             fill: new ol.style.Fill({color: '#FA0'}),
35             stroke: new ol.style.Stroke({color: '#B70', width: 2}),
36             radius: 3,
37         }),
38     }),
39     'pb': new ol.style.Style({
40         image: new ol.style.Circle({
41             fill: new ol.style.Fill({color: '#07F'}),
42             stroke: new ol.style.Stroke({color: '#05B', width: 1}),
43             radius: 3,
44         }),
45     }),
46     'pt': new ol.style.Style({
47         image: new ol.style.Circle({
48             fill: new ol.style.Fill({color: '#FA0'}),
49             stroke: new ol.style.Stroke({color: '#B70', width: 1}),
50             radius: 3,
51         }),
52     }),
53 };
54 var stops_type = ['st', 'sb', 'pt', 'pb'];
1b7c52 55 var stops_mapping = {};
88a24c 56 var stops_source = {};
JK 57 var stops_layer = {};
f4a54f 58
JK 59 var stop_selected_source = null;
60 var stop_selected_layer = null;
57b8d3 61
1d4785 62 var feature_clicked = null;
8b6250 63 var feature_xhr = null;
JK 64 var feature_timer = null;
9dd2e1 65 var path_xhr = null;
1d4785 66
JK 67 var route_source = null;
68 var route_layer = null;
07c714 69
57b8d3 70 var map = null;
d29c06 71
JK 72 var panel = null;
73
57b8d3 74 var fail_element = document.getElementById('fail');
a4d011 75 var fail_text = document.querySelector('#fail span');
57b8d3 76
7ca6a1 77 var ignore_hashchange = false;
JK 78
d29c06 79
JK 80 function Panel(element) {
81     this._element = element;
82     this._element.classList.add('panel');
83     
84     this._hide = addParaWithText(this._element, '▶');
85     this._hide.title = lang.action_collapse;
86     this._hide.className = 'hide';
87     this._hide.addEventListener('click', this.toggleExpanded.bind(this));
88     
89     this._close = addParaWithText(this._element, '×');
90     this._close.title = lang.action_close;
91     this._close.className = 'close';
92     this._close.addEventListener('click', this.close.bind(this));
93     
94     this._content = document.createElement('div');
95     this._element.appendChild(this._content);
96 };
97 Panel.prototype = {
98     _element: null,
99     _hide: null,
100     _close: null,
101     _content: null,
102     
103     _closeCallback: null,
104     _runCallback: function() {
105         var callback = this.closeCallback;
106         this.closeCallback = null;
107         if(callback) callback();
108     },
109     
110     expand: function() {
111         this._element.classList.add('expanded');
112         setText(this._hide, '▶');
113         this._hide.title = lang.action_collapse;
114     },
115     collapse: function() {
116         this._element.classList.remove('expanded');
117         setText(this._hide, '◀');
118         this._hide.title = lang.action_expand;
119     },
120     toggleExpanded: function() {
121         if(this._element.classList.contains('expanded')) {
122             this.collapse();
123         } else {
124             this.expand();
125         }
126     },
127     fail: function(message) {
128         addParaWithText(this._content, message).className = 'error';
129     },
130     show: function(contents, closeCallback) {
131         this._runCallback();
132         this.closeCallback = closeCallback;
133         
134         deleteChildren(this._content);
135         
136         this._content.appendChild(contents);
137         this._element.classList.add('enabled');
138         setTimeout(this.expand.bind(this), 1);
139     },
140     close: function() {
141         this._runCallback();
142         this._element.classList.remove('expanded');
143         this._element.classList.remove('enabled');
144     },
145 };
146
57b8d3 147 function fail(msg) {
a4d011 148     setText(fail_text, msg);
57b8d3 149     fail_element.style.top = '0.5em';
8b6250 150 }
JK 151
152 function fail_ajax_generic(data, fnc) {
57b8d3 153     // abort() is not a failure
faad2a 154     if(data.readyState === 0) return;
57b8d3 155     
faad2a 156     if(data.status === 0) {
8b6250 157         fnc(lang.error_request_failed_connectivity, data);
57b8d3 158     } else if (data.statusText) {
8b6250 159         fnc(lang.error_request_failed_status.replace('$status', data.statusText), data);
57b8d3 160     } else {
8b6250 161         fnc(lang.error_request_failed, data);
57b8d3 162     }
8b6250 163 }
JK 164
165 function fail_ajax(data) {
166     fail_ajax_generic(data, fail);
167 }
168
169 function fail_ajax_popup(data) {
d29c06 170     fail_ajax_generic(data, panel.fail.bind(panel));
57b8d3 171 }
JK 172
173 function getGeometry(object) {
07c714 174     return new ol.geom.Point(ol.proj.fromLonLat([object.longitude / 3600000.0, object.latitude / 3600000.0]));
57b8d3 175 }
JK 176
1d4785 177 function styleVehicle(vehicle, selected) {
JK 178     var color_type = 'black';
179     if(vehicle.get('vehicle_type')) {
180         switch(vehicle.get('vehicle_type').low) {
125626 181             case 0:
1d4785 182                 color_type = 'orange';
JK 183             break;
125626 184             case 1:
JK 185             case 2:
1d4785 186                 color_type = 'green';
JK 187             break;
188         }
189     }
190     
3d2caa 191     var fill = '#B70';
6cb525 192     if(vehicle.getId().startsWith('b')) {
JK 193         fill = '#05B';
194     }
195     if(selected) {
ae3207 196         fill = '#922';
6cb525 197     }
1d4785 198     
3d2caa 199     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:3" /></svg>';
1d4785 200     
f4a54f 201     vehicle.setStyle(new ol.style.Style({
1d4785 202         image: new ol.style.Icon({
JK 203             src: 'data:image/svg+xml;base64,' + btoa(image),
a8a6d1 204             rotation: Math.PI * parseFloat(vehicle.get('heading') ? vehicle.get('heading') : 0) / 180.0,
1d4785 205         }),
JK 206         text: new ol.style.Text({
207             font: 'bold 10px sans-serif',
208             text: vehicle.get('line'),
209             fill: new ol.style.Fill({color: 'white'}),
210         }),
f4a54f 211     }));
1d4785 212 }
JK 213
4bfa36 214 function markStops(stops, ttss_type, routeStyle) {
f4a54f 215     stop_selected_source.clear();
ba6e87 216     
4bfa36 217     var style = stops_layer['s' + ttss_type].getStyle().clone();
f4a54f 218     
JK 219     if(routeStyle) {
220         style.getImage().setRadius(5);
221     } else {
222         style.getImage().getStroke().setWidth(2);
223         style.getImage().getStroke().setColor('#F00');
224         style.getImage().setRadius(5);
ba6e87 225     }
1d4785 226     
f4a54f 227     stop_selected_layer.setStyle(style);
JK 228     
db4410 229     var feature, prefix;
f4a54f 230     for(var i = 0; i < stops.length; i++) {
JK 231         if(stops[i].getId) {
232             feature = stops[i];
233         } else {
234             prefix = stops[i].substr(0,2);
88a24c 235             feature = stops_source[prefix].getFeatureById(stops[i]);
f4a54f 236         }
JK 237         if(feature) {
238             stop_selected_source.addFeature(feature);
239         }
1d4785 240     }
JK 241     
f4a54f 242     stop_selected_layer.setVisible(true);
1d4785 243 }
JK 244
245 function unstyleSelectedFeatures() {
f4a54f 246     stop_selected_source.clear();
JK 247     route_source.clear();
2b6454 248     if(feature_clicked && ttss_types.includes(feature_clicked.getId().substr(0, 1))) {
f4a54f 249         styleVehicle(feature_clicked);
1d4785 250     }
JK 251 }
252
4bfa36 253 function updateVehicles(prefix) {
JK 254     if(vehicles_timer[prefix]) clearTimeout(vehicles_timer[prefix]);
255     if(vehicles_xhr[prefix]) vehicles_xhr[prefix].abort();
256     vehicles_xhr[prefix] = $.get(
257         ttss_urls[prefix] + '/geoserviceDispatcher/services/vehicleinfo/vehicles'
a8a6d1 258             + '?positionType=' + ttss_position_type
57b8d3 259             + '&colorType=ROUTE_BASED'
4bfa36 260             + '&lastUpdate=' + encodeURIComponent(vehicles_last_update[prefix])
57b8d3 261     ).done(function(data) {
4bfa36 262         vehicles_last_update[prefix] = data.lastUpdate;
57b8d3 263         
JK 264         for(var i = 0; i < data.vehicles.length; i++) {
265             var vehicle = data.vehicles[i];
266             
4bfa36 267             var vehicle_feature = vehicles_source[prefix].getFeatureById(prefix + vehicle.id);
JK 268             if(vehicle.isDeleted || !vehicle.latitude || !vehicle.longitude) {
57b8d3 269                 if(vehicle_feature) {
4bfa36 270                     vehicles_source[prefix].removeFeature(vehicle_feature);
745cfd 271                     if(feature_clicked && feature_clicked.getId() === vehicle_feature.getId()) {
07c714 272                         featureClicked();
57b8d3 273                     }
JK 274                 }
275                 continue;
276             }
277             
278             var vehicle_name_space = vehicle.name.indexOf(' ');
279             vehicle.line = vehicle.name.substr(0, vehicle_name_space);
ca42d3 280             vehicle.direction = normalizeName(vehicle.name.substr(vehicle_name_space+1));
57b8d3 281             if(special_directions[vehicle.direction]) {
JK 282                 vehicle.line = special_directions[vehicle.direction];
283             }
284             
285             vehicle.geometry = getGeometry(vehicle);
4bfa36 286             vehicle.vehicle_type = parseVehicle(prefix + vehicle.id);
57b8d3 287             
JK 288             if(!vehicle_feature) {
289                 vehicle_feature = new ol.Feature(vehicle);
4bfa36 290                 vehicle_feature.setId(prefix + vehicle.id);
57b8d3 291                 
f4a54f 292                 styleVehicle(vehicle_feature);
4bfa36 293                 vehicles_source[prefix].addFeature(vehicle_feature);
57b8d3 294             } else {
JK 295                 vehicle_feature.setProperties(vehicle);
a8a6d1 296                 vehicle_feature.getStyle().getImage().setRotation(Math.PI * parseFloat(vehicle.heading ? vehicle.heading : 0) / 180.0);
f64858 297                 vehicle_feature.getStyle().getText().setText(vehicle.line);
57b8d3 298             }
JK 299         }
300         
4bfa36 301         vehicles_timer[prefix] = setTimeout(function() {
JK 302             updateVehicles(prefix);
57b8d3 303         }, ttss_refresh);
JK 304     }).fail(fail_ajax);
7ca6a1 305     
4bfa36 306     return vehicles_xhr[prefix];
57b8d3 307 }
JK 308
88a24c 309 function updateStopSource(stops, prefix) {
JK 310     var source = stops_source[prefix];
1b7c52 311     var mapping = stops_mapping[prefix];
7e7221 312     var stop;
57b8d3 313     for(var i = 0; i < stops.length; i++) {
7e7221 314         stop = stops[i];
e61357 315         
JK 316         if(stop.category == 'other') continue;
2b6454 317         if(stops_ignored.includes(stop.shortName)) continue;
e61357 318         
57b8d3 319         stop.geometry = getGeometry(stop);
JK 320         var stop_feature = new ol.Feature(stop);
1b7c52 321         
JK 322         if(prefix.startsWith('p')) {
323             mapping[stop.stopPoint] = stop_feature;
324         } else {
325             mapping[stop.shortName] = stop_feature;
326         }
57b8d3 327         
JK 328         stop_feature.setId(prefix + stop.id);
329         
330         source.addFeature(stop_feature);
331     }
332 }
333
4bfa36 334 function updateStops(stop_type, ttss_type) {
JK 335     var methods = {
336         's': 'stops',
337         'p': 'stopPoints',
338     };
7ca6a1 339     return $.get(
4bfa36 340         ttss_urls[ttss_type] + '/geoserviceDispatcher/services/stopinfo/' + methods[stop_type]
57b8d3 341             + '?left=-648000000'
JK 342             + '&bottom=-324000000'
343             + '&right=648000000'
344             + '&top=324000000'
345     ).done(function(data) {
4bfa36 346         updateStopSource(data[methods[stop_type]], stop_type + ttss_type);
57b8d3 347     }).fail(fail_ajax);
7ca6a1 348 }
JK 349
7e7221 350 function vehiclePath(feature) {
9dd2e1 351     if(path_xhr) path_xhr.abort();
JK 352     
353     var featureId = feature.getId();
4bfa36 354     var ttss_type = featureId.substr(0, 1);
eafc1c 355     
9dd2e1 356     path_xhr = $.get(
4bfa36 357         ttss_urls[ttss_type] + '/geoserviceDispatcher/services/pathinfo/vehicle'
JK 358             + '?id=' + encodeURIComponent(featureId.substr(1))
9dd2e1 359     ).done(function(data) {
JK 360         if(!data || !data.paths || !data.paths[0] || !data.paths[0].wayPoints) return;
361         
db4410 362         var point;
9dd2e1 363         var points = [];
JK 364         for(var i = 0; i < data.paths[0].wayPoints.length; i++) {
365             point = data.paths[0].wayPoints[i];
366             points.push(ol.proj.fromLonLat([
367                 point.lon / 3600000.0,
368                 point.lat / 3600000.0,
369             ]));
370         }
371         
372         route_source.addFeature(new ol.Feature({
373             geometry: new ol.geom.LineString(points)
374         }));
375         route_layer.setVisible(true);
376     });
2b6454 377     return path_xhr;
9dd2e1 378 }
JK 379
380 function vehicleTable(feature, table) {
381     if(feature_xhr) feature_xhr.abort();
382     if(feature_timer) clearTimeout(feature_timer);
383     
384     var featureId = feature.getId();
4bfa36 385     var ttss_type = featureId.substr(0, 1);
eafc1c 386     
8b6250 387     feature_xhr = $.get(
4bfa36 388         ttss_urls[ttss_type] + '/services/tripInfo/tripPassages'
9dd2e1 389             + '?tripId=' + encodeURIComponent(feature.get('tripId'))
8b6250 390             + '&mode=departure'
JK 391     ).done(function(data) {
b6f8e3 392         if(typeof data.old === "undefined" || typeof data.actual === "undefined") {
8b6250 393             return;
JK 394         }
395         
396         deleteChildren(table);
397         
cb5a77 398         var all_departures = data.old.concat(data.actual);
db4410 399         var tr;
f4a54f 400         var stopsToMark = [];
cb5a77 401         for(var i = 0, il = all_departures.length; i < il; i++) {
db4410 402             tr = document.createElement('tr');
cb5a77 403             addCellWithText(tr, all_departures[i].actualTime || all_departures[i].plannedTime);
ca42d3 404             addCellWithText(tr, all_departures[i].stop_seq_num + '. ' + normalizeName(all_departures[i].stop.name));
1d4785 405             
cb5a77 406             if(i >= data.old.length) {
JK 407                 stopsToMark.push('s' + ttss_type + all_departures[i].stop.id);
408             }
8b6250 409             
cb5a77 410             if(i < data.old.length) {
JK 411                 tr.className = 'active';
412             } else if(all_departures[i].status === 'STOPPING') {
8b6250 413                 tr.className = 'success';
JK 414             }
415             table.appendChild(tr);
416         }
f4a54f 417         
b6f8e3 418         if(all_departures.length === 0) {
JK 419             tr = document.createElement('tr');
420             table.appendChild(tr);
421             tr = addCellWithText(tr, lang.no_data);
422             tr.colSpan = '2';
423             tr.className = 'active';
424         }
425         
4bfa36 426         markStops(stopsToMark, ttss_type, true);
8b6250 427         
9dd2e1 428         feature_timer = setTimeout(function() { vehicleTable(feature, table); }, ttss_refresh);
8b6250 429     }).fail(fail_ajax_popup);
2b6454 430     return feature_xhr;
8b6250 431 }
JK 432
0ba749 433 function stopTable(stopType, stopId, table, ttss_type) {
8b6250 434     if(feature_xhr) feature_xhr.abort();
JK 435     if(feature_timer) clearTimeout(feature_timer);
eafc1c 436     
8b6250 437     feature_xhr = $.get(
4bfa36 438         ttss_urls[ttss_type] + '/services/passageInfo/stopPassages/' + stopType
8b6250 439             + '?' + stopType + '=' + encodeURIComponent(stopId)
JK 440             + '&mode=departure'
441     ).done(function(data) {
442         deleteChildren(table);
443         
cb5a77 444         var all_departures = data.old.concat(data.actual);
db4410 445         var tr, dir_cell, vehicle, status, status_cell, delay, delay_cell;
cb5a77 446         for(var i = 0, il = all_departures.length; i < il; i++) {
db4410 447             tr = document.createElement('tr');
cb5a77 448             addCellWithText(tr, all_departures[i].patternText);
ca42d3 449             dir_cell = addCellWithText(tr, normalizeName(all_departures[i].direction));
cb5a77 450             vehicle = parseVehicle(all_departures[i].vehicleId);
8b6250 451             dir_cell.appendChild(displayVehicle(vehicle));
cb5a77 452             status = parseStatus(all_departures[i]);
db4410 453             status_cell = addCellWithText(tr, status);
cb5a77 454             delay = parseDelay(all_departures[i]);
db4410 455             delay_cell = addCellWithText(tr, delay);
8b6250 456             
cb5a77 457             if(i < data.old.length) {
db4410 458                 tr.className = 'active';
cb5a77 459             } else if(status === lang.boarding_sign) {
8b6250 460                 tr.className = 'success';
JK 461                 status_cell.className = 'status-boarding';
462             } else if(parseInt(delay) > 9) {
463                 tr.className = 'danger';
464                 delay_cell.className = 'status-delayed';
465             } else if(parseInt(delay) > 3) {
466                 tr.className = 'warning';
467             }
468             
469             table.appendChild(tr);
470         }
471         
0ba749 472         feature_timer = setTimeout(function() { stopTable(stopType, stopId, table, ttss_type); }, ttss_refresh);
8b6250 473     }).fail(fail_ajax_popup);
2b6454 474     return feature_xhr;
8b6250 475 }
JK 476
7ca6a1 477 function featureClicked(feature) {
1d4785 478     if(feature && !feature.getId()) return;
JK 479     
480     unstyleSelectedFeatures();
481     
7ca6a1 482     if(!feature) {
d29c06 483         panel.close();
7ca6a1 484         return;
JK 485     }
486     
9f0f6a 487     var div = document.createElement('div');
8b6250 488     
ca42d3 489     var name = normalizeName(feature.get('name'));
07c714 490     var additional;
8b6250 491     var table = document.createElement('table');
JK 492     var thead = document.createElement('thead');
493     var tbody = document.createElement('tbody');
494     table.appendChild(thead);
495     table.appendChild(tbody);
07c714 496     
a4d011 497     var tabular_data = true;
JK 498     
4bfa36 499     var type = feature.getId().substr(0, 1);
76f5c4 500     var full_type = feature.getId().match(/^[a-z]+/)[0];
JK 501     var typeName = lang.types[full_type];
502     if(typeof typeName === 'undefined') {
503         typeName = '';
504     }
505     
4bfa36 506     // Location
JK 507     if(type == 'l') {
508         tabular_data = false;
76f5c4 509         name = typeName;
4bfa36 510         typeName = '';
JK 511     }
512     // Vehicle
2b6454 513     else if(ttss_types.includes(type)) {
4bfa36 514         var span = displayVehicle(feature.get('vehicle_type'));
JK 515         
516         additional = document.createElement('p');
517         if(span.title) {
518             setText(additional, span.title);
519         } else {
520             setText(additional, feature.getId());
521         }
522         additional.insertBefore(span, additional.firstChild);
523         
524         addElementWithText(thead, 'th', lang.header_time);
525         addElementWithText(thead, 'th', lang.header_stop);
526         
527         vehicleTable(feature, tbody);
528         vehiclePath(feature);
529         
530         styleVehicle(feature, true);
531     }
532     // Stop or stop point
2b6454 533     else if(['s', 'p'].includes(type)) {
0ba749 534         var ttss_type = feature.getId().substr(1, 1);
4bfa36 535         if(type == 's') {
1b7c52 536             var second_type = lang.departures_for_buses;
JK 537             var mapping = stops_mapping['sb'];
4bfa36 538             
0ba749 539             if(ttss_type == 'b') {
1b7c52 540                 second_type = lang.departures_for_trams;
JK 541                 mapping = stops_mapping['st'];
542             }
0ba749 543             
JK 544             stopTable('stop', feature.get('shortName'), tbody, ttss_type);
1b7c52 545             
JK 546             if(mapping[feature.get('shortName')]) {
547                 additional = document.createElement('p');
548                 additional.className = 'small';
549                 addElementWithText(additional, 'a', second_type).addEventListener(
550                     'click',
551                     function() {
552                         featureClicked(mapping[feature.get('shortName')]);
553                     }
554                 );
a83099 555             }
4bfa36 556         } else {
0ba749 557             stopTable('stopPoint', feature.get('stopPoint'), tbody, ttss_type);
8b6250 558             
JK 559             additional = document.createElement('p');
560             additional.className = 'small';
561             addElementWithText(additional, 'a', lang.departures_for_stop).addEventListener(
562                 'click',
563                 function() {
0ba749 564                     var mapping = stops_mapping['s' + ttss_type];
1b7c52 565                     featureClicked(mapping[feature.get('shortName')]);
8b6250 566                 }
JK 567             );
4bfa36 568         }
JK 569         
570         addElementWithText(thead, 'th', lang.header_line);
571         addElementWithText(thead, 'th', lang.header_direction);
572         addElementWithText(thead, 'th', lang.header_time);
573         addElementWithText(thead, 'th', lang.header_delay);
574         
575         markStops([feature], feature.getId().substr(1,1));
576     } else {
577         panel.close();
578         return;
07c714 579     }
8b6250 580     
JK 581     var loader = addElementWithText(tbody, 'td', lang.loading);
582     loader.className = 'active';
ee4e7c 583     loader.colSpan = thead.childNodes.length;
07c714 584     
4bfa36 585     addParaWithText(div, typeName).className = 'type';
ae3207 586     
JK 587     var nameElement = addParaWithText(div, name + ' ');
588     nameElement.className = 'name';
589     
590     var showOnMapElement = addElementWithText(nameElement, 'a', lang.show_on_map);
591     var showOnMapFunction = function() {
592         setTimeout(function () {map.getView().animate({
593             center: feature.getGeometry().getCoordinates(),
594         })}, 10);
595     };
596     showOnMapElement.addEventListener('click', showOnMapFunction);
597     showOnMapElement.className = 'icon-zoom pad-left-icon';
598     showOnMapElement.title = lang.show_on_map;
07c714 599     
JK 600     if(additional) {
9f0f6a 601         div.appendChild(additional);
7ca6a1 602     }
JK 603     
a4d011 604     if(tabular_data) {
JK 605         div.appendChild(table);
606         ignore_hashchange = true;
607         window.location.hash = '#!' + feature.getId();
608     }
7ca6a1 609     
ae3207 610     showOnMapFunction();
9f0f6a 611     
d29c06 612     panel.show(div, function() {
9f0f6a 613         if(!ignore_hashchange) {
JK 614             ignore_hashchange = true;
615             window.location.hash = '';
616             
617             unstyleSelectedFeatures();
d29c06 618             feature_clicked = null;
9f0f6a 619             
9dd2e1 620             if(path_xhr) path_xhr.abort();
9f0f6a 621             if(feature_xhr) feature_xhr.abort();
JK 622             if(feature_timer) clearTimeout(feature_timer);
623         }
624     });
07c714 625     
1d4785 626     feature_clicked = feature;
a4d011 627 }
JK 628
629 function mapClicked(e) {
630     var point = e.coordinate;
631     var features = [];
632     map.forEachFeatureAtPixel(e.pixel, function(feature, layer) {
633         if(layer == stop_selected_layer) return;
634         if(feature.getId()) features.push(feature);
635     });
636     
7e7221 637     var feature = features[0];
JK 638     
a4d011 639     if(features.length > 1) {
JK 640         featureClicked();
641         
642         var div = document.createElement('div');
643         
644         addParaWithText(div, lang.select_feature);
645         
7e7221 646         var p, a, full_type, typeName;
a4d011 647         for(var i = 0; i < features.length; i++) {
db4410 648             feature = features[i];
a4d011 649             
db4410 650             p = document.createElement('p');
JK 651             a = document.createElement('a');
a4d011 652             p.appendChild(a);
JK 653             a.addEventListener('click', function(feature) { return function() {
654                 featureClicked(feature);
655             }}(feature));
656             
db4410 657             full_type = feature.getId().match(/^[a-z]+/)[0];
JK 658             typeName = lang.types[full_type];
76f5c4 659             if(typeof typeName === 'undefined') {
4bfa36 660                 typeName = '';
a4d011 661             }
JK 662             
4bfa36 663             addElementWithText(a, 'span', typeName).className = 'small';
a4d011 664             a.appendChild(document.createTextNode(' '));
ca42d3 665             addElementWithText(a, 'span', normalizeName(feature.get('name')));
a4d011 666             
JK 667             div.appendChild(p);
668         }
669         
d29c06 670         panel.show(div);
a4d011 671         
JK 672         return;
673     }
674     
675     if(!feature) {
88a24c 676         stops_type.forEach(function(type) {
JK 677             if(stops_layer[type].getVisible()) {
678                 feature = returnClosest(point, feature, stops_source[type].getClosestFeatureToCoordinate(point));
679             }
680         });
4bfa36 681         ttss_types.forEach(function(type) {
JK 682             if(vehicles_layer[type].getVisible()) {
683                 feature = returnClosest(point, feature, vehicles_source[type].getClosestFeatureToCoordinate(point));
684             }
685         });
a4d011 686         
JK 687         if(getDistance(point, feature) > map.getView().getResolution() * 20) {
688             feature = null;
689         }
690     }
691     
692     featureClicked(feature);
693 }
694
695 function trackingStop() {
d29c06 696     geolocation_button.classList.remove('clicked');
a4d011 697     geolocation.setTracking(false);
JK 698     
699     geolocation_source.clear();
700 }
701 function trackingStart() {
702     geolocation_set = 0;
d29c06 703     geolocation_button.classList.add('clicked');
a4d011 704     geolocation_feature.setGeometry(new ol.geom.Point(map.getView().getCenter()));
JK 705     geolocation_accuracy.setGeometry(new ol.geom.Circle(map.getView().getCenter(), 100000));
706     
707     geolocation_source.addFeature(geolocation_feature);
708     geolocation_source.addFeature(geolocation_accuracy);
709     
710     geolocation.setTracking(true);
711 }
712 function trackingToggle() {
713     if(geolocation.getTracking()) {
714         trackingStop();
715     } else {
716         trackingStart();
717     }
7ca6a1 718 }
JK 719
720 function hash() {
721     if(ignore_hashchange) {
722         ignore_hashchange = false;
723         return;
724     }
725     
726     var feature = null;
f4a54f 727     var vehicleId = null;
JK 728     var stopId = null;
7ca6a1 729     
JK 730     if(window.location.hash.match(/^#!t[0-9]{3}$/)) {
f4a54f 731         vehicleId = depotIdToVehicleId(window.location.hash.substr(3), 't');
JK 732     } else if(window.location.hash.match(/^#!b[0-9]{3}$/)) {
733         vehicleId = depotIdToVehicleId(window.location.hash.substr(3), 'b');
7ca6a1 734     } else if(window.location.hash.match(/^#![A-Za-z]{2}[0-9]{3}$/)) {
f4a54f 735         vehicleId = depotIdToVehicleId(window.location.hash.substr(2));
439d60 736     } else if(window.location.hash.match(/^#!v-?[0-9]+$/)) {
f4a54f 737         vehicleId = 't' + window.location.hash.substr(3);
JK 738     } else if(window.location.hash.match(/^#![tb]-?[0-9]+$/)) {
739         vehicleId = window.location.hash.substr(2);
740     } else if(window.location.hash.match(/^#![sp]-?[0-9]+$/)) {
741         stopId = window.location.hash.substr(2,1) + 't' + window.location.hash.substr(3);
742     } else if(window.location.hash.match(/^#![sp][tb]-?[0-9]+$/)) {
743         stopId = window.location.hash.substr(2);
7ca6a1 744     }
JK 745     
f4a54f 746     if(vehicleId) {
4bfa36 747         feature = vehicles_source[vehicleId.substr(0, 1)].getFeatureById(vehicleId);
7ca6a1 748     } else if(stopId) {
88a24c 749         feature = stops_source[stopId.substr(0,2)].getFeatureById(stopId);
7ca6a1 750     }
JK 751     
752     featureClicked(feature);
57b8d3 753 }
JK 754
0e60d1 755 function getDistance(c1, c2) {
JK 756     if(c1.getGeometry) {
757         c1 = c1.getGeometry().getCoordinates();
758     }
759     if(c2.getGeometry) {
760         c2 = c2.getGeometry().getCoordinates();
761     }
762     
763     var c1 = ol.proj.transform(c1, 'EPSG:3857', 'EPSG:4326');
764     var c2 = ol.proj.transform(c2, 'EPSG:3857', 'EPSG:4326');
a8a6d1 765     return ol.sphere.getDistance(c1, c2);
0e60d1 766 }
JK 767
768 function returnClosest(point, f1, f2) {
769     if(!f1) return f2;
770     if(!f2) return f1;
771     
1b7c52 772     return (getDistance(point, f1) <= getDistance(point, f2)) ? f1 : f2;
0e60d1 773 }
JK 774
57b8d3 775 function init() {
d29c06 776     panel = new Panel(document.getElementById('panel'));
57b8d3 777     
4bfa36 778     route_source = new ol.source.Vector({
JK 779         features: [],
780     });
781     route_layer = new ol.layer.Vector({
782         source: route_source,
783         style: new ol.style.Style({
784             stroke: new ol.style.Stroke({ color: [255, 153, 0, .8], width: 5 })
785         }),
786     });
787     
88a24c 788     stops_type.forEach(function(type) {
JK 789         stops_source[type] = new ol.source.Vector({
790             features: [],
791         });
792         stops_layer[type] = new ol.layer.Vector({
793             source: stops_source[type],
794             renderMode: 'image',
795             style: stops_style[type],
796         });
1b7c52 797         stops_mapping[type] = {};
f4a54f 798     });
JK 799     
800     stop_selected_source = new ol.source.Vector({
801         features: [],
802     });
803     stop_selected_layer = new ol.layer.Vector({
804         source: stop_selected_source,
57b8d3 805         visible: false,
JK 806     });
807     
4bfa36 808     ttss_types.forEach(function(type) {
JK 809         vehicles_source[type] = new ol.source.Vector({
810             features: [],
811         });
812         vehicles_layer[type] = new ol.layer.Vector({
813             source: vehicles_source[type],
814         });
815         vehicles_last_update[type] = 0;
1d4785 816     });
JK 817     
1c0616 818     ol.style.IconImageCache.shared.setSize(512);
JK 819     
a4d011 820     geolocation_feature = new ol.Feature({
JK 821         name: '',
822         style: new ol.style.Style({
823             image: new ol.style.Circle({
824                 fill: new ol.style.Fill({color: '#39C'}),
825                 stroke: new ol.style.Stroke({color: '#FFF', width: 2}),
826                 radius: 5,
827             }),
828         }),
829     });
830     geolocation_feature.setId('location_point');
831     geolocation_accuracy = new ol.Feature();
832     geolocation_source = new ol.source.Vector({
833         features: [],
834     });
835     geolocation_layer = new ol.layer.Vector({
836         source: geolocation_source,
837     });
838     geolocation_button = document.querySelector('#track button');
839     if(!navigator.geolocation) {
d29c06 840         geolocation_button.classList.add('hidden');
a4d011 841     }
JK 842     
376c6e 843     geolocation = new ol.Geolocation({projection: 'EPSG:3857'});
a4d011 844     geolocation.on('change:position', function() {
JK 845         var coordinates = geolocation.getPosition();
846         geolocation_feature.setGeometry(coordinates ? new ol.geom.Point(coordinates) : null);
847         if(geolocation_set < 1) {
848             geolocation_set = 1;
849             map.getView().animate({
850                 center: coordinates,
851             })
852         }
853     });
854     geolocation.on('change:accuracyGeometry', function() {
855         var accuracy = geolocation.getAccuracyGeometry();
856         geolocation_accuracy.setGeometry(accuracy);
857         if(geolocation_set < 2) {
858             geolocation_set = 2;
859             map.getView().fit(accuracy);
860         }
861     });
862     geolocation.on('error', function(error) {
863         fail(lang.error_location + ' ' + error.message);
864         trackingStop();
d29c06 865         geolocation_button.classList.add('hidden');
a4d011 866     });
JK 867     geolocation_button.addEventListener('click', trackingToggle);
868     
4bfa36 869     var layers = [
JK 870         new ol.layer.Tile({
871             source: new ol.source.OSM(),
872         }),
873         route_layer,
874         geolocation_layer,
875     ];
876     stops_type.forEach(function(type) {
877         layers.push(stops_layer[type]);
878     });
879     layers.push(stop_selected_layer);
880     ttss_types.forEach(function(type) {
881         layers.push(vehicles_layer[type]);
882     });
57b8d3 883     map = new ol.Map({
JK 884         target: 'map',
4bfa36 885         layers: layers,
57b8d3 886         view: new ol.View({
JK 887             center: ol.proj.fromLonLat([19.94, 50.06]),
a4d011 888             zoom: 14,
JK 889             maxZoom: 19,
57b8d3 890         }),
JK 891         controls: ol.control.defaults({
892             attributionOptions: ({
893                 collapsible: false,
894             })
895         }).extend([
896             new ol.control.Control({
897                 element: document.getElementById('title'),
898             }),
899             new ol.control.Control({
900                 element: fail_element,
a4d011 901             }),
JK 902             new ol.control.Control({
903                 element: document.getElementById('track'),
904             }),
57b8d3 905         ]),
f4a54f 906         loadTilesWhileAnimating: false,
57b8d3 907     });
JK 908     
909     // Display popup on click
a4d011 910     map.on('singleclick', mapClicked);
9f0f6a 911     
JK 912     fail_element.addEventListener('click', function() {
913         fail_element.style.top = '-10em';
914     });
f0bae0 915     
57b8d3 916     // Change mouse cursor when over marker
JK 917     map.on('pointermove', function(e) {
918         var hit = map.hasFeatureAtPixel(e.pixel);
919         var target = map.getTargetElement();
920         target.style.cursor = hit ? 'pointer' : '';
921     });
922     
923     // Change layer visibility on zoom
7e7221 924     var change_resolution = function() {
88a24c 925         stops_type.forEach(function(type) {
JK 926             if(type.startsWith('p')) {
927                 stops_layer[type].setVisible(map.getView().getZoom() >= 16);
928                 stops_layer[type].setVisible(map.getView().getZoom() >= 16);
929             }
930         });
931     };
932     map.getView().on('change:resolution', change_resolution);
933     change_resolution();
57b8d3 934     
4bfa36 935     var future_requests = [
3d2caa 936         updateVehicleInfo(),
4bfa36 937     ];
JK 938     ttss_types.forEach(function(type) {
939         future_requests.push(updateVehicles(type));
7ca6a1 940     });
4bfa36 941     stops_type.forEach(function(type) {
JK 942         future_requests.push(updateStops(type.substr(0,1), type.substr(1,1)));
943     });
2b6454 944     Deferred.all(future_requests).done(hash);
7ca6a1 945     
JK 946     window.addEventListener('hashchange', hash);
57b8d3 947     
JK 948     setTimeout(function() {
ae3207 949         ttss_types.forEach(function(type) {
JK 950             if(vehicles_xhr[type]) {
951                 vehicles_xhr[type].abort();
952             }
953             if(vehicles_timer[type]) {
954                 clearTimeout(vehicles_timer[type]);
955             }
956         });
957         
57b8d3 958         fail(lang.error_refresh);
JK 959     }, 1800000);
960 }
961
962 init();