|
| 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 | +} |
0 commit comments