paiagram/rw_data/
oudiasecond.rs

1use std::{any::Any, collections::BTreeMap, iter::zip};
2
3use kdl::{KdlDocument, KdlEntry, KdlNode, KdlValue};
4use pest::Parser;
5use pest_derive::Parser;
6
7#[derive(Parser)]
8#[grammar = "rw_data/oudiasecond.pest"]
9pub struct OuDiaSecondParser;
10
11#[derive(Debug)]
12enum OuDiaSecondStruct<'a> {
13    Struct(&'a str, Vec<OuDiaSecondStruct<'a>>),
14    Pair(&'a str, OuDiaSecondValue<'a>),
15}
16
17#[derive(Debug)]
18enum OuDiaSecondValue<'a> {
19    Single(&'a str),
20    List(Vec<&'a str>),
21}
22
23use pest::error::Error;
24
25use crate::{
26    basic::TimetableTime,
27    vehicle_set::VehicleSet,
28    vehicles::{ArrivalType, DepartureType, TimetableEntry},
29};
30
31fn parse_oud2_to_ast(file: &str) -> Result<OuDiaSecondStruct<'_>, Error<Rule>> {
32    let oud2 = OuDiaSecondParser::parse(Rule::file, file)?
33        .next()
34        .unwrap()
35        .into_inner()
36        .next()
37        .unwrap();
38    use pest::iterators::Pair;
39    fn parse_struct(pair: Pair<Rule>) -> OuDiaSecondStruct {
40        match pair.as_rule() {
41            Rule::r#struct => {
42                let mut inner = pair.into_inner();
43                let name = inner.next().unwrap().as_str();
44                let mut fields = Vec::new();
45                for field_pair in inner {
46                    let field_struct = parse_struct(field_pair);
47                    fields.push(field_struct);
48                }
49                OuDiaSecondStruct::Struct(name, fields)
50            }
51            Rule::wrapper => {
52                let inner = pair.into_inner();
53                let name = "file";
54                let mut fields = Vec::new();
55                for field_pair in inner {
56                    let field_struct = parse_struct(field_pair);
57                    fields.push(field_struct);
58                }
59                OuDiaSecondStruct::Struct(name, fields)
60            }
61            Rule::kvpair => {
62                let mut inner = pair.into_inner();
63                let key = inner.next().unwrap().as_str();
64                let val = inner.next().unwrap();
65                let val = match val.as_rule() {
66                    Rule::value => OuDiaSecondValue::Single(val.as_str()),
67                    Rule::list => {
68                        let list_vals = val.into_inner().map(|v| v.as_str()).collect();
69                        OuDiaSecondValue::List(list_vals)
70                    }
71                    _ => unreachable!(),
72                };
73                OuDiaSecondStruct::Pair(key, val)
74            }
75            _ => unreachable!(),
76        }
77    }
78    Ok(parse_struct(oud2))
79}
80
81struct OUD2Root {
82    file_type: String,
83    line: OUD2Line,
84}
85
86struct OUD2Line {
87    name: String,
88    // those are accessed by the index later, by the trains
89    stations: Vec<OUD2Station>,
90    // classes: Vec<OUD2Class>,
91    diagrams: Vec<OUD2Diagram>,
92}
93
94struct OUD2Station {
95    name: String,
96    branch_index: Option<usize>, // tracks
97}
98
99struct OUD2Diagram {
100    name: String,
101    services: Vec<OUD2Service>,
102    // vehicles are not yet implemented
103}
104
105struct OUD2Service {
106    reverse: bool,
107    name: String,
108    timetable: Vec<OUD2TimetableEntry>,
109}
110
111#[derive(Debug, Clone, Copy)]
112enum OUD2OperationType {
113    After,
114    Before,
115}
116
117/// How a train would service a station
118enum OUD2ServiceMode {
119    /// This train does not pass the station at all
120    NoPassing,
121    /// This train makes a stop at the station
122    Stop,
123    /// This train passes the station, but does not stop
124    NonStop,
125}
126
127struct OUD2TimetableEntry {
128    service_mode: OUD2ServiceMode,
129    arrival: ArrivalType,
130    departure: DepartureType,
131    track: Option<nonmax::NonMaxUsize>,
132    operations: Option<
133        // A station entry could optionally have a set of operations attached
134        Vec<(
135            Vec<(Option<nonmax::NonMaxUsize>, OUD2OperationType)>,
136            Vec<Vec<String>>,
137        )>,
138    >,
139}
140
141fn parse_oud2(file: &str) -> Result<OUD2Root, String> {
142    let ast = parse_oud2_to_ast(file).map_err(|e| e.to_string())?;
143    parse_root(ast)
144}
145
146fn parse_root(input: OuDiaSecondStruct) -> Result<OUD2Root, String> {
147    let input = match input {
148        OuDiaSecondStruct::Struct(_, vals) => vals,
149        _ => {
150            return Err("Invalid OuDiaSecond root structure".to_string());
151        }
152    };
153    let mut file_type: Option<String> = None;
154    let mut line: Option<Vec<OuDiaSecondStruct>> = None;
155    use OuDiaSecondStruct::*;
156    for entry in input {
157        match entry {
158            Pair("FileType", OuDiaSecondValue::Single(ft)) => {
159                if file_type.is_none() {
160                    file_type = Some(ft.to_string());
161                }
162            }
163            Struct("Rosen", val) => {
164                if line.is_none() {
165                    line = Some(val);
166                }
167            }
168            _ => {}
169        }
170        if file_type.is_some() && line.is_some() {
171            return Ok(OUD2Root {
172                file_type: file_type.unwrap(),
173                line: parse_line(line.unwrap())?,
174            });
175        }
176    }
177    Err("Failed to parse OuDiaSecond file: missing `FileType` or `Rosen` field(s)".to_string())
178}
179
180fn parse_line(input: Vec<OuDiaSecondStruct>) -> Result<OUD2Line, String> {
181    let mut name: Option<String> = None;
182    let mut stations: Vec<OUD2Station> = Vec::new();
183    let mut diagram: Vec<Vec<OuDiaSecondStruct>> = Vec::new();
184    use OuDiaSecondStruct::*;
185    for entry in input {
186        match entry {
187            Pair("Rosenmei", OuDiaSecondValue::Single(n)) => {
188                if name.is_none() {
189                    name = Some(n.to_string());
190                }
191            }
192            Struct("Eki", vals) => {
193                let mut station_name = None;
194                let mut branch_index = None;
195                for station_entry in vals {
196                    match station_entry {
197                        Pair("Ekimei", OuDiaSecondValue::Single(n)) => {
198                            station_name = Some(n.to_string());
199                        }
200                        Pair("BrunchCoreEkiIndex", OuDiaSecondValue::Single(idx)) => {
201                            branch_index = idx.parse::<usize>().ok();
202                        }
203                        _ => {}
204                    }
205                    if station_name.is_some() && branch_index.is_some() {
206                        break;
207                    }
208                }
209                stations.push(OUD2Station {
210                    name: station_name.unwrap_or_default(),
211                    branch_index,
212                });
213            }
214            Struct("Dia", vals) => {
215                info!("Found diagram with {} entries", vals.len());
216                diagram.push(vals);
217            }
218            _ => {}
219        }
220    }
221    if name.is_some() {
222        return Ok(OUD2Line {
223            name: name.unwrap(),
224            stations,
225            diagrams: diagram
226                .into_iter()
227                .map(parse_diagram)
228                .collect::<Result<Vec<_>, _>>()?,
229        });
230    }
231    Err("Failed to parse OuDiaSecond line: incomplete information".to_string())
232}
233
234fn parse_diagram(input: Vec<OuDiaSecondStruct>) -> Result<OUD2Diagram, String> {
235    let mut name: Option<String> = None;
236    let mut services: Vec<OUD2Service> = Vec::new();
237    use OuDiaSecondStruct::*;
238    for entry in input {
239        match entry {
240            Pair("DiaName", OuDiaSecondValue::Single(n)) => {
241                if name.is_none() {
242                    name = Some(n.to_string());
243                }
244            }
245            Struct("Kudari", vals) => {
246                services.extend(parse_services(vals, false)?);
247            }
248            Struct("Nobori", vals) => {
249                services.extend(parse_services(vals, true)?);
250            }
251            _ => {}
252        }
253    }
254    if name.is_some() {
255        return Ok(OUD2Diagram {
256            name: name.unwrap(),
257            services,
258        });
259    }
260    Err("Failed to parse OuDiaSecond diagram: missing name".to_string())
261}
262
263fn parse_services(
264    input: Vec<OuDiaSecondStruct>,
265    reverse: bool,
266) -> Result<Vec<OUD2Service>, String> {
267    use OuDiaSecondStruct::*;
268    let mut services = Vec::new();
269    for entry in input {
270        let Struct("Ressya", vals) = entry else {
271            continue;
272        };
273        let service = parse_service(vals, reverse)?;
274        let Some(service) = service else {
275            continue;
276        };
277        services.push(service);
278    }
279    Ok(services)
280}
281
282fn parse_service(
283    input: Vec<OuDiaSecondStruct>,
284    reverse: bool,
285) -> Result<Option<OUD2Service>, String> {
286    let mut name: Option<String> = None;
287    let mut timetable: Option<Vec<OUD2TimetableEntry>> = None;
288    let mut operations: Vec<(
289        Vec<(nonmax::NonMaxUsize, OUD2OperationType)>,
290        Vec<Vec<String>>,
291    )> = Vec::new();
292    use OuDiaSecondStruct::*;
293    for entry in input {
294        match entry {
295            Pair("Ressyabangou", OuDiaSecondValue::Single(n)) => {
296                if name.is_none() {
297                    name = Some(n.to_string());
298                }
299            }
300            Pair("EkiJikoku", v) => {
301                let times = match v {
302                    OuDiaSecondValue::Single(s) => vec![s],
303                    OuDiaSecondValue::List(l) => l,
304                };
305                if timetable.is_none() {
306                    timetable = Some(
307                        times
308                            .iter()
309                            .map(|t| parse_timetable_entry(t))
310                            .collect::<Result<_, _>>()?,
311                    );
312                }
313            }
314            Pair(n, v) if n.starts_with("Operation") => {
315                // remove the word Operation from the name
316                let tree = n
317                    .strip_prefix("Operation")
318                    .unwrap()
319                    .split(".")
320                    .map(|s| {
321                        // always in the form of <number><A|B>
322                        let (num, operation_type) = s.split_at(s.len() - 1);
323                        let operation_type = match operation_type {
324                            "A" => OUD2OperationType::After,
325                            "B" => OUD2OperationType::Before,
326                            _ => unreachable!(),
327                        };
328                        (num.parse::<nonmax::NonMaxUsize>().unwrap(), operation_type)
329                    })
330                    .collect::<Vec<_>>();
331                let ops = match v {
332                    OuDiaSecondValue::Single(s) => vec![s],
333                    OuDiaSecondValue::List(l) => l,
334                };
335                let ops = ops
336                    .iter()
337                    .map(|s| {
338                        OuDiaSecondParser::parse(Rule::event, s)
339                            .unwrap()
340                            .next()
341                            .unwrap()
342                            .into_inner()
343                            .map(|p| p.as_str().to_string())
344                            .collect::<Vec<String>>()
345                    })
346                    .collect::<Vec<_>>();
347                operations.push((tree, ops));
348            }
349            _ => {}
350        }
351    }
352    if timetable.is_some() {
353        return Ok(Some(OUD2Service {
354            reverse,
355            name: name.unwrap_or("<unnamed>".to_string()),
356            timetable: {
357                // modify the timetable with operations
358                let mut timetable = timetable.unwrap();
359                for (tree, ops) in operations {
360                    // the index is always the first element of the tree
361                    let mut tree = tree.iter();
362                    let (index, op_type) = tree.next().unwrap();
363                    let idx = nonmax::NonMaxUsize::get(index);
364                    match tree.next() {
365                        None => {
366                            // apply to single entry
367                            if let Some(entry) = timetable.get_mut(idx) {
368                                entry
369                                    .operations
370                                    .get_or_insert_default()
371                                    .push((vec![(None, *op_type)], ops));
372                            }
373                        }
374                        Some(entry) => {
375                            let mut remainder = vec![entry];
376                            remainder.extend(tree);
377                            if let Some(entry) = timetable.get_mut(idx) {
378                                entry.operations.get_or_insert_default().push((
379                                    {
380                                        let mut returned_remainder = vec![(None, *op_type)];
381                                        returned_remainder.extend(
382                                            remainder.iter().map(|(idx, op)| (Some(*idx), *op)),
383                                        );
384                                        returned_remainder
385                                    },
386                                    ops,
387                                ));
388                            }
389                        }
390                    }
391                }
392                timetable
393            },
394        }));
395    }
396    Ok(None)
397}
398
399#[inline(always)]
400fn parse_timetable_entry(input: &str) -> Result<OUD2TimetableEntry, String> {
401    let mut service_mode = OUD2ServiceMode::NoPassing;
402    let mut arrival = ArrivalType::Flexible;
403    let mut departure = DepartureType::NonStop;
404    let mut track = None;
405    if input.is_empty() {
406        return Ok(OUD2TimetableEntry {
407            service_mode,
408            arrival,
409            departure,
410            operations: None,
411            track,
412        });
413    }
414    let parsed = OuDiaSecondParser::parse(Rule::timetable_entry, input)
415        .unwrap()
416        .next()
417        .unwrap()
418        .into_inner();
419    for pair in parsed {
420        match pair.as_rule() {
421            Rule::service_mode => match pair.as_str() {
422                "0" => service_mode = OUD2ServiceMode::NoPassing,
423                "1" => service_mode = OUD2ServiceMode::Stop,
424                "2" => service_mode = OUD2ServiceMode::NonStop,
425                _ => return Err(format!("Unknown service mode: {}", pair.as_str())),
426            },
427            Rule::arrival => {
428                arrival = match TimetableTime::from_oud2_str(pair.as_str()) {
429                    Some(t) => ArrivalType::At(t),
430                    None => return Err(format!("Failed to parse arrival time: {}", pair.as_str())),
431                };
432            }
433            Rule::departure => {
434                departure = match TimetableTime::from_oud2_str(pair.as_str()) {
435                    Some(t) => DepartureType::At(t),
436                    None => {
437                        return Err(format!("Failed to parse departure time: {}", pair.as_str()));
438                    }
439                };
440            }
441            Rule::track => {
442                track = pair.as_str().parse::<nonmax::NonMaxUsize>().ok();
443            }
444            _ => unreachable!(),
445        }
446    }
447    // swap arrival and departure iff arrival is of flexible type and departure is of at type
448    match (&arrival, &departure) {
449        (ArrivalType::Flexible, DepartureType::At(time)) => {
450            arrival = ArrivalType::At(*time);
451            departure = DepartureType::Flexible;
452        }
453        _ => {}
454    }
455    Ok(OUD2TimetableEntry {
456        service_mode,
457        arrival,
458        departure,
459        operations: None,
460        track,
461    })
462}
463
464use super::ModifyData;
465use crate::basic::*;
466use crate::intervals::*;
467use crate::vehicles::*;
468use bevy::prelude::{Commands, Entity, MessageReader, Name, Res, info};
469use petgraph::graphmap::GraphMap;
470
471/// A pool of vehicles available at each station and track.
472/// This is because OuDiaSecond uses a service based model, while Paiagram uses a vehicle based model.
473/// OuDiaSecond does not keep track of which vehicle is assigned to which service directly, rather, each service would
474/// have events at different stations and tracks. This structure helps to map services to vehicles.
475#[rustfmt::skip]
476type StationVehicleSchedulePool =
477    Vec< // stations
478    Vec< // tracks
479    BTreeMap< // train components
480    TimetableTime, Vec< // The schedule. The TimetableTime is the last entry's arrival/departure time, whichever is later
481    Entity>>>> // each available vehicle entity
482;
483
484pub fn load_oud2(
485    mut commands: Commands,
486    mut reader: MessageReader<ModifyData>,
487    intervals_resource: Res<IntervalsResource>,
488) {
489    let mut data: Option<&str> = None;
490    for modification in reader.read() {
491        let ModifyData::LoadOuDiaSecond(d) = modification else {
492            continue;
493        };
494        data = Some(d);
495    }
496    let Some(data) = data else {
497        return;
498    };
499    let now = instant::Instant::now();
500    let oud2_data = parse_oud2(data).unwrap();
501    info!("Parsed OuDiaSecond data in {:?}", now.elapsed());
502    // save the kdl info to "parsed.kdl"
503    #[cfg(not(target_arch = "wasm32"))]
504    {
505        // TODO: remove this later
506        let kdl_string = make_kdl(&parse_oud2_to_ast(data).unwrap());
507        std::fs::write("parsed.kdl", kdl_string).unwrap();
508    }
509    let now = instant::Instant::now();
510    let (graph_map, stations) = make_graph_map(&mut commands, &oud2_data.line.stations);
511    commands.insert_resource(Graph(graph_map));
512    for diagram in oud2_data.line.diagrams {
513        make_vehicle_set(
514            &mut commands,
515            diagram,
516            &stations,
517            intervals_resource.default_depot,
518        );
519    }
520    info!("Loaded OUD2 data in {:?}", now.elapsed());
521}
522
523fn make_vehicle_set(
524    commands: &mut Commands,
525    diagram: OUD2Diagram,
526    stations: &Vec<Entity>,
527    depot: Entity,
528) -> Entity {
529    let vehicle_set_entity = commands.spawn((VehicleSet, Name::new(diagram.name))).id();
530    // collect, sort, then organize
531    let mut station_vehicle_schedule_pool: StationVehicleSchedulePool =
532        vec![Vec::new(); stations.len()];
533    let mut service_entities: Vec<Entity> = Vec::new();
534    for service in diagram.services.iter() {
535        let service_entity = commands
536            .spawn((Name::new(service.name.clone()), Service { class: None }))
537            .id();
538        service_entities.push(service_entity);
539        let mut service_schedule: Vec<Entity> = Vec::new();
540        let mut first_stop_info: Option<(usize, usize, TimetableTime)> = None;
541        for (entry_index, timetable_entry) in service.timetable.iter().enumerate() {
542            if matches!(timetable_entry.service_mode, OUD2ServiceMode::NoPassing) {
543                continue;
544            }
545            let station_index = if service.reverse {
546                stations.len() - 1 - entry_index
547            } else {
548                entry_index
549            };
550            let track_index = timetable_entry
551                .track
552                .and_then(|v| Some(v.get()))
553                .unwrap_or(0);
554            if first_stop_info.is_none() {
555                first_stop_info = Some((
556                    station_index,
557                    track_index,
558                    timetable_entry.arrival.time().unwrap(),
559                ))
560            }
561            let entry_entity = commands
562                .spawn(TimetableEntry {
563                    arrival: timetable_entry.arrival,
564                    departure: timetable_entry.departure,
565                    service: Some(service_entity),
566                    track: None,
567                    station: stations[station_index],
568                })
569                .id();
570            service_schedule.push(entry_entity)
571        }
572        let Some((station_index, track_index, first_stop_arrival_time)) = first_stop_info else {
573            continue;
574        };
575        let station_pool = station_vehicle_schedule_pool
576            .get_mut(station_index)
577            .unwrap();
578        while station_pool.len() <= track_index {
579            station_pool.push(BTreeMap::new())
580        }
581        station_pool[track_index].insert(first_stop_arrival_time, service_schedule);
582    }
583    info!(?station_vehicle_schedule_pool);
584    for (service, service_entity) in zip(diagram.services, service_entities) {
585        // TODO
586    }
587    vehicle_set_entity
588}
589
590/// Creates the graph map from the list of stations in the OuDiaSecond data.
591fn make_graph_map(
592    commands: &mut Commands,
593    oud2_stations: &Vec<OUD2Station>,
594) -> (IntervalGraphType, Vec<Entity>) {
595    let mut stations: Vec<Entity> = Vec::with_capacity(oud2_stations.len());
596    let mut prev_entity = None;
597    let mut graph_map: IntervalGraphType = GraphMap::new();
598    for (_ci, curr_station) in oud2_stations.iter().enumerate() {
599        let branch_index = curr_station.branch_index;
600        let station_entity;
601        station_entity = commands
602            .spawn((Name::new(curr_station.name.clone()), Station))
603            .id();
604        if let Some(prev) = prev_entity
605            && branch_index.is_none()
606        {
607            let interval_entity = commands
608                .spawn(Interval {
609                    // OuDiaSecond does not provide length or speed limit info
610                    length: TrackDistance::from_km(1),
611                    speed_limit: None,
612                })
613                .id();
614            graph_map.add_edge(prev, station_entity, interval_entity);
615        }
616        stations.push(station_entity);
617        prev_entity = Some(station_entity);
618    }
619    (graph_map, stations)
620}
621
622/// Converts the OuDiaSecond AST back into KDL format for debugging purposes.
623fn make_kdl(oud2_root: &OuDiaSecondStruct) -> String {
624    fn to_kdl_value(raw: &str) -> KdlValue {
625        KdlValue::String(raw.trim().to_string())
626    }
627
628    fn to_kdl_node(node: &OuDiaSecondStruct) -> KdlNode {
629        match node {
630            OuDiaSecondStruct::Struct(name, fields) => {
631                let mut kdl_node = KdlNode::new(*name);
632                if !fields.is_empty() {
633                    let mut children = KdlDocument::new();
634                    for field in fields {
635                        children.nodes_mut().push(to_kdl_node(field));
636                    }
637                    kdl_node.set_children(children);
638                }
639                kdl_node
640            }
641            OuDiaSecondStruct::Pair(key, value) => {
642                let mut kdl_node = KdlNode::new(*key);
643                match value {
644                    OuDiaSecondValue::Single(val) => {
645                        kdl_node.push(KdlEntry::new(to_kdl_value(val)));
646                    }
647                    OuDiaSecondValue::List(vals) => {
648                        for val in vals {
649                            kdl_node.push(KdlEntry::new(to_kdl_value(val)));
650                        }
651                    }
652                }
653                kdl_node
654            }
655        }
656    }
657
658    let mut document = KdlDocument::new();
659    document.nodes_mut().push(to_kdl_node(oud2_root));
660    document.autoformat();
661    document.to_string()
662}