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