use chrono::prelude::*;
use serde::{Deserialize, Serialize};
mod mapping;
static WORDS_EQUALITY_THRESHOLD: f64 = 0.70;
mod utils {
pub fn match_strings(first: &str, second: &str) -> f64 {
if first.to_lowercase() == second.to_lowercase() {
1.0
} else {
strsim::normalized_damerau_levenshtein(&first.to_lowercase(), &second.to_lowercase())
}
}
}
#[derive(Debug, Clone)]
pub enum TrainNumber {
Regionale {
number: u32,
},
RegionaleVeloce {
number: u32,
},
InterCity {
number: u32,
},
FrecciaRossa {
number: u32,
},
FrecciaArgento {
number: u32,
},
FrecciaBianca {
number: u32,
},
InterCityNotte {
number: u32,
},
EuroNight {
number: u32,
},
EuroCity {
number: u32,
},
Bus {
number: u32,
},
Unknown {
number: u32,
name: String,
},
}
impl std::string::ToString for TrainNumber {
fn to_string(&self) -> String {
format!(
"{}{}",
match self {
Self::Regionale { number: _ } => "R",
Self::RegionaleVeloce { number: _ } => "RV",
Self::InterCity { number: _ } => "IC",
Self::FrecciaRossa { number: _ } => "ES*FR",
Self::FrecciaArgento { number: _ } => "ES*FA",
Self::FrecciaBianca { number: _ } => "FB",
Self::InterCityNotte { number: _ } => "ICN",
Self::EuroNight { number: _ } => "EN",
Self::EuroCity { number: _ } => "EC",
Self::Bus { number: _ } => "BUS",
Self::Unknown { number: _, name: _ } => "?",
},
u32::from(self)
)
}
}
impl std::convert::From<&TrainNumber> for u32 {
fn from(from: &TrainNumber) -> Self {
*match from {
TrainNumber::Regionale { number } => number,
TrainNumber::RegionaleVeloce { number } => number,
TrainNumber::InterCity { number } => number,
TrainNumber::FrecciaRossa { number } => number,
TrainNumber::FrecciaArgento { number } => number,
TrainNumber::FrecciaBianca { number } => number,
TrainNumber::InterCityNotte { number } => number,
TrainNumber::EuroNight { number } => number,
TrainNumber::EuroCity { number } => number,
TrainNumber::Bus { number } => number,
TrainNumber::Unknown { number, name: _ } => number,
}
}
}
#[derive(Debug)]
pub struct TrainTripStop {
pub station: TrainStation,
pub platform: String,
pub arrival: Option<chrono::DateTime<chrono::Local>>,
pub departure: Option<chrono::DateTime<chrono::Local>>,
pub expected_arrival: Option<chrono::DateTime<chrono::Local>>,
pub expected_departure: Option<chrono::DateTime<chrono::Local>>,
}
#[derive(Debug)]
pub struct DetailedTrainTripStop {
pub station: TrainStation,
pub platform: String,
pub arrival: Option<chrono::DateTime<chrono::Local>>,
pub departure: Option<chrono::DateTime<chrono::Local>>,
pub expected_arrival: Option<chrono::DateTime<chrono::Local>>,
pub expected_departure: Option<chrono::DateTime<chrono::Local>>,
}
pub struct DetailedTrainTrip {
pub from: TrainStation,
pub to: TrainStation,
pub train_number: TrainNumber,
pub stops: Vec<TrainTripStop>,
}
#[derive(Debug, Clone)]
pub struct TrainTrip {
pub train_number: TrainNumber,
pub arrival: (TrainStation, chrono::DateTime<chrono::Local>),
pub departure: (TrainStation, chrono::DateTime<chrono::Local>),
}
impl TrainTrip {
pub fn get_duration(&self) -> chrono::Duration {
let partenza = (&self.departure.1).clone();
let arrivo = (&self.arrival.1).clone();
arrivo.signed_duration_since(partenza)
}
pub fn get_fare(&self) -> Option<f64> {
let url = format!("https://www.lefrecce.it/msite/api/solutions?origin={}&destination={}&arflag=A&adate={}&atime={}&adultno=1&childno=0&direction=A&frecce=false&onlyRegional=false",
self.departure.0.lefrecce_name.clone().unwrap().replace(" ", "%20"),
self.arrival.0.lefrecce_name.clone().unwrap().replace(" ", "%20"),
self.departure.1.format("%d/%m/%Y"),
self.departure.1.format("%H")
);
let answer = ureq::get(url.as_str())
.call()
.unwrap()
.into_string()
.unwrap();
let body: Vec<mapping::LFSolution> = serde_json::from_str(&answer).unwrap();
for result in body {
if chrono::Local.timestamp_millis(result.departuretime as i64) == self.departure.1
&& chrono::Local.timestamp_millis(result.arrivaltime as i64) == self.arrival.1
{
return result.minprice;
}
}
None
}
}
#[derive(Debug)]
pub struct TrainInfo {
pub current_station: TrainStation,
pub current_delay: i16,
pub is_at_station: bool,
pub stops: Vec<DetailedTrainTripStop>,
}
impl TrainInfo {
pub fn from(vtvec: &Vec<mapping::VTDetailedTrainTripLeg>, trenitalia: &Trenitalia) -> Self {
let mut delay: i16 = 0;
let mut current_station: TrainStation = trenitalia
.find_train_station(&vtvec[&vtvec.len() - 1].stazione)
.unwrap()
.clone();
let mut in_station: bool = false;
let mut stations_list: Vec<DetailedTrainTripStop> = Vec::new();
for stop in vtvec {
let this_station = trenitalia.find_train_station(&stop.stazione).unwrap();
if stop.stazioneCorrente {
current_station = this_station.clone();
if let Some(r) = stop.fermata.partenzaReale {
if let Some(t) = stop.fermata.partenza_teorica {
delay = ((r as i64 - t as i64) / 60000i64) as i16;
}
} else if let Some(t) = stop.fermata.partenza_teorica {
delay = (((chrono::Local::now().timestamp_millis() - t as i64) / 60000i64)
as i16)
.max(0);
}
if stop.fermata.arrivoReale.is_some() && stop.fermata.partenzaReale.is_none() {
in_station = true;
}
}
let this_stop = DetailedTrainTripStop {
arrival: stop
.fermata
.arrivoReale
.map(|ts| chrono::Local.timestamp((ts / 1000) as i64, 0)),
departure: stop
.fermata
.partenzaReale
.map(|ts| chrono::Local.timestamp((ts / 1000) as i64, 0)),
expected_arrival: stop
.fermata
.arrivo_teorico
.map(|ts| chrono::Local.timestamp((ts / 1000) as i64, 0)),
expected_departure: stop
.fermata
.partenza_teorica
.map(|ts| chrono::Local.timestamp((ts / 1000) as i64, 0)),
platform: stop
.fermata
.binarioEffettivoPartenzaDescrizione
.as_ref()
.unwrap_or(
stop.fermata
.binarioProgrammatoPartenzaDescrizione
.as_ref()
.unwrap_or(
stop.fermata
.binarioEffettivoArrivoDescrizione
.as_ref()
.unwrap_or(
stop.fermata
.binarioProgrammatoArrivoDescrizione
.as_ref()
.unwrap_or(&"?".to_string()),
),
),
)
.to_string(),
station: this_station.clone(),
};
stations_list.push(this_stop);
}
TrainInfo {
current_delay: delay,
is_at_station: in_station,
current_station: current_station,
stops: stations_list,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainStation {
pub id: String,
pub region_id: u8,
pub position: (f64, f64),
pub aliases: Vec<String>,
pub vt_id: Option<String>,
pub lefrecce_name: Option<String>,
}
impl TrainStation {
fn short_id(&self) -> Option<String> {
match &self.vt_id {
None => None,
Some(x) => Some(str::replace(x, "S", "").parse::<u16>().unwrap().to_string()),
}
}
pub fn get_name(&self) -> &str {
&self.aliases[0]
}
}
pub struct Trenitalia {
stations: Vec<TrainStation>,
fast_station_lookup: std::collections::HashMap<String, usize>,
}
impl Trenitalia {
pub fn new() -> Trenitalia {
let id_to_lf_tsv = include_str!("../id_lf_map.tsv");
let id_to_lf: std::collections::HashMap<String, String> = id_to_lf_tsv
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|&x| x.split("\t").collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>()
.iter()
.map(|x| (String::from(*&x[0]), String::from(*&x[1])))
.collect::<Vec<(String, String)>>()
.into_iter()
.collect();
let id_to_vt_tsv = include_str!("../id_vt.tsv");
let id_to_vt: std::collections::HashMap<String, String> = id_to_vt_tsv
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|&x| x.split("\t").collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>()
.iter()
.map(|x| (String::from(*&x[0]), String::from(*&x[1])))
.collect::<Vec<(String, String)>>()
.into_iter()
.collect();
let aliases_tsv = include_str!("../aliases.tsv");
let aliases: Vec<Vec<&str>> = aliases_tsv
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|&x| x.split("\t").collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>();
let station_list_tsv = include_str!("../stations.tsv");
let station_list = station_list_tsv.split("\n").collect::<Vec<&str>>();
let mapped_stations: Vec<TrainStation> = station_list
.iter()
.map(|&x| x.split("\t").collect::<Vec<&str>>())
.collect::<Vec<Vec<&str>>>()
.iter()
.map(|x| {
let mut a = vec![String::from(x[0])];
let mut v: Vec<String> = Vec::new();
for alias in &aliases {
if &alias[1] == &x[1] {
v.push(String::from(alias[0]))
}
}
a.append(&mut v);
TrainStation {
id: String::from(x[1]),
aliases: a,
position: (x[3].parse::<f64>().unwrap(), x[4].parse::<f64>().unwrap()),
region_id: x[2].parse::<u8>().unwrap(),
lefrecce_name: id_to_lf.get(x[1]).map(|x| String::from(x)),
vt_id: id_to_vt.get(x[1]).map(|x| String::from(x)),
}
})
.collect();
let mut lookup: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
for i in 0..mapped_stations.len() {
for alias in &mapped_stations[i].aliases {
lookup.insert(String::from(alias), i);
}
if mapped_stations[i].lefrecce_name.is_some() {
lookup.insert(
String::from(
mapped_stations[i]
.lefrecce_name
.as_ref()
.unwrap_or(&"".to_string()),
)
.to_uppercase(),
i,
);
}
}
Trenitalia {
stations: mapped_stations,
fast_station_lookup: lookup,
}
}
fn match_train_type(&self, description: &str, number: u32) -> TrainNumber {
let train_type = match description {
"RV" => TrainNumber::RegionaleVeloce { number: number },
"Regionale" => TrainNumber::Regionale { number: number },
"Frecciarossa" => TrainNumber::FrecciaRossa { number: number },
"Frecciaargento" => TrainNumber::FrecciaArgento { number: number },
"IC" => TrainNumber::InterCity { number: number },
"Frecciabianca" => TrainNumber::FrecciaBianca { number: number },
"ICN" => TrainNumber::InterCityNotte { number: number },
"EN" => TrainNumber::EuroNight { number: number },
"EC" => TrainNumber::EuroCity { number: number },
"REG" => TrainNumber::Regionale { number: number },
"Autobus" => TrainNumber::Bus { number: number },
"BUS" => TrainNumber::Bus { number: number },
"FR" => TrainNumber::FrecciaRossa { number: number },
"FA" => TrainNumber::FrecciaArgento { number: number },
"FB" => TrainNumber::FrecciaBianca { number: number },
"ECB" => TrainNumber::EuroCity { number: number },
_ => TrainNumber::Unknown {
number: number,
name: String::from(description),
},
};
match train_type {
TrainNumber::Unknown { number: _, name: _ } => {
let url = format!(
"https://eutampieri.eu/tipi_treno.php?tipo={}",
description.replace(" ", "%20")
);
let _ = ureq::get(url.as_str()).call();
}
_ => {}
}
train_type
}
fn find_trips_lefrecce(
&self,
from: &TrainStation,
to: &TrainStation,
when: &chrono::DateTime<chrono::Local>,
) -> Vec<Vec<TrainTrip>> {
if from.id == to.id || from.lefrecce_name.is_none() || from.lefrecce_name.is_none() {
return vec![];
}
let mut result: Vec<Vec<TrainTrip>> = Vec::new();
let client = ureq::agent();
let url = format!("https://www.lefrecce.it/msite/api/solutions?origin={}&destination={}&arflag=A&adate={}&atime={}&adultno=1&childno=0&direction=A&frecce=false&onlyRegional=false",
from.lefrecce_name.clone().unwrap().replace(" ", "%20"),
to.lefrecce_name.clone().unwrap().replace(" ", "%20"),
when.format("%d/%m/%Y"),
when.format("%H")
);
if cfg!(debug_assertions) {
println!("{}", url);
}
let body: Vec<mapping::LFSolution> = serde_json::from_value(
client
.get(url.as_str())
.call()
.unwrap()
.into_json()
.unwrap(),
)
.unwrap();
for solution in &body {
let mut train_trips: Vec<TrainTrip> = Vec::new();
let url_details = format!(
"https://www.lefrecce.it/msite/api/solutions/{}/standardoffers",
solution.idsolution
);
if cfg!(debug_assertions) {
println!("{}", url_details);
}
let body_details: mapping::LFDetailedSolution = serde_json::from_value(
client
.get(url_details.as_str())
.call()
.expect("Failed API call")
.into_json()
.unwrap(),
)
.unwrap();
for leg in &body_details.leglist {
for train in &leg.segments {
if train.trainidentifier == String::from("Same") {
continue;
}
let acronym = train
.trainacronym
.as_ref()
.map_or(String::from(""), |x| String::from(x.as_str()));
let train_name_exploded: Vec<&str> = train.trainidentifier.split(' ').collect();
let train_number = train_name_exploded[&train_name_exploded.len() - 1];
let from = &self.stations[*self
.fast_station_lookup
.get(&train.departurestation.to_uppercase())
.or_else(|| {
let url = format!(
"https://eutampieri.eu/fix_localita.php?nome={}",
&train.departurestation
);
let _ = ureq::get(url.as_str()).call();
None
})
.expect("Inconsistency in Trenitalia")];
let to = &self.stations[*self
.fast_station_lookup
.get(&train.arrivalstation.to_uppercase())
.or_else(|| {
let url = format!(
"https://eutampieri.eu/fix_localita.php?nome={}",
&train.arrivalstation
);
let _ = ureq::get(url.as_str()).call();
None
})
.expect("Inconsistency in Trenitalia")];
train_trips.push(TrainTrip {
departure: (
from.clone(),
chrono::Local
.datetime_from_str(train.departuretime.as_str(), "%+")
.expect("Data non valida"),
),
arrival: (
to.clone(),
chrono::Local
.datetime_from_str(train.arrivaltime.as_str(), "%+")
.expect("Data non valida"),
),
train_number: self.match_train_type(
&acronym,
train_number.parse::<u32>().unwrap_or(
train_number
.chars()
.into_iter()
.map(|x| if x.is_digit(10) { x } else { '0' })
.collect::<String>()
.parse::<u32>()
.unwrap(),
),
),
});
}
}
result.push(train_trips);
}
result
}
pub fn find_trips(
&self,
from: &TrainStation,
to: &TrainStation,
when: &chrono::DateTime<chrono::Local>,
) -> Vec<Vec<TrainTrip>> {
let mut result: Vec<Vec<TrainTrip>> = Vec::new();
let url = format!("http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/soluzioniViaggioNew/{}/{}/{}",
from.short_id().unwrap(),
to.short_id().unwrap(),
when.format("%FT%T")
);
if cfg!(debug_assertions) {
println!("{}", url);
}
let body: mapping::VTJourneySearchResult = serde_json::from_value(
ureq::get(url.as_str())
.call()
.expect("Failed API call")
.into_json()
.unwrap(),
)
.unwrap();
if body.soluzioni.len() == 0 {
return self.find_trips_lefrecce(from, to, when);
}
for soluzione in body.soluzioni {
let mut train_trips: Vec<TrainTrip> = Vec::new();
if cfg!(debug_assertions) {
println!(
"expected: {}, found: {}, delta: {}",
&from.get_name(),
&soluzione.vehicles[0]
.origine
.as_ref()
.unwrap_or(&String::from("")),
utils::match_strings(
&soluzione.vehicles[0]
.origine
.as_ref()
.unwrap_or(&String::from(""))
.to_lowercase(),
&from.get_name()
)
);
}
if utils::match_strings(
&soluzione.vehicles[0]
.origine
.as_ref()
.unwrap_or(&String::from("")),
&from.get_name(),
) < WORDS_EQUALITY_THRESHOLD
{
let filling_to = &self.stations[*self
.fast_station_lookup
.get(
soluzione.vehicles[0]
.origine
.as_ref()
.unwrap_or(&String::from("")),
)
.or_else(|| {
let url = format!(
"https://eutampieri.eu/fix_localita.php?nome={}",
soluzione.vehicles[0]
.origine
.as_ref()
.unwrap_or(&String::from(""))
);
let _ = ureq::get(url.as_str()).call();
None
})
.expect("Inconsistency in Trenitalia")];
if cfg!(debug_assertions) {
println!("filling_to = {:?}", filling_to);
}
let filling_solutions = self.find_trips_lefrecce(from, filling_to, when);
for filling_solution in filling_solutions.iter() {
if filling_solution[0].departure.1
>= chrono::Local.timestamp(when.timestamp(), 0)
&& filling_solution[&filling_solution.len() - 1].arrival.1
<= chrono::Local
.datetime_from_str(
soluzione.vehicles[0].orarioPartenza.as_str(),
"%FT%T",
)
.expect("Data non valida")
{
for filling_train in filling_solution {
train_trips.push(filling_train.clone());
}
break;
}
}
}
let mut old_to: Option<&str> = None;
let mut old_to_stn = to.clone();
let mut old_ts = chrono::Local.timestamp(when.timestamp(), 0);
for train_trip in soluzione.vehicles.iter() {
let from = &self.stations[*self
.fast_station_lookup
.get(train_trip.origine.as_ref().unwrap_or(&String::from("")))
.or_else(|| {
let url = format!(
"https://eutampieri.eu/fix_localita.php?nome={}",
train_trip.origine.as_ref().unwrap_or(&String::from(""))
);
let _ = ureq::get(url.as_str()).call();
None
})
.expect("Inconsistency in Trenitalia")];
let to = &self.stations[*self
.fast_station_lookup
.get(
train_trip
.destinazione
.as_ref()
.unwrap_or(&String::from("")),
)
.or_else(|| {
let url = format!(
"https://eutampieri.eu/fix_localita.php?nome={}",
train_trip
.destinazione
.as_ref()
.unwrap_or(&String::from(""))
);
let _ = ureq::get(url.as_str()).call();
None
})
.expect("Inconsistency in Trenitalia")];
if old_to.is_some() && old_to != Some(&from.get_name()) {
let filling_solutions = self.find_trips_lefrecce(&old_to_stn, from, &old_ts);
for filling_solution in filling_solutions.iter() {
if filling_solution[0].departure.1 >= old_ts
&& filling_solution[&filling_solution.len() - 1].arrival.1
<= chrono::Local
.datetime_from_str(train_trip.orarioPartenza.as_str(), "%FT%T")
.expect("Data non valida")
{
for filling_train in filling_solution {
train_trips.push(filling_train.clone());
}
break;
}
}
}
old_to = Some(&to.get_name());
old_to_stn = to.clone();
old_ts = chrono::Local
.datetime_from_str(train_trip.orarioArrivo.as_str(), "%FT%T")
.expect("Data non valida");
train_trips.push(TrainTrip {
departure: (
from.clone(),
chrono::Local
.datetime_from_str(train_trip.orarioPartenza.as_str(), "%FT%T")
.expect("Data non valida"),
),
arrival: (
to.clone(),
chrono::Local
.datetime_from_str(train_trip.orarioArrivo.as_str(), "%FT%T")
.expect("Data non valida"),
),
train_number: self.match_train_type(
&train_trip.categoriaDescrizione,
train_trip.numeroTreno.parse::<u32>().unwrap_or(
train_trip
.numeroTreno
.chars()
.into_iter()
.map(|x| if x.is_digit(10) { x } else { '0' })
.collect::<String>()
.parse::<u32>()
.unwrap(),
),
),
});
}
if cfg!(debug_assertions) {
println!(
"expected: {}, found: {}, delta: {}",
&to.get_name(),
&soluzione.vehicles[&soluzione.vehicles.len() - 1]
.destinazione
.as_ref()
.unwrap_or(&String::from("")),
utils::match_strings(
&soluzione.vehicles[&soluzione.vehicles.len() - 1]
.destinazione
.as_ref()
.unwrap_or(&String::from("")),
&to.get_name(),
)
);
}
if utils::match_strings(
&soluzione.vehicles[&soluzione.vehicles.len() - 1]
.destinazione
.as_ref()
.unwrap_or(&String::from("")),
&to.get_name(),
) < WORDS_EQUALITY_THRESHOLD
{
let filling_from = &self.stations[*self
.fast_station_lookup
.get(
soluzione.vehicles[&soluzione.vehicles.len() - 1]
.destinazione
.as_ref()
.unwrap_or(&String::from("")),
)
.or_else(|| {
let url = format!(
"https://eutampieri.eu/fix_localita.php?nome={}",
soluzione.vehicles[&soluzione.vehicles.len() - 1]
.destinazione
.as_ref()
.unwrap_or(&String::from(""))
);
let _ = ureq::get(url.as_str()).call();
None
})
.expect("Inconsistency in Trenitalia")];
if cfg!(debug_assertions) {
println!("filling_from = {:?}", filling_from);
}
let filling_solutions = self.find_trips_lefrecce(filling_from, to, when);
for filling_solution in filling_solutions.iter() {
if filling_solution[0].departure.1
>= chrono::Local
.datetime_from_str(
soluzione.vehicles[&soluzione.vehicles.len() - 1]
.orarioArrivo
.as_str(),
"%FT%T",
)
.expect("Data non valida")
{
for filling_train in filling_solution {
train_trips.push(filling_train.clone());
}
break;
}
}
}
result.push(train_trips);
}
result
}
pub fn find_train_station_online(&self, name: &str) -> Option<&TrainStation> {
let url = format!(
"http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/autocompletaStazione/{}",
name
);
if cfg!(debug_assertions) {
println!("{}", url);
}
let response = ureq::get(&url)
.call()
.expect("Failed API call")
.into_string()
.unwrap();
if response.len() == 0 {
return None;
}
let body: Vec<Vec<&str>> = response
.trim_end_matches('\n')
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|&x| x.split("|").collect::<Vec<&str>>())
.collect();
if body.len() == 0 {
None
} else {
for station in &self.stations {
let vt_id = match &station.vt_id {
Some(x) => Some(String::from(x)),
None => None,
};
if station.vt_id.is_none() {
continue;
} else if vt_id.unwrap() == body[0][1] {
return Some(station);
}
}
None
}
}
pub fn get_train_station(&self, id: &str) -> Option<&TrainStation> {
if cfg!(debug_assertions) {
println!("{:?}", id);
}
for station in &self.stations {
if &station.id == id {
return Some(station);
}
}
None
}
pub fn find_train_station(&self, name: &str) -> Option<&TrainStation> {
let mut min_diff = 0.0;
let mut found_station = &self.stations[0];
match self.fast_station_lookup.get(&name.to_uppercase()) {
Some(x) => return Some(&self.stations[*x]),
None => {
for station in &self.stations {
for alias in &station.aliases {
let diff = utils::match_strings(alias, &name);
if cfg!(debug_assertions) {
}
if diff == 1.0 {
return Some(station);
}
if diff > min_diff {
min_diff = diff;
found_station = station;
}
}
}
return if min_diff >= WORDS_EQUALITY_THRESHOLD {
Some(found_station)
} else {
None
};
}
};
}
fn train_info_raw(&self, number: u32, from: &str) -> TrainInfo {
let url = format!(
"http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/tratteCanvas/{}/{}",
from, number
);
let response: Vec<mapping::VTDetailedTrainTripLeg> = serde_json::from_value(
ureq::get(&url)
.call()
.expect("Failed API call")
.into_json()
.unwrap(),
)
.unwrap();
TrainInfo::from(&response, self)
}
pub fn train_info(&self, number: u32, from: String) -> Result<TrainInfo, &str> {
let url = format!("http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/cercaNumeroTrenoTrenoAutocomplete/{}", number);
let response = ureq::get(&url)
.call()
.expect("Failed API call")
.into_string()
.unwrap();
let body: Vec<Vec<&str>> = response
.trim_end_matches('\n')
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|&x| x.split("|").collect::<Vec<&str>>())
.collect();
let train_station_of_origination: &str = match body.len() {
1 => body[0][1].split('-').collect::<Vec<&str>>()[1],
0 => {
return Err("No train found");
}
_ => {
let mut station_code = "";
let mut min_diff = 0.0;
for option in body {
let diff = utils::match_strings(
&option[0].split('-').collect::<Vec<&str>>()[1]
.trim_start()
.to_lowercase(),
&from.to_lowercase(),
);
if diff < min_diff {
min_diff = diff;
station_code = option[1].split('-').collect::<Vec<&str>>()[1];
}
if diff == 1.0 {
break;
}
}
if min_diff == 0.0 {
return Err("Train not found");
} else {
station_code
}
}
};
Ok(self.train_info_raw(number, train_station_of_origination))
}
pub fn train_info_calling_at(
&self,
number: u32,
calling_at: &TrainStation,
) -> Result<TrainInfo, &str> {
let url = format!("http://www.viaggiatreno.it/infomobilita/resteasy/viaggiatreno/cercaNumeroTrenoTrenoAutocomplete/{}", number);
let response = ureq::get(&url)
.call()
.expect("Failed API call")
.into_string()
.unwrap();
let body: Vec<Vec<&str>> = response
.trim_end_matches('\n')
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|&x| x.split("|").collect::<Vec<&str>>())
.collect();
match body.len() {
1 => Ok(self.train_info_raw(number, body[0][1].split('-').collect::<Vec<&str>>()[1])),
0 => Err("Train not found"),
_ => {
for option in body {
let train_info =
self.train_info_raw(number, option[1].split('-').collect::<Vec<&str>>()[1]);
for stop in &train_info.stops {
if stop.station.id == calling_at.id {
return Ok(train_info);
}
}
}
return Err("Train not found");
}
}
}
pub fn nearest_station(&self, point: (f64, f64)) -> &TrainStation {
let mut min_dist = std::f64::MAX;
let mut sta = &self.stations[0];
for station in &self.stations {
let dist_sq =
(station.position.0 - point.0).powf(2.0) + (station.position.1 - point.1).powf(2.0);
if dist_sq < min_dist {
sta = station;
min_dist = dist_sq;
}
}
sta
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn lookup_test() {
let t = Trenitalia::new();
println!("{:?}", t.find_train_station("bolzano"));
assert!(t.fast_station_lookup.get("Bolzano").is_some());
}
#[test]
fn test() {
let t = Trenitalia::new();
let _calalzo = t.nearest_station((46.45, 12.383333));
let _carnia = t.nearest_station((46.374318, 13.134141));
let imola = t.nearest_station((44.3533, 11.7141));
let cesena = t.nearest_station((44.133333, 12.233333));
println!("{:?}", t.find_train_station("bologna centrale"));
let _bologna = t.find_train_station("marradi").unwrap();
println!("{:?}", t.find_trips(imola, _bologna, &chrono::Local::now())); println!(
"{:?}",
t.find_trips(cesena, imola, &chrono::Local::now())[0][0].get_fare()
);
let a = TrainNumber::EuroCity { number: 2019 };
a.to_string();
println!("{:?}", t.train_info(6568, "Piacenza".to_string()).unwrap());
}
}