1use futures::StreamExt;
2use gtfs_structures::Gtfs;
3use rand::Rng;
4use reqwest::Client;
5use serde::Deserialize;
6use serde::Serialize;
7use std::error::Error;
8
9use chrono::{DateTime, Local, LocalResult, Offset, TimeZone, Utc};
10use chrono_tz::Tz;
11
12#[derive(Debug, Serialize, Deserialize, Clone)]
13pub struct Parcour {
14 #[serde(rename = "noParcours")]
15 no_parcours: String,
16 #[serde(rename = "codeDirectionPrincipale")]
17 code_direction_principale: String,
18 #[serde(rename = "codeDirectionRetour")]
19 code_direction_retour: String,
20}
21
22#[derive(Debug, Serialize, Deserialize, Clone)]
23pub struct PositionDeBus {
24 #[serde(rename = "idAutobus")]
25 id_autobus: i64,
26 latitude: f64,
27 longitude: f64,
28 #[serde(rename = "idVoyage")]
29 id_voyage: String,
30 #[serde(rename = "dateMiseJour")]
31 date_mise_jour: String,
32}
33
34#[derive(Debug, Serialize, Deserialize, Clone)]
35pub struct ArretDansPointTemporel {
36 #[serde(rename = "noArret")]
37 no_arret: String,
38 nom: String,
39 description: String,
40 latitude: f64,
41 longitude: f64,
42 accessible: bool,
43}
44
45#[derive(Debug, Serialize, Deserialize, Clone)]
46pub struct PointTemporelDansVoyage {
47 horaire: String,
48 #[serde(rename = "horaireMinutes")]
49 horaire_minutes: i64,
50 ntr: bool,
51 arret: ArretDansPointTemporel,
52}
53
54pub async fn obtenir_la_liste_des_itinéraires(
68 client: Client,
69) -> Result<Vec<Parcour>, Box<dyn Error + Send + Sync>> {
70 let parcours_url =
71 "https://wsmobile.rtcquebec.ca/api/v3/horaire/ListeParcours?source=appmobileios";
72
73 let response = client
74 .get(parcours_url)
75 .header(
76 "User-Agent",
77 "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0",
78 )
79 .send()
80 .await?;
81
82 let parcours_texte = response.text().await?;
83
84 let parcours: Result<Vec<Parcour>, serde_json::Error> =
85 serde_json::from_str::<Vec<Parcour>>(&parcours_texte);
86
87 if let Err(parcours) = &parcours {
88 eprintln!("{:#?} parcours error from {}", parcours, parcours_texte);
89 }
90
91 let parcours = parcours?;
92
93 Ok(parcours)
94}
95
96pub async fn obtenir_liste_horaire_de_autobus(
97 id_voyage: &str,
98 id_autobus: i64,
99 client: Client,
100) -> Result<Vec<PointTemporelDansVoyage>, Box<dyn Error + Send + Sync>> {
101 let url = format!(
102 "https://wsmobile.rtcquebec.ca/api/v3/horaire/ListeHoraire_Autobus?source=appmobileios&idVoyage={}&idAutobus={}\"",
103 id_voyage,
105 id_autobus
106 );
107
108 let response = client
109 .get(&url)
110 .header(
111 "User-Agent",
112 "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0",
113 )
114 .send()
115 .await?;
116
117 let statut = response.status();
118
119 let horaires_texte = response.text().await?;
120
121 if statut.is_success() {
122 if horaires_texte == "[]" {
125 let response = client
126 .get(&url)
127 .header(
128 "User-Agent",
129 "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0",
130 )
131 .send()
132 .await?;
133
134 let horaires: Vec<PointTemporelDansVoyage> = serde_json::from_str(&horaires_texte)?;
135 Ok(horaires)
136 } else {
137 let horaires: Vec<PointTemporelDansVoyage> = serde_json::from_str(&horaires_texte)?;
138 Ok(horaires)
139 }
140 } else {
141 eprintln!(
142 "{} {} -> {:?} {}",
143 id_voyage, id_autobus, statut, horaires_texte
144 );
145
146 Err(format!(
147 "Échec de la récupération de l'horaire pour le voyage {} autobus {}: statut {}",
148 id_voyage, id_autobus, statut
149 )
150 .into())
151 }
152}
153
154pub async fn positions(
155 route: &str,
156 direction: &str,
157 client: Client,
158) -> Result<Vec<PositionDeBus>, Box<dyn Error + Send + Sync>> {
159 let url = format!(
160 "https://wssiteweb.rtcquebec.ca/api/v3//horaire/ListeAutobus_Parcours/?source=appmobileios&noParcours={route}&codeDirection={direction}"
161 );
162
163 let response = client
164 .get(&url)
165 .header(
166 "User-Agent",
167 "Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0",
168 )
169 .send()
170 .await?;
171
172 let positions_texte = response.text().await?;
173
174 let positions: Vec<PositionDeBus> = serde_json::from_str(&positions_texte)?;
175
176 Ok(positions)
177}
178
179#[derive(Clone, Debug)]
180pub struct ResponseGtfsRt {
181 pub vehicles: Option<gtfs_realtime::FeedMessage>,
182 pub voyages: Option<gtfs_realtime::FeedMessage>,
183 pub alertes: Option<gtfs_realtime::FeedMessage>,
184}
185
186pub async fn faire_les_donnees_gtfs_rt(
187 gtfs: &Gtfs,
188 client: Client,
189 proxy_urls: Option<&Vec<String>>,
190) -> Result<ResponseGtfsRt, Box<dyn Error + Send + Sync>> {
191 let start_timer = std::time::Instant::now();
192
193 let mut route_id_to_trips: std::collections::HashMap<String, Vec<String>> =
194 std::collections::HashMap::new();
195
196 for (trip_id, trip) in gtfs.trips.iter() {
197 if trip.service_id.contains("multint") {
198 continue;
199 }
200
201 if let Some(trips) = route_id_to_trips.get_mut(&trip.route_id) {
202 trips.push(trip_id.clone());
203 } else {
204 route_id_to_trips.insert(trip.route_id.clone(), vec![trip_id.clone()]);
205 }
206 }
207
208 let end_timer = start_timer.elapsed();
209
210 println!("route_id_to_trips: {:?}", end_timer);
211
212 let mut parcours: Option<_> = None;
213 let mut tries = 0;
214
215 while tries < 10 {
216 let parcours_t = obtenir_la_liste_des_itinéraires(client.clone()).await;
217
218 if let Err(p_err) = &parcours_t {
219 eprintln!("{:#?}", p_err);
220 }
221
222 let is_success = parcours_t.is_ok();
223
224 parcours = Some(parcours_t);
225
226 if is_success {
227 break;
228 } else {
229 }
230
231 tries += 1;
232 }
233
234 let parcours = parcours.unwrap();
235 let parcours = parcours?;
236
237 let mut pos_requests = vec![];
238
239 for parcour in parcours.iter() {
240 let route_id = &parcour.no_parcours;
241 let direction_principale = &parcour.code_direction_principale;
242 let direction_retour = &parcour.code_direction_retour;
243
244 for direction in [direction_principale, direction_retour] {
245 pos_requests.push({
246 let client = match proxy_urls {
247 Some(proxy_urls) => {
248 let proxy_url = &proxy_urls[rand::rng().random_range(0..proxy_urls.len())];
249 Client::builder()
250 .proxy(reqwest::Proxy::http(proxy_url).unwrap())
251 .timeout(std::time::Duration::from_secs(10))
252 .build()
253 .unwrap()
254 }
255 None => client.clone(),
256 };
257 let route_id = route_id.clone();
258 let direction = direction.clone();
259
260 async move {
261 let pos_req = positions(route_id.as_str(), direction.as_str(), client).await;
262
263 match pos_req {
264 Ok(positions) => Ok((route_id, direction, positions)),
265 Err(e) => Err(e),
266 }
267 }
268 });
269 }
270 }
271
272 let time_pos_requests = std::time::Instant::now();
273 let pos_requests_buffered = futures::stream::iter(pos_requests)
274 .buffer_unordered(64)
275 .collect::<Vec<Result<_, _>>>()
276 .await;
277 println!(
278 "temps écoulé pour demander des positions: {:?}",
279 time_pos_requests.elapsed()
280 );
281
282 let pos_requests_buffered = pos_requests_buffered
284 .into_iter()
285 .flatten()
286 .collect::<Vec<_>>();
287
288 let vec_voyage_et_autobus = pos_requests_buffered
289 .iter()
290 .map(|(_, _, positions)| {
291 positions
292 .iter()
293 .map(|position| (position.id_voyage.clone(), position.id_autobus.clone()))
294 })
295 .flatten()
296 .collect::<Vec<_>>();
297
298 let mut horaires_requests = vec![];
299
300 for (id_voyage, id_autobus) in vec_voyage_et_autobus.iter() {
301 let client = match proxy_urls {
302 Some(proxy_urls) => {
303 let proxy_url = &proxy_urls[rand::rng().random_range(0..proxy_urls.len())];
304 Client::builder()
305 .proxy(reqwest::Proxy::http(proxy_url).unwrap())
306 .timeout(std::time::Duration::from_secs(10))
307 .build()
308 .unwrap()
309 }
310 None => client.clone(),
311 };
312 let id_voyage = id_voyage.clone();
313 let id_autobus = id_autobus.clone();
314
315 horaires_requests.push(async move {
316 let mut horaires_req = None;
317 let mut tries = 0;
318
319 while tries < 5 {
320 horaires_req = Some(
321 obtenir_liste_horaire_de_autobus(
322 id_voyage.as_str(),
323 id_autobus,
324 client.clone(),
325 )
326 .await,
327 );
328
329 if horaires_req.as_ref().unwrap().is_ok() {
330 break;
331 }
332
333 tries += 1;
334 }
335
336 let horaires_req = horaires_req.unwrap();
337
338 match horaires_req {
339 Ok(horaires) => Ok((id_voyage.clone(), id_autobus, horaires)),
340 Err(e) => Err(e),
341 }
342 });
343 }
344
345 let time_horaires_requests = std::time::Instant::now();
346
347 let horaires_requests_buffered = futures::stream::iter(horaires_requests)
348 .buffer_unordered(64)
349 .collect::<Vec<Result<_, _>>>()
350 .await;
351
352 for h in horaires_requests_buffered.iter() {
353 if let Err(e) = h {
354 eprintln!("échec de la récupération des données horaires: {:?}", e);
355 }
356 }
357
358 let horaires = horaires_requests_buffered
359 .into_iter()
360 .flatten()
361 .collect::<Vec<_>>();
362
363 let mut horaires_hashtable = std::collections::HashMap::new();
364
365 for (id_voyage, id_autobus, horaires) in horaires {
366 horaires_hashtable.insert((id_voyage, id_autobus), horaires);
367 }
368
369 println!(
370 "il est temps de récupérer les heures: {:?}",
371 time_horaires_requests.elapsed()
372 );
373
374 let mut gtfs_vehicles = gtfs_realtime::FeedMessage {
377 header: gtfs_realtime::FeedHeader {
378 gtfs_realtime_version: "2.0".to_string(),
379 incrementality: None,
380 timestamp: Some(Utc::now().timestamp() as u64),
381 feed_version: None,
382 },
383 entity: vec![],
384 };
385
386 let mut gtfs_trips = gtfs_realtime::FeedMessage {
387 header: gtfs_realtime::FeedHeader {
388 gtfs_realtime_version: "2.0".to_string(),
389 incrementality: None,
390 timestamp: Some(Utc::now().timestamp() as u64),
391 feed_version: None,
392 },
393 entity: vec![],
394 };
395
396 for (parcours_id, direction, positions) in pos_requests_buffered {
397 let gtfs_parcours_id = format!("1-{}", parcours_id);
398
399 let voyages_possibles_en_gtfs = route_id_to_trips.get(gtfs_parcours_id.as_str());
400
401 let mut trip_id_to_start_and_end_time: Vec<(
402 String,
403 chrono::DateTime<chrono_tz::Tz>,
404 chrono::DateTime<chrono_tz::Tz>,
405 )> = vec![];
406
407 if let Some(voyages_possibles_en_gtfs) = voyages_possibles_en_gtfs {
408 for trip_id in voyages_possibles_en_gtfs {
409 let gtfs_trip = gtfs.trips.get(trip_id).unwrap();
410
411 let service_id = >fs_trip.service_id;
412
413 let service_date = gtfs.calendar_dates.get(service_id);
414
415 if let Some(service_date) = service_date {
416 if let Some(calendar_first) = service_date.get(0) {
417 let calendar_first = calendar_first.date;
418 let reference_midnight = calc_reference_midnight(calendar_first);
419
420 let trip_start_time_relative =
423 gtfs_trip.stop_times[0].departure_time.unwrap();
424 let trip_start_time = reference_midnight
425 + std::time::Duration::from_secs(trip_start_time_relative as u64);
426
427 let trip_end_time = reference_midnight
428 + std::time::Duration::from_secs(
429 gtfs_trip.stop_times.last().unwrap().departure_time.unwrap() as u64,
430 );
431
432 trip_id_to_start_and_end_time.push((
433 trip_id.clone(),
434 trip_start_time,
435 trip_end_time,
436 ));
437 }
438 }
439 }
440
441 trip_id_to_start_and_end_time.sort_by_key(|(_, start_time, _)| *start_time);
442
443 let trip_id_to_start_and_end_time = trip_id_to_start_and_end_time;
444
445 for position in positions.iter().filter(|x| x.id_voyage != "0") {
446 let id_voyage = &position.id_voyage;
447 let id_autobus = position.id_autobus;
448
449 let mut trip_descriptor: Option<gtfs_realtime::TripDescriptor> = None;
450
451 let mut gtfs_rt_horaire_list: Option<
452 Vec<gtfs_realtime::trip_update::StopTimeUpdate>,
453 > = None;
454
455 let horaires = horaires_hashtable.get(&(id_voyage.clone(), id_autobus));
456
457 if let Some(horaires) = horaires {
458 if horaires.len() >= 1 {
459 gtfs_rt_horaire_list = Some(
462 horaires
463 .iter()
464 .filter(|horaire| horaire.horaire_minutes != -1)
465 .map(|horaire| {
466 let stop_id = format!("1-{}", horaire.arret.no_arret);
467 let arrival_time = None;
468 let departure_time =
469 chrono::DateTime::parse_from_rfc3339(&horaire.horaire)
470 .unwrap();
471
472 let departure =
473 Some(gtfs_realtime::trip_update::StopTimeEvent {
474 delay: None,
475 time: Some(departure_time.timestamp()),
476 uncertainty: None,
477 scheduled_time: None,
478 });
479
480 gtfs_realtime::trip_update::StopTimeUpdate {
481 stop_sequence: None,
482 stop_id: Some(stop_id),
483 arrival: arrival_time,
484 departure: departure,
485 departure_occupancy_status: None,
486 schedule_relationship: None,
487 stop_time_properties: None,
488 }
489 })
490 .collect::<Vec<_>>(),
491 );
492
493 let current_date_montreal = chrono::Utc::now()
494 .with_timezone(&chrono_tz::America::Montreal)
495 .naive_local();
496
497 let voyages_gtfs_possibles_en_gtfs_pour_cette_voyage_rtc =
498 voyages_possibles_en_gtfs
499 .iter()
500 .filter(|trip_id| {
501 let split = trip_id.split('_').collect::<Vec<_>>();
502
503 if split.len() != 2 {
504 return false;
505 }
506
507 let naive_date = split[1].replace("daily", "");
508
509 if naive_date.is_empty() {
510 return false;
511 }
512
513 if let Ok(date) =
514 chrono::NaiveDate::parse_from_str(&naive_date, "%Y%m%d")
515 {
516 let diff = current_date_montreal
518 .date()
519 .signed_duration_since(date);
520 diff.num_days().abs() < 2
521 } else {
522 false
523 }
524 })
525 .filter(|trip_id| {
526 let trip = gtfs.trips.get(*trip_id);
527
528 let last_stop_gtfs =
529 trip.unwrap().stop_times.last().unwrap().stop.clone();
530
531 format!("1-{}", horaires.last().unwrap().arret.no_arret)
532 == last_stop_gtfs.id
533 })
534 .map(|x| x.to_string())
535 .collect::<Vec<String>>();
536
537 let bien_horaires = horaires
540 .iter()
541 .filter(|x| x.horaire_minutes != -1)
542 .collect::<Vec<_>>();
543
544 let trip_id_to_start_and_end_time_for_this_voyage = match bien_horaires
545 .is_empty()
546 {
547 true => vec![],
548 false => trip_id_to_start_and_end_time
549 .iter()
550 .filter(|(trip_id, _, _)| {
551 voyages_gtfs_possibles_en_gtfs_pour_cette_voyage_rtc
552 .contains(trip_id)
553 })
554 .filter(|(_, scheduled_start_time, _)| {
555 let iso_time = chrono::DateTime::parse_from_rfc3339(
556 bien_horaires[0].horaire.as_str(),
557 )
558 .unwrap();
559
560 let diff = scheduled_start_time.signed_duration_since(iso_time);
561
562 diff.num_seconds().abs() < 60 * 60 * 3
563 })
564 .collect::<Vec<_>>(),
565 };
566
567 let mut diffs = trip_id_to_start_and_end_time_for_this_voyage
568 .iter()
569 .map(|(trip_id, start_time, end_time)| {
570 let iso_time = chrono::DateTime::parse_from_rfc3339(
571 bien_horaires[bien_horaires.len() - 1].horaire.as_str(),
572 )
573 .unwrap();
574
575 let diff = end_time.signed_duration_since(iso_time);
576
577 (trip_id, diff)
578 })
579 .collect::<Vec<_>>();
580
581 diffs.sort_by_key(|(_, diff)| diff.num_seconds().abs());
583
584 if diffs.len() >= 1 {
585 if diffs[0].1.num_seconds().abs() > 60 * 60 * 2 {
586 println!(
587 "parcours: {} voyage: {}, autobus: {}, heure de départ de l'autobus: {}",
588 parcours_id, id_voyage, id_autobus, diffs[0].1
589 );
590 }
591
592 let starting_time = trip_id_to_start_and_end_time
593 .iter()
594 .find(|(trip_id, _, _)| trip_id == diffs[0].0)
595 .unwrap()
596 .1;
597
598 let starting_time =
599 starting_time.with_timezone(&chrono_tz::America::Montreal);
600
601 trip_descriptor = Some(gtfs_realtime::TripDescriptor {
616 trip_id: Some(diffs[0].0.clone()),
617 route_id: Some(gtfs_parcours_id.clone()),
618 direction_id: None,
619 start_time: None,
620 schedule_relationship: None,
621 modified_trip: None,
622 start_date: None,
623 });
624 } else {
625 eprintln!(
626 "trip_id_to_start_time_for_this_voyage est vide!!!! parcours: {} voyage: {}, autobus: {}, {:#?}",
627 parcours_id,
628 id_voyage,
629 id_autobus,
630 trip_id_to_start_and_end_time_for_this_voyage
631 );
632
633 trip_descriptor = Some(gtfs_realtime::TripDescriptor {
634 trip_id: None,
635 route_id: Some(gtfs_parcours_id.clone()),
636 direction_id: None,
637 start_time: None,
638 schedule_relationship: None,
639 modified_trip: None,
640 start_date: None,
641 });
642 }
643 }
644 } else {
645 println!(
646 "No stop times in rtc quebec! parcours: {} voyage: {}, no autobus {}",
647 parcours_id, id_voyage, id_autobus
648 );
649 }
650
651 let current_vehicle_updated_time = &position.date_mise_jour;
654 let split_d_t = current_vehicle_updated_time.split('T').collect::<Vec<_>>();
655
656 let split_date = split_d_t[0].split('-').collect::<Vec<_>>();
657 let split_time = split_d_t[1].split(':').collect::<Vec<_>>();
658
659 let y = split_date[0].parse::<i32>().unwrap();
660 let m = split_date[1].parse::<u32>().unwrap();
661 let d = split_date[2].parse::<u32>().unwrap();
662
663 let h = split_time[0].parse::<u32>().unwrap();
664 let min = split_time[1].parse::<u32>().unwrap();
665 let s = split_time[2].parse::<u32>().unwrap();
666
667 let chrono_time = chrono::NaiveDateTime::new(
668 chrono::NaiveDate::from_ymd_opt(y, m, d).unwrap(),
669 chrono::NaiveTime::from_hms_opt(h, min, s).unwrap(),
670 )
671 .and_local_timezone(chrono_tz::America::Montreal);
672
673 let find_nearest_chrono_time =
674 get_nearest_tz_from_local_result(chrono_time).unwrap();
675
676 let autobus_nouvelle_id = id_autobus - 10000;
677
678 let vehicle = Some(gtfs_realtime::VehicleDescriptor {
679 id: Some(autobus_nouvelle_id.to_string()),
680 label: Some(autobus_nouvelle_id.to_string()),
681 license_plate: None,
682 wheelchair_accessible: None,
683 });
684
685 let v = gtfs_realtime::VehiclePosition {
686 trip: trip_descriptor.clone(),
687 vehicle: vehicle.clone(),
688 position: Some(gtfs_realtime::Position {
689 latitude: position.latitude as f32,
690 longitude: position.longitude as f32,
691 bearing: None,
692 odometer: None,
693 speed: None,
694 }),
695 current_stop_sequence: None,
696 stop_id: None,
697 current_status: None,
698 timestamp: Some(find_nearest_chrono_time.timestamp() as u64),
699 congestion_level: None,
700 occupancy_status: None,
701 occupancy_percentage: None,
702 multi_carriage_details: vec![],
703 };
704
705 let id = format!("{}-{}", parcours_id, id_autobus);
706
707 if let Some(trip_descriptor) = trip_descriptor {
708 if let Some(gtfs_rt_horaire_list) = gtfs_rt_horaire_list {
709 let t = gtfs_realtime::TripUpdate {
710 trip: trip_descriptor,
711 vehicle: vehicle,
712 stop_time_update: gtfs_rt_horaire_list,
713 timestamp: None,
714 delay: None,
715 trip_properties: None,
716 };
717
718 gtfs_trips.entity.push(gtfs_realtime::FeedEntity {
719 id: id.clone(),
720 is_deleted: None,
721 trip_update: Some(t),
722 vehicle: None,
723 alert: None,
724 stop: None,
725 shape: None,
726 trip_modifications: None,
727 });
728 }
729 }
730
731 gtfs_vehicles.entity.push(gtfs_realtime::FeedEntity {
732 id: id.clone(),
733 is_deleted: None,
734 trip_update: None,
735 vehicle: Some(v),
736 alert: None,
737 stop: None,
738 shape: None,
739 trip_modifications: None,
740 });
741 }
742 }
743 }
744
745 Ok(ResponseGtfsRt {
746 vehicles: Some(gtfs_vehicles),
747 voyages: Some(gtfs_trips),
748 alertes: None,
749 })
750}
751
752fn get_nearest_tz_from_local_result(
753 local_result: LocalResult<chrono::DateTime<chrono_tz::Tz>>,
754) -> Option<chrono::DateTime<chrono_tz::Tz>> {
755 match local_result {
756 LocalResult::Single(tz) => Some(tz),
757 LocalResult::Ambiguous(tz1, tz2) => {
758 let time = Utc::now();
759
760 let offset1_sec = tz1.signed_duration_since(&time);
761 let offset2_sec = tz2.signed_duration_since(&time);
762
763 let diff1 = (offset1_sec).abs();
764 let diff2 = (offset2_sec).abs();
765
766 if diff1 <= diff2 { Some(tz1) } else { Some(tz2) }
767 }
768 LocalResult::None => None,
769 }
770}
771
772fn calc_reference_midnight(x: chrono::NaiveDate) -> chrono::DateTime<chrono_tz::Tz> {
773 let midday = x
774 .and_hms_opt(12, 0, 0)
775 .unwrap()
776 .and_local_timezone(chrono_tz::America::Montreal)
777 .unwrap();
778
779 midday - std::time::Duration::from_secs(43200)
781}
782
783#[cfg(test)]
784mod tests {
785 use super::*;
786 use std::io::Write;
787
788 const HTTP_PROXY_ADDRESSES: &[&str] = &[
789 "4.239.94.226:3128",
790 "149.56.24.51:3128",
791 "16.52.47.20:3128",
792 "51.79.80.224:3535",
793 "167.114.98.246:9595",
794 "15.223.57.73:3128",
795 "35.183.180.110:3128",
796 "158.69.59.135:80",
797 "69.70.244.34:80",
798 "23.132.28.133:3128",
799 "204.83.205.117:3128",
800 "74.48.160.189:3128",
801 "142.93.202.130:3128",
802 "23.227.38.125:80",
803 "23.227.39.52:80",
804 "23.227.38.128:80",
805 "23.227.39.65:80",
806 ];
807
808 #[tokio::test]
809 async fn tour_de_test_complet() {
810 let client = Client::new();
811 let url = "https://github.com/catenarytransit/rtc_quebec_proxy_zip/raw/refs/heads/main/rtcquebec_latest.zip";
815
816 let telechargement = std::time::Instant::now();
817
818 let mut response = client.get(url).send().await.unwrap();
819
820 println!(
821 "Téléchargement terminé, temps écoulé pour télécharger: {:?}",
822 telechargement.elapsed()
823 );
824
825 let mut file = std::fs::File::create("./rtcquebec_latest.zip").unwrap();
826
827 while let Some(chunk) = response.chunk().await.unwrap() {
828 file.write_all(&chunk).unwrap();
829 }
830
831 let mut gtfs = gtfs_structures::GtfsReader::default()
832 .read_shapes(false) .read_from_path("./rtcquebec_latest.zip")
834 .unwrap();
835
836 gtfs.trips
837 .retain(|_, trip| !trip.service_id.contains("multint"));
838
839 println!("analyse terminée, dans {:?}", gtfs.read_duration);
840
841 for (trip_id, trip) in gtfs.trips.iter() {
842 if let Some(d) = trip.stop_times[0].departure_time {
843 if d > 86400 {
844 }
846 }
847 }
848
849 let faire_les_donnees_gtfs_rt = faire_les_donnees_gtfs_rt(
850 >fs,
851 client.clone(),
852 Some(
853 &HTTP_PROXY_ADDRESSES
854 .iter()
855 .map(|x| x.to_string())
856 .collect::<Vec<String>>(),
857 ),
858 )
859 .await
860 .unwrap();
861
862 let mut error_date_count: usize = 0;
863
864 if let Some(voyages) = &faire_les_donnees_gtfs_rt.voyages {
865 for voyage in &voyages.entity {
866 if let Some(trip_update) = &voyage.trip_update {
867 if let Some(trip_id) = &trip_update.trip.trip_id {
868 let trip = gtfs.trips.get(trip_id).unwrap();
874
875 let service_id = &trip.service_id;
879 let service_date = gtfs
880 .calendar_dates
881 .get(service_id)
882 .unwrap()
883 .get(0)
884 .unwrap()
885 .date;
886
887 let reference_midnight = calc_reference_midnight(service_date);
888
889 let trip_start_time_relative = trip.stop_times[0].departure_time.unwrap();
893 let trip_start_time = reference_midnight
894 + std::time::Duration::from_secs(trip_start_time_relative as u64);
895
896 let trip_start_time =
897 trip_start_time.with_timezone(&chrono_tz::America::Montreal);
898
899 let time_from_now = trip_start_time.signed_duration_since(Utc::now());
900
901 if time_from_now.num_seconds() > 60 * 60 * 2 {
902 eprintln!(
903 "voyage {}, sur route {}, dans {}s",
904 trip_id,
905 trip.route_id,
906 time_from_now.num_seconds()
907 );
908 eprintln!(
909 "L'heure de début prévue est supérieure à 2 heures par rapport aux heures réelles"
910 );
911
912 error_date_count += 1;
913 }
914 }
915 }
916 }
917 }
918
919 println!("nombre d'erreurs de date: {}", error_date_count);
920
921 assert!(faire_les_donnees_gtfs_rt.vehicles.is_some());
924 assert!(faire_les_donnees_gtfs_rt.voyages.is_some());
925
926 println!(
927 "{} vehicles",
928 faire_les_donnees_gtfs_rt.vehicles.unwrap().entity.len()
929 );
930 println!(
931 "{} voyages",
932 faire_les_donnees_gtfs_rt.voyages.unwrap().entity.len()
933 );
934
935 }
938}