whittle down old stuff from map_editor. i think it still has value as a RawMap viewer with an easy way to move points and quickly iterate on intersection geometry, but things like lane editing and turn restriction viewing aren't useful anymore.

This commit is contained in:
Dustin Carlino 2020-08-05 08:38:04 -07:00
parent e82fe996ad
commit cd712009ec
8 changed files with 73 additions and 870 deletions

View File

@ -3,15 +3,12 @@ mod world;
use abstutil::{CmdArgs, Timer};
use ezgui::{
hotkey, Btn, Canvas, Choice, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, ScreenPt, Text, VerticalAlignment, Widget, Wizard,
GUI,
hotkey, Btn, Canvas, Color, Composite, Drawable, EventCtx, GeomBatch, GfxCtx,
HorizontalAlignment, Key, Line, Outcome, ScreenPt, Text, VerticalAlignment, Widget, GUI,
};
use geom::{ArrowCap, Distance, Line, PolyLine, Polygon, Pt2D};
use map_model::raw::{OriginalBuilding, OriginalIntersection, OriginalRoad, RestrictionType};
use map_model::{osm, NORMAL_LANE_THICKNESS};
use geom::{Distance, Line, Polygon};
use map_model::raw::{OriginalBuilding, OriginalIntersection, OriginalRoad};
use model::{Model, ID};
use std::collections::HashSet;
struct UI {
model: Model,
@ -24,30 +21,13 @@ struct UI {
}
enum State {
Viewing { short_roads: HashSet<OriginalRoad> },
Viewing,
MovingIntersection(OriginalIntersection),
MovingBuilding(OriginalBuilding),
MovingRoadPoint(OriginalRoad, usize),
CreatingRoad(OriginalIntersection),
EditingLanes(OriginalRoad, Wizard),
EditingRoadAttribs(OriginalRoad, Wizard),
SavingModel(Wizard),
// bool is if key is down
SelectingRectangle(Pt2D, Pt2D, bool),
CreatingTurnRestrictionPt1(OriginalRoad),
CreatingTurnRestrictionPt2(OriginalRoad, OriginalRoad, Wizard),
// bool is show_tooltip
PreviewIntersection(Drawable, bool),
EnteringWarp(Wizard),
StampingRoads(String, String, String, String),
}
impl State {
fn viewing() -> State {
State::Viewing {
short_roads: HashSet::new(),
}
}
}
impl UI {
@ -70,7 +50,7 @@ impl UI {
ctx.canvas.map_dims = (bounds.width(), bounds.height());
UI {
model,
state: State::viewing(),
state: State::Viewing,
composite: Composite::new(Widget::col(vec![
Line("Map Editor").small_heading().draw(ctx),
Text::new().draw(ctx).named("current info"),
@ -78,10 +58,7 @@ impl UI {
vec![
(hotkey(Key::Escape), "quit"),
(None, "save raw map"),
(hotkey(Key::J), "warp to something"),
(hotkey(Key::G), "preview all intersections"),
(None, "find overlapping intersections"),
(hotkey(Key::Z), "find/clear short roads"),
]
.into_iter()
.map(|(key, action)| Btn::text_fg(action).build_def(ctx, key))
@ -122,9 +99,7 @@ impl GUI for UI {
}
match self.state {
State::Viewing {
ref mut short_roads,
} => {
State::Viewing => {
{
let before = match self.last_id {
Some(ID::Road(r)) | Some(ID::RoadPoint(r, _)) => Some(r),
@ -154,8 +129,6 @@ impl GUI for UI {
} else if ctx.input.key_pressed(Key::Backspace) {
self.model.delete_i(i);
self.model.world.handle_mouseover(ctx);
} else if ctx.input.key_pressed(Key::T) {
self.model.toggle_i_type(i, ctx);
} else if !self.model.intersection_geom && ctx.input.key_pressed(Key::P) {
let draw = preview_intersection(i, &self.model, ctx);
self.state = State::PreviewIntersection(draw, false);
@ -170,43 +143,9 @@ impl GUI for UI {
}
}
Some(ID::Road(r)) => {
let could_swap = {
let lanes = self.model.map.roads[&r].get_spec();
lanes.fwd != lanes.back
};
if ctx.input.key_pressed(Key::Backspace) {
self.model.delete_r(r);
self.model.world.handle_mouseover(ctx);
} else if ctx.input.key_pressed(Key::E) {
self.state = State::EditingLanes(r, Wizard::new());
} else if ctx.input.key_pressed(Key::N) {
self.state = State::EditingRoadAttribs(r, Wizard::new());
} else if could_swap && ctx.input.key_pressed(Key::S) {
self.model.swap_lanes(r, ctx);
self.model.world.handle_mouseover(ctx);
} else if ctx.input.key_pressed(Key::F) {
self.model.toggle_r_sidewalks(r, ctx);
self.model.world.handle_mouseover(ctx);
} else if ctx.input.key_pressed(Key::R) {
self.state = State::CreatingTurnRestrictionPt1(r);
} else if ctx.input.key_pressed(Key::C) {
let road = &self.model.map.roads[&r];
self.state = State::StampingRoads(
road.get_spec().to_string(),
road.osm_tags
.get(osm::NAME)
.cloned()
.unwrap_or_else(|| "Unnamed street".to_string()),
road.osm_tags
.get(osm::MAXSPEED)
.cloned()
.unwrap_or_else(|| "25 mph".to_string()),
road.osm_tags
.get(osm::HIGHWAY)
.cloned()
.unwrap_or_else(|| "residential".to_string()),
);
} else if cursor.is_some() && ctx.input.key_pressed(Key::P) {
if let Some(id) = self.model.insert_r_pt(r, cursor.unwrap(), ctx) {
self.model.world.force_set_selection(id);
@ -223,12 +162,6 @@ impl GUI for UI {
self.model.world.handle_mouseover(ctx);
}
}
Some(ID::TurnRestriction(tr)) => {
if ctx.input.key_pressed(Key::Backspace) {
self.model.delete_tr(tr);
self.model.world.handle_mouseover(ctx);
}
}
None => {
match self.composite.event(ctx) {
Outcome::Clicked(x) => match x.as_ref() {
@ -238,14 +171,7 @@ impl GUI for UI {
}
"save raw map" => {
// TODO Only do this for synthetic maps
if self.model.map.name != "" {
self.model.export();
} else {
self.state = State::SavingModel(Wizard::new());
}
}
"warp to something" => {
self.state = State::EnteringWarp(Wizard::new());
self.model.export();
}
"preview all intersections" => {
if !self.model.intersection_geom {
@ -253,17 +179,6 @@ impl GUI for UI {
self.state = State::PreviewIntersection(draw, false);
}
}
"find overlapping intersections" => {
let draw = find_overlapping_intersections(&self.model, ctx);
self.state = State::PreviewIntersection(draw, false);
}
"find/clear short roads" => {
if short_roads.is_empty() {
*short_roads = find_short_roads(&self.model);
} else {
short_roads.clear();
}
}
_ => unreachable!(),
},
_ => {
@ -280,10 +195,6 @@ impl GUI for UI {
let id = self.model.create_b(pt, ctx);
self.model.world.force_set_selection(id);
}
} else if ctx.input.key_pressed(Key::LeftShift) {
if let Some(pt) = cursor {
self.state = State::SelectingRectangle(pt, pt, true);
}
}
}
}
@ -294,7 +205,7 @@ impl GUI for UI {
if let Some(pt) = cursor {
self.model.move_i(id, pt, ctx);
if ctx.input.key_released(Key::LeftControl) {
self.state = State::viewing();
self.state = State::Viewing;
}
}
}
@ -302,7 +213,7 @@ impl GUI for UI {
if let Some(pt) = cursor {
self.model.move_b(id, pt, ctx);
if ctx.input.key_released(Key::LeftControl) {
self.state = State::viewing();
self.state = State::Viewing;
}
}
}
@ -310,134 +221,22 @@ impl GUI for UI {
if let Some(pt) = cursor {
self.model.move_r_pt(r, idx, pt, ctx);
if ctx.input.key_released(Key::LeftControl) {
self.state = State::viewing();
self.state = State::Viewing;
}
}
}
State::CreatingRoad(i1) => {
if ctx.input.key_pressed(Key::Escape) {
self.state = State::viewing();
self.state = State::Viewing;
self.model.world.handle_mouseover(ctx);
} else if let Some(ID::Intersection(i2)) = self.model.world.get_selection() {
if i1 != i2 && ctx.input.key_pressed(Key::R) {
self.model.create_r(i1, i2, ctx);
self.state = State::viewing();
self.state = State::Viewing;
self.model.world.handle_mouseover(ctx);
}
}
}
State::EditingLanes(id, ref mut wizard) => {
if let Some(s) = wizard.wrap(ctx).input_string_prefilled(
"Specify the lanes",
self.model.map.roads[&id].get_spec().to_string(),
) {
self.model.edit_lanes(id, s, ctx);
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
} else if wizard.aborted() {
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
}
}
State::EditingRoadAttribs(id, ref mut wizard) => {
let (orig_name, orig_speed) = {
let r = &self.model.map.roads[&id];
(
r.osm_tags
.get(osm::NAME)
.cloned()
.unwrap_or_else(String::new),
r.osm_tags
.get(osm::MAXSPEED)
.cloned()
.unwrap_or_else(String::new),
)
};
let mut wiz = wizard.wrap(ctx);
let mut done = false;
if let Some(n) = wiz.input_string_prefilled("Name the road", orig_name) {
if let Some(s) = wiz.input_string_prefilled("What speed limit?", orig_speed) {
if let Some(h) = wiz
.choose_string("What highway type (for coloring)?", || {
vec!["motorway", "primary", "residential"]
})
{
self.model.set_r_name_and_speed(id, n, s, h, ctx);
done = true;
}
}
}
if done || wizard.aborted() {
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
}
}
State::SavingModel(ref mut wizard) => {
if let Some(name) = wizard.wrap(ctx).input_string("Name the synthetic map") {
self.model.map.name = name;
self.model.export();
self.state = State::viewing();
} else if wizard.aborted() {
self.state = State::viewing();
}
}
State::SelectingRectangle(pt1, ref mut pt2, ref mut keydown) => {
if ctx.input.key_pressed(Key::LeftShift) {
*keydown = true;
} else if ctx.input.key_released(Key::LeftShift) {
*keydown = false;
}
if *keydown {
if let Some(pt) = cursor {
*pt2 = pt;
}
}
if ctx.input.key_pressed(Key::Escape) {
self.state = State::viewing();
} else if ctx.input.key_pressed(Key::Backspace) {
if let Some(rect) = Polygon::rectangle_two_corners(pt1, *pt2) {
self.model.delete_everything_inside(rect);
self.model.world.handle_mouseover(ctx);
}
self.state = State::viewing();
}
}
State::CreatingTurnRestrictionPt1(from) => {
if ctx.input.key_pressed(Key::Escape) {
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
} else if let Some(ID::Road(to)) = self.model.world.get_selection() {
if ctx.input.key_pressed(Key::R) {
if self.model.map.can_add_turn_restriction(from, to) {
self.state = State::CreatingTurnRestrictionPt2(from, to, Wizard::new());
} else {
println!("These roads aren't connected");
}
}
}
}
State::CreatingTurnRestrictionPt2(from, to, ref mut wizard) => {
if let Some((_, restriction)) =
wizard.wrap(ctx).choose("What turn restriction?", || {
vec![
Choice::new("ban turns between", RestrictionType::BanTurns),
Choice::new(
"only allow turns between",
RestrictionType::OnlyAllowTurns,
),
]
})
{
self.model.add_tr(from, restriction, to, ctx);
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
} else if wizard.aborted() {
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
}
}
State::PreviewIntersection(_, ref mut show_tooltip) => {
if *show_tooltip && ctx.input.key_released(Key::RightAlt) {
*show_tooltip = false;
@ -447,48 +246,10 @@ impl GUI for UI {
// TODO Woops, not communicating this kind of thing anymore
if ctx.input.key_pressed(Key::P) {
self.state = State::viewing();
self.state = State::Viewing;
self.model.world.handle_mouseover(ctx);
}
}
State::EnteringWarp(ref mut wizard) => {
if let Some(line) = wizard.wrap(ctx).input_string("Warp to what?") {
let mut ok = false;
if let Ok(num) = i64::from_str_radix(&line[1..line.len()], 10) {
if &line[0..=0] == "i" {
let id = OriginalIntersection { osm_node_id: num };
ctx.canvas
.center_on_map_pt(self.model.map.intersections[&id].point);
ok = true;
}
}
if !ok {
println!("Sorry, don't understand {}", line);
}
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
} else if wizard.aborted() {
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
}
}
State::StampingRoads(ref lanespec, ref name, ref speed, ref highway) => {
if ctx.input.key_pressed(Key::Escape) {
self.state = State::viewing();
self.model.world.handle_mouseover(ctx);
} else if let Some(ID::Road(id)) = self.model.world.get_selection() {
if ctx.input.key_pressed(Key::C) {
self.model.set_r_name_and_speed(
id,
name.to_string(),
speed.to_string(),
highway.to_string(),
ctx,
);
self.model.edit_lanes(id, lanespec.to_string(), ctx);
}
}
}
}
self.popup = None;
@ -530,50 +291,10 @@ impl GUI for UI {
}
}
}
State::EditingLanes(_, ref wizard)
| State::EditingRoadAttribs(_, ref wizard)
| State::SavingModel(ref wizard)
| State::EnteringWarp(ref wizard) => {
wizard.draw(g);
}
State::Viewing { ref short_roads } => {
for r in short_roads {
if let Some(p) = self.model.world.get_unioned_polygon(ID::Road(*r)) {
g.draw_polygon(Color::CYAN, p.clone());
}
}
}
State::MovingIntersection(_)
State::Viewing
| State::MovingIntersection(_)
| State::MovingBuilding(_)
| State::MovingRoadPoint(_, _)
| State::StampingRoads(_, _, _, _) => {}
State::SelectingRectangle(pt1, pt2, _) => {
if let Some(rect) = Polygon::rectangle_two_corners(pt1, pt2) {
g.draw_polygon(Color::BLUE.alpha(0.5), rect);
}
}
State::CreatingTurnRestrictionPt1(from) => {
if let Some(cursor) = g.get_cursor_in_map_space() {
if let Ok(l) = PolyLine::new(vec![self.model.get_r_center(from), cursor]) {
g.draw_polygon(
Color::PURPLE,
l.make_arrow(NORMAL_LANE_THICKNESS, ArrowCap::Triangle),
);
}
}
}
State::CreatingTurnRestrictionPt2(from, to, ref wizard) => {
if let Ok(l) = PolyLine::new(vec![
self.model.get_r_center(from),
self.model.get_r_center(to),
]) {
g.draw_polygon(
Color::PURPLE,
l.make_arrow(NORMAL_LANE_THICKNESS, ArrowCap::Triangle),
);
}
wizard.draw(g);
}
| State::MovingRoadPoint(_, _) => {}
State::PreviewIntersection(ref draw, show_tooltip) => {
g.redraw(draw);
@ -643,61 +364,6 @@ fn preview_all_intersections(model: &Model, ctx: &EventCtx) -> Drawable {
batch.upload(ctx)
}
fn find_overlapping_intersections(model: &Model, ctx: &EventCtx) -> Drawable {
let mut timer = Timer::new("find overlapping intersections");
let mut polygons = Vec::new();
for i in model.map.intersections.keys() {
if model.map.roads_per_intersection(*i).is_empty() {
continue;
}
let (intersection, _, _) = model.map.preview_intersection(*i, &mut timer);
polygons.push((*i, intersection));
}
let mut overlap = Vec::new();
timer.start_iter(
"terrible quadratic intersection check",
polygons.len().pow(2),
);
for (i1, poly1) in &polygons {
for (i2, poly2) in &polygons {
timer.next();
if i1 >= i2 {
continue;
}
let hits = poly1.intersection(poly2);
if !hits.is_empty() {
overlap.extend(hits);
timer.warn(format!("{} hits {}", i1, i2));
}
}
}
let mut batch = GeomBatch::new();
batch.extend(Color::RED.alpha(0.5), overlap);
batch.upload(ctx)
}
// TODO OriginalRoad is dangerous, as this map changes. :\
fn find_short_roads(model: &Model) -> HashSet<OriginalRoad> {
// Assume the full map has been built. We really care about short lanes there.
let map: map_model::Map =
abstutil::read_binary(abstutil::path_map(&model.map.name), &mut Timer::throwaway());
// Buses are 12.5
let threshold = Distance::meters(13.0);
let mut roads: HashSet<OriginalRoad> = HashSet::new();
for l in map.all_lanes() {
if l.length() < threshold {
roads.insert(map.get_r(l.parent).orig_id);
}
}
println!("{} short roads", roads.len());
for r in &roads {
println!("- {}", r);
}
roads
}
fn main() {
ezgui::run(ezgui::Settings::new("Synthetic map editor"), |ctx| {
UI::new(ctx)

View File

@ -1,22 +1,16 @@
use crate::world::{Object, ObjectID, World};
use abstutil::{Tags, Timer};
use ezgui::{Color, EventCtx, Line, Text};
use geom::{
ArrowCap, Bounds, Circle, Distance, FindClosest, GPSBounds, LonLat, PolyLine, Polygon, Pt2D,
};
use geom::{Bounds, Circle, Distance, FindClosest, GPSBounds, LonLat, PolyLine, Polygon, Pt2D};
use map_model::raw::{
OriginalBuilding, OriginalIntersection, OriginalRoad, RawBuilding, RawIntersection, RawMap,
RawRoad, RestrictionType, TurnRestriction,
};
use map_model::{
osm, IntersectionType, LaneType, RoadSpec, NORMAL_LANE_THICKNESS, SIDEWALK_THICKNESS,
RawRoad,
};
use map_model::{osm, IntersectionType};
use std::collections::{BTreeMap, BTreeSet};
use std::mem;
const INTERSECTION_RADIUS: Distance = Distance::const_meters(5.0);
const BUILDING_LENGTH: Distance = Distance::const_meters(30.0);
const CENTER_LINE_THICKNESS: Distance = Distance::const_meters(0.5);
pub struct Model {
// map and world are pub. The main crate should use them directly for simple stuff, to avoid
@ -86,7 +80,9 @@ impl Model {
impl Model {
// TODO Only for truly synthetic maps...
pub fn export(&mut self) {
assert!(self.map.name != "");
if self.map.name == "" {
self.map.name = "new_synthetic_map".to_string();
}
// Shift the map to start at (0, 0)
let bounds = self.compute_bounds();
@ -140,32 +136,6 @@ impl Model {
bounds
}
pub fn delete_everything_inside(&mut self, area: Polygon) {
if self.include_bldgs {
for id in self.map.buildings.keys().cloned().collect::<Vec<_>>() {
if area.contains_pt(self.map.buildings[&id].polygon.center()) {
self.delete_b(id);
}
}
}
for id in self.map.roads.keys().cloned().collect::<Vec<_>>() {
if self.map.roads[&id]
.center_points
.iter()
.any(|pt| area.contains_pt(*pt))
{
self.delete_r(id);
}
}
for id in self.map.intersections.keys().cloned().collect::<Vec<_>>() {
if area.contains_pt(self.map.intersections[&id].point) {
self.delete_i(id);
}
}
}
pub fn describe_obj(&self, id: ID) -> Text {
let mut txt = Text::new().with_bg();
match id {
@ -204,14 +174,6 @@ impl Model {
Line(v).fg(Color::CYAN),
]);
}
for (restriction, dst) in &road.turn_restrictions {
txt.add_appended(vec![
Line("Restriction: "),
Line(format!("{:?}", restriction)).fg(Color::RED),
Line(" to "),
Line(format!("way {}", dst)).fg(Color::CYAN),
]);
}
// (MAX_CAR_LENGTH + sim::FOLLOWING_DISTANCE) from sim, but without the dependency
txt.add(Line(format!(
@ -225,11 +187,6 @@ impl Model {
txt.add_highlighted(Line(format!("Point {}", idx)), Color::BLUE);
txt.add(Line(format!("of {}", r)));
}
ID::TurnRestriction(TurnRestriction(from, restriction, to)) => {
txt.add_highlighted(Line(format!("{:?}", restriction)), Color::BLUE);
txt.add(Line(format!("from {}", from)));
txt.add(Line(format!("to {}", to)));
}
}
txt
}
@ -283,29 +240,6 @@ impl Model {
self.intersection_added(id, ctx);
}
pub fn toggle_i_type(&mut self, id: OriginalIntersection, ctx: &EventCtx) {
self.world.delete(ID::Intersection(id));
let it = match self.map.intersections[&id].intersection_type {
IntersectionType::StopSign => IntersectionType::TrafficSignal,
IntersectionType::TrafficSignal => {
if self.map.roads_per_intersection(id).len() == 1 {
IntersectionType::Border
} else {
IntersectionType::StopSign
}
}
IntersectionType::Border => IntersectionType::StopSign,
// These shouldn't exist in a basemap!
IntersectionType::Construction => unreachable!(),
};
self.map
.intersections
.get_mut(&id)
.unwrap()
.intersection_type = it;
self.intersection_added(id, ctx);
}
pub fn delete_i(&mut self, id: OriginalIntersection) {
if !self.map.can_delete_intersection(id) {
println!("Can't delete intersection used by roads");
@ -319,15 +253,11 @@ impl Model {
// Roads
impl Model {
fn road_added(&mut self, id: OriginalRoad, ctx: &EventCtx) {
for obj in self.road_objects(id) {
self.world.add(ctx, obj);
}
self.world.add(ctx, self.road_object(id));
}
fn road_deleted(&mut self, id: OriginalRoad) {
for obj in self.road_objects(id) {
self.world.delete(obj.get_id());
}
self.world.delete(ID::Road(id));
}
pub fn create_r(&mut self, i1: OriginalIntersection, i2: OriginalIntersection, ctx: &EventCtx) {
@ -348,15 +278,10 @@ impl Model {
i2,
};
let mut osm_tags = Tags::new(BTreeMap::new());
osm_tags.insert(osm::SYNTHETIC, "true");
osm_tags.insert(
osm::SYNTHETIC_LANES,
RoadSpec {
fwd: vec![LaneType::Driving, LaneType::Parking, LaneType::Sidewalk],
back: vec![LaneType::Driving, LaneType::Parking, LaneType::Sidewalk],
}
.to_string(),
);
osm_tags.insert(osm::HIGHWAY, "residential");
osm_tags.insert(osm::PARKING_BOTH, "parallel");
osm_tags.insert(osm::SIDEWALK, "both");
osm_tags.insert("lanes", "2");
osm_tags.insert(osm::ENDPT_FWD, "true");
osm_tags.insert(osm::ENDPT_BACK, "true");
osm_tags.insert(osm::OSM_WAY_ID, id.osm_way_id.to_string());
@ -379,216 +304,20 @@ impl Model {
self.road_added(id, ctx);
}
pub fn edit_lanes(&mut self, id: OriginalRoad, spec: String, ctx: &EventCtx) {
self.road_deleted(id);
if let Some(s) = RoadSpec::parse(spec.clone()) {
self.map
.roads
.get_mut(&id)
.unwrap()
.osm_tags
.insert(osm::SYNTHETIC_LANES, s.to_string());
} else {
println!("Bad RoadSpec: {}", spec);
}
self.road_added(id, ctx);
}
pub fn swap_lanes(&mut self, id: OriginalRoad, ctx: &EventCtx) {
self.road_deleted(id);
let (mut lanes, osm_tags) = {
let r = self.map.roads.get_mut(&id).unwrap();
(r.get_spec(), &mut r.osm_tags)
};
mem::swap(&mut lanes.fwd, &mut lanes.back);
osm_tags.insert(osm::SYNTHETIC_LANES, lanes.to_string());
self.road_added(id, ctx);
}
pub fn set_r_name_and_speed(
&mut self,
id: OriginalRoad,
name: String,
speed: String,
highway: String,
ctx: &EventCtx,
) {
self.road_deleted(id);
let osm_tags = &mut self.map.roads.get_mut(&id).unwrap().osm_tags;
osm_tags.insert(osm::NAME, name);
osm_tags.insert(osm::MAXSPEED, speed);
osm_tags.insert(osm::HIGHWAY, highway);
self.road_added(id, ctx);
}
pub fn toggle_r_sidewalks(&mut self, some_id: OriginalRoad, ctx: &EventCtx) {
// Update every road belonging to the way.
let osm_id = self.map.roads[&some_id]
.osm_tags
.get(osm::OSM_WAY_ID)
.unwrap();
let matching_roads = self
.map
.roads
.iter()
.filter_map(|(k, v)| {
if v.osm_tags.is(osm::OSM_WAY_ID, osm_id) {
Some(*k)
} else {
None
}
})
.collect::<Vec<_>>();
// Verify every road has the same sidewalk tags. Hints might've applied to just some parts.
// If this is really true, then the way has to be split.
let value = self.map.roads[&some_id]
.osm_tags
.get(osm::SIDEWALK)
.cloned();
for r in &matching_roads {
if self.map.roads[r].osm_tags.get(osm::SIDEWALK) != value.as_ref() {
println!(
"WARNING: {} and {} belong to same way, but have different sidewalk tags!",
some_id, r
);
}
}
for id in matching_roads {
self.road_deleted(id);
let osm_tags = &mut self.map.roads.get_mut(&id).unwrap().osm_tags;
osm_tags.remove(osm::INFERRED_SIDEWALKS);
if value == Some("both".to_string()) {
osm_tags.insert(osm::SIDEWALK, "right");
} else if value == Some("right".to_string()) {
osm_tags.insert(osm::SIDEWALK, "left");
} else if value == Some("left".to_string()) {
osm_tags.insert(osm::SIDEWALK, "none");
} else if value == Some("none".to_string()) {
osm_tags.insert(osm::SIDEWALK, "both");
}
self.road_added(id, ctx);
}
}
pub fn delete_r(&mut self, id: OriginalRoad) {
self.stop_showing_pts(id);
self.road_deleted(id);
for tr in self.map.delete_road(id) {
// We got these cases above in road_deleted
if tr.0 != id {
self.world.delete(ID::TurnRestriction(tr));
}
}
self.map.roads.remove(&id).unwrap();
}
fn road_objects(&self, id: OriginalRoad) -> Vec<Object<ID>> {
let r = &self.map.roads[&id];
let unset = r.synthetic() && r.osm_tags.is(osm::NAME, "Streety McStreetFace");
let lanes_unknown = r.osm_tags.contains_key(osm::INFERRED_SIDEWALKS);
let spec = r.get_spec();
let center_pts = PolyLine::must_new(r.center_points.clone());
let mut obj = Object::blank(ID::Road(id));
let mut offset = Distance::ZERO;
for (idx, lt) in spec.fwd.iter().enumerate() {
let width = if *lt == LaneType::Sidewalk {
SIDEWALK_THICKNESS
} else {
NORMAL_LANE_THICKNESS
};
obj.push(
Model::lt_to_color(*lt, unset, lanes_unknown),
self.map
.config
.driving_side
.right_shift(center_pts.clone(), offset + width / 2.0)
.make_polygons(width),
);
offset += width;
if idx == 0 {
obj.push(
Color::YELLOW,
center_pts.make_polygons(CENTER_LINE_THICKNESS),
);
}
}
offset = Distance::ZERO;
for lt in &spec.back {
let width = if *lt == LaneType::Sidewalk {
SIDEWALK_THICKNESS
} else {
NORMAL_LANE_THICKNESS
};
obj.push(
Model::lt_to_color(*lt, unset, lanes_unknown),
self.map
.config
.driving_side
.right_shift(center_pts.reversed(), offset + width / 2.0)
.make_polygons(width),
);
offset += width;
}
let mut result = vec![obj];
for (restriction, to) in &r.turn_restrictions {
let polygon = if id == *to {
// TODO Ideally a hollow circle with an arrow
Circle::new(
PolyLine::must_new(self.map.roads[&id].center_points.clone()).middle(),
NORMAL_LANE_THICKNESS,
)
.to_polygon()
} else {
if !self.map.roads.contains_key(to) {
// TODO Fix. When roads are clipped, need to update IDS.
println!("Turn restriction to spot is missing!{}->{}", id, to);
continue;
}
PolyLine::must_new(vec![self.get_r_center(id), self.get_r_center(*to)])
.make_arrow(NORMAL_LANE_THICKNESS, ArrowCap::Triangle)
};
result.push(Object::new(
ID::TurnRestriction(TurnRestriction(id, *restriction, *to)),
Color::PURPLE,
polygon,
));
}
result
}
// Copied from render/lane.rs. :(
fn lt_to_color(lt: LaneType, unset: bool, lanes_unknown: bool) -> Color {
let color = match lt {
LaneType::Driving => Color::BLACK,
LaneType::Bus => Color::rgb(190, 74, 76),
LaneType::Parking => Color::grey(0.2),
LaneType::Sidewalk | LaneType::Shoulder => Color::grey(0.8),
LaneType::Biking => Color::rgb(15, 125, 75),
LaneType::SharedLeftTurn => Color::YELLOW,
LaneType::Construction => Color::rgb(255, 109, 0),
LaneType::LightRail => Color::hex("#844204"),
};
if unset {
Color::rgba_f(0.9, color.g, color.b, 0.5)
} else if lanes_unknown {
Color::rgba_f(color.r, color.g, 0.9, 0.5)
} else {
color
}
fn road_object(&self, id: OriginalRoad) -> Object<ID> {
let (center, total_width) =
self.map.roads[&id].get_geometry(id, self.map.config.driving_side);
Object::new(
ID::Road(id),
Color::grey(0.8),
center.make_polygons(total_width),
)
}
pub fn show_r_points(&mut self, id: OriginalRoad, ctx: &EventCtx) {
@ -706,39 +435,6 @@ impl Model {
self.intersection_added(id.i2, ctx);
self.show_r_points(id, ctx);
}
pub fn get_r_center(&self, id: OriginalRoad) -> Pt2D {
PolyLine::must_new(self.map.roads[&id].center_points.clone()).middle()
}
}
// Turn restrictions
impl Model {
pub fn add_tr(
&mut self,
from: OriginalRoad,
restriction: RestrictionType,
to: OriginalRoad,
ctx: &EventCtx,
) {
self.road_deleted(from);
assert!(self.map.can_add_turn_restriction(from, to));
// TODO Worry about dupes
self.map
.roads
.get_mut(&from)
.unwrap()
.turn_restrictions
.push((restriction, to));
self.road_added(from, ctx);
}
pub fn delete_tr(&mut self, tr: TurnRestriction) {
self.map.delete_turn_restriction(tr);
self.world.delete(ID::TurnRestriction(tr));
}
}
// Buildings
@ -794,7 +490,6 @@ pub enum ID {
Intersection(OriginalIntersection),
Road(OriginalRoad),
RoadPoint(OriginalRoad, usize),
TurnRestriction(TurnRestriction),
}
impl ObjectID for ID {
@ -804,7 +499,6 @@ impl ObjectID for ID {
ID::Intersection(_) => 1,
ID::Building(_) => 2,
ID::RoadPoint(_, _) => 3,
ID::TurnRestriction(_) => 4,
}
}
}

View File

@ -22,21 +22,6 @@ impl<ID: ObjectID> Object<ID> {
geometry: vec![(color, poly)],
}
}
pub fn blank(id: ID) -> Object<ID> {
Object {
id,
geometry: Vec::new(),
}
}
pub fn get_id(&self) -> ID {
self.id
}
pub fn push(&mut self, color: Color, poly: Polygon) {
self.geometry.push((color, poly));
}
}
struct WorldObject {
@ -154,8 +139,4 @@ impl<ID: ObjectID> World<ID> {
let obj = self.objects.remove(&id).unwrap();
self.quadtree.remove(obj.quadtree_id).unwrap();
}
pub fn get_unioned_polygon(&self, id: ID) -> Option<&Polygon> {
Some(&self.objects.get(&id)?.unioned_polygon)
}
}

View File

@ -13,7 +13,6 @@ pub use crate::city::City;
pub use crate::edits::{
EditCmd, EditEffects, EditIntersection, MapEdits, OriginalLane, PermanentMapEdits,
};
pub use crate::make::initial::lane_specs::RoadSpec;
pub use crate::map::MapConfig;
pub use crate::objects::area::{Area, AreaID, AreaType};
pub use crate::objects::building::{Building, BuildingID, BuildingType, OffstreetParking};

View File

@ -1,8 +1,7 @@
use crate::{osm, LaneType, NORMAL_LANE_THICKNESS, SHOULDER_THICKNESS, SIDEWALK_THICKNESS};
use abstutil::Tags;
use geom::Distance;
use serde::{Deserialize, Serialize};
use std::{fmt, iter};
use std::iter;
pub struct LaneSpec {
pub lane_type: LaneType,
@ -56,14 +55,6 @@ impl LaneSpec {
//
// TODO This is ripe for unit testing.
pub fn get_lane_specs(tags: &Tags) -> Vec<LaneSpec> {
if let Some(s) = tags.get(osm::SYNTHETIC_LANES) {
if let Some(spec) = RoadSpec::parse(s.to_string()) {
return LaneSpec::normal(spec.fwd, spec.back);
} else {
panic!("Bad {} RoadSpec: {}", osm::SYNTHETIC_LANES, s);
}
}
// Easy special cases first.
if tags.is_any("railway", vec!["light_rail", "rail"]) {
return LaneSpec::normal(vec![LaneType::LightRail], Vec::new());
@ -293,78 +284,3 @@ pub fn get_lane_specs(tags: &Tags) -> Vec<LaneSpec> {
specs
}
// This is a convenient way for map_editor to plumb instructions here.
#[derive(Serialize, Deserialize)]
pub struct RoadSpec {
pub fwd: Vec<LaneType>,
pub back: Vec<LaneType>,
}
impl fmt::Display for RoadSpec {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for lt in &self.fwd {
write!(f, "{}", RoadSpec::lt_to_char(*lt))?;
}
write!(f, "/")?;
for lt in &self.back {
write!(f, "{}", RoadSpec::lt_to_char(*lt))?;
}
Ok(())
}
}
impl RoadSpec {
pub fn parse(s: String) -> Option<RoadSpec> {
let mut fwd: Vec<LaneType> = Vec::new();
let mut back: Vec<LaneType> = Vec::new();
let mut seen_slash = false;
for c in s.chars() {
if !seen_slash && c == '/' {
seen_slash = true;
} else if let Some(lt) = RoadSpec::char_to_lt(c) {
if seen_slash {
back.push(lt);
} else {
fwd.push(lt);
}
} else {
return None;
}
}
if seen_slash && (fwd.len() + back.len()) > 0 {
Some(RoadSpec { fwd, back })
} else {
None
}
}
fn lt_to_char(lt: LaneType) -> char {
match lt {
LaneType::Driving => 'd',
LaneType::Parking => 'p',
LaneType::Sidewalk => 's',
LaneType::Shoulder => 'S',
LaneType::Biking => 'b',
LaneType::Bus => 'u',
LaneType::SharedLeftTurn => 'l',
LaneType::Construction => 'c',
LaneType::LightRail => 'r',
}
}
fn char_to_lt(c: char) -> Option<LaneType> {
match c {
'd' => Some(LaneType::Driving),
'p' => Some(LaneType::Parking),
's' => Some(LaneType::Sidewalk),
'S' => Some(LaneType::Shoulder),
'b' => Some(LaneType::Biking),
'u' => Some(LaneType::Bus),
'l' => Some(LaneType::SharedLeftTurn),
'c' => Some(LaneType::Construction),
'r' => Some(LaneType::LightRail),
_ => None,
}
}
}

View File

@ -3,7 +3,7 @@ pub mod lane_specs;
pub use self::geometry::intersection_polygon;
use crate::raw::{DrivingSide, OriginalIntersection, OriginalRoad, RawMap, RawRoad};
use crate::{IntersectionType, LaneType};
use crate::IntersectionType;
use abstutil::Timer;
use geom::{Bounds, Distance, PolyLine, Pt2D};
use lane_specs::LaneSpec;
@ -30,31 +30,7 @@ pub struct Road {
impl Road {
pub fn new(id: OriginalRoad, r: &RawRoad, driving_side: DrivingSide) -> Road {
let lane_specs = lane_specs::get_lane_specs(&r.osm_tags);
let mut total_width = Distance::ZERO;
let mut sidewalk_right = None;
let mut sidewalk_left = None;
for l in &lane_specs {
total_width += l.width;
if l.lane_type == LaneType::Sidewalk || l.lane_type == LaneType::Shoulder {
if l.reverse_pts {
sidewalk_left = Some(l.width);
} else {
sidewalk_right = Some(l.width);
}
}
}
// If there's a sidewalk on only one side, adjust the true center of the road.
let mut trimmed_center_pts = PolyLine::new(r.center_points.clone()).expect(&id.to_string());
match (sidewalk_right, sidewalk_left) {
(Some(w), None) => {
trimmed_center_pts = driving_side.right_shift(trimmed_center_pts, w / 2.0);
}
(None, Some(w)) => {
trimmed_center_pts = driving_side.left_shift(trimmed_center_pts, w / 2.0);
}
_ => {}
}
let (trimmed_center_pts, total_width) = r.get_geometry(id, driving_side);
Road {
id,

View File

@ -21,10 +21,6 @@ pub const OSM_REL_ID: &str = "abst:osm_rel_id";
pub const ENDPT_FWD: &str = "abst:endpt_fwd";
pub const ENDPT_BACK: &str = "abst:endpt_back";
// Synthetic roads have (some of) these.
pub const SYNTHETIC: &str = "abst:synthetic";
pub const SYNTHETIC_LANES: &str = "abst:synthetic_lanes";
// Any roads might have these.
pub const INFERRED_PARKING: &str = "abst:parking_inferred";
pub const INFERRED_SIDEWALKS: &str = "abst:sidewalks_inferred";

View File

@ -1,5 +1,5 @@
use crate::make::initial::lane_specs::get_lane_specs;
use crate::{osm, AreaType, IntersectionType, MapConfig, RoadSpec};
use crate::{osm, AreaType, IntersectionType, LaneType, MapConfig};
use abstutil::{deserialize_btreemap, serialize_btreemap, Tags, Timer};
use geom::{Angle, Distance, GPSBounds, Line, PolyLine, Polygon, Pt2D, Ring};
use petgraph::graphmap::DiGraphMap;
@ -217,21 +217,6 @@ impl RawMap {
// Mutations and supporting queries
impl RawMap {
// Return a list of turn restrictions deleted along the way.
pub fn delete_road(&mut self, r: OriginalRoad) -> BTreeSet<TurnRestriction> {
// First delete and warn about turn restrictions
let restrictions = self.turn_restrictions_involving(r);
for tr in &restrictions {
println!(
"Deleting {}, but first deleting turn restriction {:?} {}->{}",
r, tr.1, tr.0, tr.2
);
self.delete_turn_restriction(*tr);
}
self.roads.remove(&r).unwrap();
restrictions
}
pub fn can_delete_intersection(&self, i: OriginalIntersection) -> bool {
self.roads_per_intersection(i).is_empty()
}
@ -246,35 +231,6 @@ impl RawMap {
self.intersections.remove(&id).unwrap();
}
pub fn can_add_turn_restriction(&self, from: OriginalRoad, to: OriginalRoad) -> bool {
let (i1, i2) = (from.i1, from.i2);
let (i3, i4) = (to.i1, to.i2);
i1 == i3 || i1 == i4 || i2 == i3 || i2 == i4
}
fn turn_restrictions_involving(&self, r: OriginalRoad) -> BTreeSet<TurnRestriction> {
let mut results = BTreeSet::new();
for (tr, to) in &self.roads[&r].turn_restrictions {
results.insert(TurnRestriction(r, *tr, *to));
}
for (src, road) in &self.roads {
for (tr, to) in &road.turn_restrictions {
if r == *to {
results.insert(TurnRestriction(*src, *tr, *to));
}
}
}
results
}
pub fn delete_turn_restriction(&mut self, tr: TurnRestriction) {
self.roads
.get_mut(&tr.0)
.unwrap()
.turn_restrictions
.retain(|(rt, to)| tr.1 != *rt || tr.2 != *to);
}
pub fn move_intersection(
&mut self,
id: OriginalIntersection,
@ -340,21 +296,40 @@ pub struct RawRoad {
}
impl RawRoad {
pub fn get_spec(&self) -> RoadSpec {
let mut fwd = Vec::new();
let mut back = Vec::new();
for spec in get_lane_specs(&self.osm_tags) {
if spec.reverse_pts {
back.push(spec.lane_type);
} else {
fwd.push(spec.lane_type);
// Returns the corrected center and half width
pub fn get_geometry(
&self,
id: OriginalRoad,
driving_side: DrivingSide,
) -> (PolyLine, Distance) {
let lane_specs = get_lane_specs(&self.osm_tags);
let mut total_width = Distance::ZERO;
let mut sidewalk_right = None;
let mut sidewalk_left = None;
for l in &lane_specs {
total_width += l.width;
if l.lane_type == LaneType::Sidewalk || l.lane_type == LaneType::Shoulder {
if l.reverse_pts {
sidewalk_left = Some(l.width);
} else {
sidewalk_right = Some(l.width);
}
}
}
RoadSpec { fwd, back }
}
pub fn synthetic(&self) -> bool {
self.osm_tags.is(osm::SYNTHETIC, "true")
// If there's a sidewalk on only one side, adjust the true center of the road.
let mut true_center = PolyLine::new(self.center_points.clone()).expect(&id.to_string());
match (sidewalk_right, sidewalk_left) {
(Some(w), None) => {
true_center = driving_side.right_shift(true_center, w / 2.0);
}
(None, Some(w)) => {
true_center = driving_side.left_shift(true_center, w / 2.0);
}
_ => {}
}
(true_center, total_width)
}
// TODO For the moment, treating all rail things as light rail