use std::collections::BTreeSet;
use std::fmt;
use serde::{Deserialize, Serialize};
use abstutil::{deserialize_usize, serialize_usize, wraparound_get, Tags};
use geom::{Distance, Line, PolyLine, Polygon, Pt2D, Ring};
use crate::{
osm, BusStopID, DirectedRoadID, Direction, IntersectionID, Map, MapConfig, Road, RoadID,
TurnType,
};
pub const PARKING_LOT_SPOT_LENGTH: Distance = Distance::const_meters(6.4);
pub const NORMAL_LANE_THICKNESS: Distance = Distance::const_meters(2.5);
const SERVICE_ROAD_LANE_THICKNESS: Distance = Distance::const_meters(1.5);
pub const SIDEWALK_THICKNESS: Distance = Distance::const_meters(1.5);
const SHOULDER_THICKNESS: Distance = Distance::const_meters(0.5);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct LaneID(
#[serde(
serialize_with = "serialize_usize",
deserialize_with = "deserialize_usize"
)]
pub usize,
);
impl fmt::Display for LaneID {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Lane #{}", self.0)
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum LaneType {
Driving,
Parking,
Sidewalk,
Shoulder,
Biking,
Bus,
SharedLeftTurn,
Construction,
LightRail,
}
impl LaneType {
pub fn is_for_moving_vehicles(self) -> bool {
match self {
LaneType::Driving => true,
LaneType::Biking => true,
LaneType::Bus => true,
LaneType::Parking => false,
LaneType::Sidewalk => false,
LaneType::Shoulder => false,
LaneType::SharedLeftTurn => false,
LaneType::Construction => false,
LaneType::LightRail => true,
}
}
pub fn supports_any_movement(self) -> bool {
match self {
LaneType::Driving => true,
LaneType::Biking => true,
LaneType::Bus => true,
LaneType::Parking => false,
LaneType::Sidewalk => true,
LaneType::Shoulder => true,
LaneType::SharedLeftTurn => false,
LaneType::Construction => false,
LaneType::LightRail => true,
}
}
pub fn is_walkable(self) -> bool {
self == LaneType::Sidewalk || self == LaneType::Shoulder
}
pub fn describe(self) -> &'static str {
match self {
LaneType::Driving => "a general-purpose driving lane",
LaneType::Biking => "a protected bike lane",
LaneType::Bus => "a bus-only lane",
LaneType::Parking => "an on-street parking lane",
LaneType::Sidewalk => "a sidewalk",
LaneType::Shoulder => "a shoulder",
LaneType::SharedLeftTurn => "a shared left-turn lane",
LaneType::Construction => "a lane that's closed for construction",
LaneType::LightRail => "a light rail track",
}
}
pub fn short_name(self) -> &'static str {
match self {
LaneType::Driving => "driving lane",
LaneType::Biking => "bike lane",
LaneType::Bus => "bus lane",
LaneType::Parking => "parking lane",
LaneType::Sidewalk => "sidewalk",
LaneType::Shoulder => "shoulder",
LaneType::SharedLeftTurn => "left-turn lane",
LaneType::Construction => "construction",
LaneType::LightRail => "light rail track",
}
}
pub fn from_short_name(x: &str) -> Option<LaneType> {
match x {
"driving lane" => Some(LaneType::Driving),
"bike lane" => Some(LaneType::Biking),
"bus lane" => Some(LaneType::Bus),
"parking lane" => Some(LaneType::Parking),
"sidewalk" => Some(LaneType::Sidewalk),
"shoulder" => Some(LaneType::Shoulder),
"left-turn lane" => Some(LaneType::SharedLeftTurn),
"construction" => Some(LaneType::Construction),
"light rail track" => Some(LaneType::LightRail),
_ => None,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Lane {
pub id: LaneID,
pub parent: RoadID,
pub lane_type: LaneType,
pub lane_center_pts: PolyLine,
pub width: Distance,
pub dir: Direction,
pub src_i: IntersectionID,
pub dst_i: IntersectionID,
pub bus_stops: BTreeSet<BusStopID>,
pub driving_blackhole: bool,
pub biking_blackhole: bool,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct LaneSpec {
pub lt: LaneType,
pub dir: Direction,
pub width: Distance,
}
impl Lane {
pub fn first_pt(&self) -> Pt2D {
self.lane_center_pts.first_pt()
}
pub fn last_pt(&self) -> Pt2D {
self.lane_center_pts.last_pt()
}
pub fn first_line(&self) -> Line {
self.lane_center_pts.first_line()
}
pub fn last_line(&self) -> Line {
self.lane_center_pts.last_line()
}
pub fn endpoint(&self, i: IntersectionID) -> Pt2D {
if i == self.src_i {
self.first_pt()
} else if i == self.dst_i {
self.last_pt()
} else {
panic!("{} isn't an endpoint of {}", i, self.id);
}
}
pub fn end_line(&self, i: IntersectionID) -> Line {
if i == self.src_i {
self.first_line().reverse()
} else if i == self.dst_i {
self.last_line()
} else {
panic!("{} isn't an endpoint of {}", i, self.id);
}
}
pub fn dist_along_of_point(&self, pt: Pt2D) -> Option<Distance> {
self.lane_center_pts
.dist_along_of_point(pt)
.map(|(dist, _)| dist)
}
pub fn length(&self) -> Distance {
self.lane_center_pts.length()
}
pub fn intersections(&self) -> Vec<IntersectionID> {
vec![self.src_i, self.dst_i]
}
pub fn number_parking_spots(&self, cfg: &MapConfig) -> usize {
assert_eq!(self.lane_type, LaneType::Parking);
let spots = (self.length() / cfg.street_parking_spot_length).floor() - 2.0;
if spots >= 1.0 {
spots as usize
} else {
0
}
}
pub fn is_driving(&self) -> bool {
self.lane_type == LaneType::Driving
}
pub fn is_biking(&self) -> bool {
self.lane_type == LaneType::Biking
}
pub fn is_bus(&self) -> bool {
self.lane_type == LaneType::Bus
}
pub fn is_walkable(&self) -> bool {
self.lane_type.is_walkable()
}
pub fn is_sidewalk(&self) -> bool {
self.lane_type == LaneType::Sidewalk
}
pub fn is_shoulder(&self) -> bool {
self.lane_type == LaneType::Shoulder
}
pub fn is_parking(&self) -> bool {
self.lane_type == LaneType::Parking
}
pub fn is_light_rail(&self) -> bool {
self.lane_type == LaneType::LightRail
}
pub fn get_directed_parent(&self) -> DirectedRoadID {
DirectedRoadID {
id: self.parent,
dir: self.dir,
}
}
pub fn get_turn_restrictions(&self, road: &Road) -> Option<BTreeSet<TurnType>> {
if !self.is_driving() {
return None;
}
let all = if self.dir == Direction::Fwd && road.osm_tags.contains_key(osm::ENDPT_FWD) {
road.osm_tags
.get("turn:lanes:forward")
.or_else(|| road.osm_tags.get("turn:lanes"))?
} else if self.dir == Direction::Back && road.osm_tags.contains_key(osm::ENDPT_BACK) {
road.osm_tags.get("turn:lanes:backward")?
} else {
return None;
};
let parts: Vec<&str> = all.split('|').collect();
let lanes: Vec<LaneID> = road
.children(self.dir)
.into_iter()
.filter(|(_, lt)| *lt == LaneType::Driving || *lt == LaneType::Bus)
.map(|(id, _)| id)
.collect();
if parts.len() != lanes.len() {
warn!("{}'s turn restrictions don't match the lanes", road.orig_id);
return None;
}
let part = parts[lanes.iter().position(|l| *l == self.id)?];
if part == "no" || part == "none" || part == "yes" || part == "psv" || part == "bus" {
return None;
}
if part.is_empty() {
return None;
}
Some(
part.split(';')
.flat_map(|s| match s {
"left" | "left\\left" => vec![TurnType::Left],
"right" => vec![TurnType::Right],
"through" | "" => vec![TurnType::Straight],
"slight_right" | "slight right" | "merge_to_right" | "sharp_right" => {
vec![TurnType::Straight, TurnType::Right]
}
"slight_left" | "slight left" | "merge_to_left" | "sharp_left" => {
vec![TurnType::Straight, TurnType::Left]
}
"reverse" => {
vec![TurnType::Left]
}
s => {
warn!("Unknown turn restriction {}", s);
vec![]
}
})
.collect(),
)
}
pub fn trace_around_block(&self, map: &Map) -> Option<(Polygon, BTreeSet<LaneID>)> {
let start = self.id;
let mut pts = Vec::new();
let mut current = start;
let mut fwd = map.get_parent(start).lanes_ltr()[0].0 == start;
let mut visited = BTreeSet::new();
loop {
let l = map.get_l(current);
let lane_pts = if fwd {
l.lane_center_pts.shift_left(l.width / 2.0)
} else {
l.lane_center_pts.reversed().shift_left(l.width / 2.0)
}
.unwrap()
.into_points();
if let Some(last_pt) = pts.last().cloned() {
if last_pt != lane_pts[0] {
let last_i = if fwd { l.src_i } else { l.dst_i };
if let Some(pl) = map
.get_i(last_i)
.polygon
.clone()
.into_ring()
.get_shorter_slice_btwn(last_pt, lane_pts[0])
{
pts.extend(pl.into_points());
}
}
}
pts.extend(lane_pts);
let i = if fwd { l.dst_i } else { l.src_i };
let mut roads = map
.get_i(i)
.get_roads_sorted_by_incoming_angle(map.all_roads());
roads.retain(|r| !map.get_r(*r).is_footway());
let idx = roads.iter().position(|r| *r == l.parent).unwrap();
let next_road = map.get_r(*wraparound_get(&roads, (idx as isize) + 1));
let next_lane = if next_road.src_i == i {
next_road.lanes_ltr()[0].0
} else {
next_road.lanes_ltr().last().unwrap().0
};
if next_lane == start {
break;
}
if visited.contains(¤t) {
return None;
}
visited.insert(current);
current = next_lane;
fwd = map.get_l(current).src_i == i;
}
pts.push(pts[0]);
pts.dedup();
Some((Ring::new(pts).ok()?.into_polygon(), visited))
}
}
impl LaneSpec {
pub fn typical_lane_widths(lt: LaneType, tags: &Tags) -> Vec<(Distance, &'static str)> {
let rank = if let Some(x) = tags.get(osm::HIGHWAY) {
osm::RoadRank::from_highway(x)
} else {
osm::RoadRank::Local
};
match lt {
LaneType::Driving => {
let mut choices = vec![
(Distance::feet(8.0), "narrow"),
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
(Distance::feet(10.0), "typical"),
(Distance::feet(12.0), "highway"),
];
if rank == osm::RoadRank::Highway
&& tags
.get(osm::HIGHWAY)
.map(|x| !x.ends_with("_link"))
.unwrap_or(true)
{
choices.rotate_right(1);
} else if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
choices.swap(1, 0);
}
choices
}
LaneType::Biking => vec![
(Distance::meters(2.0), "standard"),
(Distance::meters(1.5), "absolute minimum"),
],
LaneType::Bus => vec![
(Distance::feet(12.0), "normal"),
(Distance::feet(10.0), "minimum"),
],
LaneType::Parking => {
let mut choices = vec![
(Distance::feet(7.0), "narrow"),
(SERVICE_ROAD_LANE_THICKNESS, "alley"),
(Distance::feet(9.0), "wide"),
(Distance::feet(15.0), "loading zone"),
];
if tags.is(osm::HIGHWAY, "service") || tags.is("narrow", "yes") {
choices.swap(1, 0);
}
choices
}
LaneType::SharedLeftTurn => vec![(NORMAL_LANE_THICKNESS, "default")],
LaneType::Construction => vec![(NORMAL_LANE_THICKNESS, "default")],
LaneType::LightRail => vec![(NORMAL_LANE_THICKNESS, "default")],
LaneType::Sidewalk => vec![
(SIDEWALK_THICKNESS, "default"),
(Distance::feet(6.0), "wide"),
],
LaneType::Shoulder => vec![(SHOULDER_THICKNESS, "default")],
}
}
}