mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 23:43:25 +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",
|
"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)",
|
||||||
|
@ -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()) {
|
||||||
|
@ -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(),
|
||||||
|
@ -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"
|
@ -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::{
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user