mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 23:15:24 +03:00
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:
parent
5e57c5ee92
commit
885fdefb5d
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -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)",
|
||||
|
@ -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()) {
|
||||
|
@ -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(),
|
||||
|
@ -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"
|
@ -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::{
|
||||
|
@ -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<OriginalBuilding, RawBuilding>,
|
||||
@ -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<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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<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 {
|
||||
|
@ -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<BuildingID> = 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),
|
||||
|
Loading…
Reference in New Issue
Block a user