playing with processing modifications (#183)

* 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 <dabreegster@gmail.com>
This commit is contained in:
Mateusz Konieczny 2020-07-13 18:56:05 +02:00 committed by GitHub
parent 5e57c5ee92
commit 885fdefb5d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 168 additions and 26 deletions

2
Cargo.lock generated
View File

@ -1814,6 +1814,8 @@ dependencies = [
"geom 0.1.0", "geom 0.1.0",
"nbez 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "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)", "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)", "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)", "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)", "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",

View File

@ -39,7 +39,7 @@ pub enum OnstreetParking {
Blockface(String), Blockface(String),
// If OSM data is missing, then infer parking lanes on some percentage of // If OSM data is missing, then infer parking lanes on some percentage of
// "highway=residential" roads. // "highway=residential" roads.
SomeResidential { SomeAdditionalWhereNoData {
// [0, 100] // [0, 100]
pct: usize, pct: usize,
}, },
@ -86,11 +86,15 @@ pub fn convert(opts: Options, timer: &mut abstutil::Timer) -> RawMap {
OnstreetParking::Blockface(ref path) => { OnstreetParking::Blockface(ref path) => {
use_parking_hints(&mut map, path.clone(), timer); use_parking_hints(&mut map, path.clone(), timer);
} }
OnstreetParking::SomeResidential { pct } => { OnstreetParking::SomeAdditionalWhereNoData { pct } => {
let pct = pct as i64; let pct = pct as i64;
for (id, r) in map.roads.iter_mut() { for (id, r) in map.roads.iter_mut() {
if r.osm_tags.contains_key(osm::INFERRED_PARKING) 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 && id.osm_way_id % 100 <= pct
{ {
if r.osm_tags.get("oneway") == Some(&"yes".to_string()) { if r.osm_tags.get("oneway") == Some(&"yes".to_string()) {

View File

@ -31,9 +31,10 @@ pub fn osm_to_raw(name: &str) {
bikes_can_use_bus_lanes: false, 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, 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, elevation: None,
}, },
&mut abstutil::Timer::throwaway(), &mut abstutil::Timer::throwaway(),

View File

@ -15,3 +15,5 @@ petgraph = "0.5.0"
serde = "1.0.110" serde = "1.0.110"
thread_local = "1.0.1" thread_local = "1.0.1"
seattle_traffic_signals = { git = "https://github.com/dabreegster/seattle_traffic_signals" } seattle_traffic_signals = { git = "https://github.com/dabreegster/seattle_traffic_signals" }
rand = "0.7.0"
rand_xorshift = "0.2.0"

View File

@ -16,7 +16,9 @@ pub use crate::edits::{
pub use crate::make::initial::lane_specs::RoadSpec; pub use crate::make::initial::lane_specs::RoadSpec;
pub use crate::map::MapConfig; pub use crate::map::MapConfig;
pub use crate::objects::area::{Area, AreaID, AreaType}; 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::bus_stop::{BusRoute, BusRouteID, BusStop, BusStopID};
pub use crate::objects::intersection::{Intersection, IntersectionID, IntersectionType}; pub use crate::objects::intersection::{Intersection, IntersectionID, IntersectionType};
pub use crate::objects::lane::{ pub use crate::objects::lane::{

View File

@ -1,12 +1,14 @@
use crate::make::match_points_to_lanes; use crate::make::match_points_to_lanes;
use crate::raw::{OriginalBuilding, RawBuilding, RawParkingLot}; use crate::raw::{OriginalBuilding, RawBuilding, RawParkingLot};
use crate::{ use crate::{
osm, Building, BuildingID, FrontPath, LaneID, LaneType, Map, OffstreetParking, ParkingLot, osm, Building, BuildingID, BuildingType, FrontPath, LaneID, LaneType, Map, OffstreetParking,
ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_LOT_SPOT_LENGTH, ParkingLot, ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_LOT_SPOT_LENGTH,
}; };
use abstutil::Timer; use abstutil::Timer;
use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring}; 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( pub fn make_all_buildings(
input: &BTreeMap<OriginalBuilding, RawBuilding>, input: &BTreeMap<OriginalBuilding, RawBuilding>,
@ -57,6 +59,7 @@ pub fn make_all_buildings(
}; };
let id = BuildingID(results.len()); let id = BuildingID(results.len());
let mut rng = XorShiftRng::seed_from_u64(orig_id.osm_way_id as u64);
let mut bldg = Building { let mut bldg = Building {
id, id,
polygon: b.polygon.clone(), polygon: b.polygon.clone(),
@ -70,6 +73,7 @@ pub fn make_all_buildings(
amenities: b.amenities.clone(), amenities: b.amenities.clone(),
parking: None, parking: None,
label_center: b.polygon.polylabel(), 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. // 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 true
} }
fn classify_bldg(
tags: &BTreeMap<String, String>,
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::<usize>().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<String, String>);
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
}
}
}

View File

@ -50,6 +50,7 @@ pub struct Building {
// Where a text label should be centered to have the best chances of being contained within the // Where a text label should be centered to have the best chances of being contained within the
// polygon. // polygon.
pub label_center: Pt2D, pub label_center: Pt2D,
// TODO Might fold these into BuildingType::Commercial
// (Name, amenity) // (Name, amenity)
pub amenities: BTreeSet<(String, String)>, 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 // Every building can't have OffstreetParking, because the nearest usable driving lane (not in
// a parking blackhole) might be far away // a parking blackhole) might be far away
pub parking: Option<OffstreetParking>, pub parking: Option<OffstreetParking>,
pub bldg_type: BuildingType,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum BuildingType {
// An estimated number of residents
Residential(usize),
ResidentialCommercial(usize),
Commercial,
Empty,
} }
impl Building { impl Building {

View File

@ -4,7 +4,7 @@ use crate::{
}; };
use abstutil::Timer; use abstutil::Timer;
use geom::{Distance, Duration, Time}; 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::seq::SliceRandom;
use rand::Rng; use rand::Rng;
use rand_xorshift::XorShiftRng; use rand_xorshift::XorShiftRng;
@ -418,29 +418,41 @@ fn rand_time(rng: &mut XorShiftRng, low: Time, high: Time) -> Time {
impl ScenarioGenerator { impl ScenarioGenerator {
// Designed in https://github.com/dabreegster/abstreet/issues/154 // Designed in https://github.com/dabreegster/abstreet/issues/154
pub fn proletariat_robot(map: &Map, rng: &mut XorShiftRng, timer: &mut Timer) -> Scenario { 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 residences: Vec<(BuildingID, usize)> = Vec::new();
let mut workplaces: Vec<BuildingID> = Vec::new(); let mut workplaces: Vec<BuildingID> = Vec::new();
let mut total_ppl = 0; let mut total_ppl = 0;
for b in map.all_buildings() { for b in map.all_buildings() {
// These are scraped from OSM "shop" and "amenity" tags. match b.bldg_type {
if b.amenities.is_empty() { BuildingType::Residential(num_ppl) => {
// TODO Guess number of residences based on OSM tags. residences.push((b.id, num_ppl));
let num_ppl = rng.gen_range(1, 5); total_ppl += num_ppl;
}
BuildingType::ResidentialCommercial(num_ppl) => {
residences.push((b.id, num_ppl)); residences.push((b.id, num_ppl));
total_ppl += num_ppl; total_ppl += num_ppl;
} else {
workplaces.push(b.id); workplaces.push(b.id);
} }
BuildingType::Commercial => {
workplaces.push(b.id);
}
BuildingType::Empty => {}
}
} }
let mut s = Scenario::empty(map, "random people going to/from work"); let mut s = Scenario::empty(map, "random people going to/from work");
s.only_seed_buses = None;
timer.start_iter("create people", total_ppl); timer.start_iter("create people", total_ppl);
for (home, num_ppl) in residences { for (home, num_ppl) in residences {
for _ in 0..num_ppl { for _ in 0..num_ppl {
timer.next(); timer.next();
// Make a person going from their home to a random workplace, then back again later. // 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(); let work = *workplaces.choose(rng).unwrap();
// Decide mode based on walking distance. // Decide mode based on walking distance.
let dist = if let Some(path) = map.pathfind(PathRequest { let dist = if let Some(path) = map.pathfind(PathRequest {
@ -454,14 +466,33 @@ impl ScenarioGenerator {
// this person. // this person.
continue; continue;
}; };
// Trips over 2 miles will drive 90% of the time, the other 10% will attempt if home.0 == work.0 {
// transit (falling back to a very long walk). // 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 // 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 TripMode::Walk
} else if dist < Distance::miles(2.0) { } 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 TripMode::Bike
} else if rng.gen_bool(0.9) { }
} else if rng.gen_bool(0.005) {
// low chance for really, really dedicated cyclists
TripMode::Bike
} else if rng.gen_bool(0.7) {
TripMode::Drive TripMode::Drive
} else { } else {
TripMode::Transit TripMode::Transit
@ -470,17 +501,32 @@ impl ScenarioGenerator {
// TODO This will cause a single morning and afternoon rush. Outside of these times, // 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 // it'll be really quiet. Probably want a normal distribution centered around these
// peak times, but with a long tail. // peak times, but with a long tail.
let depart_am = rand_time( let mut depart_am = rand_time(
rng, rng,
Time::START_OF_DAY + Duration::hours(7), Time::START_OF_DAY + Duration::hours(7),
Time::START_OF_DAY + Duration::hours(10), Time::START_OF_DAY + Duration::hours(10),
); );
let depart_pm = rand_time( let mut depart_pm = rand_time(
rng, rng,
Time::START_OF_DAY + Duration::hours(17), Time::START_OF_DAY + Duration::hours(17),
Time::START_OF_DAY + Duration::hours(19), 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 ( let (goto_work, return_home) = match (
SpawnTrip::new( SpawnTrip::new(
TripEndpoint::Bldg(home), TripEndpoint::Bldg(home),