mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 06:55:40 +03:00
Speed up the 'find perfect home' tool by starting Dijkstra's floodfill with multiple sources. Only implemented for walking right now.
This commit is contained in:
parent
b29a613b42
commit
8d6a184a1f
@ -1,6 +1,6 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use abstutil::{Counter, Parallelism, Timer};
|
||||
use abstutil::{prettyprint_usize, Counter, Parallelism, Timer};
|
||||
use geom::Percent;
|
||||
use map_gui::tools::PopupMsg;
|
||||
use map_model::{AmenityType, BuildingID};
|
||||
@ -89,33 +89,28 @@ fn score_houses(
|
||||
options: Options,
|
||||
timer: &mut Timer,
|
||||
) -> HashMap<BuildingID, Percent> {
|
||||
let num_categories = amenities.len();
|
||||
let mut satisfied_per_bldg: Counter<BuildingID> = Counter::new();
|
||||
|
||||
// There may be more clever ways to calculate this, but we'll take the brute-force approach of
|
||||
// calculating the walkshed from every matching business.
|
||||
let num_categories = amenities.len();
|
||||
for category in amenities {
|
||||
let mut stores: HashSet<BuildingID> = HashSet::new();
|
||||
for b in app.map.all_buildings() {
|
||||
if b.has_amenity(category) {
|
||||
stores.insert(b.id);
|
||||
let map = &app.map;
|
||||
for times in timer.parallelize(
|
||||
"find houses close to amenities",
|
||||
Parallelism::Fastest,
|
||||
amenities,
|
||||
|category| {
|
||||
// For each category, find all matching stores
|
||||
let mut stores = Vec::new();
|
||||
for b in map.all_buildings() {
|
||||
if b.has_amenity(category) {
|
||||
stores.push(b.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut houses: HashSet<BuildingID> = HashSet::new();
|
||||
let map = &app.map;
|
||||
for times in timer.parallelize(
|
||||
&format!("find houses close to {}", category),
|
||||
Parallelism::Fastest,
|
||||
stores.into_iter().collect(),
|
||||
|b| options.clone().time_to_reach_building(map, b),
|
||||
) {
|
||||
for (b, _) in times {
|
||||
houses.insert(b);
|
||||
}
|
||||
}
|
||||
|
||||
for b in houses {
|
||||
// Then find all buildings reachable from any of those starting points
|
||||
options.clone().times_from_buildings(map, stores)
|
||||
},
|
||||
) {
|
||||
for (b, _) in times {
|
||||
satisfied_per_bldg.inc(b);
|
||||
}
|
||||
}
|
||||
@ -140,12 +135,22 @@ impl Results {
|
||||
scores: HashMap<BuildingID, Percent>,
|
||||
amenities: Vec<AmenityType>,
|
||||
) -> Box<dyn State<App>> {
|
||||
// TODO Show imperfect matches with different colors.
|
||||
let mut batch = GeomBatch::new();
|
||||
let mut count = 0;
|
||||
for (b, pct) in scores {
|
||||
if pct == Percent::int(100) {
|
||||
batch.push(Color::RED, app.map.get_b(b).polygon.clone());
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
let panel = Panel::new(Widget::col(vec![
|
||||
Line("Results for your walkable home")
|
||||
.small_heading()
|
||||
.into_widget(ctx),
|
||||
// TODO Adjust text to say bikeshed, or otherwise reflect the options chosen
|
||||
"Here are all of the matching houses.".text_widget(ctx),
|
||||
format!("{} houses match", prettyprint_usize(count)).text_widget(ctx),
|
||||
format!(
|
||||
"Containing at least 1 of each: {}",
|
||||
amenities
|
||||
@ -164,14 +169,6 @@ impl Results {
|
||||
.aligned(HorizontalAlignment::RightInset, VerticalAlignment::TopInset)
|
||||
.build(ctx);
|
||||
|
||||
// TODO Show imperfect matches with different colors.
|
||||
let mut batch = GeomBatch::new();
|
||||
for (b, pct) in scores {
|
||||
if pct == Percent::int(100) {
|
||||
batch.push(Color::RED, app.map.get_b(b).polygon.clone());
|
||||
}
|
||||
}
|
||||
|
||||
SimpleState::new(
|
||||
panel,
|
||||
Box::new(Results {
|
||||
|
@ -38,18 +38,19 @@ pub enum Options {
|
||||
}
|
||||
|
||||
impl Options {
|
||||
pub fn time_to_reach_building(
|
||||
// TODO doc
|
||||
pub fn times_from_buildings(
|
||||
self,
|
||||
map: &Map,
|
||||
start: BuildingID,
|
||||
starts: Vec<BuildingID>,
|
||||
) -> HashMap<BuildingID, Duration> {
|
||||
match self {
|
||||
Options::Walking(opts) => {
|
||||
connectivity::all_walking_costs_from(map, start, Duration::minutes(15), opts)
|
||||
connectivity::all_walking_costs_from(map, starts, Duration::minutes(15), opts)
|
||||
}
|
||||
Options::Biking => connectivity::all_vehicle_costs_from(
|
||||
map,
|
||||
start,
|
||||
starts,
|
||||
Duration::minutes(15),
|
||||
PathConstraints::Bike,
|
||||
),
|
||||
@ -59,7 +60,7 @@ impl Options {
|
||||
|
||||
impl Isochrone {
|
||||
pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID, options: Options) -> Isochrone {
|
||||
let time_to_reach_building = options.clone().time_to_reach_building(&app.map, start);
|
||||
let time_to_reach_building = options.clone().times_from_buildings(&app.map, vec![start]);
|
||||
|
||||
let mut amenities_reachable = MultiMap::new();
|
||||
let mut population = 0;
|
||||
|
@ -50,11 +50,12 @@ pub fn find_scc(map: &Map, constraints: PathConstraints) -> (HashSet<LaneID>, Ha
|
||||
(largest_group, disconnected)
|
||||
}
|
||||
|
||||
/// Starting from one building, calculate the cost to all others. If a destination isn't reachable,
|
||||
/// it won't be included in the results. Ignore results greater than the time_limit away.
|
||||
/// Starting from some initial buildings, calculate the cost to all others. If a destination isn't
|
||||
/// reachable, it won't be included in the results. Ignore results greater than the time_limit
|
||||
/// away.
|
||||
pub fn all_vehicle_costs_from(
|
||||
map: &Map,
|
||||
start: BuildingID,
|
||||
starts: Vec<BuildingID>,
|
||||
time_limit: Duration,
|
||||
constraints: PathConstraints,
|
||||
) -> HashMap<BuildingID, Duration> {
|
||||
@ -77,7 +78,8 @@ pub fn all_vehicle_costs_from(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(start_road) = bldg_to_road.get(&start) {
|
||||
// TODO Use all starting points
|
||||
if let Some(start_road) = bldg_to_road.get(&starts[0]) {
|
||||
let graph = build_graph_for_vehicles(map, constraints);
|
||||
let cost_per_road = petgraph::algo::dijkstra(&graph, *start_road, None, |(_, _, mvmnt)| {
|
||||
vehicle_cost(mvmnt.from, *mvmnt, constraints, map.routing_params(), map)
|
||||
|
@ -56,27 +56,34 @@ impl Ord for Item {
|
||||
}
|
||||
}
|
||||
|
||||
/// Starting from one building, calculate the cost to all others. If a destination isn't reachable,
|
||||
/// it won't be included in the results. Ignore results greater than the time_limit away.
|
||||
/// Starting from some initial buildings, calculate the cost to all others. If a destination isn't
|
||||
/// reachable, it won't be included in the results. Ignore results greater than the time_limit
|
||||
/// away.
|
||||
///
|
||||
/// If the start building is on the shoulder of a road and `!opts.allow_shoulders`, then the
|
||||
/// results will always be empty.
|
||||
/// If all of the start buildings are on the shoulder of a road and `!opts.allow_shoulders`, then
|
||||
/// the results will always be empty.
|
||||
pub fn all_walking_costs_from(
|
||||
map: &Map,
|
||||
start: BuildingID,
|
||||
starts: Vec<BuildingID>,
|
||||
time_limit: Duration,
|
||||
opts: WalkingOptions,
|
||||
) -> HashMap<BuildingID, Duration> {
|
||||
let start_lane = map.get_l(map.get_b(start).sidewalk_pos.lane());
|
||||
if start_lane.lane_type == LaneType::Shoulder && !opts.allow_shoulders {
|
||||
return HashMap::new();
|
||||
if !opts.allow_shoulders {
|
||||
if starts
|
||||
.iter()
|
||||
.all(|b| map.get_l(map.get_b(*b).sidewalk()).lane_type == LaneType::Shoulder)
|
||||
{
|
||||
return HashMap::new();
|
||||
}
|
||||
}
|
||||
|
||||
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
||||
queue.push(Item {
|
||||
cost: Duration::ZERO,
|
||||
node: WalkingNode::closest(map.get_b(start).sidewalk_pos, map),
|
||||
});
|
||||
for b in starts {
|
||||
queue.push(Item {
|
||||
cost: Duration::ZERO,
|
||||
node: WalkingNode::closest(map.get_b(b).sidewalk_pos, map),
|
||||
});
|
||||
}
|
||||
|
||||
let mut cost_per_node: HashMap<WalkingNode, Duration> = HashMap::new();
|
||||
while let Some(current) = queue.pop() {
|
||||
|
Loading…
Reference in New Issue
Block a user