diff --git a/editor/color_scheme b/editor/color_scheme index a4dca105a6..38805106c1 100644 --- a/editor/color_scheme +++ b/editor/color_scheme @@ -132,12 +132,6 @@ 0.3, 1.0 ], - "ParcelInterior": [ - 0.694, - 0.941, - 0.725, - 1.0 - ], "RoadOrientation": [ 1.0, 1.0, diff --git a/editor/src/colors.rs b/editor/src/colors.rs index 1e9b7722aa..3ab04874ff 100644 --- a/editor/src/colors.rs +++ b/editor/src/colors.rs @@ -34,7 +34,6 @@ pub enum Colors { BuildingPath, BuildingBoundary, ParcelBoundary, - ParcelInterior, RoadOrientation, SearchResult, Visited, diff --git a/editor/src/ui.rs b/editor/src/ui.rs index 4cee4740de..092c22fc31 100644 --- a/editor/src/ui.rs +++ b/editor/src/ui.rs @@ -310,10 +310,27 @@ impl UI { // Returns (boundary, fill) color fn color_parcel(&self, id: map_model::ParcelID) -> (Color, Color) { - let _p = self.map.get_p(id); + const COLORS: [Color; 14] = [ + // TODO these are awful choices + [1.0, 1.0, 0.0, 1.0], + [1.0, 0.0, 1.0, 1.0], + [0.0, 1.0, 1.0, 1.0], + [0.5, 0.2, 0.7, 1.0], + [0.5, 0.5, 0.0, 0.5], + [0.5, 0.0, 0.5, 0.5], + [0.0, 0.5, 0.5, 0.5], + [0.0, 0.0, 0.5, 0.5], + [0.3, 0.2, 0.5, 0.5], + [0.4, 0.2, 0.5, 0.5], + [0.5, 0.2, 0.5, 0.5], + [0.6, 0.2, 0.5, 0.5], + [0.7, 0.2, 0.5, 0.5], + [0.8, 0.2, 0.5, 0.5], + ]; + let p = self.map.get_p(id); ( self.cs.get(Colors::ParcelBoundary), - self.cs.get(Colors::ParcelInterior), + COLORS[p.block % COLORS.len()], ) } diff --git a/map_model/src/geometry.rs b/map_model/src/geometry.rs index 500ed46f7e..d3ef021846 100644 --- a/map_model/src/geometry.rs +++ b/map_model/src/geometry.rs @@ -1,6 +1,7 @@ // Copyright 2018 Google LLC, licensed under http://www.apache.org/licenses/LICENSE-2.0 use aabb_quadtree::geom::{Point, Rect}; +use geo; use geom::{Angle, Bounds, PolyLine, Pt2D}; use graphics::math::Vec2d; use std::f64; @@ -114,3 +115,19 @@ pub fn regular_polygon(center: Pt2D, sides: usize, length: f64) -> Vec { pts.push(first_pt); pts } + +pub fn polygons_intersect(pts1: &Vec, pts2: &Vec) -> bool { + use geo::prelude::Intersects; + + let poly1 = geo::Polygon::new( + pts1.iter() + .map(|pt| geo::Point::new(pt.x(), pt.y())) + .collect(), + Vec::new()); + let poly2 = geo::Polygon::new( + pts2.iter() + .map(|pt| geo::Point::new(pt.x(), pt.y())) + .collect(), + Vec::new()); + poly1.intersects(&poly2) +} diff --git a/map_model/src/make/mod.rs b/map_model/src/make/mod.rs index 20b22fbce0..564cf08fae 100644 --- a/map_model/src/make/mod.rs +++ b/map_model/src/make/mod.rs @@ -1,5 +1,6 @@ mod buildings; mod lanes; +mod parcels; mod trim_lines; mod turns; @@ -7,3 +8,4 @@ pub(crate) use self::buildings::make_building; pub(crate) use self::lanes::get_lane_specs; pub(crate) use self::trim_lines::trim_lines; pub(crate) use self::turns::make_all_turns; +pub(crate) use self::parcels::group_parcels; diff --git a/map_model/src/make/parcels.rs b/map_model/src/make/parcels.rs new file mode 100644 index 0000000000..1b077d0c22 --- /dev/null +++ b/map_model/src/make/parcels.rs @@ -0,0 +1,64 @@ +use {Parcel, ParcelID, geometry}; +use std::collections::BTreeSet; +use abstutil::MultiMap; + +pub fn group_parcels(parcels: &mut Vec) { + // First compute which parcels intersect + let mut adjacency: MultiMap = MultiMap::new(); + // TODO could use quadtree to prune + println!("Precomputing adjacency between {} parcels...", parcels.len()); + let mut adj_counter = 0; + for p1 in parcels.iter() { + for p2 in parcels.iter() { + if p1.id < p2.id && geometry::polygons_intersect(&p1.points, &p2.points) { + // TODO could do something more clever later to avoid double memory + adjacency.insert(p1.id, p2.id); + adjacency.insert(p2.id, p1.id); + adj_counter += 1; + } + } + } + println!("{} adjacencies, now doing floodfilling to group them", adj_counter); + + // Union-find might also be good inspiration. + fn floodfill(from: ParcelID, adj: &MultiMap) -> BTreeSet { + let mut visited: BTreeSet = BTreeSet::new(); + let mut queue: Vec = vec![from]; + while !queue.is_empty() { + let current = queue.pop().unwrap(); + if visited.contains(¤t) { + continue; + } + visited.insert(current); + for next in adj.get(current).iter() { + queue.push(*next); + } + } + visited + } + + let mut block_per_parcel: Vec> = Vec::new(); + for _ in parcels.iter() { + block_per_parcel.push(None); + } + + let mut block_counter = 0; + for base_p in parcels.iter() { + // A previous iteration might have filled it out + if block_per_parcel[base_p.id.0].is_some() { + continue; + } + + let new_block = Some(block_counter); + block_counter += 1; + for p in floodfill(base_p.id, &adjacency).iter() { + assert!(!block_per_parcel[p.0].is_some()); + block_per_parcel[p.0] = new_block; + } + } + println!("{} parcels grouped into {} blocks", parcels.len(), block_counter); + + for (idx, block) in block_per_parcel.iter().enumerate() { + parcels[idx].block = block.unwrap(); + } +} diff --git a/map_model/src/map.rs b/map_model/src/map.rs index 383f6a01aa..9ce9aecb4d 100644 --- a/map_model/src/map.rs +++ b/map_model/src/map.rs @@ -161,8 +161,10 @@ impl Map { .iter() .map(|coord| Pt2D::from_gps(coord, &bounds)) .collect(), + block: 0, }); } + make::group_parcels(&mut m.parcels); Ok(m) } diff --git a/map_model/src/parcel.rs b/map_model/src/parcel.rs index 361db78a90..8661096e4c 100644 --- a/map_model/src/parcel.rs +++ b/map_model/src/parcel.rs @@ -17,6 +17,8 @@ impl fmt::Display for ParcelID { pub struct Parcel { pub id: ParcelID, pub points: Vec, + // All parcels of the same block have the same number. + pub block: usize, } impl PartialEq for Parcel {