1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use std::cmp::Ordering;
use std::collections::{BinaryHeap, HashMap};

use geom::{Distance, Duration, Speed};

use crate::pathfind::WalkingNode;
use crate::{BuildingID, LaneType, Map, PathConstraints};

#[derive(Clone)]
pub struct WalkingOptions {
    /// If true, allow walking on shoulders.
    pub allow_shoulders: bool,
    pub walking_speed: Speed,
}

impl WalkingOptions {
    pub fn default() -> WalkingOptions {
        WalkingOptions {
            allow_shoulders: true,
            walking_speed: Speed::meters_per_second(1.34),
        }
    }

    fn cost(&self, dist: Distance) -> Duration {
        dist / self.walking_speed
    }
}

#[derive(PartialEq, Eq)]
struct Item {
    cost: Duration,
    node: WalkingNode,
}
impl PartialOrd for Item {
    fn partial_cmp(&self, other: &Item) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Item {
    fn cmp(&self, other: &Item) -> Ordering {
        // BinaryHeap is a max-heap, so reverse the comparison to get smallest times first.
        let ord = other.cost.cmp(&self.cost);
        if ord != Ordering::Equal {
            return ord;
        }
        self.node.cmp(&other.node)
    }
}

/// 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.
///
/// If the start building is 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,
    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();
    }

    let mut queue: BinaryHeap<Item> = BinaryHeap::new();
    queue.push(Item {
        cost: Duration::ZERO,
        node: WalkingNode::closest(map.get_b(start).sidewalk_pos, map),
    });

    let mut cost_per_node: HashMap<WalkingNode, Duration> = HashMap::new();
    while let Some(current) = queue.pop() {
        if cost_per_node.contains_key(&current.node) {
            continue;
        }
        if current.cost > time_limit {
            continue;
        }
        cost_per_node.insert(current.node, current.cost);

        let (l, is_dst_i) = match current.node {
            WalkingNode::SidewalkEndpoint(l, is_dst_i) => (l, is_dst_i),
            _ => unreachable!(),
        };
        let lane = map.get_l(l);
        // Cross the lane
        if opts.allow_shoulders || lane.lane_type != LaneType::Shoulder {
            queue.push(Item {
                cost: current.cost + opts.cost(lane.length()),
                node: WalkingNode::SidewalkEndpoint(lane.id, !is_dst_i),
            });
        }
        // All turns from the lane
        for turn in map.get_turns_for(lane.id, PathConstraints::Pedestrian) {
            if (turn.id.parent == lane.dst_i) != is_dst_i {
                continue;
            }
            queue.push(Item {
                cost: current.cost + opts.cost(turn.geom.length()),
                node: WalkingNode::SidewalkEndpoint(
                    turn.id.dst,
                    map.get_l(turn.id.dst).dst_i == turn.id.parent,
                ),
            });
        }
    }

    let mut results = HashMap::new();
    // Assign every building a cost based on which end of the sidewalk it's closest to
    // TODO We could try to get a little more accurate by accounting for the distance from that
    // end of the sidewalk to the building
    for b in map.all_buildings() {
        if let Some(cost) = cost_per_node.get(&WalkingNode::closest(b.sidewalk_pos, map)) {
            results.insert(b.id, *cost);
        }
    }

    results
}