mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-24 23:15:24 +03:00
map_editor tool to quickly absorb name, speed limit, lanes of nearby road for synthetic roads
This commit is contained in:
parent
5f9cf2accd
commit
0d27a11b2b
@ -2,7 +2,8 @@ use crate::common::ColorLegend;
|
||||
use crate::game::{State, Transition};
|
||||
use crate::ui::UI;
|
||||
use ezgui::{
|
||||
ScreenPt, hotkey, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, ModalMenu, MultiText, Text,
|
||||
hotkey, Color, Drawable, EventCtx, GeomBatch, GfxCtx, Key, Line, ModalMenu, MultiText,
|
||||
ScreenPt, Text,
|
||||
};
|
||||
use geom::{Distance, Duration, PolyLine, Polygon, Pt2D};
|
||||
use sim::TripMode;
|
||||
@ -154,21 +155,31 @@ impl ShowStats {
|
||||
.map(|(name, color, _)| (*name, *color))
|
||||
.collect(),
|
||||
);
|
||||
let max_y = stats.samples.iter().map(|s|
|
||||
lines.iter().map(|(_, _, getter)| getter(s)).max().unwrap()
|
||||
).max().unwrap();
|
||||
let max_y = stats
|
||||
.samples
|
||||
.iter()
|
||||
.map(|s| lines.iter().map(|(_, _, getter)| getter(s)).max().unwrap())
|
||||
.max()
|
||||
.unwrap();
|
||||
// Y-axis labels
|
||||
for i in 0..=5 {
|
||||
let percent = (i as f64) / 5.0;
|
||||
labels.add(Text::from(Line(((percent * (max_y as f64)) as usize).to_string())), ScreenPt::new(x1, y2 - percent * (y2 - y1)));
|
||||
labels.add(
|
||||
Text::from(Line(((percent * (max_y as f64)) as usize).to_string())),
|
||||
ScreenPt::new(x1, y2 - percent * (y2 - y1)),
|
||||
);
|
||||
}
|
||||
// X-axis labels (currently nonlinear!)
|
||||
{
|
||||
let num_pts = stats.samples.len().min(5);
|
||||
for i in 0..num_pts {
|
||||
let percent_x = (i as f64) / ((num_pts - 1) as f64);
|
||||
let t = stats.samples[(percent_x * ((stats.samples.len() - 1) as f64)) as usize].time;
|
||||
labels.add(Text::from(Line(t.to_string())), ScreenPt::new(x1 + percent_x * (x2 - x1), y2));
|
||||
let t =
|
||||
stats.samples[(percent_x * ((stats.samples.len() - 1) as f64)) as usize].time;
|
||||
labels.add(
|
||||
Text::from(Line(t.to_string())),
|
||||
ScreenPt::new(x1 + percent_x * (x2 - x1), y2),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ enum State {
|
||||
CreatingTurnRestrictionPt2(StableRoadID, StableRoadID, Wizard),
|
||||
PreviewIntersection(Drawable, Vec<(Text, Pt2D)>, bool),
|
||||
EnteringWarp(Wizard),
|
||||
StampingRoads(String, String, String),
|
||||
}
|
||||
|
||||
impl UI {
|
||||
@ -66,6 +67,146 @@ impl GUI for UI {
|
||||
}
|
||||
|
||||
match self.state {
|
||||
State::Viewing => {
|
||||
let cursor = ctx.canvas.get_cursor_in_map_space();
|
||||
match self.model.world.get_selection() {
|
||||
Some(ID::Intersection(i)) => {
|
||||
if ctx.input.key_pressed(Key::LeftControl, "move intersection") {
|
||||
self.state = State::MovingIntersection(i);
|
||||
} else if ctx.input.key_pressed(Key::R, "create road") {
|
||||
self.state = State::CreatingRoad(i);
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, &format!("delete {}", i))
|
||||
{
|
||||
self.model.delete_i(i);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::T, "toggle intersection type") {
|
||||
self.model.toggle_i_type(i, ctx.prerender);
|
||||
} else if ctx.input.key_pressed(Key::L, "label intersection") {
|
||||
self.state = State::LabelingIntersection(i, Wizard::new());
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::P, "preview intersection geometry")
|
||||
{
|
||||
let (draw, labels) = preview_intersection(i, &self.model, ctx);
|
||||
self.state = State::PreviewIntersection(draw, labels, false);
|
||||
}
|
||||
}
|
||||
Some(ID::Building(b)) => {
|
||||
if ctx.input.key_pressed(Key::LeftControl, "move building") {
|
||||
self.state = State::MovingBuilding(b);
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, &format!("delete {}", b))
|
||||
{
|
||||
self.model.delete_b(b);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::L, "label building") {
|
||||
self.state = State::LabelingBuilding(b, Wizard::new());
|
||||
}
|
||||
}
|
||||
Some(ID::Lane(r, dir, _)) => {
|
||||
if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, &format!("delete {}", r))
|
||||
{
|
||||
self.model.delete_r(r);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::E, "edit lanes") {
|
||||
self.state = State::EditingLanes(r, Wizard::new());
|
||||
} else if ctx.input.key_pressed(Key::N, "edit name/speed") {
|
||||
self.state = State::EditingRoadAttribs(r, Wizard::new());
|
||||
} else if ctx.input.key_pressed(Key::S, "swap lanes") {
|
||||
self.model.swap_lanes(r, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::L, "label side of the road") {
|
||||
self.state = State::LabelingRoad((r, dir), Wizard::new());
|
||||
} else if self.model.showing_pts.is_none()
|
||||
&& ctx.input.key_pressed(Key::P, "move road points")
|
||||
{
|
||||
self.model.show_r_points(r, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::M, "merge road") {
|
||||
self.model.merge_r(r, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::R, "create turn restriction from here")
|
||||
{
|
||||
self.state = State::CreatingTurnRestrictionPt1(r);
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::C, "copy road name and speed to other roads")
|
||||
{
|
||||
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(String::new),
|
||||
road.osm_tags
|
||||
.get(osm::MAXSPEED)
|
||||
.cloned()
|
||||
.unwrap_or_else(String::new),
|
||||
);
|
||||
}
|
||||
}
|
||||
Some(ID::RoadPoint(r, idx)) => {
|
||||
if ctx.input.key_pressed(Key::LeftControl, "move point") {
|
||||
self.state = State::MovingRoadPoint(r, idx);
|
||||
} else if ctx.input.key_pressed(Key::Backspace, "delete point") {
|
||||
self.model.delete_r_pt(r, idx, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
}
|
||||
Some(ID::TurnRestriction(from, to, idx)) => {
|
||||
if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, "delete turn restriction")
|
||||
{
|
||||
self.model.delete_tr(from, to, idx, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if ctx.input.unimportant_key_pressed(Key::Escape, "quit") {
|
||||
process::exit(0);
|
||||
} else if ctx.input.key_pressed(Key::S, "save") {
|
||||
if self.model.map.name != "" {
|
||||
self.model.export();
|
||||
} else {
|
||||
self.state = State::SavingModel(Wizard::new());
|
||||
}
|
||||
} else if ctx.input.key_pressed(Key::F, "save map fixes") {
|
||||
self.model.save_fixes();
|
||||
} else if ctx.input.key_pressed(Key::I, "create intersection") {
|
||||
if let Some(pt) = cursor {
|
||||
self.model.create_i(pt, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
// TODO Silly bug: Mouseover doesn't actually work! I think the cursor being
|
||||
// dead-center messes up the precomputed triangles.
|
||||
} else if ctx.input.key_pressed(Key::B, "create building") {
|
||||
if let Some(pt) = cursor {
|
||||
self.model.create_b(pt, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
} else if ctx.input.key_pressed(Key::LeftShift, "select area") {
|
||||
if let Some(pt) = cursor {
|
||||
self.state = State::SelectingRectangle(pt, pt, true);
|
||||
}
|
||||
} else if self.model.showing_pts.is_some()
|
||||
&& ctx.input.key_pressed(Key::P, "stop moving road points")
|
||||
{
|
||||
self.model.stop_showing_pts();
|
||||
} else if ctx.input.key_pressed(Key::J, "warp to something") {
|
||||
self.state = State::EnteringWarp(Wizard::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
State::MovingIntersection(id) => {
|
||||
if let Some(cursor) = ctx.canvas.get_cursor_in_map_space() {
|
||||
self.model.move_i(id, cursor, ctx.prerender);
|
||||
@ -192,130 +333,6 @@ impl GUI for UI {
|
||||
self.state = State::Viewing;
|
||||
}
|
||||
}
|
||||
State::Viewing => {
|
||||
let cursor = ctx.canvas.get_cursor_in_map_space();
|
||||
match self.model.world.get_selection() {
|
||||
Some(ID::Intersection(i)) => {
|
||||
if ctx.input.key_pressed(Key::LeftControl, "move intersection") {
|
||||
self.state = State::MovingIntersection(i);
|
||||
} else if ctx.input.key_pressed(Key::R, "create road") {
|
||||
self.state = State::CreatingRoad(i);
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, &format!("delete {}", i))
|
||||
{
|
||||
self.model.delete_i(i);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::T, "toggle intersection type") {
|
||||
self.model.toggle_i_type(i, ctx.prerender);
|
||||
} else if ctx.input.key_pressed(Key::L, "label intersection") {
|
||||
self.state = State::LabelingIntersection(i, Wizard::new());
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::P, "preview intersection geometry")
|
||||
{
|
||||
let (draw, labels) = preview_intersection(i, &self.model, ctx);
|
||||
self.state = State::PreviewIntersection(draw, labels, false);
|
||||
}
|
||||
}
|
||||
Some(ID::Building(b)) => {
|
||||
if ctx.input.key_pressed(Key::LeftControl, "move building") {
|
||||
self.state = State::MovingBuilding(b);
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, &format!("delete {}", b))
|
||||
{
|
||||
self.model.delete_b(b);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::L, "label building") {
|
||||
self.state = State::LabelingBuilding(b, Wizard::new());
|
||||
}
|
||||
}
|
||||
Some(ID::Lane(r, dir, _)) => {
|
||||
if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, &format!("delete {}", r))
|
||||
{
|
||||
self.model.delete_r(r);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::E, "edit lanes") {
|
||||
self.state = State::EditingLanes(r, Wizard::new());
|
||||
} else if ctx.input.key_pressed(Key::N, "edit name/speed") {
|
||||
self.state = State::EditingRoadAttribs(r, Wizard::new());
|
||||
} else if ctx.input.key_pressed(Key::S, "swap lanes") {
|
||||
self.model.swap_lanes(r, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::L, "label side of the road") {
|
||||
self.state = State::LabelingRoad((r, dir), Wizard::new());
|
||||
} else if self.model.showing_pts.is_none()
|
||||
&& ctx.input.key_pressed(Key::P, "move road points")
|
||||
{
|
||||
self.model.show_r_points(r, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx.input.key_pressed(Key::M, "merge road") {
|
||||
self.model.merge_r(r, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if ctx
|
||||
.input
|
||||
.key_pressed(Key::R, "create turn restriction from here")
|
||||
{
|
||||
self.state = State::CreatingTurnRestrictionPt1(r);
|
||||
}
|
||||
}
|
||||
Some(ID::RoadPoint(r, idx)) => {
|
||||
if ctx.input.key_pressed(Key::LeftControl, "move point") {
|
||||
self.state = State::MovingRoadPoint(r, idx);
|
||||
} else if ctx.input.key_pressed(Key::Backspace, "delete point") {
|
||||
self.model.delete_r_pt(r, idx, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
}
|
||||
Some(ID::TurnRestriction(from, to, idx)) => {
|
||||
if ctx
|
||||
.input
|
||||
.key_pressed(Key::Backspace, "delete turn restriction")
|
||||
{
|
||||
self.model.delete_tr(from, to, idx, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
if ctx.input.unimportant_key_pressed(Key::Escape, "quit") {
|
||||
process::exit(0);
|
||||
} else if ctx.input.key_pressed(Key::S, "save") {
|
||||
if self.model.map.name != "" {
|
||||
self.model.export();
|
||||
} else {
|
||||
self.state = State::SavingModel(Wizard::new());
|
||||
}
|
||||
} else if ctx.input.key_pressed(Key::F, "save map fixes") {
|
||||
self.model.save_fixes();
|
||||
} else if ctx.input.key_pressed(Key::I, "create intersection") {
|
||||
if let Some(pt) = cursor {
|
||||
self.model.create_i(pt, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
// TODO Silly bug: Mouseover doesn't actually work! I think the cursor being
|
||||
// dead-center messes up the precomputed triangles.
|
||||
} else if ctx.input.key_pressed(Key::B, "create building") {
|
||||
if let Some(pt) = cursor {
|
||||
self.model.create_b(pt, ctx.prerender);
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
} else if ctx.input.key_pressed(Key::LeftShift, "select area") {
|
||||
if let Some(pt) = cursor {
|
||||
self.state = State::SelectingRectangle(pt, pt, true);
|
||||
}
|
||||
} else if self.model.showing_pts.is_some()
|
||||
&& ctx.input.key_pressed(Key::P, "stop moving road points")
|
||||
{
|
||||
self.model.stop_showing_pts();
|
||||
} else if ctx.input.key_pressed(Key::J, "warp to something") {
|
||||
self.state = State::EnteringWarp(Wizard::new());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
State::SelectingRectangle(pt1, ref mut pt2, ref mut keydown) => {
|
||||
if ctx.input.key_pressed(Key::LeftShift, "select area") {
|
||||
*keydown = true;
|
||||
@ -421,6 +438,29 @@ impl GUI for UI {
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
}
|
||||
}
|
||||
State::StampingRoads(ref lanespec, ref name, ref speed) => {
|
||||
if ctx
|
||||
.input
|
||||
.key_pressed(Key::Escape, "stop copying road metadata")
|
||||
{
|
||||
self.state = State::Viewing;
|
||||
self.model.world.handle_mouseover(ctx);
|
||||
} else if let Some(ID::Lane(id, _, _)) = self.model.world.get_selection() {
|
||||
if ctx.input.key_pressed(
|
||||
Key::C,
|
||||
&format!("set name={}, speed={}, lanes={}", name, speed, lanespec),
|
||||
) {
|
||||
self.model.set_r_name_and_speed(
|
||||
id,
|
||||
name.to_string(),
|
||||
speed.to_string(),
|
||||
ctx.prerender,
|
||||
);
|
||||
self.model
|
||||
.edit_lanes(id, lanespec.to_string(), ctx.prerender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.osd = Text::new();
|
||||
@ -497,7 +537,8 @@ impl GUI for UI {
|
||||
}
|
||||
State::MovingIntersection(_)
|
||||
| State::MovingBuilding(_)
|
||||
| State::MovingRoadPoint(_, _) => {}
|
||||
| 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);
|
||||
|
@ -478,12 +478,14 @@ impl Model {
|
||||
|
||||
let mut result = Vec::new();
|
||||
let synthetic = r.synthetic();
|
||||
let unset =
|
||||
synthetic && r.osm_tags.get(osm::NAME) == Some(&"Streety McStreetFace".to_string());
|
||||
let spec = r.get_spec();
|
||||
let center_pts = PolyLine::new(r.center_points.clone());
|
||||
for (idx, lt) in spec.fwd.iter().enumerate() {
|
||||
let mut obj = Object::new(
|
||||
ID::Lane(id, FORWARDS, idx),
|
||||
Model::lt_to_color(*lt, synthetic),
|
||||
Model::lt_to_color(*lt, synthetic, unset),
|
||||
center_pts
|
||||
.shift_right(LANE_THICKNESS * (0.5 + (idx as f64)))
|
||||
.unwrap()
|
||||
@ -503,7 +505,7 @@ impl Model {
|
||||
for (idx, lt) in spec.back.iter().enumerate() {
|
||||
let mut obj = Object::new(
|
||||
ID::Lane(id, BACKWARDS, idx),
|
||||
Model::lt_to_color(*lt, synthetic),
|
||||
Model::lt_to_color(*lt, synthetic, unset),
|
||||
center_pts
|
||||
.reversed()
|
||||
.shift_right(LANE_THICKNESS * (0.5 + (idx as f64)))
|
||||
@ -544,7 +546,7 @@ impl Model {
|
||||
}
|
||||
|
||||
// Copied from render/lane.rs. :(
|
||||
fn lt_to_color(lt: LaneType, synthetic: bool) -> Color {
|
||||
fn lt_to_color(lt: LaneType, synthetic: bool, unset: bool) -> Color {
|
||||
let color = match lt {
|
||||
LaneType::Driving => Color::BLACK,
|
||||
LaneType::Bus => Color::rgb(190, 74, 76),
|
||||
@ -553,7 +555,14 @@ impl Model {
|
||||
LaneType::Biking => Color::rgb(15, 125, 75),
|
||||
};
|
||||
if synthetic {
|
||||
color.alpha(0.5)
|
||||
if unset {
|
||||
match color {
|
||||
Color::RGBA(_, g, b, _) => Color::rgba_f(0.9, g, b, 0.5),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
color.alpha(0.5)
|
||||
}
|
||||
} else {
|
||||
color
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user