diff --git a/apps/game/src/lib.rs b/apps/game/src/lib.rs index 6c606f5bd0..9050df5a43 100644 --- a/apps/game/src/lib.rs +++ b/apps/game/src/lib.rs @@ -529,13 +529,15 @@ fn finish_app_setup( } } Mode::SomethingElse => { + let start_time = setup.start_time.unwrap_or(Duration::hours(6)); + // Not attempting to keep the primary and secondary simulations synchronized at the // same time yet. Just handle this one startup case, so we can switch maps without // constantly flopping day/night mode. if let Some(ref mut secondary) = app.secondary { secondary.sim.timed_step( &secondary.map, - Duration::hours(6), + start_time, &mut None, &mut Timer::throwaway(), ); @@ -546,7 +548,7 @@ fn finish_app_setup( SandboxMode::async_new( app, GameplayMode::Freeform(app.primary.map.get_name().clone()), - jump_to_time_upon_startup(Duration::hours(6)), + jump_to_time_upon_startup(start_time), ) } Mode::TutorialIntro => sandbox::gameplay::Tutorial::start(ctx, app), diff --git a/apps/game/src/sandbox/gameplay/play_scenario.rs b/apps/game/src/sandbox/gameplay/play_scenario.rs index c35a8a8e5d..2cdc4c0881 100644 --- a/apps/game/src/sandbox/gameplay/play_scenario.rs +++ b/apps/game/src/sandbox/gameplay/play_scenario.rs @@ -34,7 +34,7 @@ impl PlayScenario { modifiers: Vec, ) -> Box { URLManager::update_url_free_param( - // For dynamiclly generated scenarios like "random" and "home_to_work", this winds up + // For dynamically generated scenarios like "random" and "home_to_work", this winds up // making up a filename that doesn't actually exist. But if you pass that in, it winds // up working, because we call abstio::parse_scenario_path() on the other side. abstio::path_scenario(app.primary.map.get_name(), name) diff --git a/convert_osm/src/gtfs.rs b/convert_osm/src/gtfs.rs index d35f2936db..f283a640d8 100644 --- a/convert_osm/src/gtfs.rs +++ b/convert_osm/src/gtfs.rs @@ -50,6 +50,10 @@ pub fn import(map: &mut RawMap) -> Result<()> { } // Scrape all shape data. Map from shape_id to points and the sequence number + // + // If this file is missing, one idea is to just draw straight lines between stops. We only use + // the shape currently to pick an entry/exit border, so this could be a half-reasonable + // workaround. let mut raw_shapes: HashMap> = HashMap::new(); for rec in csv::Reader::from_reader(File::open(map.name.city.input_path("gtfs/shapes.txt"))?) .deserialize() @@ -189,6 +193,8 @@ struct Route { route_id: RouteID, route_short_name: String, route_long_name: String, + // Missing from São Paulo + #[serde(default)] route_desc: String, route_type: usize, } diff --git a/data/MANIFEST.json b/data/MANIFEST.json index 0c781d241a..b1b521544b 100644 --- a/data/MANIFEST.json +++ b/data/MANIFEST.json @@ -70,6 +70,56 @@ "uncompressed_size_bytes": 5608139, "compressed_size_bytes": 1056533 }, + "data/input/br/sao_paulo/gtfs/agency.txt": { + "checksum": "3dd694b14c7bacfa33d8ad774db99100", + "uncompressed_size_bytes": 155, + "compressed_size_bytes": 139 + }, + "data/input/br/sao_paulo/gtfs/calendar.txt": { + "checksum": "21622d9185a6a3e5d0775712640f222c", + "uncompressed_size_bytes": 451, + "compressed_size_bytes": 150 + }, + "data/input/br/sao_paulo/gtfs/fare_attributes.txt": { + "checksum": "9e8ca239a9545d1b75cf87b7af027efc", + "uncompressed_size_bytes": 371, + "compressed_size_bytes": 169 + }, + "data/input/br/sao_paulo/gtfs/fare_rules.txt": { + "checksum": "04104cc98ef5504e37717a7cab49edf4", + "uncompressed_size_bytes": 206440, + "compressed_size_bytes": 16286 + }, + "data/input/br/sao_paulo/gtfs/frequencies.txt": { + "checksum": "d92632062fa60f01449c5091fd45398f", + "uncompressed_size_bytes": 1651443, + "compressed_size_bytes": 140539 + }, + "data/input/br/sao_paulo/gtfs/routes.txt": { + "checksum": "cb359e55a553bc9a462fbcbb08ae5574", + "uncompressed_size_bytes": 111291, + "compressed_size_bytes": 18029 + }, + "data/input/br/sao_paulo/gtfs/shapes.txt": { + "checksum": "a1f5500e7a79f96776589f4f4fbcd38e", + "uncompressed_size_bytes": 58972562, + "compressed_size_bytes": 15500119 + }, + "data/input/br/sao_paulo/gtfs/stop_times.txt": { + "checksum": "11ef62b51bb8d23aac88cba96a8def7b", + "uncompressed_size_bytes": 4900105, + "compressed_size_bytes": 958512 + }, + "data/input/br/sao_paulo/gtfs/stops.txt": { + "checksum": "5191a36f2d3f8ef45b435b380e890921", + "uncompressed_size_bytes": 2133889, + "compressed_size_bytes": 594221 + }, + "data/input/br/sao_paulo/gtfs/trips.txt": { + "checksum": "f2eb999c99e5ae82a4cfc3f7ed6a0121", + "uncompressed_size_bytes": 130243, + "compressed_size_bytes": 26310 + }, "data/input/br/sao_paulo/osm/aricanduva.osm": { "checksum": "3708fb4be649c4f16d1de7f7c99369b6", "uncompressed_size_bytes": 128106393, @@ -91,19 +141,19 @@ "compressed_size_bytes": 649718446 }, "data/input/br/sao_paulo/raw_maps/aricanduva.bin": { - "checksum": "a7443e903c5ebb8a9ef7eebbf3a625b1", - "uncompressed_size_bytes": 34483916, - "compressed_size_bytes": 8619214 + "checksum": "27dff0b36d49f5fed136e794ad9ed258", + "uncompressed_size_bytes": 34864623, + "compressed_size_bytes": 8850565 }, "data/input/br/sao_paulo/raw_maps/center.bin": { - "checksum": "accfa249216653e27a47d13e1f1fd7e4", - "uncompressed_size_bytes": 9473758, - "compressed_size_bytes": 2496742 + "checksum": "1344afc8a37b52cffc81b79c32e99506", + "uncompressed_size_bytes": 10230115, + "compressed_size_bytes": 2926542 }, "data/input/br/sao_paulo/raw_maps/sao_miguel_paulista.bin": { - "checksum": "3fe087174603b33c01b10cca95c516c5", - "uncompressed_size_bytes": 591404, - "compressed_size_bytes": 145386 + "checksum": "befa29b3a81ff1e63c2d817044efeff4", + "uncompressed_size_bytes": 798773, + "compressed_size_bytes": 246669 }, "data/input/ca/ca/osm/plateau.osm": { "checksum": "d41d8cd98f00b204e9800998ecf8427e", @@ -3106,24 +3156,24 @@ "compressed_size_bytes": 9015011 }, "data/system/br/sao_paulo/maps/aricanduva.bin": { - "checksum": "ddd45366409816df6c0c581d14055559", - "uncompressed_size_bytes": 54363220, - "compressed_size_bytes": 20757520 + "checksum": "3cda5656c2d6969d2c9fadd6657038ca", + "uncompressed_size_bytes": 54515170, + "compressed_size_bytes": 20816515 }, "data/system/br/sao_paulo/maps/center.bin": { - "checksum": "69b08ea00b97339ddc3d79745dd5cd2d", - "uncompressed_size_bytes": 18742932, - "compressed_size_bytes": 7122663 + "checksum": "209300c85a3095771b09647e67f560fa", + "uncompressed_size_bytes": 18882279, + "compressed_size_bytes": 7158757 }, "data/system/br/sao_paulo/maps/sao_miguel_paulista.bin": { - "checksum": "9144373938117fe60b0db641bda5ecf2", - "uncompressed_size_bytes": 958882, - "compressed_size_bytes": 326208 + "checksum": "441e7c85870d58d54b415d4a0688e727", + "uncompressed_size_bytes": 986925, + "compressed_size_bytes": 335105 }, "data/system/br/sao_paulo/prebaked_results/sao_miguel_paulista/Full.bin": { - "checksum": "565cc8549ce2b275cab59ccb2189532e", - "uncompressed_size_bytes": 27726150, - "compressed_size_bytes": 9401792 + "checksum": "ccea922412a35fa314392b26eb269337", + "uncompressed_size_bytes": 30436191, + "compressed_size_bytes": 9398838 }, "data/system/br/sao_paulo/scenarios/sao_miguel_paulista/Full.bin": { "checksum": "541fdf1f2ba80b4b6cb88f36b186a707", diff --git a/importer/src/map_config.rs b/importer/src/map_config.rs index 2afd69da9f..66cc775221 100644 --- a/importer/src/map_config.rs +++ b/importer/src/map_config.rs @@ -77,6 +77,8 @@ pub fn config_for_map(name: &MapName) -> convert_osm::Options { Some("http://metro.kingcounty.gov/GTFS/google_transit.zip".to_string()) } else if name.city == CityName::new("us", "san_francisco") { Some("https://gtfs.sfmta.com/transitdata/google_transit.zip".to_string()) + } else if name.city == CityName::new("br", "sao_paulo") { + Some("https://github.com/transitland/gtfs-archives-not-hosted-elsewhere/blob/master/sao-paulo-sptrans.zip?raw=true".to_string()) } else { None }, diff --git a/importer/src/utils.rs b/importer/src/utils.rs index db534dfcca..45bbcf1e22 100644 --- a/importer/src/utils.rs +++ b/importer/src/utils.rs @@ -23,7 +23,7 @@ pub async fn download(config: &ImporterConfiguration, output: String, url: &str) println!("- Missing {}, so downloading {}", output, url); abstio::download_to_file(url, None, tmp).await.unwrap(); - if url.ends_with(".zip") { + if url.contains(".zip") { let unzip_to = if output.ends_with('/') { output } else { @@ -34,7 +34,7 @@ pub async fn download(config: &ImporterConfiguration, output: String, url: &str) println!("- Unzipping into {}", unzip_to); must_run_cmd(Command::new(&config.unzip).arg(tmp).arg("-d").arg(unzip_to)); fs_err::remove_file(tmp).unwrap(); - } else if url.ends_with(".gz") { + } else if url.contains(".gz") { println!("- Gunzipping"); fs_err::rename(tmp, format!("{}.gz", output)).unwrap(); diff --git a/map_model/src/make/transit.rs b/map_model/src/make/transit.rs index 44be1ff142..e8eccef6e7 100644 --- a/map_model/src/make/transit.rs +++ b/map_model/src/make/transit.rs @@ -200,7 +200,16 @@ fn create_route( &snapper.train_outgoing_borders }; match borders.closest_pt(exit_pt, border_snap_threshold) { - Some((l, _)) => Some(l), + Some((lane, _)) => { + // Edge case: the last stop is on the same road as the border. We can't lane-change + // suddenly, so match the lane in that case. + let last_stop_lane = map.get_ts(*stops.last().unwrap()).driving_pos.lane(); + Some(if lane.road == last_stop_lane.road { + last_stop_lane + } else { + lane + }) + } None => bail!( "Couldn't find a {:?} border near end {}", route.route_type, @@ -238,6 +247,15 @@ fn create_route( req ); } + if req.start.lane().road == req.end.lane().road + && req.start.dist_along() > req.end.dist_along() + { + bail!( + "Two consecutive stops are on the same road, but they travel backwards: {}", + req + ); + } + if let Err(err) = map.pathfind(req) { bail!("Created the route, but pathfinding failed: {}", err); } diff --git a/map_model/src/pathfind/v2.rs b/map_model/src/pathfind/v2.rs index f2b0007464..11099b53b8 100644 --- a/map_model/src/pathfind/v2.rs +++ b/map_model/src/pathfind/v2.rs @@ -158,6 +158,9 @@ impl PathV2 { // At the simulation layer, we may need to block intermediate lanes to exit a driveway, // so reflect that cost here. The high cost should only be worth it when the v2 path // requires that up-front turn from certain lanes. + // + // TODO This is only valid if we were leaving from a driveway! This is making some + // buses warp after making a stop. let idx_dist = (start_lane_idx - (l.offset as isize)).abs(); let cost = 100 * idx_dist as usize; let fake_turn = TurnID { diff --git a/sim/src/mechanics/car.rs b/sim/src/mechanics/car.rs index bb41ca5012..d7cf6c3cb9 100644 --- a/sim/src/mechanics/car.rs +++ b/sim/src/mechanics/car.rs @@ -34,14 +34,19 @@ pub(crate) struct Car { impl Car { /// Assumes the current head of the path is the thing to cross. pub fn crossing_state(&self, start_dist: Distance, start_time: Time, map: &Map) -> CarState { - let dist_int = DistanceInterval::new_driving( - start_dist, - if self.router.last_step() { - self.router.get_end_dist() - } else { - self.router.head().get_polyline(map).length() - }, - ); + let end_dist = if self.router.last_step() { + self.router.get_end_dist() + } else { + self.router.head().get_polyline(map).length() + }; + if end_dist < start_dist { + panic!( + "{} trying to make a crossing_state from {} to {} at {}. Something's very wrong", + self.vehicle.id, start_dist, end_dist, start_time + ); + } + + let dist_int = DistanceInterval::new_driving(start_dist, end_dist); self.crossing_state_with_end_dist(dist_int, start_time, map) } diff --git a/sim/src/transit.rs b/sim/src/transit.rs index 88d17a6219..c2265394e2 100644 --- a/sim/src/transit.rs +++ b/sim/src/transit.rs @@ -102,6 +102,8 @@ impl TransitSimState { }); continue; } + // TODO Why're we calculating these again? Use bus_route.all_path_requests(), so + // that all the nice checks in the map_model layer are preserved here let req = PathRequest::vehicle( stop1.driving_pos, map.get_ts(bus_route.stops[idx + 1]).driving_pos, @@ -112,6 +114,15 @@ impl TransitSimState { if path.is_empty() { panic!("Empty path between stops?! {}", path.get_req()); } + if stop1.driving_pos != path.get_req().start { + panic!( + "{} will warp from {} to {}", + bus_route.long_name, + stop1.driving_pos, + path.get_req().start, + ); + } + stops.push(Stop { id: stop1.id, driving_pos: stop1.driving_pos,