From 2bc9da3893c8869468d20f6ec723ea3d96ca1ff1 Mon Sep 17 00:00:00 2001 From: Jacek Kowalski <Jacek@jacekk.info> Date: Fri, 29 May 2020 17:06:27 +0000 Subject: [PATCH] Initial version of map.js designed for EventSource/XHR pull --- index.js | 8 map.js | 523 +++++++++++++++++++++++++-------------- common.js | 149 +++++++---- lang_en.js | 3 map.html | 4 copyrights.html | 54 ++++ lang_pl.js | 3 7 files changed, 497 insertions(+), 247 deletions(-) diff --git a/common.js b/common.js index 4c7b840..af26776 100644 --- a/common.js +++ b/common.js @@ -33,13 +33,13 @@ return new Deferred(this.promise, this.request); }, done: function(func) { - return new Deferred(this.promise.then(func), this.request); + return new Deferred(this.promise.then(func.bind(this)), this.request); }, fail: function(func) { - return new Deferred(this.promise.catch(func), this.request); + return new Deferred(this.promise.catch(func.bind(this)), this.request); }, always: function(func) { - return new Deferred(this.promise.finally(func), this.request); + return new Deferred(this.promise.finally(func.bind(this)), this.request); }, }; Deferred.all = function(iterable) { @@ -55,16 +55,23 @@ var $ = { timeout: 10000, dataType: 'json', - get: function(url) { + get: function(url, headers) { var self = this; var request = new XMLHttpRequest(); var promise = new Promise(function(resolve, reject) { request.open('GET', url, true); + if(headers) { + Object.keys(headers).forEach(function (header) { + request.setRequestHeader(header, headers[header]); + }); + } request.timeout = self.timeout; request.onreadystatechange = function() { if(this.readyState == 4) { - if(this.status == 200) { - if(self.dataType == 'json') { + if(this.status == 304) { + resolve(); + } else if(this.status == 200) { + if(self.dataType === 'json') { resolve(JSON.parse(this.responseText)); } else { resolve(this.responseText); @@ -88,8 +95,6 @@ var script_version; var script_version_xhr; -var vehicles_info = {}; - function checkVersion() { if(script_version_xhr) script_version_xhr.abort(); @@ -111,6 +116,23 @@ function checkVersionInit() { checkVersion(); setInterval(checkVersion, 3600000); +} + +/********** + * ARRAYS * + **********/ + +function deepMerge(a1, a2) { + if(typeof a1 !== 'object' || typeof a2 !== 'object') { + return a2; + } + Object.keys(a2).forEach(function (key) { + a1[key] = deepMerge(a1[key], a2[key]); + if(a1[key] === null) { + delete a1[key]; + } + }); + return a1; } @@ -141,6 +163,69 @@ deleteChildren(element); element.appendChild(document.createTextNode(text)); } + + +/***************** + * VEHICLES INFO * + *****************/ + +function VehiclesInfo() { + this.data = {}; + this.watchers = []; +} +VehiclesInfo.prototype = { + update: function () { + return $.get( + 'https://mpk.jacekk.net/vehicles/' + ).done(function(data) { + this.data = data; + this.watchers.forEach(function(watcher) { + watcher(this); + }); + }.bind(this)); + }, + addWatcher: function(callback) { + this.watchers.push(callback); + }, + + get: function(vehicleId) { + if(!vehicleId) return false; + if(typeof this.data[vehicleId] === "undefined") { + return false; + } + return this.data[vehicleId]; + }, + getParsed: function (vehicleId) { + var vehicle = this.get(vehicleId); + if(!vehicle) return false; + return { + vehicleId: vehicleId, + prefix: vehicle['num'].substr(0, 2), + id: vehicle['num'].substr(2, 3), + num: vehicle['num'], + type: vehicle['type'], + low: vehicle['low'] + }; + }, + depotIdToVehicleId: function(depotId, typeHelper) { + var prop; + depotId = depotId.toString(); + if(typeHelper) { + for(prop in this.data) { + if(prop.substr(0,1) === typeHelper && this.data[prop]['num'].substr(2) === depotId) { + return prop; + } + } + } else { + for(prop in this.data) { + if(this.data[prop]['num'] === depotId) { + return prop; + } + } + } + }, +}; +var vehicles_info = new VehiclesInfo(); /*********** @@ -194,48 +279,6 @@ return lang.time_minutes_prefix + ((actual.getTime() - planned.getTime()) / 1000 / 60) + lang.time_minutes_suffix; } -function parseVehicle(vehicleId) { - if(!vehicleId) return false; - if(!vehicles_info || !vehicles_info[vehicleId]) { - return false; - } else { - var vehicle = vehicles_info[vehicleId]; - return { - vehicleId: vehicleId, - prefix: vehicle['num'].substr(0, 2), - id: vehicle['num'].substr(2, 3), - num: vehicle['num'], - type: vehicle['type'], - low: vehicle['low'] - }; - } -} - -function updateVehicleInfo() { - return $.get( - 'https://mpk.jacekk.net/vehicles/' - ).done(function(data) { - vehicles_info = data; - }); -} - -function depotIdToVehicleId(depotId, typeHelper) { - var prop; - if(typeHelper) { - for(prop in vehicles_info) { - if(prop.substr(0,1) == typeHelper && vehicles_info[prop]['num'].substr(2) == depotId) { - return prop; - } - } - } else { - for(prop in vehicles_info) { - if(vehicles_info[prop]['num'] == depotId) { - return prop; - } - } - } -} - function displayVehicle(vehicleInfo) { if(!vehicleInfo) return document.createTextNode(''); @@ -243,13 +286,13 @@ span.className = 'vehicleInfo'; var floor_type = ''; - if(vehicleInfo.low == 0) { + if(vehicleInfo.low === 0) { setText(span, lang.high_floor_sign); floor_type = lang.high_floor; - } else if(vehicleInfo.low == 1) { + } else if(vehicleInfo.low === 1) { setText(span, lang.partially_low_floor_sign); floor_type = lang.partially_low_floor; - } else if(vehicleInfo.low == 2) { + } else if(vehicleInfo.low === 2) { setText(span, lang.low_floor_sign); floor_type = lang.low_floor; } diff --git a/copyrights.html b/copyrights.html new file mode 100644 index 0000000..c6fad94 --- /dev/null +++ b/copyrights.html @@ -0,0 +1,54 @@ +<!DOCTYPE html> +<title>TTSS - prawa autorskie i pochodzenie informacji</title> +<meta charset="utf-8" /> + +<p>Wykonanie strony: Jacek Kowalski</p> + +<p> + Kod źródłowy aplikacji można znaleźć pod adresem: + <a href="https://git.jacekk.net/project/ttss">https://git.jacekk.net/project/ttss</a>, + a także w serwisie GitHub: + <a href="https://github.com/search?q=user%3Ajacekkow+ttss">https://github.com/search?q=user%3Ajacekkow+ttss</a> +</p> + +<p> + Dane na stronie pochodzą z serwerów i serwisów Zarządu Transportu Publicznego w Krakowie + i/lub Miejskiego Przedsiębiorstwa Komunikacyjnego SA w Krakowie: +</p> + +<ul> + <li><a href="ftp://ztp.krakow.pl/pliki-gtfs/">ftp://ztp.krakow.pl/pliki-gtfs/</a></li> + <li><a href="http://www.ttss.krakow.pl/internetservice/">http://www.ttss.krakow.pl/internetservice/</a></li> + <li><a href="http://ttss.mpk.krakow.pl/internetservice/">http://ttss.mpk.krakow.pl/internetservice/</a> </li> +</ul> + +<p>Dane są przetwarzane zgodnie z ustawą o ponownym wykorzystaniu informacji sektora publicznego + oraz <a href="https://www.bip.krakow.pl/?dok_id=48482">warunkami Gminy Miejskiej Kraków</a>.</p> + +<p> + Z zastrzeżeniem dostępności wskazanych wyżej usług i problemów technicznych, dane są przetwarzane na bieżąco. + Dokładne informacje o czasie ostatniego dostępu do i przetworzenia danych o pojazdach znajdują się pod adresami: +</p> + +<ul> + <li><a href="https://ttss.pl/vehicles/vehicles_A.html">https://ttss.pl/vehicles/vehicles_A.html</a> - dla autobusów,</li> + <li><a href="https://ttss.pl/vehicles/vehicles_T.html">https://ttss.pl/vehicles/vehicles_T.html</a> - dla tramwajów.</li> +</ul> + +<p>a także w nagłówku Etag bądź w polu id każdej odpowiedzi z danymi, w których znajduje się Unix timestamp oznaczający czas wytworzenia.</p> + +<p>Gmina Miejska Kraków nie ponosi odpowiedzialności za:</p> +<ul> + <li> + szkody spowodowane pozyskaniem informacji sektora publicznego lub ponownym wykorzystywaniem + informacji sektora publicznego, zamieszczonej na stronach BIP MK lub w innym miejskim serwisie internetowym, + udostępnianej na wniosek lub pozyskanej w inny sposób, wykorzystywanej ponownie z naruszeniem warunków + udostępniania lub ponownego wykorzystywania informacji sektora publicznego;</li> + <li> + szkody spowodowane przez dalsze udostępnienie informacji sektora publicznego przez podmioty + ponownie ją wykorzystujące z naruszeniem przepisów prawa powszechnie obowiązującego, w tym dalsze + udostępnianie informacji sektora publicznego z naruszeniem przepisów regulujących ich ochronę + m.in. przepisów ustawy o prawie autorskim i prawach pokrewnych, ustawy o ochronie baz danych, + przepisów o ochronie danych osobowych, ustawy o ochronie informacji niejawnych itd. + </li> +</ul> diff --git a/index.js b/index.js index f496856..2927c12 100644 --- a/index.js +++ b/index.js @@ -195,7 +195,7 @@ var candidate; for(var i = 0; i < stop_name_autocomplete.options.length; i++) { candidate = stop_name_autocomplete.options[i].value; - if(candidate.substr(0, 1) != prefix && candidate.substr(1) == stop) { + if(candidate.substr(0, 1) !== prefix && candidate.substr(1) == stop) { alternative_stop = candidate; break; } @@ -215,7 +215,7 @@ if(alternative_stop !== null) { var a = addParaWithText(times_alerts, ''); - a = addElementWithText(a, 'a', (prefix == 'b' ? lang.departures_for_trams : lang.departures_for_buses)); + a = addElementWithText(a, 'a', (prefix === 'b' ? lang.departures_for_trams : lang.departures_for_buses)); a.href = ''; a.onclick = function(e) { e.preventDefault(); @@ -235,7 +235,7 @@ tr = document.createElement('tr'); addCellWithText(tr, all_departures[i].patternText); dir_cell = addCellWithText(tr, all_departures[i].direction); - vehicle = parseVehicle(prefix + all_departures[i].vehicleId); + vehicle = vehicles_info.getParsed(prefix + all_departures[i].vehicleId); dir_cell.appendChild(displayVehicle(vehicle)); addCellWithText(tr, (vehicle ? vehicle.num : '')).className = 'vehicleData'; status = parseStatus(all_departures[i]); @@ -419,7 +419,7 @@ setText(vehicle_data_style, '.vehicleData { display: table-cell; }') }); - updateVehicleInfo() + vehicles_info.update(); hash(); diff --git a/lang_en.js b/lang_en.js index db691a6..bf8f333 100644 --- a/lang_en.js +++ b/lang_en.js @@ -91,10 +91,13 @@ help_hover_for_more: 'Hover the icon for more details.', help_source: 'Source', help_license: 'License', + help_data_attribution: 'Data: <a href="copyrights.html">ZTP w Krakowie</a>', + help_website_attribution: 'Strona: <a href="copyrights.html">Jacek Kowalski</a>', error_title: 'Error occured!', error_request_failed: 'Internet request failed.', error_request_failed_status: 'Internet request failed with error: $status.', + error_request_failed_no_data: 'No data on the server.', error_request_failed_connectivity: 'Request failed - please check your network connectivity.', error_new_version: 'Website has been updated, reloading...', error_refresh: 'Error! Refresh the page to update information.', diff --git a/lang_pl.js b/lang_pl.js index 9682cd6..32b5aaf 100644 --- a/lang_pl.js +++ b/lang_pl.js @@ -91,10 +91,13 @@ help_hover_for_more: 'Najedź na ikonę, by uzyskać więcej informacji.', help_source: 'Kod źródłowy', help_license: 'Licencja', + help_data_attribution: 'Dane: <a href="copyrights.html">ZTP w Krakowie</a>', + help_website_attribution: 'Strona: <a href="copyrights.html">Jacek Kowalski</a>', error_title: 'Wystąpił błąd!', error_request_failed: 'Wykonanie żądania internetowego nie udało się.', error_request_failed_status: 'Wykonanie żądania internetowego nie udało się. Błąd: $status.', + error_request_failed_no_data: 'Brak danych na serwerze.', error_request_failed_connectivity: 'Wykonanie żądania internetowego nie udało się - sprawdź połączenie z siecią.', error_new_version: 'Strona została zaktualizowana, przeładowuję...', error_refresh: 'Błąd! Odśwież stronę, by zaktualizować dane.', diff --git a/map.html b/map.html index 597a563..e929fe3 100644 --- a/map.html +++ b/map.html @@ -4,7 +4,7 @@ <title>TTSS Kraków - Mapa</title> <meta charset="utf-8" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width" /> -<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v5.3.0/css/ol.css" integrity="sha384-C7SzZySesoxngSK5V0BaD1DUap0LPZGWZpnXQGoIwvBXFc8G21y4s1QYvyr84FNa" crossorigin="anonymous"> +<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/css/ol.css" integrity="sha384-6Ku0bdGS1If4LlZPWpe2wTx/qu/Y+YJob6lvk4Yi49uWWFqASeOF130VK3KjIPgh" crossorigin="anonymous"> <link rel="stylesheet" href="map.css?v6" type="text/css" /> <link rel="manifest" href="map.manifest" /> </head> @@ -19,7 +19,7 @@ </div> <div id="panel"></div> <script src="https://polyfill.io/v3/polyfill.min.js?features=Array.prototype.forEach,Array.prototype.includes,Array.prototype.map,Element.prototype.classList,Promise,String.prototype.startsWith,XMLHttpRequest,requestAnimationFrame"></script> -<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v5.3.0/build/ol.js" integrity="sha384-iQkGyyH4ioz3m+maM3s9MX1Oq67mACa4B9Z3ovUv3Sv37LJ96fx3WnZfLoiC3Wfl" crossorigin="anonymous"></script> +<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.2.1/build/ol.js" integrity="sha384-Dg6Qd9Ovz0HgMsjQJjA+5upeLnRSrseIbJbT86LMVpuzuLfzffIleOxTCHIAUp6y" crossorigin="anonymous"></script> <script tyle="text/javascript" src="lang_pl.js?v10" id="lang_script"></script> <script tyle="text/javascript" src="common.js?v11"></script> <script tyle="text/javascript" src="map.js?v25"></script> diff --git a/map.js b/map.js index 6f628c9..4eb5861 100644 --- a/map.js +++ b/map.js @@ -1,7 +1,7 @@ 'use strict'; -var ttss_refresh = 10000; // 10 seconds -var ttss_position_type = 'RAW'; +var api_refresh = 10000; // 10 seconds +var api_poll_url = 'http://127.0.0.1/sub'; var geolocation = null; var geolocation_set = 0; @@ -11,15 +11,9 @@ var geolocation_source = null; var geolocation_layer = null; -var vehicles_xhr = {}; -var vehicles_timer = {}; -var vehicles_last_update = {}; -var vehicles_source = {}; -var vehicles_layer = {}; +var vehicles = {}; +var hash = null; -var vehicles_info = {}; - -var stops_xhr = null; var stops_ignored = ['131', '744', '1263', '3039']; var stops_style = { 'sb': new ol.style.Style({ @@ -74,8 +68,6 @@ var fail_element = document.getElementById('fail'); var fail_text = document.querySelector('#fail span'); - -var ignore_hashchange = false; function Panel(element) { @@ -212,8 +204,7 @@ this.timeout = setTimeout(this.find.bind(this), 100); }, open: function(panel) { - ignore_hashchange = true; - window.location.hash = '#!f'; + setHash('f'); panel.show(this.div, this.close.bind(this)); this.input.focus(); @@ -223,6 +214,227 @@ }, }; +function Vehicles(prefix) { + this.prefix = prefix; + this.source = new ol.source.Vector({ + features: [], + }); + this.layer = new ol.layer.Vector({ + source: this.source, + }); +} +Vehicles.prototype = { + prefix: '', + + layer: null, + source: null, + + lastUpdate: 0, + xhr: null, + es: null, + + selectedFeatureId: null, + deselectCallback: null, + + style: function(feature, clicked) { + var color_type = 'black'; + + var vehicleType = vehicles_info.getParsed(feature.getId()); + if(vehicleType) { + switch(vehicleType.low) { + case 0: + color_type = 'orange'; + break; + case 1: + case 2: + color_type = 'green'; + break; + } + } + + var fill = '#B70'; + if(this.prefix === 'b') { + fill = '#05B'; + } + if(clicked) { + fill = '#922'; + } + + 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>'; + + feature.setStyle(new ol.style.Style({ + image: new ol.style.Icon({ + src: 'data:image/svg+xml;base64,' + btoa(image), + imgSize: [20,30], + rotation: Math.PI * feature.get('angle') / 180.0, + }), + text: new ol.style.Text({ + font: 'bold 10px sans-serif', + // TODO: special directions + // vehicle.line = vehicle.name.substr(0, vehicle_name_space); + // vehicle.direction = normalizeName(vehicle.name.substr(vehicle_name_space+1)); + // if(special_directions[vehicle.direction]) { + // vehicle.line = special_directions[vehicle.direction]; + // } + text: feature.get('name').substr(0, feature.get('name').indexOf(' ')), + fill: new ol.style.Fill({color: 'white'}), + }), + })); + }, + select: function(feature, callback) { + if(feature instanceof ol.Feature) { + feature = feature.getId(); + } + feature = this.source.getFeatureById(feature); + if(!feature) { + this.deselect(); + return; + } + this.style(feature, true); + + this.selectedFeatureId = feature.getId(); + this.deselectCallback = callback; + }, + deselect: function() { + if(!this.selectedFeatureId) return false; + var feature = this.source.getFeatureById(this.selectedFeatureId); + this.style(feature); + + this._internalDeselect(); + }, + _internalDeselect: function() { + var callback = this.deselectCallback; + this.deselectCallback = null; + this.selectedFeatureId = null; + if(callback) callback(); + }, + + typesUpdated: function() { + this.source.forEachFeature(function (feature) { + this.style(feature); + }.bind(this)); + }, + + _newFeature: function(id, data) { + var feature = new ol.Feature(); + feature.setId(this.prefix + id); + feature.setProperties(data); + feature.setGeometry(getGeometryPair(feature.get('pos'))); + this.style(feature); + return feature; + }, + loadFullData: function(data) { + var features = []; + for(var id in data) { + features.push(this._newFeature(id, data[id])); + } + this.source.clear(); + this.source.addFeatures(features); + + if(this.selectedFeatureId) { + this.select(this.selectedFeatureId); + } + }, + loadDiffData: function(data) { + for(var id in data) { + var feature = this.source.getFeatureById(this.prefix + id); + var vehicle = data[id]; + + // TODO: handle vehicleInfo updates + + if(vehicle === null) { + if(feature) { + this.source.removeFeature(feature); + if (this.selectedFeatureId === feature.getId()) { + this._internalDeselect(); + } + } + } else if(feature) { + var isPosModified = false; + Object.keys(vehicle).forEach(function (key) { + feature.set(key, deepMerge(feature.get(key), vehicle[key])); + if(key === 'pos') { + feature.setGeometry(getGeometryPair(feature.get('pos'))); + } else if (key === 'angle') { + feature.getStyle().getImage().setRotation(Math.PI * parseFloat(vehicle.angle ? vehicle.angle : 0) / 180.0); + } else if (key === 'name') { + // TODO: Special directions + feature.getStyle().getText().setText(vehicle.name.substr(0, vehicle.name.indexOf(' '))); + } + }); + } else { + this.source.addFeature(this._newFeature(id, data[id])); + } + } + }, + + fetch: function() { + var self = this; + var result = this.fetchFull(); + + // TODO: XHR only as fallback + result.done(function() { + setTimeout(self.fetchDiff.bind(self), 1); + }); + + // TODO: updates (EventSource) + // TODO: error handling (reconnect) + // TODO: error handling (indicator) + + return result; + }, + fetchFull: function() { + var self = this; + this.xhr = $.get( + api_poll_url + '?id=' + this.prefix + '-full' + ).done(function(data) { + try { + self.lastUpdate = this.request.getResponseHeader('Etag'); + self.loadFullData(data); + } catch(e) { + console.log(e); + throw e; + } + }).fail(this.failXhr.bind(this)); + return this.xhr; + }, + fetchDiff: function() { + var self = this; + this.xhr = $.get( + api_poll_url + '?id=' + this.prefix + '-diff', + {'If-None-Match': this.lastUpdate} + ).done(function(data) { + try { + if(this.request.status == 304) { + setTimeout(self.fetchDiff.bind(self), 1000); + return; + } + self.lastUpdate = this.request.getResponseHeader('Etag'); + self.loadDiffData(data); + setTimeout(self.fetchDiff.bind(self), 1); + } catch(e) { + console.log(e); + throw e; + } + }).fail(this.failXhr.bind(this)); + return this.xhr; + }, + + failXhr: function(result) { + // abort() is not a failure + if(result.readyState === 0) return; + + if(result.status === 0) { + fail(lang.error_request_failed_connectivity, result); + } else if (result.status === 304) { + fail(lang.error_request_failed_no_data, result); + } if (result.statusText) { + fail(lang.error_request_failed_status.replace('$status', result.statusText), result); + } else { + fail(lang.error_request_failed, result); + } + }, +}; function fail(msg) { setText(fail_text, msg); @@ -250,46 +462,11 @@ fail_ajax_generic(data, panel.fail.bind(panel)); } -function getGeometry(object) { - return new ol.geom.Point(ol.proj.fromLonLat([object.longitude / 3600000.0, object.latitude / 3600000.0])); +function getGeometryPair(pair) { + return new ol.geom.Point(ol.proj.fromLonLat(pair)); } - -function styleVehicle(vehicle, selected) { - var color_type = 'black'; - if(vehicle.get('vehicle_type')) { - switch(vehicle.get('vehicle_type').low) { - case 0: - color_type = 'orange'; - break; - case 1: - case 2: - color_type = 'green'; - break; - } - } - - var fill = '#B70'; - if(vehicle.getId().startsWith('b')) { - fill = '#05B'; - } - if(selected) { - fill = '#922'; - } - - 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>'; - - vehicle.setStyle(new ol.style.Style({ - image: new ol.style.Icon({ - src: 'data:image/svg+xml;base64,' + btoa(image), - imgSize: [20,30], - rotation: Math.PI * parseFloat(vehicle.get('heading') ? vehicle.get('heading') : 0) / 180.0, - }), - text: new ol.style.Text({ - font: 'bold 10px sans-serif', - text: vehicle.get('line'), - fill: new ol.style.Fill({color: 'white'}), - }), - })); +function getGeometry(object) { + return getGeometryPair([object.longitude / 3600000.0, object.latitude / 3600000.0]); } function markStops(stops, ttss_type, routeStyle) { @@ -326,65 +503,9 @@ function unstyleSelectedFeatures() { stop_selected_source.clear(); route_source.clear(); - if(feature_clicked && ttss_types.includes(feature_clicked.getId().substr(0, 1))) { - styleVehicle(feature_clicked); - } -} - -function updateVehicles(prefix) { - if(vehicles_timer[prefix]) clearTimeout(vehicles_timer[prefix]); - if(vehicles_xhr[prefix]) vehicles_xhr[prefix].abort(); - vehicles_xhr[prefix] = $.get( - ttss_urls[prefix] + '/geoserviceDispatcher/services/vehicleinfo/vehicles' - + '?positionType=' + ttss_position_type - + '&colorType=ROUTE_BASED' - + '&lastUpdate=' + encodeURIComponent(vehicles_last_update[prefix]) - ).done(function(data) { - vehicles_last_update[prefix] = data.lastUpdate; - - for(var i = 0; i < data.vehicles.length; i++) { - var vehicle = data.vehicles[i]; - - var vehicle_feature = vehicles_source[prefix].getFeatureById(prefix + vehicle.id); - if(vehicle.isDeleted || !vehicle.latitude || !vehicle.longitude) { - if(vehicle_feature) { - vehicles_source[prefix].removeFeature(vehicle_feature); - if(feature_clicked && feature_clicked.getId() === vehicle_feature.getId()) { - panel.close(); - } - } - continue; - } - - var vehicle_name_space = vehicle.name.indexOf(' '); - vehicle.line = vehicle.name.substr(0, vehicle_name_space); - vehicle.direction = normalizeName(vehicle.name.substr(vehicle_name_space+1)); - if(special_directions[vehicle.direction]) { - vehicle.line = special_directions[vehicle.direction]; - } - - vehicle.geometry = getGeometry(vehicle); - vehicle.vehicle_type = parseVehicle(prefix + vehicle.id); - - if(!vehicle_feature) { - vehicle_feature = new ol.Feature(vehicle); - vehicle_feature.setId(prefix + vehicle.id); - - styleVehicle(vehicle_feature); - vehicles_source[prefix].addFeature(vehicle_feature); - } else { - vehicle_feature.setProperties(vehicle); - vehicle_feature.getStyle().getImage().setRotation(Math.PI * parseFloat(vehicle.heading ? vehicle.heading : 0) / 180.0); - vehicle_feature.getStyle().getText().setText(vehicle.line); - } - } - - vehicles_timer[prefix] = setTimeout(function() { - updateVehicles(prefix); - }, ttss_refresh); - }).fail(fail_ajax); - - return vehicles_xhr[prefix]; + ttss_types.forEach(function(type) { + vehicles[type].deselect(); + }); } function updateStopSource(stops, prefix) { @@ -394,7 +515,7 @@ for(var i = 0; i < stops.length; i++) { stop = stops[i]; - if(stop.category == 'other') continue; + if(stop.category === 'other') continue; if(stops_ignored.includes(stop.shortName)) continue; stop.geometry = getGeometry(stop); @@ -467,7 +588,7 @@ feature_xhr = $.get( ttss_urls[ttss_type] + '/services/tripInfo/tripPassages' - + '?tripId=' + encodeURIComponent(feature.get('tripId')) + + '?tripId=' + encodeURIComponent(feature.get('trip')) + '&mode=departure' ).done(function(data) { if(typeof data.old === "undefined" || typeof data.actual === "undefined") { @@ -506,7 +627,7 @@ markStops(stopsToMark, ttss_type, true); - feature_timer = setTimeout(function() { vehicleTable(feature, table); }, ttss_refresh); + feature_timer = setTimeout(function() { vehicleTable(feature, table); }, api_refresh); }).fail(fail_ajax_popup); return feature_xhr; } @@ -528,7 +649,7 @@ tr = document.createElement('tr'); addCellWithText(tr, all_departures[i].patternText); dir_cell = addCellWithText(tr, normalizeName(all_departures[i].direction)); - vehicle = parseVehicle(all_departures[i].vehicleId); + vehicle = vehicles_info.getParsed(all_departures[i].vehicleId); dir_cell.appendChild(displayVehicle(vehicle)); status = parseStatus(all_departures[i]); status_cell = addCellWithText(tr, status); @@ -550,7 +671,7 @@ table.appendChild(tr); } - feature_timer = setTimeout(function() { stopTable(stopType, stopId, table, ttss_type); }, ttss_refresh); + feature_timer = setTimeout(function() { stopTable(stopType, stopId, table, ttss_type); }, api_refresh); }).fail(fail_ajax_popup); return feature_xhr; } @@ -592,7 +713,7 @@ } // Vehicle else if(ttss_types.includes(type)) { - styleVehicle(feature, true); + vehicles[type].select(feature); var span = displayVehicle(feature.get('vehicle_type')); @@ -684,24 +805,19 @@ if(tabular_data) { div.appendChild(table); - ignore_hashchange = true; - window.location.hash = '#!' + feature.getId(); + hash.set(feature.getId()); } showOnMapFunction(); panel.show(div, function() { - if(!ignore_hashchange) { - ignore_hashchange = true; - window.location.hash = ''; - - unstyleSelectedFeatures(); - feature_clicked = null; - - if(path_xhr) path_xhr.abort(); - if(feature_xhr) feature_xhr.abort(); - if(feature_timer) clearTimeout(feature_timer); - } + hash.set(''); + + unstyleSelectedFeatures(); + + if(path_xhr) path_xhr.abort(); + if(feature_xhr) feature_xhr.abort(); + if(feature_timer) clearTimeout(feature_timer); }); feature_clicked = feature; @@ -769,8 +885,8 @@ } }); ttss_types.forEach(function(type) { - if(vehicles_layer[type].getVisible()) { - feature = returnClosest(point, feature, vehicles_source[type].getClosestFeatureToCoordinate(point)); + if(vehicles[type].layer.getVisible()) { + feature = returnClosest(point, feature, vehicles[type].source.getClosestFeatureToCoordinate(point)); } }); @@ -807,44 +923,76 @@ } } - -function hash() { - if(ignore_hashchange) { - ignore_hashchange = false; - return; - } - - var feature = null; - var vehicleId = null; - var stopId = null; - - if(window.location.hash.match(/^#!t[0-9]{3}$/)) { - vehicleId = depotIdToVehicleId(window.location.hash.substr(3), 't'); - } else if(window.location.hash.match(/^#!b[0-9]{3}$/)) { - vehicleId = depotIdToVehicleId(window.location.hash.substr(3), 'b'); - } else if(window.location.hash.match(/^#![A-Za-z]{2}[0-9]{3}$/)) { - vehicleId = depotIdToVehicleId(window.location.hash.substr(2)); - } else if(window.location.hash.match(/^#!v-?[0-9]+$/)) { - vehicleId = 't' + window.location.hash.substr(3); - } else if(window.location.hash.match(/^#![tb]-?[0-9]+$/)) { - vehicleId = window.location.hash.substr(2); - } else if(window.location.hash.match(/^#![sp]-?[0-9]+$/)) { - stopId = window.location.hash.substr(2,1) + 't' + window.location.hash.substr(3); - } else if(window.location.hash.match(/^#![sp][tb]-?[0-9]+$/)) { - stopId = window.location.hash.substr(2); - } else if(window.location.hash.match(/^#!f$/)) { - find.open(panel); - return; - } - - if(vehicleId) { - feature = vehicles_source[vehicleId.substr(0, 1)].getFeatureById(vehicleId); - } else if(stopId) { - feature = stops_source[stopId.substr(0,2)].getFeatureById(stopId); - } - - featureClicked(feature); +function Hash() { } +Hash.prototype = { + _ignoreChange: false, + + _set: function(id) { + var value = '#!' + id; + if(value !== window.location.hash) { + window.location.hash = value; + return true; + } + return false; + }, + _updateOld: function() { + if(window.location.hash.match(/^#!t[0-9]{3}$/)) { + this.go(depotIdToVehicleId(window.location.hash.substr(3), 't')); + } else if(window.location.hash.match(/^#!b[0-9]{3}$/)) { + this.go(depotIdToVehicleId(window.location.hash.substr(3), 'b')); + } else if(window.location.hash.match(/^#![A-Za-z]{2}[0-9]{3}$/)) { + this.go(depotIdToVehicleId(window.location.hash.substr(2))); + } else if(window.location.hash.match(/^#!v-?[0-9]+$/)) { + this.go('t' + window.location.hash.substr(3)); + } + }, + ready: function() { + this._updateOld(); + this.changed(); + window.addEventListener('hashchange', this.changed, false); + }, + go: function(id) { + this._ignoreChange = false; + return this._set(id); + }, + set: function(id) { + this._ignoreChange = true; + return this._set(id); + }, + changed: function() { + if(this._ignoreChange) { + this._ignoreChange = false; + return false; + } + + var feature = null; + var vehicleId = null; + var stopId = null; + + if(window.location.hash.match(/^#![tb]-?[0-9]+$/)) { + vehicleId = window.location.hash.substr(2); + } else if(window.location.hash.match(/^#![sp]-?[0-9]+$/)) { + stopId = window.location.hash.substr(2,1) + 't' + window.location.hash.substr(3); + } else if(window.location.hash.match(/^#![sp][tb]-?[0-9]+$/)) { + stopId = window.location.hash.substr(2); + } else if(window.location.hash.match(/^#!f$/)) { + find.open(panel); + return; + } + + if(vehicleId) { + vehicles[vehicleId.substr(0, 1)].select(vehicleId); + return true; + } else if(stopId) { + feature = stops_source[stopId.substr(0,2)].getFeatureById(stopId); + } + + featureClicked(feature); + + return true; + }, +}; function getDistance(c1, c2) { if(c1.getGeometry) { @@ -854,8 +1002,8 @@ c2 = c2.getGeometry().getCoordinates(); } - var c1 = ol.proj.transform(c1, 'EPSG:3857', 'EPSG:4326'); - var c2 = ol.proj.transform(c2, 'EPSG:3857', 'EPSG:4326'); + c1 = ol.proj.transform(c1, 'EPSG:3857', 'EPSG:4326'); + c2 = ol.proj.transform(c2, 'EPSG:3857', 'EPSG:4326'); return ol.sphere.getDistance(c1, c2); } @@ -871,6 +1019,7 @@ find = new Find(); route_source = new ol.source.Vector({ + attributions: [lang.help_data_attribution], features: [], }); route_layer = new ol.layer.Vector({ @@ -901,14 +1050,7 @@ }); ttss_types.forEach(function(type) { - vehicles_source[type] = new ol.source.Vector({ - features: [], - }); - vehicles_layer[type] = new ol.layer.Vector({ - source: vehicles_source[type], - renderMode: 'image', - }); - vehicles_last_update[type] = 0; + vehicles[type] = new Vehicles(type); }); geolocation_feature = new ol.Feature({ @@ -961,11 +1103,15 @@ geolocation_button.addEventListener('click', trackingToggle); document.getElementById('find').addEventListener('click', find.open.bind(find, panel)); - + + var pixelRatio = ol.has.DEVICE_PIXEL_RATIO > 1 ? 2 : 1; var layers = [ new ol.layer.Tile({ - source: new ol.source.OSM({ - url: 'https://tiles.ttss.pl/{z}/{x}/{y}.png', + source: new ol.source.XYZ({ + attributions: [ol.source.OSM.ATTRIBUTION], + url: 'https://tiles.ttss.pl/x' + pixelRatio + '/{z}/{x}/{y}.png', + maxZoom: 19, + tilePixelRatio: pixelRatio, }), }), route_layer, @@ -976,7 +1122,7 @@ }); layers.push(stop_selected_layer); ttss_types.forEach(function(type) { - layers.push(vehicles_layer[type]); + layers.push(vehicles[type].layer); }); map = new ol.Map({ target: 'map', @@ -1031,17 +1177,18 @@ change_resolution(); var future_requests = [ - updateVehicleInfo(), + vehicles_info.update(), ]; ttss_types.forEach(function(type) { - future_requests.push(updateVehicles(type)); + vehicles_info.addWatcher(vehicles[type].typesUpdated.bind(vehicles[type])); + future_requests.push(vehicles[type].fetch()); }); stops_type.forEach(function(type) { future_requests.push(updateStops(type.substr(0,1), type.substr(1,1))); }); - Deferred.all(future_requests).done(hash); - window.addEventListener('hashchange', hash); + hash = new Hash(); + Deferred.all(future_requests).done(hash.ready.bind(hash)); setTimeout(function() { ttss_types.forEach(function(type) { -- Gitblit v1.9.1