Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Id<Stop> instead of Arc<Stop>: Id as string #126

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ read-url = ["reqwest", "futures"]
[dependencies]
bytes = "1"
csv = "1.1"
derivative = "2.1"
derivative = "2.2.0"
serde = "1.0"
serde_derive = "1.0"
chrono = "0.4"
Expand Down
33 changes: 31 additions & 2 deletions examples/gtfs_reader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ fn main() {
/* Gtfs::new will try to guess if you provide a path, a local zip file or a remote zip file.
You can also use Gtfs::from_path, Gtfs::from_url
*/
let gtfs = gtfs_structures::GtfsReader::default()
.read_stop_times(false)
let mut gtfs = gtfs_structures::GtfsReader::default()
.read_stop_times(true)
.read("fixtures/basic")
.expect("impossible to read gtfs");
gtfs.print_stats();
Expand All @@ -12,4 +12,33 @@ fn main() {

let route_1 = gtfs.routes.get("1").expect("no route 1");
println!("{}: {:?}", route_1.short_name, route_1);

// you can access a stop by a &str
let _ = gtfs
.get_stop_by_raw_id("stop1")
.expect("unable to find stop Stop Area");

let trip = gtfs.trips.get("trip1").expect("no route 1");
let stop_id: &gtfs_structures::Id<gtfs_structures::Stop> =
&trip.stop_times.first().expect("no stoptimes").stop;

// or with a typed id if you have one

// if no stops have been removed from the gtfs, you can safely access the stops by it's id
let s = &gtfs.stops[stop_id];
println!("stop name: {}", &s.name);

// if some removal have been done, you can also you those method to get an Option<Stop>
let s = gtfs.get_stop(stop_id).expect("this stop should exists");
println!("stop description: {}", &s.description);

// or you can access it via `stops.get`
let s = gtfs.stops.get(stop_id).expect("this stop should exists");
println!("stop location type: {:?}", &s.location_type);

let mut s = gtfs
.stops
.get_mut(stop_id)
.expect("this stop should exists");
s.code = Some("code".into());
}
2 changes: 1 addition & 1 deletion fixtures/basic/stops.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
stop_id,stop_name,stop_desc,stop_lat,stop_lon,zone_id,stop_url,location_type,parent_station,wheelchair_boarding
stop1,"Stop Area",, 48.796058 ,2.449386,,,1,,
stop2,"StopPoint",,48.796058,2.449386,,,,,
stop2,"StopPoint",some description,48.796058,2.449386,,,,,
stop3,"Stop Point child of 1",,48.796058,2.449386,,,0,1,
stop4,"StopPoint2",,48.796058,2.449386,,,,,
stop5,"Stop Point child of 1 bis",,48.796058,2.449386,,,0,1,
Expand Down
75 changes: 38 additions & 37 deletions src/gtfs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use crate::{objects::*, Error, RawGtfs};
use crate::{id::Collection, id::Id, objects::*, Error, RawGtfs};
use chrono::prelude::NaiveDate;
use chrono::Duration;
use std::collections::{HashMap, HashSet};
use std::convert::TryFrom;
use std::sync::Arc;

/// Data structure with all the GTFS objects
///
Expand All @@ -27,8 +26,8 @@ pub struct Gtfs {
pub calendar: HashMap<String, Calendar>,
/// All calendar dates grouped by service_id
pub calendar_dates: HashMap<String, Vec<CalendarDate>>,
/// All stop by `stop_id`. Stops are in an [Arc] because they are also referenced by each [StopTime]
pub stops: HashMap<String, Arc<Stop>>,
/// All stop by `stop_id`
pub stops: Collection<Stop>,
/// All routes by `route_id`
pub routes: HashMap<String, Route>,
/// All trips by `trip_id`
Expand All @@ -49,12 +48,12 @@ impl TryFrom<RawGtfs> for Gtfs {
///
/// It might fail if some mandatory files couldn’t be read or if there are references to other objects that are invalid.
fn try_from(raw: RawGtfs) -> Result<Gtfs, Error> {
let stops = to_stop_map(
let frequencies = raw.frequencies.unwrap_or_else(|| Ok(Vec::new()))?;
let stops = to_stop_collection(
raw.stops?,
raw.transfers.unwrap_or_else(|| Ok(Vec::new()))?,
raw.pathways.unwrap_or(Ok(Vec::new()))?,
)?;
let frequencies = raw.frequencies.unwrap_or_else(|| Ok(Vec::new()))?;
let trips = create_trips(raw.trips?, raw.stop_times?, frequencies, &stops)?;

Ok(Gtfs {
Expand Down Expand Up @@ -175,11 +174,18 @@ impl Gtfs {
}

/// Gets a [Stop] by its `stop_id`
pub fn get_stop<'a>(&'a self, id: &str) -> Result<&'a Stop, Error> {
match self.stops.get(id) {
Some(stop) => Ok(stop),
None => Err(Error::ReferenceError(id.to_owned())),
}
pub fn get_stop<'a>(&'a self, raw_id: &Id<Stop>) -> Result<&'a Stop, Error> {
self.stops
.get(raw_id)
.ok_or_else(|| Error::ReferenceError(raw_id.to_string()))
}

/// Gets a [Stop] by a &str
pub fn get_stop_by_raw_id<'a>(&'a self, raw_id: &str) -> Result<&'a Stop, Error> {
self.stops
.get_by_str(raw_id)
.ok_or_else(|| Error::ReferenceError(raw_id.to_string()))
.map(|(_stop_id, s)| s)
}

/// Gets a [Trip] by its `trip_id`
Expand Down Expand Up @@ -225,44 +231,39 @@ impl Gtfs {
}
}

fn to_map<O: Id>(elements: impl IntoIterator<Item = O>) -> HashMap<String, O> {
fn to_map<O: WithId>(elements: impl IntoIterator<Item = O>) -> HashMap<String, O> {
elements
.into_iter()
.map(|e| (e.id().to_owned(), e))
.collect()
}

fn to_stop_map(
fn to_stop_collection(
stops: Vec<Stop>,
raw_transfers: Vec<RawTransfer>,
raw_pathways: Vec<RawPathway>,
) -> Result<HashMap<String, Arc<Stop>>, Error> {
let mut stop_map: HashMap<String, Stop> =
stops.into_iter().map(|s| (s.id.clone(), s)).collect();

) -> Result<Collection<Stop>, Error> {
let mut stop_map: Collection<Stop> = stops.into_iter().collect();
for transfer in raw_transfers {
stop_map
.get(&transfer.to_stop_id)
let to_stop_id = stop_map
.get_id(&transfer.to_stop_id)
.ok_or_else(|| Error::ReferenceError(transfer.to_stop_id.to_string()))?;
stop_map
.entry(transfer.from_stop_id.clone())
.and_modify(|stop| stop.transfers.push(StopTransfer::from(transfer)));
let (_, s) = stop_map
.get_mut_by_str(&transfer.from_stop_id)
.ok_or_else(|| Error::ReferenceError(transfer.from_stop_id.to_string()))?;
s.transfers.push(StopTransfer::from((transfer, to_stop_id)));
}

for pathway in raw_pathways {
stop_map
.get(&pathway.to_stop_id)
let to_stop_id = stop_map
.get_id(&pathway.to_stop_id)
.ok_or_else(|| Error::ReferenceError(pathway.to_stop_id.to_string()))?;
stop_map
.entry(pathway.from_stop_id.clone())
.and_modify(|stop| stop.pathways.push(Pathway::from(pathway)));
let (_, s) = stop_map
.get_mut_by_str(&pathway.from_stop_id)
.ok_or_else(|| Error::ReferenceError(pathway.from_stop_id.to_string()))?;
s.pathways.push(Pathway::from((pathway, to_stop_id)));
}

let res = stop_map
.into_iter()
.map(|(i, s)| (i, Arc::new(s)))
.collect();
Ok(res)
Ok(stop_map)
}

fn to_shape_map(shapes: Vec<Shape>) -> HashMap<String, Vec<Shape>> {
Expand Down Expand Up @@ -292,7 +293,7 @@ fn create_trips(
raw_trips: Vec<RawTrip>,
raw_stop_times: Vec<RawStopTime>,
raw_frequencies: Vec<RawFrequency>,
stops: &HashMap<String, Arc<Stop>>,
stops: &Collection<Stop>,
) -> Result<HashMap<String, Trip>, Error> {
let mut trips = to_map(raw_trips.into_iter().map(|rt| Trip {
id: rt.id,
Expand All @@ -312,10 +313,10 @@ fn create_trips(
let trip = &mut trips
.get_mut(&s.trip_id)
.ok_or_else(|| Error::ReferenceError(s.trip_id.to_string()))?;
let stop = stops
.get(&s.stop_id)
let stop_id = stops
.get_id(&s.stop_id)
.ok_or_else(|| Error::ReferenceError(s.stop_id.to_string()))?;
trip.stop_times.push(StopTime::from(&s, Arc::clone(stop)));
trip.stop_times.push(StopTime::from(&s, stop_id));
}

for trip in &mut trips.values_mut() {
Expand Down
171 changes: 171 additions & 0 deletions src/id.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
use crate::WithId;
use core::marker::PhantomData;
use std::{
collections::{hash_map, HashMap},
iter::FromIterator,
};

/// Typed Id over a [Collection]
#[derive(Derivative, Serialize, Deserialize)]
#[derivative(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
pub struct Id<T> {
id: String,
#[serde(skip)]
#[derivative(Debug(bound = ""))]
#[derivative(Debug = "ignore")]
#[derivative(Clone(bound = ""))]
#[derivative(Eq(bound = ""))]
#[derivative(PartialEq(bound = ""))]
#[derivative(Hash(bound = ""))]
_phantom: PhantomData<T>,
}

impl<T> Id<T> {
/// private method to build an Id that exists in the [Collection]
fn must_exists(s: String) -> Id<T> {
Id {
id: s,
_phantom: PhantomData,
}
}

/// Extracts a string slice containing the entire [Id]
pub fn as_str(&self) -> &str {
self
}
}

impl<T> std::convert::AsRef<str> for Id<T> {
fn as_ref(&self) -> &str {
self
}
}

impl<T> std::ops::Deref for Id<T> {
type Target = str;
fn deref(&self) -> &str {
&self.id
}
}

// Implements Borrow to be able to look in the hashmap with just a &str instead of a String
impl<T> std::borrow::Borrow<str> for Id<T> {
fn borrow(&self) -> &str {
&self.id
}
}

/// Collection with typed Ids
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Collection<T>(HashMap<Id<T>, T>);

impl<T> Default for Collection<T> {
fn default() -> Self {
Collection(HashMap::default())
}
}

impl<T> Collection<T> {
/// Get a typed [Id] from a raw &str
/// An [Id] can be returned only if it exists in the [Collection]
pub fn get_id(&self, raw_id: &str) -> Option<Id<T>> {
self.get_by_str(raw_id).map(|(k, _v)| k.clone())
}

/// Get an &[Id] and a reference to the object associated with it if it exists in the [Collection]
pub(crate) fn get_by_str(&self, raw_id: &str) -> Option<(&Id<T>, &T)> {
self.0.get_key_value(raw_id)
}

/// Get an &[Id] and a mutable reference to the object associated with it if it exists in the [Collection]
pub(crate) fn get_mut_by_str(&mut self, raw_id: &str) -> Option<(Id<T>, &mut T)> {
self.0
.get_mut(raw_id)
.map(|v| (Id::must_exists(raw_id.to_owned()), v))
}

/// Get the object associated to the typed [Id]
pub fn get(&self, id: &Id<T>) -> Option<&T> {
self.0.get(id)
}

/// Get a mutable reference on the object associated to the typed [Id]
pub fn get_mut(&mut self, id: &Id<T>) -> Option<&mut T> {
self.0.get_mut(id)
}

/// Returns the number of objects in the [Collection]
pub fn len(&self) -> usize {
self.0.len()
}

// Return true if the collection has no objects.
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}

/// Iterates over the ([Id]<T>, &T) of the [Collection].
pub fn iter(&self) -> hash_map::Iter<Id<T>, T> {
self.0.iter()
}

/// Iterates over the &T of the [Collection].
pub fn values(&self) -> hash_map::Values<'_, Id<T>, T> {
self.0.values()
}

/// Iterates over the &mut T of the [Collection].
pub fn values_mut(&mut self) -> hash_map::ValuesMut<'_, Id<T>, T> {
self.0.values_mut()
}
}

// Implements FromIterator to be able to easily build a [Collection] if we know how to associate an object with its [Id]
impl<T: WithId> FromIterator<T> for Collection<T> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let mut c = Self::default();

for i in iter {
let _ = c.0.insert(Id::must_exists(i.id().to_owned()), i);
}

c
}
}

impl<'a, T> IntoIterator for &'a Collection<T> {
type Item = (&'a Id<T>, &'a T);
type IntoIter = hash_map::Iter<'a, Id<T>, T>;

fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}

impl<T> IntoIterator for Collection<T> {
type Item = (Id<T>, T);
type IntoIter = hash_map::IntoIter<Id<T>, T>;

fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}

impl<Q, T> std::ops::Index<&Q> for Collection<T>
where
Id<T>: std::borrow::Borrow<Q>,
Q: Eq + std::hash::Hash,
{
type Output = T;

/// Returns a reference to the value corresponding to the supplied key.
///
/// # Panics
///
/// Panics if the key is not present in the [Collection].
/// This can happens only if the key was removed from the collection at one point.
/// If no key are removed from the [Collection], you can safely use this method.
fn index(&self, k: &Q) -> &Self::Output {
&self.0[k]
}
}
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod enums;
pub mod error;
mod gtfs;
mod gtfs_reader;
mod id;
pub(crate) mod objects;
mod raw_gtfs;
mod serde_helpers;
Expand All @@ -57,5 +58,6 @@ mod tests;
pub use error::Error;
pub use gtfs::Gtfs;
pub use gtfs_reader::GtfsReader;
pub use id::Id;
pub use objects::*;
pub use raw_gtfs::RawGtfs;
Loading