From 1936f5f75e8b6b1f7d2e51f928a0a9c97e8f4bf6 Mon Sep 17 00:00:00 2001 From: Dustin Carlino Date: Sun, 25 Aug 2019 13:19:35 -0700 Subject: [PATCH] match offstreet parking KML to buildings --- convert_osm/src/lib.rs | 48 +++++++++++++++++++++++++++++++-- convert_osm/src/osm.rs | 1 + docs/articles/map/article.md | 6 ++++- editor/src/common/mod.rs | 9 ++++++- import.sh | 3 ++- map_model/src/building.rs | 7 +++++ map_model/src/lib.rs | 2 +- map_model/src/make/buildings.rs | 1 + map_model/src/raw_data.rs | 3 ++- synthetic/src/lib.rs | 1 + tests/src/map_conversion.rs | 1 + 11 files changed, 75 insertions(+), 7 deletions(-) diff --git a/convert_osm/src/lib.rs b/convert_osm/src/lib.rs index b4e51f5a07..3ebfcf57cb 100644 --- a/convert_osm/src/lib.rs +++ b/convert_osm/src/lib.rs @@ -5,9 +5,9 @@ mod remove_disconnected; mod split_ways; use abstutil::Timer; -use geom::{FindClosest, GPSBounds, LonLat, PolyLine, Pt2D}; +use geom::{Distance, FindClosest, GPSBounds, LonLat, PolyLine, Polygon, Pt2D}; use kml::ExtraShapes; -use map_model::{raw_data, LANE_THICKNESS}; +use map_model::{raw_data, OffstreetParking, LANE_THICKNESS}; use std::fs::File; use std::io::{BufRead, BufReader}; use structopt::StructOpt; @@ -23,6 +23,10 @@ pub struct Flags { #[structopt(long = "parking_shapes", default_value = "")] pub parking_shapes: String, + /// KML file with offstreet parking info. Optional. + #[structopt(long = "offstreet_parking", default_value = "")] + pub offstreet_parking: String, + /// GTFS directory. Optional. #[structopt(long = "gtfs", default_value = "")] pub gtfs: String, @@ -67,6 +71,9 @@ pub fn convert(flags: &Flags, timer: &mut abstutil::Timer) -> raw_data::Map { if !flags.parking_shapes.is_empty() { use_parking_hints(&mut map, &flags.parking_shapes, timer); } + if !flags.offstreet_parking.is_empty() { + use_offstreet_parking(&mut map, &flags.offstreet_parking, timer); + } if !flags.gtfs.is_empty() { timer.start("load GTFS"); map.bus_routes = gtfs::load(&flags.gtfs).unwrap(); @@ -155,3 +162,40 @@ fn read_osmosis_polygon(path: &str) -> Vec { } pts } + +fn use_offstreet_parking(map: &mut raw_data::Map, path: &str, timer: &mut Timer) { + timer.start("match offstreet parking points"); + let shapes = kml::load(path, &map.gps_bounds, timer).expect("loading offstreet_parking failed"); + + // Building indices + let mut closest: FindClosest = FindClosest::new(&map.gps_bounds.to_bounds()); + for (idx, b) in map.buildings.iter().enumerate() { + let mut pts = map.gps_bounds.must_convert(&b.points); + // Close off the polygon + pts.push(pts[0]); + closest.add(idx, &pts); + } + + // TODO Another function just to use ?. Try blocks would rock. + let mut handle_shape: Box Option<()>> = Box::new(|s| { + assert_eq!(s.points.len(), 1); + let pt = Pt2D::from_gps(s.points[0], &map.gps_bounds)?; + let (idx, _) = closest.closest_pt(pt, Distance::meters(50.0))?; + // TODO If we ditched LonLat up-front, things like this would be much easier. + let poly = Polygon::new(&map.gps_bounds.must_convert(&map.buildings[idx].points)); + // TODO Handle parking lots. + if !poly.contains_pt(pt) { + return None; + } + let name = s.attributes.get("DEA_FACILITY_NAME")?.to_string(); + let num_stalls = s.attributes.get("DEA_STALLS")?.parse::().ok()?; + assert_eq!(map.buildings[idx].parking, None); + map.buildings[idx].parking = Some(OffstreetParking { name, num_stalls }); + None + }); + + for s in shapes.shapes.into_iter() { + handle_shape(s); + } + timer.stop("match offstreet parking points"); +} diff --git a/convert_osm/src/osm.rs b/convert_osm/src/osm.rs index 0f659dbc1e..39b0bad60e 100644 --- a/convert_osm/src/osm.rs +++ b/convert_osm/src/osm.rs @@ -79,6 +79,7 @@ pub fn osm_to_raw_roads( osm_way_id: way.id, points: pts, osm_tags: tags, + parking: None, }); } else if let Some(at) = get_area_type(&tags) { areas.push(raw_data::Area { diff --git a/docs/articles/map/article.md b/docs/articles/map/article.md index 091fad63e2..eb5158f00a 100644 --- a/docs/articles/map/article.md +++ b/docs/articles/map/article.md @@ -47,7 +47,8 @@ for some portion of Seattle. Each map has these objects: distinguish crosswalks at each end of a sidewalk.) - **Buildings**: A building has a position, OSM metadata, and a **front path** connecting the edge of the building to the nearest sidewalk. Most trips in A/B - Street begin and end at buildings. + Street begin and end at buildings. Some buildings also contain a number of + off-street parking spots. - **Area**: An area has geometry and OSM metadata and represents a body of water, forest, park, etc. They're just used for drawing. - **Bus stop**: A bus stop is placed some distance along a sidewalk, with a @@ -130,6 +131,9 @@ it only takes a few seconds to load a serialized map. - `lib.rs`: Apply parking hints from a King County GIS blockface dataset - Match each blockface to the nearest edge of a road - Interpret the metadata to assign on-street parking there or not +- `lib.rs`: Apply offstreet parking hints from a King County GIS dataset + - Match each point to the building containing it, plumbing through the number + of spots - `lib.rs` using the `gtfs` crate: Load bus route info from GTFS - `neighborhoods.rs`: Load neighborhood polygons from an extra geojson file - If the polygon isn't completely in-bounds, just remove it diff --git a/editor/src/common/mod.rs b/editor/src/common/mod.rs index a0493d85fe..bdcfb31a91 100644 --- a/editor/src/common/mod.rs +++ b/editor/src/common/mod.rs @@ -112,9 +112,16 @@ impl CommonState { osd.append(map.get_parent(*l).get_name(), Some(name_color)); } Some(ID::Building(b)) => { + let bldg = map.get_b(*b); osd.append(format!("{}", b), Some(id_color)); osd.append(" is ".to_string(), None); - osd.append(map.get_b(*b).get_name(), Some(name_color)); + osd.append(bldg.get_name(), Some(name_color)); + if let Some(ref p) = bldg.parking { + osd.append( + format!(" ({} parking spots via {})", p.num_stalls, p.name), + None, + ); + } } Some(ID::Turn(t)) => { osd.append( diff --git a/import.sh b/import.sh index 495841667f..611c31e3d9 100755 --- a/import.sh +++ b/import.sh @@ -95,10 +95,11 @@ for poly in `ls ../data/polygons/`; do RUST_BACKTRACE=1 cargo run --release -- \ --osm=../data/input/$name.osm \ --parking_shapes=../data/shapes/blockface.bin \ + --offstreet_parking=../data/input/offstreet_parking.kml \ --gtfs=../data/input/google_transit_2018_18_08 \ --neighborhoods=../data/input/neighborhoods.geojson \ --clip=../data/polygons/$name.poly \ --output=../data/raw_maps/$name.bin done -# To run manually: cargo run -- --osm=../data/input/montlake.osm --parking_shapes=../data/shapes/blockface.bin --gtfs=../data/input/google_transit_2018_18_08 --neighborhoods=../data/input/neighborhoods.geojson --clip=../data/polygons/montlake.poly --output=../data/raw_maps/montlake.bin --fast_dev +# To run manually: cargo run -- --osm=../data/input/montlake.osm --parking_shapes=../data/shapes/blockface.bin --offstreet_parking=../data/input/offstreet_parking.kml --gtfs=../data/input/google_transit_2018_18_08 --neighborhoods=../data/input/neighborhoods.geojson --clip=../data/polygons/montlake.poly --output=../data/raw_maps/montlake.bin --fast_dev diff --git a/map_model/src/building.rs b/map_model/src/building.rs index 1acce2502d..fa53feff99 100644 --- a/map_model/src/building.rs +++ b/map_model/src/building.rs @@ -23,6 +23,12 @@ pub struct FrontPath { pub line: Line, } +#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] +pub struct OffstreetParking { + pub name: String, + pub num_stalls: usize, +} + #[derive(Serialize, Deserialize, Debug)] pub struct Building { pub id: BuildingID, @@ -34,6 +40,7 @@ pub struct Building { pub label_center: Pt2D, pub front_path: FrontPath, + pub parking: Option, } impl Building { diff --git a/map_model/src/lib.rs b/map_model/src/lib.rs index 7022845a88..08935c3659 100644 --- a/map_model/src/lib.rs +++ b/map_model/src/lib.rs @@ -16,7 +16,7 @@ mod traversable; mod turn; pub use crate::area::{Area, AreaID, AreaType}; -pub use crate::building::{Building, BuildingID, FrontPath}; +pub use crate::building::{Building, BuildingID, FrontPath, OffstreetParking}; pub use crate::bus_stop::{BusRoute, BusRouteID, BusStop, BusStopID}; pub use crate::edits::MapEdits; pub use crate::intersection::{Intersection, IntersectionID, IntersectionType}; diff --git a/map_model/src/make/buildings.rs b/map_model/src/make/buildings.rs index e0907b8dc4..75ca405dac 100644 --- a/map_model/src/make/buildings.rs +++ b/map_model/src/make/buildings.rs @@ -54,6 +54,7 @@ pub fn make_all_buildings( sidewalk: *sidewalk_pos, line, }, + parking: input[idx].parking.clone(), label_center: Polygon::polylabel(&points), }); } diff --git a/map_model/src/raw_data.rs b/map_model/src/raw_data.rs index ec355f5142..2af62516eb 100644 --- a/map_model/src/raw_data.rs +++ b/map_model/src/raw_data.rs @@ -1,6 +1,6 @@ use crate::make::get_lane_types; pub use crate::make::{Hint, Hints, InitialMap}; -use crate::{AreaType, IntersectionType, RoadSpec}; +use crate::{AreaType, IntersectionType, OffstreetParking, RoadSpec}; use geom::{GPSBounds, LonLat}; use gtfs::Route; use serde_derive::{Deserialize, Serialize}; @@ -164,6 +164,7 @@ pub struct Building { pub points: Vec, pub osm_tags: BTreeMap, pub osm_way_id: i64, + pub parking: Option, } #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] diff --git a/synthetic/src/lib.rs b/synthetic/src/lib.rs index a6d89b47a0..55cf6cabe7 100644 --- a/synthetic/src/lib.rs +++ b/synthetic/src/lib.rs @@ -363,6 +363,7 @@ impl Model { points: b.polygon().points().iter().map(|p| pt(*p)).collect(), osm_tags, osm_way_id: idx as i64, + parking: None, }); } diff --git a/tests/src/map_conversion.rs b/tests/src/map_conversion.rs index f0b6406870..badb9edf74 100644 --- a/tests/src/map_conversion.rs +++ b/tests/src/map_conversion.rs @@ -8,6 +8,7 @@ pub fn run(t: &mut TestRunner) { let flags = convert_osm::Flags { osm: "../data/input/montlake.osm".to_string(), parking_shapes: "../data/shapes/blockface.bin".to_string(), + offstreet_parking: "../data/input/offstreet_parking.kml".to_string(), gtfs: "../data/input/google_transit_2018_18_08".to_string(), neighborhoods: "../data/input/neighborhoods.geojson".to_string(), clip: abstutil::path_polygon("montlake"),