From b55e0ae263fcfe4621765a7d4a8d208ab5b89e76 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Fri, 10 Aug 2018 13:28:34 -0700 Subject: [PATCH] displaying extra arbitraryish kml for debugging --- editor/Cargo.toml | 1 + editor/color_scheme | 6 ++ editor/src/colors.rs | 1 + editor/src/kml.rs | 128 +++++++++++++++++++++++++++++++ editor/src/main.rs | 21 +++-- editor/src/plugins/selection.rs | 31 +++++++- editor/src/render/extra_shape.rs | 44 +++++++++++ editor/src/render/map.rs | 36 ++++++++- editor/src/render/mod.rs | 2 + editor/src/ui.rs | 52 ++++++++++++- 10 files changed, 312 insertions(+), 10 deletions(-) create mode 100644 editor/src/kml.rs create mode 100644 editor/src/render/extra_shape.rs diff --git a/editor/Cargo.toml b/editor/Cargo.toml index a87edbea36..eb8bf849b8 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -18,6 +18,7 @@ piston2d-graphics = "*" piston2d-opengl_graphics = "*" pistoncore-glutin_window = "*" pretty_assertions = "0.5.1" +quick-xml = "0.10.0" rand = "0.5.1" serde = "1.0" serde_derive = "1.0" diff --git a/editor/color_scheme b/editor/color_scheme index 38805106c1..e0a1d4919e 100644 --- a/editor/color_scheme +++ b/editor/color_scheme @@ -174,6 +174,12 @@ 0.7, 1.0 ], + "ExtraShape": [ + 1.0, + 0.0, + 0.0, + 1.0 + ], "MatchClassification": [ 0.0, 1.0, diff --git a/editor/src/colors.rs b/editor/src/colors.rs index 3ab04874ff..2a01a916a5 100644 --- a/editor/src/colors.rs +++ b/editor/src/colors.rs @@ -41,6 +41,7 @@ pub enum Colors { NextQueued, TurnIconCircle, TurnIconInactive, + ExtraShape, MatchClassification, DontMatchClassification, diff --git a/editor/src/kml.rs b/editor/src/kml.rs new file mode 100644 index 0000000000..e618ba48d1 --- /dev/null +++ b/editor/src/kml.rs @@ -0,0 +1,128 @@ +use geom::{Bounds, LonLat, PolyLine, Pt2D}; +use quick_xml::events::Event; +use quick_xml::reader::Reader; +use std::collections::HashMap; +use std::fs::File; +use std::{f64, fmt, io}; + +#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] +pub struct ExtraShapeID(pub usize); + +impl fmt::Display for ExtraShapeID { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "ExtraShapeID({0})", self.0) + } +} + +#[derive(Debug)] +pub struct ExtraShape { + pub id: ExtraShapeID, + pub pts: PolyLine, + pub attributes: HashMap, +} + +pub fn load(path: &String, gps_bounds: &Bounds) -> Result, io::Error> { + println!("Opening {}", path); + let f = File::open(path).unwrap(); + let mut reader = Reader::from_reader(io::BufReader::new(f)); + reader.trim_text(true); + + let mut buf = Vec::new(); + let mut last_progress_byte = 0; + + // TODO uncomfortably stateful + let mut shapes = Vec::new(); + let mut scanned_schema = false; + let mut attributes: HashMap = HashMap::new(); + let mut attrib_key: Option = None; + + let mut skipped_count = 0; + + loop { + if reader.buffer_position() - last_progress_byte >= 1024 * 1024 * 10 { + last_progress_byte = reader.buffer_position(); + println!( + "Processed {} MB of {}", + last_progress_byte / (1024 * 1024), + path + ); + } + match reader.read_event(&mut buf) { + Ok(Event::Start(e)) => { + let name = e.unescape_and_decode(&reader).unwrap(); + if name == "Placemark" { + scanned_schema = true; + } else if name.starts_with("SimpleData name=\"") { + attrib_key = Some(name["SimpleData name=\"".len()..name.len() - 1].to_string()); + } else if name == "coordinates" { + attrib_key = Some(name); + } else { + attrib_key = None; + } + } + Ok(Event::Text(e)) => { + if scanned_schema { + if let Some(ref key) = attrib_key { + let text = e.unescape_and_decode(&reader).unwrap(); + if key == "coordinates" { + let mut ok = true; + let mut pts: Vec = Vec::new(); + for pair in text.split(" ") { + if let Some(pt) = parse_pt(pair, gps_bounds) { + pts.push(pt); + } else { + ok = false; + break; + } + } + if ok { + let id = ExtraShapeID(shapes.len()); + shapes.push(ExtraShape { + id, + pts: PolyLine::new(pts), + attributes: attributes.clone(), + }); + } else { + skipped_count += 1; + } + attributes.clear(); + } else { + attributes.insert(key.to_string(), text); + } + } + } + } + Ok(Event::Eof) => break, + Err(e) => panic!( + "XML error at position {}: {:?}", + reader.buffer_position(), + e + ), + _ => (), + } + buf.clear(); + } + + println!( + "Got {} shapes from {} and skipped {} shapes", + shapes.len(), + path, + skipped_count + ); + return Ok(shapes); +} + +fn parse_pt(input: &str, gps_bounds: &Bounds) -> Option { + let coords: Vec<&str> = input.split(",").collect(); + if coords.len() != 2 { + return None; + } + return match (coords[0].parse::(), coords[1].parse::()) { + (Ok(lon), Ok(lat)) => if gps_bounds.contains(lon, lat) { + Some(Pt2D::from_gps(&LonLat::new(lon, lat), gps_bounds)) + } else { + None + }, + _ => None, + }; +} diff --git a/editor/src/main.rs b/editor/src/main.rs index 9b10decc11..d253683297 100644 --- a/editor/src/main.rs +++ b/editor/src/main.rs @@ -14,6 +14,7 @@ extern crate graphics; extern crate map_model; extern crate opengl_graphics; extern crate piston; +extern crate quick_xml; #[macro_use] extern crate pretty_assertions; extern crate rand; @@ -26,6 +27,14 @@ extern crate strum; #[macro_use] extern crate strum_macros; +mod colors; +mod experimental; +mod gui; +mod kml; +mod plugins; +mod render; +mod ui; + use ezgui::input::UserInput; use glutin_window::GlutinWindow; use opengl_graphics::{Filter, GlGraphics, GlyphCache, OpenGL, TextureSettings}; @@ -34,13 +43,6 @@ use piston::input::RenderEvent; use piston::window::{Window, WindowSettings}; use structopt::StructOpt; -mod colors; -mod experimental; -mod gui; -mod plugins; -mod render; -mod ui; - #[derive(StructOpt, Debug)] #[structopt(name = "editor")] struct Flags { @@ -59,6 +61,10 @@ struct Flags { /// Use the old parametric sim #[structopt(long = "parametric_sim")] parametric_sim: bool, + + /// Extra KML to display + #[structopt(long = "kml")] + kml: Option, } fn main() { @@ -103,6 +109,7 @@ fn main() { window_size, flags.rng_seed, flags.parametric_sim, + flags.kml, ), ); } diff --git a/editor/src/plugins/selection.rs b/editor/src/plugins/selection.rs index 19719090e6..8115c0022f 100644 --- a/editor/src/plugins/selection.rs +++ b/editor/src/plugins/selection.rs @@ -6,6 +6,7 @@ use ezgui::canvas::Canvas; use ezgui::input::UserInput; use ezgui::GfxCtx; use graphics::types::Color; +use kml::ExtraShapeID; use map_model; use map_model::{BuildingID, IntersectionID, LaneID, Map, TurnID}; use piston::input::{Button, Key, ReleaseEvent}; @@ -21,6 +22,7 @@ pub enum ID { Building(BuildingID), Car(CarID), Pedestrian(PedestrianID), + ExtraShape(ExtraShapeID), //Parcel(ParcelID), } @@ -36,6 +38,7 @@ pub enum SelectionState { SelectedTurn(TurnID), SelectedCar(CarID), SelectedPedestrian(PedestrianID), + SelectedExtraShape(ExtraShapeID), Tooltip(ID), } @@ -135,6 +138,14 @@ impl SelectionState { false } } + SelectionState::SelectedExtraShape(id) => { + if input.key_pressed(Key::LCtrl, &format!("Hold Ctrl to show {}'s tooltip", id)) { + new_state = Some(SelectionState::Tooltip(ID::ExtraShape(*id))); + true + } else { + false + } + } SelectionState::Empty => false, }; if let Some(s) = new_state { @@ -158,7 +169,8 @@ impl SelectionState { | SelectionState::SelectedTurn(_) | SelectionState::SelectedBuilding(_) | SelectionState::SelectedCar(_) - | SelectionState::SelectedPedestrian(_) => {} + | SelectionState::SelectedPedestrian(_) + | SelectionState::SelectedExtraShape(_) => {} SelectionState::SelectedIntersection(id) => { if let Some(signal) = control_map.traffic_signals.get(&id) { let (cycle, _) = signal.current_cycle_and_remaining_time(sim.time.as_time()); @@ -199,6 +211,7 @@ impl SelectionState { ID::Pedestrian(id) => sim.ped_tooltip(id), ID::Intersection(id) => vec![format!("{}", id)], ID::Turn(id) => vec![format!("{}", id)], + ID::ExtraShape(id) => draw_map.get_es(id).tooltip_lines(), }; canvas.draw_mouse_tooltip(g, &lines); } @@ -259,6 +272,16 @@ impl SelectionState { _ => None, } } + + pub fn color_es(&self, es: ExtraShapeID, cs: &ColorScheme) -> Option { + match *self { + SelectionState::SelectedExtraShape(id) if es == id => Some(cs.get(Colors::Selected)), + SelectionState::Tooltip(ID::ExtraShape(id)) if es == id => { + Some(cs.get(Colors::Selected)) + } + _ => None, + } + } } fn selection_state_for(some_id: ID) -> SelectionState { @@ -269,6 +292,7 @@ fn selection_state_for(some_id: ID) -> SelectionState { ID::Turn(id) => SelectionState::SelectedTurn(id), ID::Car(id) => SelectionState::SelectedCar(id), ID::Pedestrian(id) => SelectionState::SelectedPedestrian(id), + ID::ExtraShape(id) => SelectionState::SelectedExtraShape(id), } } @@ -294,6 +318,7 @@ impl Hider { SelectionState::SelectedIntersection(id) => Some(ID::Intersection(*id)), SelectionState::SelectedLane(id, _) => Some(ID::Lane(*id)), SelectionState::SelectedBuilding(id) => Some(ID::Building(*id)), + SelectionState::SelectedExtraShape(id) => Some(ID::ExtraShape(*id)), _ => None, }; if let Some(id) = item { @@ -318,4 +343,8 @@ impl Hider { pub fn show_i(&self, id: IntersectionID) -> bool { !self.items.contains(&ID::Intersection(id)) } + + pub fn show_es(&self, id: ExtraShapeID) -> bool { + !self.items.contains(&ID::ExtraShape(id)) + } } diff --git a/editor/src/render/extra_shape.rs b/editor/src/render/extra_shape.rs new file mode 100644 index 0000000000..4a1d53327f --- /dev/null +++ b/editor/src/render/extra_shape.rs @@ -0,0 +1,44 @@ +use aabb_quadtree::geom::Rect; +use ezgui::GfxCtx; +use geom::{Polygon, Pt2D}; +use graphics::types::Color; +use kml::{ExtraShape, ExtraShapeID}; +use render::{get_bbox, EXTRA_SHAPE_THICKNESS}; +use std::collections::HashMap; + +#[derive(Debug)] +pub struct DrawExtraShape { + pub id: ExtraShapeID, + polygon: Polygon, + attributes: HashMap, +} + +impl DrawExtraShape { + pub fn new(s: ExtraShape) -> DrawExtraShape { + DrawExtraShape { + id: s.id, + polygon: s.pts.make_polygons(EXTRA_SHAPE_THICKNESS).unwrap(), + attributes: s.attributes, + } + } + + pub fn draw(&self, g: &mut GfxCtx, color: Color) { + g.draw_polygon(color, &self.polygon); + } + + pub fn contains_pt(&self, pt: Pt2D) -> bool { + self.polygon.contains_pt(pt) + } + + pub fn get_bbox(&self) -> Rect { + get_bbox(&self.polygon.get_bounds()) + } + + pub fn tooltip_lines(&self) -> Vec { + let mut lines = Vec::new(); + for (k, v) in &self.attributes { + lines.push(format!("{} = {}", k, v)); + } + lines + } +} diff --git a/editor/src/render/map.rs b/editor/src/render/map.rs index fe7d0877a2..df9a2ef452 100644 --- a/editor/src/render/map.rs +++ b/editor/src/render/map.rs @@ -3,9 +3,11 @@ use aabb_quadtree::geom::{Point, Rect}; use aabb_quadtree::QuadTree; use geom::{LonLat, Pt2D}; +use kml::{ExtraShape, ExtraShapeID}; use map_model::{BuildingID, IntersectionID, Lane, LaneID, Map, ParcelID, Turn, TurnID}; use plugins::selection::Hider; use render::building::DrawBuilding; +use render::extra_shape::DrawExtraShape; use render::intersection::DrawIntersection; use render::lane::DrawLane; use render::parcel::DrawParcel; @@ -18,16 +20,18 @@ pub struct DrawMap { pub turns: HashMap, pub buildings: Vec, pub parcels: Vec, + pub extra_shapes: Vec, lanes_quadtree: QuadTree, intersections_quadtree: QuadTree, buildings_quadtree: QuadTree, parcels_quadtree: QuadTree, + extra_shapes_quadtree: QuadTree, } impl DrawMap { // Also returns the center of the map in map-space - pub fn new(map: &Map) -> (DrawMap, Pt2D) { + pub fn new(map: &Map, raw_extra_shapes: Vec) -> (DrawMap, Pt2D) { let mut lanes: Vec = Vec::new(); for l in map.all_lanes() { lanes.push(DrawLane::new(l, map)); @@ -56,6 +60,11 @@ impl DrawMap { .map(|p| DrawParcel::new(p)) .collect(); + let extra_shapes: Vec = raw_extra_shapes + .into_iter() + .map(|s| DrawExtraShape::new(s)) + .collect(); + // min_y here due to the wacky y inversion let bounds = map.get_gps_bounds(); let max_screen_pt = Pt2D::from_gps(&LonLat::new(bounds.max_x, bounds.min_y), &bounds); @@ -84,6 +93,11 @@ impl DrawMap { parcels_quadtree.insert_with_box(p.id, p.get_bbox()); } + let mut extra_shapes_quadtree = QuadTree::default(map_bbox); + for s in &extra_shapes { + extra_shapes_quadtree.insert_with_box(s.id, s.get_bbox()); + } + ( DrawMap { lanes, @@ -91,11 +105,13 @@ impl DrawMap { turns, buildings, parcels, + extra_shapes, lanes_quadtree, intersections_quadtree, buildings_quadtree, parcels_quadtree, + extra_shapes_quadtree, }, Pt2D::new(max_screen_pt.x() / 2.0, max_screen_pt.y() / 2.0), ) @@ -159,6 +175,10 @@ impl DrawMap { &self.parcels[id.0] } + pub fn get_es(&self, id: ExtraShapeID) -> &DrawExtraShape { + &self.extra_shapes[id.0] + } + pub fn get_loads_onscreen(&self, screen_bbox: Rect, hider: &Hider) -> Vec<&DrawLane> { let mut v = Vec::new(); for &(id, _, _) in &self.lanes_quadtree.query(screen_bbox) { @@ -200,4 +220,18 @@ impl DrawMap { } v } + + pub fn get_extra_shapes_onscreen( + &self, + screen_bbox: Rect, + hider: &Hider, + ) -> Vec<&DrawExtraShape> { + let mut v = Vec::new(); + for &(id, _, _) in &self.extra_shapes_quadtree.query(screen_bbox) { + if hider.show_es(*id) { + v.push(self.get_es(*id)); + } + } + v + } } diff --git a/editor/src/render/mod.rs b/editor/src/render/mod.rs index 98c2c76727..7f731ee885 100644 --- a/editor/src/render/mod.rs +++ b/editor/src/render/mod.rs @@ -1,6 +1,7 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 mod building; +mod extra_shape; mod intersection; mod lane; mod map; @@ -17,6 +18,7 @@ use std::f64; // These are all in meters const PARCEL_BOUNDARY_THICKNESS: f64 = 0.5; +const EXTRA_SHAPE_THICKNESS: f64 = 1.0; const TURN_ICON_ARROW_THICKNESS: f64 = geometry::BIG_ARROW_THICKNESS / 3.0; const BIG_ARROW_TIP_LENGTH: f64 = 1.0; diff --git a/editor/src/ui.rs b/editor/src/ui.rs index c5b349bf71..dae0a8c989 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -13,6 +13,7 @@ use ezgui::{GfxCtx, ToggleableLayer}; use geom::Pt2D; use graphics::types::Color; use gui; +use kml; use map_model; use map_model::{Edits, IntersectionID}; use piston::input::{Key, MouseCursorEvent}; @@ -50,6 +51,7 @@ pub struct UI { show_buildings: ToggleableLayer, show_intersections: ToggleableLayer, show_parcels: ToggleableLayer, + show_extra_shapes: ToggleableLayer, debug_mode: ToggleableLayer, // This is a particularly special plugin, since it's always kind of active and other things @@ -81,12 +83,20 @@ impl UI { window_size: Size, rng_seed: Option, parametric_sim: bool, + kml: Option, ) -> UI { let edits: Edits = abstutil::read_json("road_edits.json").unwrap_or(Edits::new()); println!("Opening {}", abst_path); let map = map_model::Map::new(abst_path, &edits).expect("Couldn't load map"); - let (draw_map, center_pt) = render::DrawMap::new(&map); + + let extra_shapes = if let Some(path) = kml { + kml::load(&path, &map.get_gps_bounds()).expect("Couldn't load extra KML shapes") + } else { + Vec::new() + }; + + let (draw_map, center_pt) = render::DrawMap::new(&map, extra_shapes); let control_map = ControlMap::new(&map); let steepness_viz = SteepnessVisualizer::new(&map); @@ -110,6 +120,12 @@ impl UI { Some(MIN_ZOOM_FOR_LANES), ), show_parcels: ToggleableLayer::new("parcels", Key::D4, "4", Some(MIN_ZOOM_FOR_PARCELS)), + show_extra_shapes: ToggleableLayer::new( + "extra KML shapes", + Key::D7, + "7", + Some(MIN_ZOOM_FOR_LANES), + ), debug_mode: ToggleableLayer::new("debug mode", Key::G, "G", None), current_selection_state: SelectionState::Empty, @@ -155,6 +171,7 @@ impl UI { self.show_buildings.handle_zoom(old_zoom, new_zoom); self.show_intersections.handle_zoom(old_zoom, new_zoom); self.show_parcels.handle_zoom(old_zoom, new_zoom); + self.show_extra_shapes.handle_zoom(old_zoom, new_zoom); self.debug_mode.handle_zoom(old_zoom, new_zoom); } @@ -211,6 +228,16 @@ impl UI { } } + if self.show_extra_shapes.is_enabled() { + for s in &self.draw_map + .get_extra_shapes_onscreen(screen_bbox, &self.hider) + { + if s.contains_pt(pt) { + return Some(ID::ExtraShape(s.id)); + } + } + } + if self.show_lanes.is_enabled() { for l in &lanes_onscreen { if l.contains_pt(pt) { @@ -447,6 +474,15 @@ impl gui::GUI for UI { } return gui::EventLoopMode::InputOnly; } + if self.show_extra_shapes.handle_event(input) { + if let SelectionState::SelectedExtraShape(_) = self.current_selection_state { + self.current_selection_state = SelectionState::Empty; + } + if let SelectionState::Tooltip(ID::ExtraShape(_)) = self.current_selection_state { + self.current_selection_state = SelectionState::Empty; + } + return gui::EventLoopMode::InputOnly; + } stop_if_done!(self.show_parcels.handle_event(input)); stop_if_done!(self.debug_mode.handle_event(input)); @@ -611,6 +647,20 @@ impl gui::GUI for UI { } } + if self.show_extra_shapes.is_enabled() { + for s in &self.draw_map + .get_extra_shapes_onscreen(screen_bbox, &self.hider) + { + // TODO no separate color method? + s.draw( + g, + self.current_selection_state + .color_es(s.id, &self.cs) + .unwrap_or(self.cs.get(Colors::ExtraShape)), + ); + } + } + self.current_selection_state.draw( &self.map, &self.canvas,