Move more lane transformation logic into raw_map

This commit is contained in:
Dustin Carlino 2022-06-26 12:01:18 -05:00
parent 9841dad3ec
commit e47a985e54
5 changed files with 243 additions and 244 deletions

View File

@ -1,240 +0,0 @@
use abstutil::Tags;
use map_model::{Direction, DrivingSide, EditRoad, LaneSpec, LaneType};
/// Returns the index where the new lane was inserted
pub fn add_new_lane(
road: &mut EditRoad,
lt: LaneType,
osm_tags: &Tags,
driving_side: DrivingSide,
) -> usize {
let mut dir = Direction::Fwd;
let mut idx = 0;
match lt {
LaneType::Driving => {
dir = determine_lane_dir(road, lt, true);
// In the middle (where the direction changes)
idx = road
.lanes_ltr
.windows(2)
.position(|pair| pair[0].dir != pair[1].dir)
.map(|x| x + 1)
.unwrap_or(road.lanes_ltr.len());
}
LaneType::Biking | LaneType::Bus | LaneType::Parking | LaneType::Construction => {
let relevant_lanes: Vec<&LaneSpec> =
road.lanes_ltr.iter().filter(|x| x.lt == lt).collect();
dir = if !relevant_lanes.is_empty() {
// When a lane already exists, then default to the direction on the other side of
// the road
relevant_lanes[0].dir.opposite()
} else {
// If no lanes exist, then default to the majority direction, to help deal with
// one-way streets
determine_lane_dir(road, lt, false)
};
// Place on the dir side, before any sidewalk
idx = default_outside_lane_placement(road, dir);
}
LaneType::Sidewalk => {
// Place where it's missing
if !road.lanes_ltr[0].lt.is_walkable() {
idx = 0;
dir = if driving_side == DrivingSide::Right {
Direction::Back
} else {
Direction::Fwd
};
} else {
idx = road.lanes_ltr.len();
dir = if driving_side == DrivingSide::Right {
Direction::Fwd
} else {
Direction::Back
};
}
}
LaneType::Buffer(_) => {
// Look for the bike lane that's missing a buffer
let mut fwd_bike = None;
let mut back_bike = None;
for (idx, spec) in road.lanes_ltr.iter().enumerate() {
if spec.lt == LaneType::Biking {
if spec.dir == Direction::Fwd {
fwd_bike = Some(idx);
} else {
back_bike = Some(idx);
}
}
}
// TODO This is US-centric, since it assumes the Fwd direction is on the right. We
// should probably decompose into sides like maybe_add_bike_lanes.
if let Some(i) = fwd_bike {
// If there's nothing to the left of this bike lane, not sure what's going on...
if road
.lanes_ltr
.get(i - 1)
.map(|spec| !matches!(spec.lt, LaneType::Buffer(_)))
.unwrap_or(false)
{
dir = Direction::Fwd;
idx = i;
}
}
if let Some(i) = back_bike {
if road
.lanes_ltr
.get(i + 1)
.map(|spec| !matches!(spec.lt, LaneType::Buffer(_)))
.unwrap_or(false)
{
dir = Direction::Back;
idx = i + 1;
}
}
}
_ => unreachable!(),
};
road.lanes_ltr.insert(
idx,
LaneSpec {
lt,
dir,
width: LaneSpec::typical_lane_widths(lt, osm_tags)[0].0,
},
);
idx
}
/// Place the new lane according to its direction on the outside unless the outside is walkable in
/// which case place inside the walkable lane
fn default_outside_lane_placement(road: &mut EditRoad, dir: Direction) -> usize {
if road.lanes_ltr[0].dir == dir {
if road.lanes_ltr[0].lt.is_walkable() {
1
} else {
0
}
} else if road.lanes_ltr.last().unwrap().lt.is_walkable() {
road.lanes_ltr.len() - 1
} else {
road.lanes_ltr.len()
}
}
/// If there are more lanes of type lt pointing forward, then insert the new one backwards, and
/// vice versa
fn determine_lane_dir(road: &mut EditRoad, lt: LaneType, minority: bool) -> Direction {
if (road
.lanes_ltr
.iter()
.filter(|x| x.dir == Direction::Fwd && x.lt == lt)
.count() as f64
/ road.lanes_ltr.iter().filter(|x| x.lt == lt).count() as f64)
<= 0.5
{
if minority {
Direction::Fwd
} else {
Direction::Back
}
} else if minority {
Direction::Back
} else {
Direction::Fwd
}
}
#[cfg(test)]
mod tests {
use super::*;
use map_model::BufferType;
#[test]
fn test_add_new_lane() {
let mut ok = true;
for (description, input_lt, input_dir, new_lt, expected_lt, expected_dir) in vec![
(
"Two-way with parking, adding bike lane to first side",
"spddps",
"vvv^^^",
LaneType::Biking,
// TODO Current heuristics put it between parking and sidewalk, but this isn't
// right
"spddpbs",
"vvv^^^^",
),
(
"Two-way with parking, adding bike lane to second side",
"spddpbs",
"vvv^^^^",
LaneType::Biking,
// TODO Current heuristics put it between parking and sidewalk, but this isn't
// right
"sbpddpbs",
"vvvv^^^^",
),
(
"Add driving lane, balanced numbers",
"sdds",
"vv^^",
LaneType::Driving,
"sddds",
"vv^^^",
),
(
"Add driving lane, imbalanced",
"sddds",
"vv^^^",
LaneType::Driving,
"sdddds",
"vvv^^^",
),
(
"Add buffer, one bike lane fwd",
"sddbs",
"vv^^^",
LaneType::Buffer(BufferType::Stripes),
"sdd|bs",
"vv^^^^",
),
(
"Add buffer, one bike lane back",
"sbdds",
"vvv^^",
LaneType::Buffer(BufferType::Stripes),
"sb|dds",
"vvvv^^",
),
(
"Add second buffer",
"sbdd|bs",
"vvv^^^^",
LaneType::Buffer(BufferType::Stripes),
"sb|dd|bs",
"vvvv^^^^",
),
] {
let input = EditRoad::create_for_test(input_lt, input_dir);
let mut actual_output = input.clone();
add_new_lane(
&mut actual_output,
new_lt,
&Tags::empty(),
DrivingSide::Right,
);
actual_output.check_lanes_ltr(
description.to_string(),
input_lt,
input_dir,
expected_lt,
expected_dir,
&mut ok,
);
}
assert!(ok);
}
}

View File

@ -25,7 +25,6 @@ use crate::debug::DebugMode;
use crate::sandbox::{GameplayMode, SandboxMode, TimeWarpScreen}; use crate::sandbox::{GameplayMode, SandboxMode, TimeWarpScreen};
mod crosswalks; mod crosswalks;
mod heuristics;
mod multiple_roads; mod multiple_roads;
mod roads; mod roads;
mod routes; mod routes;

View File

@ -15,7 +15,6 @@ use widgetry::{
use crate::app::{App, Transition}; use crate::app::{App, Transition};
use crate::common::Warping; use crate::common::Warping;
use crate::edit::heuristics::add_new_lane;
use crate::edit::zones::ZoneEditor; use crate::edit::zones::ZoneEditor;
use crate::edit::{apply_map_edits, can_edit_lane, speed_limit_choices}; use crate::edit::{apply_map_edits, can_edit_lane, speed_limit_choices};
@ -322,8 +321,8 @@ impl State<App> for RoadEditor {
let mut edits = app.primary.map.get_edits().clone(); let mut edits = app.primary.map.get_edits().clone();
let old = app.primary.map.get_r_edit(self.r); let old = app.primary.map.get_r_edit(self.r);
let mut new = old.clone(); let mut new = old.clone();
let idx = add_new_lane( let idx = LaneSpec::add_new_lane(
&mut new, &mut new.lanes_ltr,
lt, lt,
&app.primary.map.get_r(self.r).osm_tags, &app.primary.map.get_r(self.r).osm_tags,
app.primary.map.get_config().driving_side, app.primary.map.get_config().driving_side,

View File

@ -0,0 +1,240 @@
use abstutil::Tags;
use crate::{Direction, DrivingSide, LaneSpec, LaneType};
impl LaneSpec {
/// Returns the index where the new lane was inserted
pub fn add_new_lane(
lanes_ltr: &mut Vec<LaneSpec>,
lt: LaneType,
osm_tags: &Tags,
driving_side: DrivingSide,
) -> usize {
let mut dir = Direction::Fwd;
let mut idx = 0;
match lt {
LaneType::Driving => {
dir = determine_lane_dir(lanes_ltr, lt, true);
// In the middle (where the direction changes)
idx = lanes_ltr
.windows(2)
.position(|pair| pair[0].dir != pair[1].dir)
.map(|x| x + 1)
.unwrap_or(lanes_ltr.len());
}
LaneType::Biking | LaneType::Bus | LaneType::Parking | LaneType::Construction => {
let relevant_lanes: Vec<&LaneSpec> =
lanes_ltr.iter().filter(|x| x.lt == lt).collect();
dir = if !relevant_lanes.is_empty() {
// When a lane already exists, then default to the direction on the other side of
// the road
relevant_lanes[0].dir.opposite()
} else {
// If no lanes exist, then default to the majority direction, to help deal with
// one-way streets
determine_lane_dir(lanes_ltr, lt, false)
};
// Place on the dir side, before any sidewalk
idx = default_outside_lane_placement(lanes_ltr, dir);
}
LaneType::Sidewalk => {
// Place where it's missing
if !lanes_ltr[0].lt.is_walkable() {
idx = 0;
dir = if driving_side == DrivingSide::Right {
Direction::Back
} else {
Direction::Fwd
};
} else {
idx = lanes_ltr.len();
dir = if driving_side == DrivingSide::Right {
Direction::Fwd
} else {
Direction::Back
};
}
}
LaneType::Buffer(_) => {
// Look for the bike lane that's missing a buffer
let mut fwd_bike = None;
let mut back_bike = None;
for (idx, spec) in lanes_ltr.iter().enumerate() {
if spec.lt == LaneType::Biking {
if spec.dir == Direction::Fwd {
fwd_bike = Some(idx);
} else {
back_bike = Some(idx);
}
}
}
// TODO This is US-centric, since it assumes the Fwd direction is on the right. We
// should probably decompose into sides like maybe_add_bike_lanes.
if let Some(i) = fwd_bike {
// If there's nothing to the left of this bike lane, not sure what's going on...
if lanes_ltr
.get(i - 1)
.map(|spec| !matches!(spec.lt, LaneType::Buffer(_)))
.unwrap_or(false)
{
dir = Direction::Fwd;
idx = i;
}
}
if let Some(i) = back_bike {
if lanes_ltr
.get(i + 1)
.map(|spec| !matches!(spec.lt, LaneType::Buffer(_)))
.unwrap_or(false)
{
dir = Direction::Back;
idx = i + 1;
}
}
}
_ => unreachable!(),
};
lanes_ltr.insert(
idx,
LaneSpec {
lt,
dir,
width: LaneSpec::typical_lane_widths(lt, osm_tags)[0].0,
},
);
idx
}
}
/// Place the new lane according to its direction on the outside unless the outside is walkable in
/// which case place inside the walkable lane
fn default_outside_lane_placement(lanes_ltr: &[LaneSpec], dir: Direction) -> usize {
if lanes_ltr[0].dir == dir {
if lanes_ltr[0].lt.is_walkable() {
1
} else {
0
}
} else if lanes_ltr.last().unwrap().lt.is_walkable() {
lanes_ltr.len() - 1
} else {
lanes_ltr.len()
}
}
/// If there are more lanes of type lt pointing forward, then insert the new one backwards, and
/// vice versa
fn determine_lane_dir(lanes_ltr: &[LaneSpec], lt: LaneType, minority: bool) -> Direction {
if (lanes_ltr
.iter()
.filter(|x| x.dir == Direction::Fwd && x.lt == lt)
.count() as f64
/ lanes_ltr.iter().filter(|x| x.lt == lt).count() as f64)
<= 0.5
{
if minority {
Direction::Fwd
} else {
Direction::Back
}
} else if minority {
Direction::Back
} else {
Direction::Fwd
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::BufferType;
#[test]
fn test_add_new_lane() {
let mut ok = true;
for (description, input_lt, input_dir, new_lt, expected_lt, expected_dir) in vec![
(
"Two-way with parking, adding bike lane to first side",
"spddps",
"vvv^^^",
LaneType::Biking,
// TODO Current heuristics put it between parking and sidewalk, but this isn't
// right
"spddpbs",
"vvv^^^^",
),
(
"Two-way with parking, adding bike lane to second side",
"spddpbs",
"vvv^^^^",
LaneType::Biking,
// TODO Current heuristics put it between parking and sidewalk, but this isn't
// right
"sbpddpbs",
"vvvv^^^^",
),
(
"Add driving lane, balanced numbers",
"sdds",
"vv^^",
LaneType::Driving,
"sddds",
"vv^^^",
),
(
"Add driving lane, imbalanced",
"sddds",
"vv^^^",
LaneType::Driving,
"sdddds",
"vvv^^^",
),
(
"Add buffer, one bike lane fwd",
"sddbs",
"vv^^^",
LaneType::Buffer(BufferType::Stripes),
"sdd|bs",
"vv^^^^",
),
(
"Add buffer, one bike lane back",
"sbdds",
"vvv^^",
LaneType::Buffer(BufferType::Stripes),
"sb|dds",
"vvvv^^",
),
(
"Add second buffer",
"sbdd|bs",
"vvv^^^^",
LaneType::Buffer(BufferType::Stripes),
"sb|dd|bs",
"vvvv^^^^",
),
] {
let input = LaneSpec::create_for_test(input_lt, input_dir);
let mut actual_output = input.clone();
LaneSpec::add_new_lane(
&mut actual_output,
new_lt,
&Tags::empty(),
DrivingSide::Right,
);
LaneSpec::check_lanes_ltr(
&actual_output,
description.to_string(),
input_lt,
input_dir,
expected_lt,
expected_dir,
&mut ok,
);
}
assert!(ok);
}
}

View File

@ -2,6 +2,7 @@
// testing infrastructure. // testing infrastructure.
mod add_bike_lanes; mod add_bike_lanes;
mod add_new_lane;
mod one_ways; mod one_ways;
use geom::Distance; use geom::Distance;