@@ -6,24 +6,25 @@ import { LocateControl } from 'leaflet.locatecontrol';
66import { depots_data , get_vehicle_depot } from '/data/depots' ;
77import { 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' ;
1112import { WEBSOCKET_URL } from './config' ;
1213import { set_route_classes , proper_inv_number , proper_inv_number_for_sorting , register_vehicle_view } from './utils' ;
1314import { is_vehicle_expected_on_line } from '/data/expected_models' ;
1415
1516var websocket_connection = null ;
16- var cache = [ ] ;
17+ export var cache = [ ] ;
1718
1819function 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
7069function 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 = [ ] ;
158157function 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+
227220window . 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
235259function 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
287316function 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