Skip to content

Commit baff9ab

Browse files
committed
FeedReader and TransitFeed help deal with complete feeds
FeedReader helps access transit feed entries in a compressed archive or directory. TransitFeed is a helpful container for transit feed entries that can be filled with a feed reader. Addresses some of georust#5.
1 parent f3dae53 commit baff9ab

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

Diff for: src/feed.rs

+272
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
1+
use serde;
2+
use std::collections::HashMap;
3+
use std::path::Path;
4+
use std::fs::File;
5+
use zip::ZipArchive;
6+
7+
use transit::{Agency, Stop, Route, Trip, StopTime, Calendar, CalendarDate, FareAttribute, FareRule, ShapePoint, Frequency, Transfer, FeedInfo};
8+
use gtfs::GTFSIterator;
9+
use gtfs::Error;
10+
11+
pub struct FeedReader {
12+
pub feed_path: String
13+
}
14+
15+
impl FeedReader {
16+
pub fn from_zip(zipfile: &str, output: &str) -> Result<Self, Error> {
17+
let output_path = Path::new(output);
18+
let mut zip = zip::ZipArchive::new(File::open(zipfile).unwrap()).unwrap();
19+
extract_zip(&mut zip, output_path);
20+
Ok(FeedReader { feed_path: output.to_string() })
21+
}
22+
23+
pub fn agencies(&self) -> Result<GTFSIterator<File, Agency>, Error> {
24+
self.make_iterator("agency.txt")
25+
}
26+
27+
pub fn stops(&self) -> Result<GTFSIterator<File, Stop>, Error> {
28+
self.make_iterator("stops.txt")
29+
}
30+
31+
pub fn routes(&self) -> Result<GTFSIterator<File, Route>, Error> {
32+
self.make_iterator("routes.txt")
33+
}
34+
35+
pub fn trips(&self) -> Result<GTFSIterator<File, Trip>, Error> {
36+
self.make_iterator("trips.txt")
37+
}
38+
39+
pub fn stop_times(&self) -> Result<GTFSIterator<File, StopTime>, Error> {
40+
self.make_iterator("stop_times.txt")
41+
}
42+
43+
pub fn calendars(&self) -> Result<GTFSIterator<File, Calendar>, Error> {
44+
self.make_iterator("calendar.txt")
45+
}
46+
47+
pub fn calendar_dates(&self) -> Result<GTFSIterator<File, CalendarDate>, Error> {
48+
self.make_iterator("calendar_dates.txt")
49+
}
50+
51+
pub fn fare_attributes(&self) -> Result<GTFSIterator<File, FareAttribute>, Error> {
52+
self.make_iterator("fare_attributes.txt")
53+
}
54+
55+
pub fn fare_rules(&self) -> Result<GTFSIterator<File, FareRule>, Error> {
56+
self.make_iterator("fare_rules.txt")
57+
}
58+
59+
pub fn shapes(&self) -> Result<GTFSIterator<File, ShapePoint>, Error> {
60+
self.make_iterator("shapes.txt")
61+
}
62+
63+
pub fn frequencies(&self) -> Result<GTFSIterator<File, Frequency>, Error> {
64+
self.make_iterator("frequencies.txt")
65+
}
66+
67+
pub fn transfers(&self) -> Result<GTFSIterator<File, Transfer>, Error> {
68+
self.make_iterator("transfers.txt")
69+
}
70+
71+
pub fn feed_info(&self) -> Result<GTFSIterator<File, FeedInfo>, Error> {
72+
self.make_iterator("feed_info.txt")
73+
}
74+
75+
fn make_iterator<T>(&self, filename: &str) -> Result<GTFSIterator<File, T>, Error>
76+
where T: serde::de::DeserializeOwned
77+
{
78+
Ok(GTFSIterator::from_path(Path::new(&self.feed_path).join(filename).to_str().unwrap())?)
79+
}
80+
}
81+
82+
/// Container for all transit records
83+
pub struct TransitFeed {
84+
pub agencies: Vec<Agency>,
85+
pub stops: Vec<Stop>,
86+
pub routes: Vec<Route>,
87+
pub trips: Vec<Trip>,
88+
pub stoptimes: Vec<StopTime>,
89+
pub calendars: Vec<Calendar>,
90+
pub calendar_dates: Option<Vec<CalendarDate>>,
91+
pub fare_attributes: Option<Vec<FareAttribute>>,
92+
pub fare_rules: Option<Vec<FareRule>>,
93+
pub shapes: Option<Vec<ShapePoint>>,
94+
pub frequencies: Option<Vec<Frequency>>,
95+
pub transfers: Option<Vec<Transfer>>,
96+
pub feedinfo: Option<FeedInfo>,
97+
98+
stop_map: HashMap<String, usize>,
99+
route_map: HashMap<String, usize>,
100+
trip_map: HashMap<String, usize>,
101+
}
102+
103+
impl TransitFeed {
104+
pub fn from_reader(reader: FeedReader) -> Result<Self, Error> {
105+
let agencies = load_feed_file(try!(reader.agencies()));
106+
let stops = load_feed_file(try!(reader.stops()));
107+
let routes = load_feed_file(try!(reader.routes()));
108+
let trips = load_feed_file(try!(reader.trips()));
109+
let stoptimes = load_feed_file(try!(reader.stop_times()));
110+
let calendars = load_feed_file(try!(reader.calendars()));
111+
112+
let stop_map = make_map(&stops, |stop: &Stop| stop.stop_id.clone());
113+
let route_map = make_map(&routes, |route: &Route| route.route_id.clone());
114+
let trip_map = make_map(&trips, |trip: &Trip| trip.trip_id.clone());
115+
116+
Ok(TransitFeed {
117+
agencies: agencies,
118+
stops: stops,
119+
stop_map: stop_map,
120+
routes: routes,
121+
route_map: route_map,
122+
trips: trips,
123+
trip_map: trip_map,
124+
stoptimes: stoptimes,
125+
calendars: calendars,
126+
calendar_dates: load_optional_feed_file(reader.calendar_dates()),
127+
fare_attributes: load_optional_feed_file(reader.fare_attributes()),
128+
fare_rules: load_optional_feed_file(reader.fare_rules()),
129+
shapes: load_optional_feed_file(reader.shapes()),
130+
frequencies: load_optional_feed_file(reader.frequencies()),
131+
transfers: load_optional_feed_file(reader.transfers()),
132+
feedinfo: match load_optional_feed_file(reader.feed_info()) {
133+
Some(mut records) => {
134+
if records.len() != 1 {
135+
println!("Unexpected number of entries in feed_info.txt");
136+
}
137+
records.pop()
138+
},
139+
None => None
140+
},
141+
})
142+
}
143+
144+
pub fn find_stop(&self, id: &str) -> Option<&Stop> {
145+
TransitFeed::find_record(id, &self.stop_map, &self.stops)
146+
}
147+
148+
pub fn find_route(&self, id: &str) -> Option<&Route> {
149+
TransitFeed::find_record(id, &self.route_map, &self.routes)
150+
}
151+
152+
pub fn find_trip(&self, id: &str) -> Option<&Trip> {
153+
TransitFeed::find_record(id, &self.trip_map, &self.trips)
154+
}
155+
156+
fn find_record<'a, T>(record_id: &str, map: &HashMap<String, usize>, records: &'a Vec<T>) -> Option<&'a T> {
157+
map.get(record_id).map(|index| &records[*index])
158+
}
159+
}
160+
161+
// TODO: Need to log stuff here
162+
fn load_feed_file<R, T>(iter: GTFSIterator<R, T>) -> Vec<T>
163+
where R: std::io::Read,
164+
for<'de> T: serde::Deserialize<'de>
165+
{
166+
iter.filter_map(|r| match r {
167+
Ok(r) => Some(r),
168+
Err(e) => { println!("SKIPPING - {}", e); None }
169+
}).collect()
170+
}
171+
172+
fn load_optional_feed_file<R, T>(result: Result<GTFSIterator<R, T>, Error>) -> Option<Vec<T>>
173+
where R: std::io::Read,
174+
for<'de> T: serde::Deserialize<'de>
175+
{
176+
match result {
177+
Ok(iter) => Some(load_feed_file(iter)),
178+
Err(e) => { println!("SKIPPING optional file - {}", e); None }
179+
}
180+
}
181+
182+
fn make_map<T, F: (Fn(&T) -> String)>(records: &Vec<T>, key_fn: F) -> HashMap<String, usize> {
183+
records.iter().enumerate()
184+
.map(|(index, record)| (key_fn(record), index))
185+
.collect()
186+
}
187+
188+
// move this elsewhere
189+
use std;
190+
use std::io;
191+
use std::fs;
192+
#[cfg(unix)]
193+
use std::os::unix::fs::PermissionsExt;
194+
use zip;
195+
196+
fn extract_zip<T: io::Read + io::Seek>(archive: &mut ZipArchive<T>, output: &Path) {
197+
for i in 0..archive.len()
198+
{
199+
let mut file = archive.by_index(i).unwrap();
200+
let outpath = output.join(sanitize_filename(file.name()));
201+
println!("{}", outpath.display());
202+
203+
{
204+
let comment = file.comment();
205+
if comment.len() > 0 { println!(" File comment: {}", comment); }
206+
}
207+
208+
// shouldn't need this for GTFS data?
209+
//create_directory(outpath.parent().unwrap_or(std::path::Path::new("")), None);
210+
211+
let perms = convert_permissions(file.unix_mode());
212+
213+
// also suspicious but why not?
214+
if (&*file.name()).ends_with("/") {
215+
create_directory(&outpath, perms);
216+
217+
}
218+
else {
219+
write_file(&mut file, &outpath, perms);
220+
}
221+
}
222+
}
223+
224+
#[cfg(unix)]
225+
fn convert_permissions(mode: Option<u32>) -> Option<fs::Permissions>
226+
{
227+
match mode {
228+
Some(mode) => Some(fs::Permissions::from_mode(mode)),
229+
None => None,
230+
}
231+
}
232+
#[cfg(not(unix))]
233+
fn convert_permissions(_mode: Option<u32>) -> Option<fs::Permissions>
234+
{
235+
None
236+
}
237+
238+
fn write_file(file: &mut zip::read::ZipFile, outpath: &std::path::Path, perms: Option<fs::Permissions>)
239+
{
240+
let mut outfile = fs::File::create(&outpath).unwrap();
241+
io::copy(file, &mut outfile).unwrap();
242+
if let Some(perms) = perms {
243+
fs::set_permissions(outpath, perms).unwrap();
244+
}
245+
}
246+
247+
fn create_directory(outpath: &std::path::Path, perms: Option<fs::Permissions>)
248+
{
249+
fs::create_dir_all(&outpath).unwrap();
250+
if let Some(perms) = perms {
251+
fs::set_permissions(outpath, perms).unwrap();
252+
}
253+
}
254+
255+
fn sanitize_filename(filename: &str) -> std::path::PathBuf
256+
{
257+
let no_null_filename = match filename.find('\0') {
258+
Some(index) => &filename[0..index],
259+
None => filename,
260+
};
261+
262+
std::path::Path::new(no_null_filename)
263+
.components()
264+
.filter(|component| match *component {
265+
std::path::Component::Normal(..) => true,
266+
_ => false
267+
})
268+
.fold(std::path::PathBuf::new(), |mut path, ref cur| {
269+
path.push(cur.as_os_str());
270+
path
271+
})
272+
}

Diff for: src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ extern crate zip;
1111

1212
mod transit;
1313
mod gtfs;
14+
mod feed;
1415

1516
pub use transit::*;
1617
pub use gtfs::{GTFSIterator, Error};
18+
pub use feed::{FeedReader, TransitFeed};

0 commit comments

Comments
 (0)