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