paiagram/rw_data/
qetrc.rs

1use crate::basic::*;
2use crate::intervals::*;
3use crate::lines::*;
4use crate::vehicle_set::VehicleSet;
5use crate::vehicles::*;
6use bevy::platform::collections::HashMap;
7use serde::Deserialize;
8use serde_json;
9
10#[derive(Deserialize)]
11struct RawQETRCRoot {
12    // qetrc_release: u32,
13    // qetrc_version: String,
14    #[serde(rename = "trains")]
15    services: Vec<RawQETRCService>,
16    // qETRC has the line field and the lines array, both contains line data
17    // but for some unknown(tm) reason sometimes the `lines` field is missing
18    // hence Option<T>
19    /// A single line
20    line: RawQETRCLine,
21    /// Additional lines. This field does not exist in pyETRC, only in qETRC.
22    lines: Option<Vec<RawQETRCLine>>,
23    #[serde(rename = "circuits")]
24    vehicles: Vec<RawQETRCVehicle>,
25}
26
27#[derive(Deserialize)]
28struct RawQETRCLine {
29    name: String,
30    stations: Vec<RawQETRCStation>,
31}
32
33#[derive(Deserialize)]
34struct RawQETRCStation {
35    #[serde(rename = "zhanming")]
36    name: String,
37    #[serde(rename = "licheng")]
38    distance: f32,
39}
40
41#[derive(Deserialize)]
42struct RawQETRCService {
43    #[serde(rename = "checi")]
44    service_number: Vec<String>,
45    // #[serde(rename = "type")]
46    // service_type: String,
47    timetable: Vec<RawQETRCTimetableEntry>,
48}
49
50#[derive(Deserialize)]
51struct RawQETRCTimetableEntry {
52    #[serde(rename = "ddsj")]
53    arrival: String,
54    #[serde(rename = "cfsj")]
55    departure: String,
56    #[serde(rename = "zhanming")]
57    station_name: String,
58}
59
60#[derive(Deserialize)]
61struct RawQETRCVehicle {
62    #[serde(rename = "model")]
63    make: String,
64    name: String,
65    #[serde(rename = "order")]
66    services: Vec<RawQETRCVehicleServiceEntry>,
67}
68
69#[derive(Deserialize)]
70struct RawQETRCVehicleServiceEntry {
71    #[serde(rename = "checi")]
72    service_number: String,
73}
74
75struct QETRCRoot {
76    // release: u32,
77    // version: String,
78    services: Vec<QETRCService>,
79    lines: Vec<QETRCLine>,
80    vehicles: Vec<QETRCVehicle>,
81}
82
83struct QETRCLine {
84    name: String,
85    stations: Vec<QETRCStation>,
86}
87
88struct QETRCStation {
89    name: String,
90    distance: f32,
91}
92
93struct QETRCService {
94    name: String,
95    // service_type: String,
96    timetable: Vec<QETRCTimetableEntry>,
97}
98
99impl QETRCService {
100    fn shift_time(&mut self, time: TimetableTime) {
101        self.timetable.iter_mut().for_each(|entry| {
102            entry.arrival += time;
103            entry.departure += time;
104        });
105    }
106}
107
108struct QETRCTimetableEntry {
109    arrival: TimetableTime,
110    departure: TimetableTime,
111    station_name: String,
112}
113
114struct QETRCVehicle {
115    make: String,
116    name: String,
117    services: Vec<QETRCService>,
118}
119
120impl TryFrom<RawQETRCRoot> for QETRCRoot {
121    type Error = String;
122    fn try_from(value: RawQETRCRoot) -> Result<Self, Self::Error> {
123        let mut services = HashMap::with_capacity(value.services.len());
124        for raw_service in value.services {
125            let service = QETRCService::try_from(raw_service)?;
126            services.insert(service.name.clone(), service);
127        }
128        let mut vehicles = Vec::with_capacity(value.vehicles.len());
129        for raw_vehicle in value.vehicles {
130            // consume the services that matches
131            // keep in track of the last entry
132            let mut vehicle_services = Vec::with_capacity(raw_vehicle.services.len());
133            let mut last_entry: Option<&QETRCTimetableEntry> = None;
134            for raw_service in raw_vehicle.services {
135                if let Some((_, mut service)) = services.remove_entry(&raw_service.service_number) {
136                    let current_first_entry = service.timetable.first();
137                    // if there is a last entry, and the current first entry is before it, shift
138                    if let (Some(last), Some(current_first)) = (last_entry, current_first_entry)
139                        && current_first.arrival < last.departure
140                    {
141                        // shift by 24 hours
142                        service.shift_time(TimetableTime(86400));
143                    }
144                    vehicle_services.push(service);
145                    last_entry = vehicle_services.last().and_then(|s| s.timetable.last());
146                }
147            }
148            vehicles.push(QETRCVehicle {
149                make: raw_vehicle.make,
150                name: raw_vehicle.name,
151                services: vehicle_services,
152            });
153        }
154        // make the remaining orphaned services into a vec
155        let services = services.into_values().collect::<Vec<_>>();
156        let mut lines = Vec::with_capacity(1 + value.lines.iter().len());
157        lines.push(QETRCLine::try_from(value.line)?);
158        if let Some(raw_lines) = value.lines {
159            lines.extend(
160                raw_lines
161                    .into_iter()
162                    .map(QETRCLine::try_from)
163                    .collect::<Result<Vec<_>, _>>()?,
164            );
165        }
166        Ok(QETRCRoot {
167            // release: value.qetrc_release,
168            // version: value.qetrc_version,
169            services,
170            lines,
171            vehicles,
172        })
173    }
174}
175
176impl TryFrom<RawQETRCLine> for QETRCLine {
177    type Error = String;
178    fn try_from(value: RawQETRCLine) -> Result<Self, Self::Error> {
179        Ok(QETRCLine {
180            name: value.name,
181            stations: value
182                .stations
183                .into_iter()
184                .map(QETRCStation::try_from)
185                .collect::<Result<Vec<_>, _>>()?,
186        })
187    }
188}
189
190impl TryFrom<RawQETRCStation> for QETRCStation {
191    type Error = String;
192    fn try_from(value: RawQETRCStation) -> Result<Self, Self::Error> {
193        Ok(QETRCStation {
194            name: value.name,
195            distance: value.distance,
196        })
197    }
198}
199
200impl TryFrom<RawQETRCService> for QETRCService {
201    type Error = String;
202    fn try_from(value: RawQETRCService) -> Result<Self, Self::Error> {
203        let name = value.service_number.first().cloned().unwrap_or_default();
204        let mut timetable = value
205            .timetable
206            .into_iter()
207            .map(QETRCTimetableEntry::try_from)
208            .collect::<Result<Vec<_>, _>>()?;
209        let mut last_departure = TimetableTime(0);
210        for entry in &mut timetable {
211            if entry.arrival < last_departure {
212                entry.arrival.0 += 86400;
213                entry.departure.0 += 86400;
214            }
215            if entry.arrival > entry.departure {
216                entry.arrival.0 += 86400;
217            }
218            last_departure = entry.departure;
219        }
220        Ok(QETRCService {
221            name,
222            // service_type: value.service_type,
223            timetable,
224        })
225    }
226}
227
228impl TryFrom<RawQETRCTimetableEntry> for QETRCTimetableEntry {
229    type Error = String;
230    fn try_from(value: RawQETRCTimetableEntry) -> Result<Self, Self::Error> {
231        Ok(QETRCTimetableEntry {
232            arrival: TimetableTime::from_str(&value.arrival).unwrap_or_default(),
233            departure: TimetableTime::from_str(&value.departure).unwrap_or_default(),
234            station_name: value.station_name,
235        })
236    }
237}
238
239fn parse_qetrc(json_str: &str) -> Result<QETRCRoot, String> {
240    let raw: RawQETRCRoot = serde_json::from_str(json_str).map_err(|e| e.to_string())?;
241    let qetrc_data: QETRCRoot = raw.try_into().map_err(|e: String| e.to_string())?;
242    // Adjust timetable entries to ensure strictly increasing arrival times
243    Ok(qetrc_data)
244}
245
246use super::ModifyData;
247use bevy::prelude::*;
248
249// try to parse QETRC data into bevy ECS components
250
251pub fn load_qetrc(
252    mut commands: Commands,
253    mut reader: MessageReader<ModifyData>,
254    mut existing_graph: ResMut<Graph>,
255) {
256    let mut data: Option<&str> = None;
257    for modification in reader.read() {
258        let ModifyData::LoadQETRC(d) = modification else {
259            continue;
260        };
261        data = Some(d);
262    }
263    let Some(data) = data else {
264        return;
265    };
266    let vehicle_set_entity = commands
267        .spawn((VehicleSet, Name::new("qETRC Vehicle Set")))
268        .id();
269    let now = instant::Instant::now();
270    let qetrc_data = parse_qetrc(data).map_err(|e| e.to_string()).unwrap();
271    info!("Parsed QETRC data in {:.2?}", now.elapsed());
272    let now = instant::Instant::now();
273    // dedup first, then create entities
274    // station hashmap for deduplication
275    let mut stations = std::collections::HashMap::new();
276    // graph hashmap that stores the intervals
277    // create stations and intervals from lines
278    for line in qetrc_data.lines {
279        create_line_entities(&mut commands, line, &mut stations, &mut existing_graph.0);
280    }
281    // create services and their timetables
282    // reuse the stations hashmap for looking up station entities
283    ensure_stations_exist(
284        &mut commands,
285        &qetrc_data.services,
286        &qetrc_data.vehicles,
287        &mut stations,
288    );
289    // create vehicle entities
290    for vehicle in qetrc_data.vehicles {
291        let new_vehicle_entity = create_vehicle(&mut commands, vehicle, &stations);
292        commands
293            .entity(vehicle_set_entity)
294            .add_child(new_vehicle_entity);
295    }
296    // now create the orphaned services and their timetables
297    for service in qetrc_data.services {
298        let new_vehicle_entity = create_vehicle_from_service(&mut commands, service, &stations);
299        commands
300            .entity(vehicle_set_entity)
301            .add_child(new_vehicle_entity);
302    }
303    info!("Loaded QETRC data in {:.2?}", now.elapsed());
304}
305
306fn create_vehicle(
307    commands: &mut Commands,
308    vehicle: QETRCVehicle,
309    stations: &std::collections::HashMap<String, Entity>,
310) -> Entity {
311    let vehicle_entity = commands
312        .spawn((
313            Vehicle,
314            Name::new(format!("{} [{}]", vehicle.name, vehicle.make)),
315        ))
316        .id();
317    let mut timetable_entries = Vec::new();
318    for service in vehicle.services {
319        let service_entity = commands
320            .spawn((Service { class: None }, Name::new(service.name)))
321            .id();
322        commands.entity(vehicle_entity).add_child(service_entity);
323        let service_entries = create_timetable_entries(
324            commands,
325            &service.timetable,
326            stations,
327            vehicle_entity,
328            Some(service_entity),
329        );
330        timetable_entries.extend(service_entries);
331    }
332    commands
333        .entity(vehicle_entity)
334        .insert(crate::vehicles::Schedule(
335            ScheduleStart::default(),
336            timetable_entries,
337        ));
338    vehicle_entity
339}
340
341fn create_vehicle_from_service(
342    commands: &mut Commands,
343    service: QETRCService,
344    stations: &std::collections::HashMap<String, Entity>,
345) -> Entity {
346    let vehicle_entity = commands
347        .spawn((Vehicle, Name::new(service.name.clone())))
348        .id();
349    let service_entity = commands
350        .spawn((Service { class: None }, Name::new(service.name)))
351        .id();
352    commands.entity(vehicle_entity).add_child(service_entity);
353    let timetable_entries = create_timetable_entries(
354        commands,
355        &service.timetable,
356        stations,
357        vehicle_entity,
358        Some(service_entity),
359    );
360    commands
361        .entity(vehicle_entity)
362        .insert(crate::vehicles::Schedule(
363            ScheduleStart::default(),
364            timetable_entries,
365        ));
366    vehicle_entity
367}
368
369fn create_timetable_entries(
370    commands: &mut Commands,
371    timetable: &[QETRCTimetableEntry],
372    stations: &std::collections::HashMap<String, Entity>,
373    vehicle_entity: Entity,
374    service_entity: Option<Entity>,
375) -> Vec<Entity> {
376    let mut entries = Vec::with_capacity(timetable.len());
377    for (i, entry) in timetable.iter().enumerate() {
378        let Some(&station_entity) = stations.get(&entry.station_name) else {
379            continue;
380        };
381        let timetable_entry = commands
382            .spawn(if i == 0 {
383                TimetableEntry {
384                    arrival: if entry.arrival == entry.departure {
385                        ArrivalType::Flexible
386                    } else {
387                        ArrivalType::At(entry.arrival)
388                    },
389                    departure: DepartureType::At(entry.departure),
390                    station: station_entity,
391                    service: service_entity,
392                    track: None,
393                }
394            } else {
395                TimetableEntry {
396                    arrival: ArrivalType::At(entry.arrival),
397                    departure: if entry.arrival == entry.departure {
398                        DepartureType::NonStop
399                    } else {
400                        DepartureType::At(entry.departure)
401                    },
402                    station: station_entity,
403                    service: service_entity,
404                    track: None,
405                }
406            })
407            .id();
408        entries.push(timetable_entry);
409        commands.entity(vehicle_entity).add_child(timetable_entry);
410    }
411    entries
412}
413
414fn create_line_entities(
415    commands: &mut Commands,
416    line: QETRCLine,
417    stations: &mut std::collections::HashMap<String, Entity>,
418    graph_map: &mut IntervalGraphType,
419) {
420    let mut intervals: DisplayedLineType = Vec::with_capacity(line.stations.len());
421    let Some(first_station) = line.stations.first() else {
422        commands.spawn((DisplayedLine(intervals), Name::new(line.name)));
423        return;
424    };
425    let first_entity = get_or_create_station(commands, stations, first_station);
426    intervals.push((first_entity, CanvasDistance::from_mm(0.0, 0.0)));
427    let mut prev_station = first_station;
428    let mut prev_entity = first_entity;
429    for station in line.stations.iter().skip(1) {
430        let next_entity = get_or_create_station(commands, stations, station);
431        let distance_delta = (station.distance - prev_station.distance).abs();
432        if !graph_map.contains_edge(prev_entity, next_entity) {
433            let interval_entity = commands
434                .spawn(crate::intervals::Interval {
435                    length: TrackDistance::from_km(distance_delta),
436                    speed_limit: None,
437                })
438                .id();
439            graph_map.add_edge(prev_entity, next_entity, interval_entity);
440        }
441        intervals.push((next_entity, CanvasDistance::from_mm(distance_delta, 1.0)));
442        prev_station = station;
443        prev_entity = next_entity;
444    }
445
446    commands.spawn((DisplayedLine(intervals), Name::new(line.name)));
447}
448
449fn get_or_create_station(
450    commands: &mut Commands,
451    stations: &mut std::collections::HashMap<String, Entity>,
452    station: &QETRCStation,
453) -> Entity {
454    if let Some(&entity) = stations.get(&station.name) {
455        entity
456    } else {
457        let entity = commands
458            .spawn((Station, Name::new(station.name.clone())))
459            .id();
460        stations.insert(station.name.clone(), entity);
461        entity
462    }
463}
464
465fn ensure_stations_exist(
466    commands: &mut Commands,
467    services: &[QETRCService],
468    vehicles: &[QETRCVehicle],
469    stations: &mut std::collections::HashMap<String, Entity>,
470) {
471    let mut create_station_if_needed = |station_name: &str| {
472        if stations.contains_key(station_name) {
473            return;
474        }
475        let station = commands
476            .spawn((Station, Name::new(station_name.to_string())))
477            .id();
478        stations.insert(station_name.to_string(), station);
479    };
480
481    for vehicle in vehicles.iter() {
482        for service in vehicle.services.iter() {
483            for entry in service.timetable.iter() {
484                create_station_if_needed(&entry.station_name);
485            }
486        }
487    }
488    for service in services.iter() {
489        for entry in service.timetable.iter() {
490            create_station_if_needed(&entry.station_name);
491        }
492    }
493}