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