mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-25 07:25:47 +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 geom::Percent;
|
||||||
use map_gui::tools::PopupMsg;
|
use map_gui::tools::PopupMsg;
|
||||||
use map_model::{AmenityType, BuildingID};
|
use map_model::{AmenityType, BuildingID};
|
||||||
@ -89,33 +89,28 @@ fn score_houses(
|
|||||||
options: Options,
|
options: Options,
|
||||||
timer: &mut Timer,
|
timer: &mut Timer,
|
||||||
) -> HashMap<BuildingID, Percent> {
|
) -> HashMap<BuildingID, Percent> {
|
||||||
|
let num_categories = amenities.len();
|
||||||
let mut satisfied_per_bldg: Counter<BuildingID> = Counter::new();
|
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 mut houses: HashSet<BuildingID> = HashSet::new();
|
|
||||||
let map = &app.map;
|
let map = &app.map;
|
||||||
for times in timer.parallelize(
|
for times in timer.parallelize(
|
||||||
&format!("find houses close to {}", category),
|
"find houses close to amenities",
|
||||||
Parallelism::Fastest,
|
Parallelism::Fastest,
|
||||||
stores.into_iter().collect(),
|
amenities,
|
||||||
|b| options.clone().time_to_reach_building(map, b),
|
|category| {
|
||||||
) {
|
// For each category, find all matching stores
|
||||||
for (b, _) in times {
|
let mut stores = Vec::new();
|
||||||
houses.insert(b);
|
for b in map.all_buildings() {
|
||||||
|
if b.has_amenity(category) {
|
||||||
|
stores.push(b.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
satisfied_per_bldg.inc(b);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -140,12 +135,22 @@ impl Results {
|
|||||||
scores: HashMap<BuildingID, Percent>,
|
scores: HashMap<BuildingID, Percent>,
|
||||||
amenities: Vec<AmenityType>,
|
amenities: Vec<AmenityType>,
|
||||||
) -> Box<dyn State<App>> {
|
) -> 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![
|
let panel = Panel::new(Widget::col(vec![
|
||||||
Line("Results for your walkable home")
|
Line("Results for your walkable home")
|
||||||
.small_heading()
|
.small_heading()
|
||||||
.into_widget(ctx),
|
.into_widget(ctx),
|
||||||
// TODO Adjust text to say bikeshed, or otherwise reflect the options chosen
|
// 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!(
|
format!(
|
||||||
"Containing at least 1 of each: {}",
|
"Containing at least 1 of each: {}",
|
||||||
amenities
|
amenities
|
||||||
@ -164,14 +169,6 @@ impl Results {
|
|||||||
.aligned(HorizontalAlignment::RightInset, VerticalAlignment::TopInset)
|
.aligned(HorizontalAlignment::RightInset, VerticalAlignment::TopInset)
|
||||||
.build(ctx);
|
.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(
|
SimpleState::new(
|
||||||
panel,
|
panel,
|
||||||
Box::new(Results {
|
Box::new(Results {
|
||||||
|
@ -38,18 +38,19 @@ pub enum Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Options {
|
impl Options {
|
||||||
pub fn time_to_reach_building(
|
// TODO doc
|
||||||
|
pub fn times_from_buildings(
|
||||||
self,
|
self,
|
||||||
map: &Map,
|
map: &Map,
|
||||||
start: BuildingID,
|
starts: Vec<BuildingID>,
|
||||||
) -> HashMap<BuildingID, Duration> {
|
) -> HashMap<BuildingID, Duration> {
|
||||||
match self {
|
match self {
|
||||||
Options::Walking(opts) => {
|
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(
|
Options::Biking => connectivity::all_vehicle_costs_from(
|
||||||
map,
|
map,
|
||||||
start,
|
starts,
|
||||||
Duration::minutes(15),
|
Duration::minutes(15),
|
||||||
PathConstraints::Bike,
|
PathConstraints::Bike,
|
||||||
),
|
),
|
||||||
@ -59,7 +60,7 @@ impl Options {
|
|||||||
|
|
||||||
impl Isochrone {
|
impl Isochrone {
|
||||||
pub fn new(ctx: &mut EventCtx, app: &App, start: BuildingID, options: Options) -> 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 amenities_reachable = MultiMap::new();
|
||||||
let mut population = 0;
|
let mut population = 0;
|
||||||
|
@ -50,11 +50,12 @@ pub fn find_scc(map: &Map, constraints: PathConstraints) -> (HashSet<LaneID>, Ha
|
|||||||
(largest_group, disconnected)
|
(largest_group, disconnected)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Starting from one building, calculate the cost to all others. If a destination isn't reachable,
|
/// Starting from some initial buildings, calculate the cost to all others. If a destination isn't
|
||||||
/// it won't be included in the results. Ignore results greater than the time_limit away.
|
/// reachable, it won't be included in the results. Ignore results greater than the time_limit
|
||||||
|
/// away.
|
||||||
pub fn all_vehicle_costs_from(
|
pub fn all_vehicle_costs_from(
|
||||||
map: &Map,
|
map: &Map,
|
||||||
start: BuildingID,
|
starts: Vec<BuildingID>,
|
||||||
time_limit: Duration,
|
time_limit: Duration,
|
||||||
constraints: PathConstraints,
|
constraints: PathConstraints,
|
||||||
) -> HashMap<BuildingID, Duration> {
|
) -> 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 graph = build_graph_for_vehicles(map, constraints);
|
||||||
let cost_per_road = petgraph::algo::dijkstra(&graph, *start_road, None, |(_, _, mvmnt)| {
|
let cost_per_road = petgraph::algo::dijkstra(&graph, *start_road, None, |(_, _, mvmnt)| {
|
||||||
vehicle_cost(mvmnt.from, *mvmnt, constraints, map.routing_params(), map)
|
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,
|
/// Starting from some initial buildings, calculate the cost to all others. If a destination isn't
|
||||||
/// it won't be included in the results. Ignore results greater than the time_limit away.
|
/// 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
|
/// If all of the start buildings are on the shoulder of a road and `!opts.allow_shoulders`, then
|
||||||
/// results will always be empty.
|
/// the results will always be empty.
|
||||||
pub fn all_walking_costs_from(
|
pub fn all_walking_costs_from(
|
||||||
map: &Map,
|
map: &Map,
|
||||||
start: BuildingID,
|
starts: Vec<BuildingID>,
|
||||||
time_limit: Duration,
|
time_limit: Duration,
|
||||||
opts: WalkingOptions,
|
opts: WalkingOptions,
|
||||||
) -> HashMap<BuildingID, Duration> {
|
) -> HashMap<BuildingID, Duration> {
|
||||||
let start_lane = map.get_l(map.get_b(start).sidewalk_pos.lane());
|
if !opts.allow_shoulders {
|
||||||
if start_lane.lane_type == LaneType::Shoulder && !opts.allow_shoulders {
|
if starts
|
||||||
|
.iter()
|
||||||
|
.all(|b| map.get_l(map.get_b(*b).sidewalk()).lane_type == LaneType::Shoulder)
|
||||||
|
{
|
||||||
return HashMap::new();
|
return HashMap::new();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
let mut queue: BinaryHeap<Item> = BinaryHeap::new();
|
||||||
|
for b in starts {
|
||||||
queue.push(Item {
|
queue.push(Item {
|
||||||
cost: Duration::ZERO,
|
cost: Duration::ZERO,
|
||||||
node: WalkingNode::closest(map.get_b(start).sidewalk_pos, map),
|
node: WalkingNode::closest(map.get_b(b).sidewalk_pos, map),
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut cost_per_node: HashMap<WalkingNode, Duration> = HashMap::new();
|
let mut cost_per_node: HashMap<WalkingNode, Duration> = HashMap::new();
|
||||||
while let Some(current) = queue.pop() {
|
while let Some(current) = queue.pop() {
|
||||||
|
Loading…
Reference in New Issue
Block a user