Skip to content

Commit 1c1363b

Browse files
committed
update project to use GTFS data
- add support for GTFS-RT (incl. delays, speed, etc. data) - add stops - add new panel for stop times - refactor existing code - add support for custom format of AVL data - allow changing between GTFS and AVL data
1 parent 3032990 commit 1c1363b

File tree

10 files changed

+695
-409
lines changed

10 files changed

+695
-409
lines changed

data/expected_models.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1186,7 +1186,7 @@ const expected_models_per_line = [
11861186

11871187
export function is_vehicle_expected_on_line(type, route_ref, inv_number) {
11881188
if(typeof inv_number === 'string') {
1189-
inv_number = parseInt(inv_number.split('+')[0]);
1189+
inv_number = parseInt(inv_number.split('/')[0]);
11901190
}
11911191
const line = expected_models_per_line.find(line =>
11921192
line.type === type

data/models.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ const models = {
293293

294294
export function get_vehicle_model(type, inv_number) {
295295
if(typeof inv_number === 'string') {
296-
inv_number = parseInt(inv_number.split('+')[0]);
296+
inv_number = parseInt(inv_number.split('/')[0]);
297297
}
298298

299299
for (const model of models[type]) {

src/js/app.js

Lines changed: 146 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,25 @@ import { LocateControl } from 'leaflet.locatecontrol';
66
import { depots_data, get_vehicle_depot } from '/data/depots';
77
import { get_vehicle_model } from '/data/models';
88

9-
import { preprocess_vehicle, handle_tram_compositions, add_to_cache } from './cache';
10-
import { update_map_markers } from './map';
9+
import { handle_tram_compositions, add_to_cache } from './cache';
10+
import { update_map_markers, show_markers_in_view } from './map_vehicles';
11+
import { load_stops, show_stops_in_view } from './map_stops';
1112
import { WEBSOCKET_URL } from './config';
1213
import { set_route_classes, proper_inv_number, proper_inv_number_for_sorting, register_vehicle_view } from './utils';
1314
import { is_vehicle_expected_on_line } from '/data/expected_models';
1415

1516
var websocket_connection = null;
16-
var cache = [];
17+
export var cache = [];
1718

1819
function init_websocket(attempts=1) {
1920
if(websocket_connection !== null) {
2021
websocket_connection.close();
2122
websocket_connection = null;
2223
}
2324
if(attempts >= 2) {
24-
const el = document.querySelector('.container.mb-3');
25+
const el = document.querySelector('body');
2526
const alert = document.createElement('div');
26-
alert.classList.add('alert', 'alert-danger', 'text-center');
27+
alert.classList.add('alert', 'alert-danger', 'text-center', 'm-3');
2728
alert.textContent = 'Услугата е временно недостъпна. Моля опитайте по-късно.';
2829
el.innerHTML = '';
2930
el.appendChild(alert);
@@ -34,38 +35,36 @@ function init_websocket(attempts=1) {
3435
let data = JSON.parse(ev.data);
3536
const now = Date.now();
3637

37-
console.time('update cache', data.avl.length);
38+
console.time('update cache', data.length);
3839
const tables_to_update = new Set();
3940
const already_processed = new Set();
40-
for(const vehicle of data.avl) {
41-
if(already_processed.has(vehicle.vehicleId)) {
42-
continue;
41+
for(const vehicle of data) {
42+
if(!vehicle.route_ref && vehicle.cgm_route_id) {
43+
vehicle.route_ref = routes.find(r => r.cgm_id == vehicle.cgm_route_id)?.route_ref;
4344
}
44-
already_processed.add(vehicle.vehicleId);
45-
const processed = preprocess_vehicle(vehicle, now, routes);
46-
if(!processed) {
47-
continue;
45+
if(!vehicle.type && vehicle.cgm_route_id) {
46+
vehicle.type = routes.find(r => r.cgm_id == vehicle.cgm_route_id)?.type;
4847
}
49-
add_to_cache(processed, tables_to_update, cache);
48+
const fake_trolleys = ['60', '73', '74', '123', '288', '801'];
49+
if(fake_trolleys.includes(vehicle.route_ref)) {
50+
vehicle.type = 'bus';
51+
}
52+
add_to_cache(vehicle, tables_to_update, cache);
5053
}
51-
handle_tram_compositions(cache);
54+
handle_tram_compositions(cache, get_setting('data_source'));
55+
hide_inactive_vehicles();
5256
update_map_markers(cache, map);
57+
show_markers_in_view(map, vehicles_layer, cache);
5358
console.timeEnd('update cache');
54-
for(const table of tables_to_update) {
55-
if(table == '') {
56-
continue;
57-
}
58-
const [type, line] = table.split('/');
59-
update_route_table(type, line);
60-
}
59+
update_route_tables(tables_to_update);
6160
apply_filters();
6261
};
6362
websocket_connection.onerror = () => {
6463
setTimeout(() => init_websocket(attempts + 1), 1000);
6564
}
6665
}
6766

68-
var map = null;
67+
export var map = null;
6968

7069
function init_map() {
7170
map = L.map('map', {
@@ -154,7 +153,7 @@ function init_map() {
154153
new LocateControl({position: 'topright'}).addTo(map);
155154
}
156155

157-
var routes = [];
156+
export let routes = [];
158157
function init_routes_tables() {
159158
return fetch('https://raw.githubusercontent.com/Dimitar5555/sofiatraffic-schedules/refs/heads/master/data/routes.json')
160159
.then(data => data.json())
@@ -169,12 +168,6 @@ function init_routes_tables() {
169168
const tbody = generate_route_table(route.type, route.route_ref);
170169
table.appendChild(tbody);
171170
}
172-
//skip metro
173-
//fill table
174-
//section per line
175-
//also include models?
176-
//typical models?
177-
//filter/search on top by line line_ref() or by type(select) or by inv_number and type
178171
});
179172
}
180173

@@ -216,29 +209,60 @@ function init_depots() {
216209
if(!depot.hide && depot.geometry) {
217210
if(depot.geometry) {
218211
depot.polygon = polygon(depot.geometry);
219-
for(const geometry of depot.geometry) {
220-
L.polygon(geometry).addTo(map).bindPopup(depot.name);
221-
}
222212
}
223213
}
224214
});
225215
}
226216

217+
let vehicles_layer = null;
218+
export let stops_layer = null;
219+
227220
window.onload = async () => {
228221
await init_routes_tables();
229222
init_map();
230223
init_depots();
231224
init_websocket();
232225
init_selectors();
226+
init_settings();
227+
vehicles_layer = L.layerGroup().addTo(map);
228+
stops_layer = L.layerGroup().addTo(map);
229+
load_stops(stops_layer);
230+
map.on('load', () => {
231+
console.log('Fired map load');
232+
show_stops_in_view(map, stops_layer);
233+
show_markers_in_view(map, vehicles_layer, cache);
234+
});
235+
map.on('zoomend', () => {
236+
show_stops_in_view(map, stops_layer);
237+
show_markers_in_view(map, vehicles_layer, cache);
238+
});
239+
map.on('moveend', () => {
240+
show_stops_in_view(map, stops_layer);
241+
show_markers_in_view(map, vehicles_layer, cache);
242+
});
243+
244+
document.addEventListener('keyup', (e) => {
245+
if(e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') {
246+
return;
247+
}
248+
const prev_btn = document.querySelector('.bi-arrow-left')?.parentElement;
249+
const next_btn = document.querySelector('.bi-arrow-right')?.parentElement;
250+
if(e.key === 'ArrowLeft' && prev_btn && !prev_btn.hasAttribute('disabled')) {
251+
prev_btn.click();
252+
}
253+
else if(e.key === 'ArrowRight' && next_btn && !next_btn.hasAttribute('disabled')) {
254+
next_btn.click();
255+
}
256+
});
233257
};
234258

235259
function generate_route_table(type, route_ref) {
236-
let tbody = document.createElement('tbody');
260+
const tbody = document.createElement('tbody');
237261
tbody.setAttribute('id', `${type}_${route_ref}`);
238262
tbody.setAttribute('data-type', type);
239263
{
240-
let tr = document.createElement('tr');
241-
let th = document.createElement('th');
264+
const tr = document.createElement('tr');
265+
const th = document.createElement('th');
242266
set_route_classes(th, type, route_ref);
243267
th.colSpan = 2;
244268
tr.appendChild(th);
@@ -251,16 +275,18 @@ function populate_route_table(relevant_vehicles, tbody) {
251275
relevant_vehicles.sort((a, b) => proper_inv_number_for_sorting(a.inv_number)-proper_inv_number_for_sorting(b.inv_number));
252276
const tr = document.createElement('tr');
253277
const td = document.createElement('td');
278+
const btns = [];
254279
for(const vehicle of relevant_vehicles) {
255280
const btn = document.createElement('button');
256281
btn.classList.add('vehicle-btn', 'btn', 'btn-outline-dark', 'btn-sm');
257282
btn.addEventListener('click', (e) => {
258283
zoom_to_vehicle(vehicle.type, vehicle.inv_number);
259284
});
260-
const vehicle_inv_number = typeof vehicle.inv_number == 'string' ? vehicle.inv_number.split('+')[0] : vehicle.inv_number;
285+
const vehicle_inv_number = typeof vehicle.inv_number == 'string' ? vehicle.inv_number.split('/')[0] : vehicle.inv_number;
261286
const depot = get_vehicle_depot(vehicle.type, vehicle_inv_number);
287+
if(!depot) console.log(depot, vehicle.type, vehicle.inv_number);
262288
btn.setAttribute('data-depot-id', depot.id);
263-
btn.setAttribute('data-inv-number', vehicle.inv_number);
289+
btn.setAttribute('data-inv-number', vehicle.full_inv_number ?? vehicle.inv_number);
264290
if(vehicle.is_unexpected) {
265291
btn.classList.add('btn-warning');
266292
btn.classList.remove('btn-outline-dark');
@@ -273,9 +299,12 @@ function populate_route_table(relevant_vehicles, tbody) {
273299
tbody.setAttribute('data-double-decker', 'true');
274300
}
275301
btn.classList.add('text-center', 'align-middle')
276-
btn.innerText = proper_inv_number(vehicle.inv_number);
277-
td.appendChild(btn);
302+
btn.setAttribute('data-car', vehicle.car);
303+
btn.innerText = `${vehicle.car ? vehicle.car + ' / ' : ''}${proper_inv_number(vehicle.inv_number)}`;
304+
btns.push(btn);
278305
}
306+
btns.sort((a, b) => a.dataset.car - b.dataset.car);
307+
btns.forEach(btn => td.appendChild(btn));
279308
tr.appendChild(td);
280309
tbody.appendChild(tr);
281310
}
@@ -285,40 +314,92 @@ function is_screen_width_lg_or_less() {
285314
}
286315

287316
function zoom_to_vehicle(type, inv_number) {
288-
const marker = cache.find(v => v.type == type && v.inv_number == inv_number).marker;
289-
const left_panel = document.querySelector('#left_panel');
317+
const vehicle = cache.find(v => v.type === type && v.inv_number === inv_number);
318+
const marker = vehicle.marker;
319+
const vehicles_panel = document.querySelector('#vehicles-panel');
290320
if(is_screen_width_lg_or_less()){
291-
left_panel.classList.add('d-none');
321+
vehicles_panel.classList.add('d-none');
292322
}
293-
map.flyTo(marker.getLatLng(), 17, { animate: false });
323+
map.flyTo(vehicle.coords, 17, { animate: false });
294324
marker.fireEvent('click');
295325
register_vehicle_view(type, inv_number);
296326
}
327+
window.zoom_to_vehicle = zoom_to_vehicle;
297328

298-
function update_route_table(type, route_ref) {
299-
if(route_ref === 'null' || route_ref === 'undefined') {
300-
route_ref = 'outOfService';
301-
}
302-
let old_tbody = document.querySelector(`#${type}_${route_ref}`);
303-
try {
304-
let relevant_vehicles;
305-
if(route_ref != 'outOfService') {
306-
const cgm_route_id = routes.find(route => route.type == type && route.route_ref == route_ref).cgm_id;
307-
relevant_vehicles = cache.filter(vehicle => vehicle.cgm_route_id == cgm_route_id && vehicle.route_ref && !vehicle.hidden);
329+
function update_route_tables(route_tables) {
330+
for(const table of route_tables) {
331+
let [type, route_ref] = table.split('/');
332+
if(route_ref === 'undefined') {
333+
route_ref = 'outOfService';
308334
}
309-
else {
310-
relevant_vehicles = cache.filter(vehicle => vehicle.type == type && !vehicle.route_ref && !vehicle.hidden);
335+
336+
const old_tbody = document.querySelector(`#${type}_${route_ref}`);
337+
try {
338+
const cgm_route_id = routes.find(route => route.type == type && route.route_ref == route_ref).cgm_id;
339+
const relevant_vehicles = cache.filter(vehicle => vehicle.cgm_route_id == cgm_route_id && vehicle.route_ref && vehicle.hidden !== true);
340+
for(const v of relevant_vehicles) {
341+
v.is_unexpected = !is_vehicle_expected_on_line(type, route_ref, v.inv_number);
342+
}
343+
const new_tbody = old_tbody.cloneNode();
344+
new_tbody.appendChild(old_tbody.children[0]);
345+
populate_route_table(relevant_vehicles, new_tbody)
346+
old_tbody.replaceWith(new_tbody);
311347
}
312-
for(const v of relevant_vehicles) {
313-
v.is_unexpected = !is_vehicle_expected_on_line(type, route_ref, v.inv_number);
348+
catch (err){
349+
console.error(err)
350+
console.log(old_tbody, route_ref,`#${type}_${route_ref}`);
314351
}
315-
const new_tbody = old_tbody.cloneNode();
316-
new_tbody.appendChild(old_tbody.children[0]);
317-
populate_route_table(relevant_vehicles, new_tbody)
318-
old_tbody.replaceWith(new_tbody);
319352
}
320-
catch (err){
321-
console.error(err)
322-
console.log(old_tbody, route_ref,`#${type}_${route_ref}`);
353+
}
354+
355+
356+
function hide_inactive_vehicles() {
357+
const update_tables = new Set();
358+
const now = Date.now() / 1000;
359+
cache.forEach(vehicle => {
360+
if(now - vehicle.timestamp <= 120) {
361+
return;
362+
}
363+
if(vehicle.marker) {
364+
vehicle.marker.remove();
365+
vehicle.marker = null;
366+
}
367+
vehicle.hidden = true;
368+
update_tables.add(`${vehicle.type}/${vehicle.route_ref}`);
369+
});
370+
update_route_tables(update_tables);
371+
}
372+
373+
export function get_setting(key) {
374+
const defaults = {
375+
data_source: 'gtfs'
376+
};
377+
return localStorage.getItem(`livemap_${key}`) || defaults[key];
378+
}
379+
380+
function set_setting(key, value) {
381+
localStorage.setItem(`livemap_${key}`, value);
382+
}
383+
384+
function update_data_source(new_source) {
385+
const old_source = get_setting('data_source');
386+
if(new_source === old_source) {
387+
return;
323388
}
389+
set_setting('data_source', new_source);
390+
location.reload();
391+
}
392+
// window.update_data_source = update_data_source;
393+
394+
function init_settings() {
395+
const data_source = get_setting('data_source');
396+
const data_source_radios = document.querySelectorAll('input[name="positions_data_source"]');
397+
data_source_radios.forEach(radio => {
398+
radio.toggleAttribute('checked', radio.value === data_source);
399+
radio.addEventListener('change', (e) => {
400+
if(e.target.checked) {
401+
update_data_source(e.target.value);
402+
}
403+
});
404+
});
324405
}

0 commit comments

Comments
 (0)