Improved www.ttss.krakow.pl
Jacek Kowalski
2017-04-23 0e60d1e6087985f6cb25cbaadb786f20f05d8938
commit | author | age
57b8d3 1 //var ttss_base = 'http://www.ttss.krakow.pl/internetservice';
8b6250 2 var ttss_base = 'proxy.php';
f36d1f 3 var ttss_refresh = 10000; // 10 seconds
57b8d3 4
JK 5 var vehicles_xhr = null;
6 var vehicles_timer = null;
7 var vehicles_last_update = 0;
8 var vehicles_source = null;
9 var vehicles_layer = null;
10
11 var stops_xhr = null;
12 var stops_source = null;
13 var stops_layer = null;
14 var stop_points_source = null;
15 var stop_points_layer = null;
16
07c714 17 var feature_id = null;
8b6250 18 var feature_xhr = null;
JK 19 var feature_timer = null;
07c714 20
57b8d3 21 var map = null;
0e60d1 22 var map_sphere = null;
57b8d3 23 var popup_element = document.getElementById('popup');
JK 24 var fail_element = document.getElementById('fail');
25
7ca6a1 26 var ignore_hashchange = false;
JK 27
57b8d3 28 function fail(msg) {
JK 29     console.log(msg);
30     
31     setText(fail_element, msg);
32     fail_element.style.top = '0.5em';
33 }
34
8b6250 35 function fail_popup(msg) {
JK 36     addElementWithText(popup_element, 'p', msg).className = 'error';
37 }
38
39 function fail_ajax_generic(data, fnc) {
57b8d3 40     // abort() is not a failure
JK 41     if(data.readyState == 0 && data.statusText == 'abort') return;
42     
43     if(data.status == 0) {
8b6250 44         fnc(lang.error_request_failed_connectivity, data);
57b8d3 45     } else if (data.statusText) {
8b6250 46         fnc(lang.error_request_failed_status.replace('$status', data.statusText), data);
57b8d3 47     } else {
8b6250 48         fnc(lang.error_request_failed, data);
57b8d3 49     }
8b6250 50 }
JK 51
52 function fail_ajax(data) {
53     fail_ajax_generic(data, fail);
54 }
55
56 function fail_ajax_popup(data) {
57     fail_ajax_generic(data, fail_popup);
57b8d3 58 }
JK 59
60 function getGeometry(object) {
07c714 61     return new ol.geom.Point(ol.proj.fromLonLat([object.longitude / 3600000.0, object.latitude / 3600000.0]));
57b8d3 62 }
JK 63
64 function updateVehicles() {
65     if(vehicles_timer) clearTimeout(vehicles_timer);
66     if(vehicles_xhr) vehicles_xhr.abort();
67     
68     vehicles_xhr = $.get(
69         ttss_base + '/geoserviceDispatcher/services/vehicleinfo/vehicles' 
70             + '?positionType=CORRECTED'
71             + '&colorType=ROUTE_BASED'
72             + '&lastUpdate=' + encodeURIComponent(vehicles_last_update)
73     ).done(function(data) {
74         vehicles_last_update = data.lastUpdate;
75         
76         for(var i = 0; i < data.vehicles.length; i++) {
77             var vehicle = data.vehicles[i];
78             
79             var vehicle_feature = vehicles_source.getFeatureById('v' + vehicle.id);
80             if(vehicle.isDeleted) {
81                 if(vehicle_feature) {
82                     vehicles_source.removeFeature(vehicle_feature);
07c714 83                     if(feature_id == vehicle_feature.getId()) {
JK 84                         featureClicked();
57b8d3 85                     }
JK 86                 }
87                 continue;
88             }
89             
90             var vehicle_name_space = vehicle.name.indexOf(' ');
91             vehicle.line = vehicle.name.substr(0, vehicle_name_space);
92             vehicle.direction = vehicle.name.substr(vehicle_name_space+1);
93             if(special_directions[vehicle.direction]) {
94                 vehicle.line = special_directions[vehicle.direction];
95             }
96             
97             vehicle.geometry = getGeometry(vehicle);
98             vehicle.vehicle_type = parseVehicle(vehicle.id);
99             
100             if(!vehicle_feature) {
101                 vehicle_feature = new ol.Feature(vehicle);
102                 vehicle_feature.setId('v' + vehicle.id);
103                 
104                 var color_type = 'black';
105                 if(vehicle.vehicle_type) {
106                     switch(vehicle.vehicle_type.low) {
107                         case 0:
108                             color_type = 'orange';
109                             break;
110                         case 1:
111                             color_type = 'blue';
112                             break;
113                         case 2:
114                             color_type = 'green';
115                             break;
116                     }
117                 }
118                 
119                 vehicle_feature.setStyle(new ol.style.Style({
7a2fc1 120                     image: new ol.style.Icon({
JK 121                         src: 'data:image/svg+xml;base64,' + btoa('<svg xmlns="http://www.w3.org/2000/svg" height="30" width="20"><polygon points="10,0 20,23 0,23" style="fill:#3399ff;stroke:'+color_type+';stroke-width:2" /></svg>'),
57b8d3 122                         rotation: Math.PI * parseFloat(vehicle.heading) / 180.0,
JK 123                     }),
124                     text: new ol.style.Text({
125                         font: 'bold 10px sans-serif',
126                         text: vehicle.line,
127                         fill: new ol.style.Fill({color: 'white'}),
128                     }),
129                 }));
130                 vehicles_source.addFeature(vehicle_feature);
131             } else {
132                 vehicle_feature.setProperties(vehicle);
133                 vehicle_feature.getStyle().getImage().setRotation(Math.PI * parseFloat(vehicle.heading) / 180.0);
134             }
135         }
136         
137         vehicles_timer = setTimeout(function() {
138             updateVehicles();
139         }, ttss_refresh);
140     }).fail(fail_ajax);
7ca6a1 141     
JK 142     return vehicles_xhr;
57b8d3 143 }
JK 144
145 function updateStopSource(stops, prefix, source) {
146     source.clear();
147     
148     for(var i = 0; i < stops.length; i++) {
149         var stop = stops[i];
e61357 150         
JK 151         if(stop.category == 'other') continue;
152         
57b8d3 153         stop.geometry = getGeometry(stop);
JK 154         var stop_feature = new ol.Feature(stop);
155         
156         stop_feature.setId(prefix + stop.id);
157         stop_feature.setStyle(new ol.style.Style({
158             image: new ol.style.Circle({
159                 fill: new ol.style.Fill({color: 'orange'}),
160                 stroke: new ol.style.Stroke({color: 'red', width: 1}),
161                 radius: 3,
162             }),
163         }));
164         
165         source.addFeature(stop_feature);
166     }
167 }
168
169 function updateStops() {
7ca6a1 170     return $.get(
57b8d3 171         ttss_base + '/geoserviceDispatcher/services/stopinfo/stops'
JK 172             + '?left=-648000000'
173             + '&bottom=-324000000'
174             + '&right=648000000'
175             + '&top=324000000'
176     ).done(function(data) {
177         updateStopSource(data.stops, 's', stops_source);
178     }).fail(fail_ajax);
179 }
180
181 function updateStopPoints() {
7ca6a1 182     return $.get(
57b8d3 183         ttss_base + '/geoserviceDispatcher/services/stopinfo/stopPoints'
JK 184             + '?left=-648000000'
185             + '&bottom=-324000000'
186             + '&right=648000000'
187             + '&top=324000000'
188     ).done(function(data) {
189         updateStopSource(data.stopPoints, 'p', stop_points_source);
190     }).fail(fail_ajax);
7ca6a1 191 }
JK 192
8b6250 193 function vehicleTable(tripId, table) {
JK 194     if(feature_xhr) feature_xhr.abort();
195     if(feature_timer) clearTimeout(feature_timer);
196     
197     feature_xhr = $.get(
198         ttss_base + '/services/tripInfo/tripPassages'
199             + '?tripId=' + encodeURIComponent(tripId)
200             + '&mode=departure'
201     ).done(function(data) {
202         if(!data.routeName || !data.directionText || data.old.length + data.actual.length == 0) {
203             return;
204         }
205         
206         deleteChildren(table);
207         
208         for(var i = 0, il = data.old.length; i < il; i++) {
209             var tr = document.createElement('tr');
210             addCellWithText(tr, data.old[i].actualTime || data.old[i].plannedTime);
211             addCellWithText(tr, data.old[i].stop_seq_num + '. ' + data.old[i].stop.name);
212             
213             tr.className = 'active';
214             table.appendChild(tr);
215         }
216         
217         for(var i = 0, il = data.actual.length; i < il; i++) {
218             var tr = document.createElement('tr');
219             addCellWithText(tr, data.actual[i].actualTime || data.actual[i].plannedTime);
220             addCellWithText(tr, data.actual[i].stop_seq_num + '. ' + data.actual[i].stop.name);
221             
222             if(data.actual[i].status == 'STOPPING') {
223                 tr.className = 'success';
224             }
225             table.appendChild(tr);
226         }
227         
228         feature_timer = setTimeout(function() { vehicleTable(tripId, table); }, ttss_refresh);
229     }).fail(fail_ajax_popup);
230 }
231
232 function stopTable(stopType, stopId, table) {
233     if(feature_xhr) feature_xhr.abort();
234     if(feature_timer) clearTimeout(feature_timer);
235     
236     feature_xhr = $.get(
237         ttss_base + '/services/passageInfo/stopPassages/' + stopType
238             + '?' + stopType + '=' + encodeURIComponent(stopId)
239             + '&mode=departure'
240     ).done(function(data) {
241         deleteChildren(table);
242         
243         for(var i = 0, il = data.old.length; i < il; i++) {
244             var tr = document.createElement('tr');
245             addCellWithText(tr, data.old[i].patternText);
246             var dir_cell = addCellWithText(tr, data.old[i].direction);
247             var vehicle = parseVehicle(data.old[i].vehicleId);
248             dir_cell.appendChild(displayVehicle(vehicle));
249             var status = parseStatus(data.old[i]);
250             addCellWithText(tr, status);
251             addCellWithText(tr, '');
252             
253             tr.className = 'active';
254             table.appendChild(tr);
255         }
256         
257         for(var i = 0, il = data.actual.length; i < il; i++) {
258             var tr = document.createElement('tr');
259             addCellWithText(tr, data.actual[i].patternText);
260             var dir_cell = addCellWithText(tr, data.actual[i].direction);
261             var vehicle = parseVehicle(data.actual[i].vehicleId);
262             dir_cell.appendChild(displayVehicle(vehicle));
263             var status = parseStatus(data.actual[i]);
264             var status_cell = addCellWithText(tr, status);
265             var delay = parseDelay(data.actual[i]);
266             var delay_cell = addCellWithText(tr, delay);
267             
268             if(status == lang.boarding_sign) {
269                 tr.className = 'success';
270                 status_cell.className = 'status-boarding';
271             } else if(parseInt(delay) > 9) {
272                 tr.className = 'danger';
273                 delay_cell.className = 'status-delayed';
274             } else if(parseInt(delay) > 3) {
275                 tr.className = 'warning';
276             }
277             
278             table.appendChild(tr);
279         }
280         
281         feature_timer = setTimeout(function() { stopTable(stopType, stopId, table); }, ttss_refresh);
282     }).fail(fail_ajax_popup);
283 }
284
7ca6a1 285 function featureClicked(feature) {
JK 286     if(!feature) {
07c714 287         feature_id = null;
JK 288         
289         $(popup_element).removeClass('show');
7ca6a1 290         
JK 291         ignore_hashchange = true;
292         window.location.hash = '';
293         
294         return;
295     }
296     
297     var coordinates = feature.getGeometry().getCoordinates();
298     
299     deleteChildren(popup_element);
300     
8b6250 301     var close = addParaWithText(popup_element, '×');
JK 302     close.className = 'close';
303     close.addEventListener('click', function() { featureClicked(); });
304     
07c714 305     var type;
JK 306     var name = feature.get('name');
307     var additional;
8b6250 308     var table = document.createElement('table');
JK 309     var thead = document.createElement('thead');
310     var tbody = document.createElement('tbody');
311     table.appendChild(thead);
312     table.appendChild(tbody);
07c714 313     
7ca6a1 314     switch(feature.getId().substr(0, 1)) {
JK 315         case 'v':
07c714 316             type = lang.type_vehicle;
JK 317             
318             if(!feature.get('vehicle_type')) {
319                 break;
7ca6a1 320             }
07c714 321             
JK 322             var span = displayVehicle(feature.get('vehicle_type'));
323             
324             additional = document.createElement('p');
325             setText(additional, span.title);
326             additional.insertBefore(span, additional.firstChild);
8b6250 327             
JK 328             addElementWithText(thead, 'th', lang.header_time);
329             addElementWithText(thead, 'th', lang.header_stop);
330             
331             vehicleTable(feature.get('tripId'), tbody);
7ca6a1 332         break;
07c714 333         case 's':
JK 334             type = lang.type_stop;
8b6250 335             
JK 336             addElementWithText(thead, 'th', lang.header_line);
337             addElementWithText(thead, 'th', lang.header_direction);
338             addElementWithText(thead, 'th', lang.header_time);
339             addElementWithText(thead, 'th', lang.header_delay);
340             
341             stopTable('stop', feature.get('shortName'), tbody);
07c714 342         break;
JK 343         case 'p':
344             type = lang.type_stoppoint;
8b6250 345             
JK 346             additional = document.createElement('p');
347             additional.className = 'small';
348             addElementWithText(additional, 'a', lang.departures_for_stop).addEventListener(
349                 'click',
350                 function() {
351                     featureClicked(stops_source.forEachFeature(function(stop_feature) {
352                         if(stop_feature.get('shortName') == feature.get('shortName')) {
353                             return stop_feature;
354                         }
355                     }));
356                 }
357             );
358             
359             addElementWithText(thead, 'th', lang.header_line);
360             addElementWithText(thead, 'th', lang.header_direction);
361             addElementWithText(thead, 'th', lang.header_time);
362             addElementWithText(thead, 'th', lang.header_delay);
363             
364             stopTable('stopPoint', feature.get('stopPoint'), tbody);
07c714 365         break;
JK 366     }
8b6250 367     
JK 368     var loader = addElementWithText(tbody, 'td', lang.loading);
369     loader.className = 'active';
370     loader.colspan = thead.childNodes.length;
07c714 371     
JK 372     addParaWithText(popup_element, type).className = 'type';
373     addParaWithText(popup_element, name).className = 'name';
374     
375     if(additional) {
376         popup_element.appendChild(additional);
7ca6a1 377     }
JK 378     
8b6250 379     popup_element.appendChild(table);
JK 380     
7ca6a1 381     ignore_hashchange = true;
JK 382     window.location.hash = '#!' + feature.getId();
383     
07c714 384     map.getView().animate({
JK 385         center: feature.getGeometry().getCoordinates(),
386     });
387     
388     $(popup_element).addClass('show');
389     
390     feature_id = feature.getId();
7ca6a1 391 }
JK 392
393 function hash() {
394     if(ignore_hashchange) {
395         ignore_hashchange = false;
396         return;
397     }
398     
399     var tramId = null;
400     
401     var vehicleId = null;
402     var stopId = null;
403     var stopPointId = null;
404     
405     var feature = null;
406     
407     if(window.location.hash.match(/^#!t[0-9]{3}$/)) {
408         tramId = parseInt(window.location.hash.substr(3));
409     } else if(window.location.hash.match(/^#![A-Za-z]{2}[0-9]{3}$/)) {
410         tramId = parseInt(window.location.hash.substr(4));
411     } else if(window.location.hash.match(/^#!v[0-9]+$/)) {
412         vehicleId = window.location.hash.substr(3);
413     } else if(window.location.hash.match(/^#!s[0-9]+$/)) {
414         stopId = window.location.hash.substr(3);
415     } else if(window.location.hash.match(/^#!p[0-9]+$/)) {
416         stopPointId = window.location.hash.substr(3);
417     }
418     
419     if(tramId) {
420         vehicleId = tramIdToVehicleId(tramId);
421     }
422     
423     if(vehicleId) {
424         feature = vehicles_source.getFeatureById('v' + vehicleId);
425     } else if(stopId) {
426         feature = stops_source.getFeatureById('s' + stopId);
427     } else if(stopPointId) {
428         feature = stop_points_source.getFeatureById('p' + stopPointId);
429     }
430     
431     featureClicked(feature);
57b8d3 432 }
JK 433
0e60d1 434 function getDistance(c1, c2) {
JK 435     if(c1.getGeometry) {
436         c1 = c1.getGeometry().getCoordinates();
437     }
438     if(c2.getGeometry) {
439         c2 = c2.getGeometry().getCoordinates();
440     }
441     
442     var c1 = ol.proj.transform(c1, 'EPSG:3857', 'EPSG:4326');
443     var c2 = ol.proj.transform(c2, 'EPSG:3857', 'EPSG:4326');
444     return map_sphere.haversineDistance(c1, c2);
445 }
446
447 function returnClosest(point, f1, f2) {
448     if(!f1) return f2;
449     if(!f2) return f1;
450     
451     return (getDistance(point, f1) < getDistance(point, f2)) ? f1 : f2;
452 }
453
57b8d3 454 function init() {
JK 455     if(!window.jQuery) {
456         fail(lang.jquery_not_loaded);
457         return;
458     }
459     
460     $.ajaxSetup({
461         dataType: 'json',
462         timeout: 10000,
463     });
464     
465     stops_source = new ol.source.Vector({
466         features: [],
467     });
468     stops_layer = new ol.layer.Vector({
469         source: stops_source,
470     });
471     
472     stop_points_source = new ol.source.Vector({
473         features: [],
474     });
475     stop_points_layer = new ol.layer.Vector({
476         source: stop_points_source,
477         visible: false,
478     });
479     
480     vehicles_source = new ol.source.Vector({
481         features: [],
482     });
483     vehicles_layer = new ol.layer.Vector({
484         source: vehicles_source,
485     });
486     
487     map = new ol.Map({
488         target: 'map',
489         layers: [
490             new ol.layer.Tile({
491                 source: new ol.source.OSM()
492             }),
493             stops_layer,
494             stop_points_layer,
495             vehicles_layer,
496         ],
497         view: new ol.View({
498             center: ol.proj.fromLonLat([19.94, 50.06]),
499             zoom: 13
500         }),
501         controls: ol.control.defaults({
502             attributionOptions: ({
503                 collapsible: false,
504             })
505         }).extend([
506             new ol.control.Control({
507                 element: document.getElementById('title'),
508             }),
509             new ol.control.Control({
510                 element: fail_element,
511             })
512         ]),
513     });
0e60d1 514     map_sphere = new ol.Sphere(6378137);
57b8d3 515     
JK 516     // Display popup on click
517     map.on('singleclick', function(e) {
0e60d1 518         var point = e.coordinate;
57b8d3 519         var feature = map.forEachFeatureAtPixel(e.pixel, function(feature) { return feature; });
0e60d1 520         
JK 521         if(!feature) {
522             if(stops_layer.getVisible()) {
523                 feature = returnClosest(point, feature, stops_source.getClosestFeatureToCoordinate(point));
524             }
525             if(stop_points_layer.getVisible()) {
526                 feature = returnClosest(point, feature, stop_points_source.getClosestFeatureToCoordinate(point));
527             }
528             if(vehicles_layer.getVisible()) {
529                 feature = returnClosest(point, feature, vehicles_source.getClosestFeatureToCoordinate(point));
530             }
531             
532             if(getDistance(point, feature) > 200) {
533                 feature = null;
534             }
535         }
536         
7ca6a1 537         featureClicked(feature);
57b8d3 538     });
JK 539
540     // Change mouse cursor when over marker
541     map.on('pointermove', function(e) {
542         var hit = map.hasFeatureAtPixel(e.pixel);
543         var target = map.getTargetElement();
544         target.style.cursor = hit ? 'pointer' : '';
545     });
546     
547     // Change layer visibility on zoom
548     map.getView().on('change:resolution', function(e) {
549         if(map.getView().getZoom() >= 16) {
550             stops_layer.setVisible(false);
551             stop_points_layer.setVisible(true);
552         } else {
553             stops_layer.setVisible(true);
554             stop_points_layer.setVisible(false);
555         }
556     });
557     
7ca6a1 558     $.when(
JK 559         updateVehicles(),
560         updateStops(),
561         updateStopPoints()
562     ).done(function() {
563         hash();
564     });
565     
566     window.addEventListener('hashchange', hash);
57b8d3 567     
JK 568     setTimeout(function() {
569         if(vehicles_xhr) vehicles_xhr.abort();
570         if(vehicles_timer) clearTimeout(vehicles_timer);
571           
572         fail(lang.error_refresh);
573     }, 1800000);
574 }
575
576 init();