1use bevy::{platform::collections::HashMap, prelude::*};
2use moonshine_core::kind::*;
3use serde::Deserialize;
4use serde_json;
5
6use crate::{
7 graph::{Graph, Interval, Station as IntervalStation},
8 lines::DisplayedLine,
9 rw_data::ModifyData,
10 units::{distance::Distance, time::TimetableTime},
11 vehicles::{
12 entries::{TravelMode, VehicleSchedule},
13 services::VehicleService,
14 vehicle_set::VehicleSet,
15 },
16};
17
18#[derive(Deserialize)]
20struct Root {
21 #[serde(rename = "trains")]
25 services: Vec<Service>,
26 line: Line,
32 lines: Option<Vec<Line>>,
34 #[serde(rename = "circuits")]
38 vehicles: Vec<Vehicle>,
39}
40
41#[derive(Deserialize)]
43struct Line {
44 name: String,
46 stations: Vec<Station>,
48}
49
50#[derive(Deserialize)]
51struct Station {
52 #[serde(rename = "zhanming")]
54 name: String,
55 #[serde(rename = "licheng")]
57 distance_km: f32,
58}
59
60#[derive(Deserialize)]
61struct Service {
62 #[serde(rename = "checi")]
65 service_number: Vec<String>,
66 timetable: Vec<TimetableEntry>,
70}
71
72#[derive(Deserialize)]
73struct TimetableEntry {
74 #[serde(rename = "business")]
76 would_stop: Option<bool>,
77 #[serde(rename = "ddsj")]
79 arrival: String,
80 #[serde(rename = "cfsj")]
82 departure: String,
83 #[serde(rename = "zhanming")]
85 station_name: String,
86}
87
88#[derive(Deserialize)]
89struct Vehicle {
90 #[serde(rename = "model")]
92 make: String,
93 name: String,
95 #[serde(rename = "order")]
97 services: Vec<VehicleServiceEntry>,
98}
99
100#[derive(Deserialize)]
101struct VehicleServiceEntry {
102 #[serde(rename = "checi")]
104 service_number: String,
105}
106
107struct ProcessedEntry {
108 arrival: TimetableTime,
109 departure: TimetableTime,
110 station_entity: Instance<IntervalStation>,
111 service_entity: Instance<VehicleService>,
112}
113
114pub fn load_qetrc(
115 mut commands: Commands,
116 mut reader: MessageReader<ModifyData>,
117 mut graph: ResMut<Graph>,
118) {
119 let mut str: Option<&str> = None;
120 for modification in reader.read() {
121 match modification {
122 ModifyData::LoadQETRC(s) => str = Some(s.as_str()),
123 _ => {}
124 }
125 }
126 let Some(str) = str else {
127 return;
128 };
129 let root: Root = match serde_json::from_str(str) {
130 Ok(r) => r,
131 Err(e) => {
134 warn!("Failed to parse QETRC data: {e:?}");
135 return;
136 }
137 };
138 let lines_iter = std::iter::once(root.line).chain(root.lines.into_iter().flatten());
139 let mut station_map: HashMap<String, Instance<crate::graph::Station>> = HashMap::new();
140 fn make_station(
141 name: String,
142 commands: &mut Commands,
143 station_map: &mut HashMap<String, Instance<crate::graph::Station>>,
144 graph: &mut Graph,
145 ) -> Instance<crate::graph::Station> {
146 if let Some(&entity) = station_map.get(&name) {
147 return entity;
148 }
149 let station_entity = commands
150 .spawn(Name::new(name.clone()))
151 .insert_instance(crate::graph::Station::default())
152 .into();
153 station_map.insert(name, station_entity);
154 graph.add_node(station_entity);
155 station_entity
156 }
157 for line in lines_iter {
158 let mut entity_distances: Vec<(Instance<crate::graph::Station>, f32)> =
159 Vec::with_capacity(line.stations.len());
160 for station in line.stations {
161 let e = make_station(station.name, &mut commands, &mut station_map, &mut graph);
162 entity_distances.push((e, station.distance_km));
163 }
164 for w in entity_distances.windows(2) {
165 let [(prev, prev_d), (this, this_d)] = w else {
166 unreachable!()
167 };
168 let e1 = commands
170 .spawn_instance(Interval {
171 speed_limit: None,
172 length: Distance::from_km((this_d - prev_d).abs()),
173 })
174 .into();
175 let e2 = commands
176 .spawn_instance(Interval {
177 speed_limit: None,
178 length: Distance::from_km((this_d - prev_d).abs()),
179 })
180 .into();
181 graph.add_edge(*prev, *this, e1);
182 graph.add_edge(*this, *prev, e2);
183 }
184 let mut previous_distance_km = entity_distances.first().map_or(0.0, |(_, d)| *d);
185 for (_, distance_km) in entity_distances.iter_mut().skip(1) {
186 let current_distance_km = *distance_km;
187 *distance_km -= previous_distance_km;
188 previous_distance_km = current_distance_km;
189 }
190 commands.spawn((
192 Name::new(line.name),
193 DisplayedLine::new(entity_distances.into_iter().map(|(e, d)| (e, d)).collect()),
194 ));
195 }
196 let mut service_pool: HashMap<String, Vec<ProcessedEntry>> =
197 HashMap::with_capacity(root.services.len());
198 for service in root.services {
199 let service_name = service
200 .service_number
201 .get(0)
202 .cloned()
203 .unwrap_or("<Unnamed>".into());
204 let service_entity = commands
206 .spawn((Name::new(service_name.clone()),))
207 .insert_instance(VehicleService { class: None })
208 .into();
209 let mut processed_entries: Vec<ProcessedEntry> =
210 Vec::with_capacity(service.timetable.len());
211 for entry in service.timetable {
212 let station_entity = make_station(
213 entry.station_name,
214 &mut commands,
215 &mut station_map,
216 &mut graph,
217 );
218 let a = TimetableTime::from_str(&entry.arrival).unwrap_or_default();
219 let d = TimetableTime::from_str(&entry.departure).unwrap_or_default();
220 processed_entries.push(ProcessedEntry {
221 arrival: a,
222 departure: d,
223 station_entity,
224 service_entity,
225 });
226 }
227 service_pool.insert(service_name, processed_entries);
228 }
229 let vehicle_set_entity = commands
230 .spawn((Name::new("qETRC Vehicle Set"), VehicleSet))
231 .id();
232 for vehicle in root.vehicles {
233 let processed_entries: Vec<ProcessedEntry> = vehicle
234 .services
235 .iter()
236 .filter_map(|s| service_pool.remove(&s.service_number))
237 .flatten()
238 .collect();
239 make_vehicle(
240 format!("{} [{}]", vehicle.name, vehicle.make),
241 &mut commands,
242 processed_entries,
243 vehicle_set_entity,
244 );
245 }
246 for (service_name, entries) in service_pool {
247 make_vehicle(service_name, &mut commands, entries, vehicle_set_entity);
248 }
249}
250
251fn make_vehicle(
252 name: String,
253 commands: &mut Commands,
254 mut processed_entries: Vec<ProcessedEntry>,
255 vehicle_set_entity: Entity,
256) {
257 let vehicle_entity = commands
258 .spawn((Name::new(name), crate::vehicles::Vehicle))
259 .id();
260 commands
261 .entity(vehicle_set_entity)
262 .add_child(vehicle_entity);
263 let mut entry_entites: Vec<Entity> = Vec::with_capacity(processed_entries.len());
264 super::normalize_times(
265 processed_entries
266 .iter_mut()
267 .flat_map(|t| std::iter::once(&mut t.arrival).chain(std::iter::once(&mut t.departure))),
268 );
269 for ps in processed_entries {
270 let (arrival_mode, departure_mode) = if ps.arrival == ps.departure {
271 (TravelMode::At(ps.arrival), None)
272 } else {
273 (
274 TravelMode::At(ps.arrival),
275 Some(TravelMode::At(ps.departure)),
276 )
277 };
278 let entry_entity = commands
279 .spawn(crate::vehicles::entries::TimetableEntry {
280 arrival: arrival_mode,
281 departure: departure_mode,
282 station: ps.station_entity.entity(),
283 service: Some(ps.service_entity),
284 track: None,
285 })
286 .id();
287 commands.entity(vehicle_entity).add_child(entry_entity);
288 entry_entites.push(entry_entity);
289 }
290 commands.entity(vehicle_entity).insert(VehicleSchedule {
291 entities: entry_entites,
292 ..Default::default()
293 });
294}