paiagram/rw_data/
qetrc.rs

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