rtc_quebec_gtfs_rt/
lib.rs

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
54/*#[derive(Debug, Serialize, Deserialize, Clone)]
55pub struct BorneVirtuelleArretParcours {
56    pub parcours: ParcourPourBorneVirtuelle,
57    pub arret: ArretInfoBorneVirtuelle,
58    #[serde(rename = "arretNonDesservi")]
59    pub arret_non_desservi: bool,
60    #[serde(rename = "descenteSeulement")]
61    pub descente_seulement: bool,
62    #[serde(rename = "typeParcours")]
63    pub type_parcours: String,
64    pub horaires: Vec<HeureBorneVirtuelle>,
65}*/
66
67pub 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        //https://wsmobile.rtcquebec.ca/api/v3/horaire/ListeHoraire_Autobus?source=appmobileios&idVoyage=1261&idAutobus=11171
104        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 still empty, try again
123
124        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    //parcours id, direction, positions
283    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    //compute gtfs now
375
376    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 = &gtfs_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                        //trip start time
421
422                        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                        //println!("{} voyage: {}, no autobus {}", parcours_id, id_voyage, id_autobus);
460
461                        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                                        //check if date is not more than 1 day from now
517                                        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                        //println!("{:?} pour {}, no de bus: {}", voyages_gtfs_possibles_en_gtfs_pour_cette_voyage_rtc, parcours_id, position.id_autobus);
538
539                        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                        //sort by abs of time delta
582                        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                            /*if (starting_time.signed_duration_since(chrono::Utc::now())).num_seconds().abs() > 60 * 60 * 2 {
602
603                            println!(
604                                "
605                            trip_id_to_start_time_for_this_voyage: {:?}",
606                                trip_id_to_start_time_for_this_voyage
607                            );
608
609                                    println!(
610                                        "parcours: {} voyage: {}, autobus: {}, starting_time: {}",
611                                        parcours_id, id_voyage, id_autobus, starting_time
612                                    );
613                                }*/
614
615                            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                // make vehicle position
652
653                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    //subtract 12 hours
780    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        //download file https://github.com/catenarytransit/rtc_quebec_proxy_zip/raw/refs/heads/main/rtcquebec_latest.zip
812        //and place it in the root of the project
813
814        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) // Won’t read shapes to save time and memory
833            .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                    //  println!("{}: {}", trip_id, d);
845                }
846            }
847        }
848
849        let faire_les_donnees_gtfs_rt = faire_les_donnees_gtfs_rt(
850            &gtfs,
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                        //println!("{}", trip_id);
869
870                        // récupérer le voyage à partir du fichier GTFS
871                        //get trip from gtfs file
872
873                        let trip = gtfs.trips.get(trip_id).unwrap();
874
875                        // obtenir la date de début du voyage
876                        // get start date from trip
877
878                        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                        // obtenir l'heure de début du voyage
890                        // get start time from trip
891
892                        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        //println!("{:#?}", faire_les_donnees_gtfs_rt);
922
923        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        //   assert!(faire_les_donnees_gtfs_rt.vehicles.unwrap().entity.len() > 0);
936        //  assert!(faire_les_donnees_gtfs_rt.voyages.unwrap().entity.len() > 0);
937    }
938}