From 885fdefb5dd445589d09ef353e374781c0721f1c Mon Sep 17 00:00:00 2001 From: Mateusz Konieczny Date: Mon, 13 Jul 2020 18:56:05 +0200 Subject: [PATCH] playing with processing modifications (#183) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WIP set up plumbing for calculating building info in the Raw layer and using it later for #154 * WIP playing with processing modifications * experiment with some possibilities for tag processing * fix an embarassing typo * enable busses in work-home traffic * refactor, add multiuse * seed more parking in Kraków * parse integers properly - thanks for a help! * rebalance generated trips * add hack providing some background traffic more realistic traffic even mess realistic people * attempt to further reduce parking deficit * add TODO Co-authored-by: Dustin Carlino --- Cargo.lock | 2 + convert_osm/src/lib.rs | 10 ++-- importer/src/krakow.rs | 5 +- map_model/Cargo.toml | 2 + map_model/src/lib.rs | 4 +- map_model/src/make/buildings.rs | 80 +++++++++++++++++++++++++++++-- map_model/src/objects/building.rs | 11 +++++ sim/src/make/generator.rs | 80 ++++++++++++++++++++++++------- 8 files changed, 168 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e6c0bb8ee..5b429b081a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1814,6 +1814,8 @@ dependencies = [ "geom 0.1.0", "nbez 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "petgraph 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_xorshift 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "seattle_traffic_signals 0.1.0 (git+https://github.com/dabreegster/seattle_traffic_signals)", "serde 1.0.110 (registry+https://github.com/rust-lang/crates.io-index)", "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/convert_osm/src/lib.rs b/convert_osm/src/lib.rs index a05f47bbb8..db4d60e011 100644 --- a/convert_osm/src/lib.rs +++ b/convert_osm/src/lib.rs @@ -39,7 +39,7 @@ pub enum OnstreetParking { Blockface(String), // If OSM data is missing, then infer parking lanes on some percentage of // "highway=residential" roads. - SomeResidential { + SomeAdditionalWhereNoData { // [0, 100] pct: usize, }, @@ -86,11 +86,15 @@ pub fn convert(opts: Options, timer: &mut abstutil::Timer) -> RawMap { OnstreetParking::Blockface(ref path) => { use_parking_hints(&mut map, path.clone(), timer); } - OnstreetParking::SomeResidential { pct } => { + OnstreetParking::SomeAdditionalWhereNoData { pct } => { let pct = pct as i64; for (id, r) in map.roads.iter_mut() { if r.osm_tags.contains_key(osm::INFERRED_PARKING) - && r.osm_tags.get(osm::HIGHWAY) == Some(&"residential".to_string()) + && ( + r.osm_tags.get(osm::HIGHWAY) == Some(&"residential".to_string()) + || r.osm_tags.get(osm::HIGHWAY) == Some(&"tertiary".to_string()) + || r.osm_tags.get(osm::HIGHWAY) == Some(&"living_street".to_string()) + ) && id.osm_way_id % 100 <= pct { if r.osm_tags.get("oneway") == Some(&"yes".to_string()) { diff --git a/importer/src/krakow.rs b/importer/src/krakow.rs index 0a62156c17..4c9cec3452 100644 --- a/importer/src/krakow.rs +++ b/importer/src/krakow.rs @@ -31,9 +31,10 @@ pub fn osm_to_raw(name: &str) { bikes_can_use_bus_lanes: false, }, - onstreet_parking: convert_osm::OnstreetParking::SomeResidential { pct: 50 }, + onstreet_parking: convert_osm::OnstreetParking::SomeAdditionalWhereNoData { pct: 90 }, public_offstreet_parking: convert_osm::PublicOffstreetParking::None, - private_offstreet_parking: convert_osm::PrivateOffstreetParking::FixedPerBldg(1), + private_offstreet_parking: convert_osm::PrivateOffstreetParking::FixedPerBldg(3), // TODO: support amenity=parking_entrance + // TODO: investigate why some many buildings drop their private parkings elevation: None, }, &mut abstutil::Timer::throwaway(), diff --git a/map_model/Cargo.toml b/map_model/Cargo.toml index 11707268c6..e2e698407b 100644 --- a/map_model/Cargo.toml +++ b/map_model/Cargo.toml @@ -15,3 +15,5 @@ petgraph = "0.5.0" serde = "1.0.110" thread_local = "1.0.1" seattle_traffic_signals = { git = "https://github.com/dabreegster/seattle_traffic_signals" } +rand = "0.7.0" +rand_xorshift = "0.2.0" \ No newline at end of file diff --git a/map_model/src/lib.rs b/map_model/src/lib.rs index e454d7103a..f87c67ef0a 100644 --- a/map_model/src/lib.rs +++ b/map_model/src/lib.rs @@ -16,7 +16,9 @@ pub use crate::edits::{ pub use crate::make::initial::lane_specs::RoadSpec; pub use crate::map::MapConfig; pub use crate::objects::area::{Area, AreaID, AreaType}; -pub use crate::objects::building::{Building, BuildingID, FrontPath, OffstreetParking}; +pub use crate::objects::building::{ + Building, BuildingID, BuildingType, FrontPath, OffstreetParking, +}; pub use crate::objects::bus_stop::{BusRoute, BusRouteID, BusStop, BusStopID}; pub use crate::objects::intersection::{Intersection, IntersectionID, IntersectionType}; pub use crate::objects::lane::{ diff --git a/map_model/src/make/buildings.rs b/map_model/src/make/buildings.rs index 58662d2395..d1676754e7 100644 --- a/map_model/src/make/buildings.rs +++ b/map_model/src/make/buildings.rs @@ -1,12 +1,14 @@ use crate::make::match_points_to_lanes; use crate::raw::{OriginalBuilding, RawBuilding, RawParkingLot}; use crate::{ - osm, Building, BuildingID, FrontPath, LaneID, LaneType, Map, OffstreetParking, ParkingLot, - ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_LOT_SPOT_LENGTH, + osm, Building, BuildingID, BuildingType, FrontPath, LaneID, LaneType, Map, OffstreetParking, + ParkingLot, ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_LOT_SPOT_LENGTH, }; use abstutil::Timer; use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring}; -use std::collections::{BTreeMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashSet}; +use rand::{Rng, SeedableRng}; +use rand_xorshift::XorShiftRng; pub fn make_all_buildings( input: &BTreeMap, @@ -57,6 +59,7 @@ pub fn make_all_buildings( }; let id = BuildingID(results.len()); + let mut rng = XorShiftRng::seed_from_u64(orig_id.osm_way_id as u64); let mut bldg = Building { id, polygon: b.polygon.clone(), @@ -70,6 +73,7 @@ pub fn make_all_buildings( amenities: b.amenities.clone(), parking: None, label_center: b.polygon.polylabel(), + bldg_type: classify_bldg(&b.osm_tags, &b.amenities, b.polygon.area(), &mut rng), }; // Can this building have a driveway? If it's not next to a driving lane, then no. @@ -354,3 +358,73 @@ fn line_valid( true } + +fn classify_bldg( + tags: &BTreeMap, + amenities: &BTreeSet<(String, String)>, + area_sq_meters: f64, + rng: &mut rand_xorshift::XorShiftRng, +) -> BuildingType { + // used: top values from https://taginfo.openstreetmap.org/keys/building#values (>100k uses) + let tags = Tags(tags); + + let mut commercial = false; + let mut workers = 0; + + // These are (name, amenity type) pairs, produced by get_bldg_amenities in + // convert_osm/src/osm_reader.rs. + if !amenities.is_empty() { + commercial = true; + } + + if tags.is("ruins", "yes") { + if commercial { + return BuildingType::Commercial; + } + return BuildingType::Empty; + } + + if tags.is_any("building", vec!["office", "industrial", "commercial", "retail", "warehouse", "civic", "public"]) { + return BuildingType::Commercial; + } else if tags.is_any("building", vec!["school", "university", "construction", "church"]) { + // TODO: special handling in future + return BuildingType::Empty; + } else if tags.is_any("building", vec!["garage", "garages", "shed", "roof", "greenhouse", "farm_auxiliary", "barn", "service"]) { + return BuildingType::Empty; + } else if tags.is_any("building", vec!["house", "detached", "semidetached_house", "farm"]) { + workers = rng.gen_range(0, 3); + } else if tags.is_any("building", vec!["hut", "static_caravan", "cabin"]) { + workers = rng.gen_range(0, 2); + } else if tags.is_any("building", vec!["apartments", "terrace", "residential"]) { + let levels = tags.0.get("building:levels").and_then(|x| x.parse::().ok()).unwrap_or(1); + // TODO is it worth using height or building:height as an alternative if not tagged? + // 1 person per 10 square meters + let residents = (levels as f64 * area_sq_meters / 10.0) as usize; + workers = (residents / 3) as usize; + } else { + workers = rng.gen_range(0, 2); + } + if commercial { + if workers > 0 { + return BuildingType::ResidentialCommercial(workers); + } + return BuildingType::Commercial; + } + return BuildingType::Residential(workers); +} + +// TODO Refactor with lane_specs +struct Tags<'a>(&'a BTreeMap); +impl<'a> Tags<'a> { + fn is(&self, k: &str, v: &str) -> bool { + self.0.get(k) == Some(&v.to_string()) + } + + fn is_any(&self, k: &str, values: Vec<&str>) -> bool { + if let Some(v) = self.0.get(k) { + values.contains(&v.as_ref()) + } else { + false + } + } +} diff --git a/map_model/src/objects/building.rs b/map_model/src/objects/building.rs index a8438a9c09..1c198d926d 100644 --- a/map_model/src/objects/building.rs +++ b/map_model/src/objects/building.rs @@ -50,6 +50,7 @@ pub struct Building { // Where a text label should be centered to have the best chances of being contained within the // polygon. pub label_center: Pt2D, + // TODO Might fold these into BuildingType::Commercial // (Name, amenity) pub amenities: BTreeSet<(String, String)>, @@ -57,6 +58,16 @@ pub struct Building { // Every building can't have OffstreetParking, because the nearest usable driving lane (not in // a parking blackhole) might be far away pub parking: Option, + pub bldg_type: BuildingType, +} + +#[derive(Serialize, Deserialize, Debug)] +pub enum BuildingType { + // An estimated number of residents + Residential(usize), + ResidentialCommercial(usize), + Commercial, + Empty, } impl Building { diff --git a/sim/src/make/generator.rs b/sim/src/make/generator.rs index b128524dd2..689d75574c 100644 --- a/sim/src/make/generator.rs +++ b/sim/src/make/generator.rs @@ -4,7 +4,7 @@ use crate::{ }; use abstutil::Timer; use geom::{Distance, Duration, Time}; -use map_model::{BuildingID, DirectedRoadID, Map, PathConstraints, PathRequest}; +use map_model::{BuildingID, BuildingType, DirectedRoadID, Map, PathConstraints, PathRequest}; use rand::seq::SliceRandom; use rand::Rng; use rand_xorshift::XorShiftRng; @@ -418,29 +418,41 @@ fn rand_time(rng: &mut XorShiftRng, low: Time, high: Time) -> Time { impl ScenarioGenerator { // Designed in https://github.com/dabreegster/abstreet/issues/154 pub fn proletariat_robot(map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) -> Scenario { - // First classify buildings into residences (with a number of people) or workplaces. No - // mixed-use yet; every building is one or the other. let mut residences: Vec<(BuildingID, usize)> = Vec::new(); let mut workplaces: Vec = Vec::new(); let mut total_ppl = 0; for b in map.all_buildings() { - // These are scraped from OSM "shop" and "amenity" tags. - if b.amenities.is_empty() { - // TODO Guess number of residences based on OSM tags. - let num_ppl = rng.gen_range(1, 5); - residences.push((b.id, num_ppl)); - total_ppl += num_ppl; - } else { - workplaces.push(b.id); + match b.bldg_type { + BuildingType::Residential(num_ppl) => { + residences.push((b.id, num_ppl)); + total_ppl += num_ppl; + } + BuildingType::ResidentialCommercial(num_ppl) => { + residences.push((b.id, num_ppl)); + total_ppl += num_ppl; + workplaces.push(b.id); + } + BuildingType::Commercial => { + workplaces.push(b.id); + } + BuildingType::Empty => {} } } let mut s = Scenario::empty(map, "random people going to/from work"); + s.only_seed_buses = None; timer.start_iter("create people", total_ppl); for (home, num_ppl) in residences { for _ in 0..num_ppl { timer.next(); // Make a person going from their home to a random workplace, then back again later. + + // TODO refactor + // function or associated item not found in `rand_xorshift::XorShiftRng` + // so why it works in buildings.rs? + //let mut deterministicRng = XorShiftRng::seed_from_u64(home.0 as u64); + //let work = *workplaces.choose(deterministicRng).unwrap(); + let work = *workplaces.choose(rng).unwrap(); // Decide mode based on walking distance. let dist = if let Some(path) = map.pathfind(PathRequest { @@ -454,14 +466,33 @@ impl ScenarioGenerator { // this person. continue; }; - // Trips over 2 miles will drive 90% of the time, the other 10% will attempt - // transit (falling back to a very long walk). + if home.0 == work.0 { + // working and living in the same building + continue; + } + // Longer trips will mostly drive of the time, remaining will attempt + // transit (falling back to a very long walk), with some small number of people cycling. // TODO Make this probabilistic - let mode = if dist < Distance::miles(1.0) { + // TODO - do not select based on distance but select one that is fastest/best in the given situation + // excellent bus connection / plenty of parking / cycleways / suitable rail connection + // all strongly influence selected mode of transport, distance is not the sole influence + let mode = if dist < Distance::miles(0.5) { TripMode::Walk } else if dist < Distance::miles(2.0) { + if rng.gen_bool(0.3) { + // 30% + TripMode::Transit + } else if rng.gen_bool(0.8) { + // 0.7 * 0.8 = 56% + TripMode::Drive + } else { + // 14% + TripMode::Bike + } + } else if rng.gen_bool(0.005) { + // low chance for really, really dedicated cyclists TripMode::Bike - } else if rng.gen_bool(0.9) { + } else if rng.gen_bool(0.7) { TripMode::Drive } else { TripMode::Transit @@ -470,17 +501,32 @@ impl ScenarioGenerator { // TODO This will cause a single morning and afternoon rush. Outside of these times, // it'll be really quiet. Probably want a normal distribution centered around these // peak times, but with a long tail. - let depart_am = rand_time( + let mut depart_am = rand_time( rng, Time::START_OF_DAY + Duration::hours(7), Time::START_OF_DAY + Duration::hours(10), ); - let depart_pm = rand_time( + let mut depart_pm = rand_time( rng, Time::START_OF_DAY + Duration::hours(17), Time::START_OF_DAY + Duration::hours(19), ); + if rng.gen_bool(0.1) { + // hacky hack to get some background traffic + // TODO - avoid mutable variable + depart_am = rand_time( + rng, + Time::START_OF_DAY + Duration::hours(0), + Time::START_OF_DAY + Duration::hours(12), + ); + depart_pm = rand_time( + rng, + Time::START_OF_DAY + Duration::hours(12), + Time::START_OF_DAY + Duration::hours(24), + ); + } + let (goto_work, return_home) = match ( SpawnTrip::new( TripEndpoint::Bldg(home),