for #231, split out parking methods in convert_osm

This commit is contained in:
Dustin Carlino 2020-07-24 19:20:31 -07:00
parent f06c2764f5
commit bebfac1322
2 changed files with 206 additions and 195 deletions

View File

@ -1,16 +1,13 @@
mod clip;
mod osm_reader;
mod parking;
mod split_ways;
mod srtm;
use abstutil::Timer;
use geom::{Distance, FindClosest, PolyLine, Pt2D};
use kml::ExtraShapes;
use map_model::raw::{OriginalBuilding, OriginalRoad, RawMap};
use map_model::{osm, MapConfig};
// Just used for matching hints to different sides of a road.
const DIRECTED_ROAD_THICKNESS: Distance = Distance::const_meters(2.5);
use geom::{Distance, FindClosest, Pt2D};
use map_model::raw::{OriginalBuilding, RawMap};
use map_model::MapConfig;
pub struct Options {
pub osm_input: String,
@ -73,7 +70,6 @@ pub fn convert(opts: Options, timer: &mut abstutil::Timer) -> RawMap {
timer,
);
clip::clip_map(&mut map, timer);
map.config = opts.map_config;
// Need to do a first pass of removing cul-de-sacs here, or we wind up with loop PolyLines when
// doing the parking hint matching.
@ -81,200 +77,16 @@ pub fn convert(opts: Options, timer: &mut abstutil::Timer) -> RawMap {
use_amenities(&mut map, amenities, timer);
match opts.onstreet_parking {
OnstreetParking::JustOSM => {}
OnstreetParking::Blockface(ref path) => {
use_parking_hints(&mut map, path.clone(), timer);
}
OnstreetParking::SomeAdditionalWhereNoData { pct } => {
let pct = pct as i64;
for (id, r) in map.roads.iter_mut() {
if r.osm_tags.contains_key(osm::INFERRED_PARKING)
&& r.osm_tags
.is_any(osm::HIGHWAY, vec!["residential", "tertiary"])
&& id.osm_way_id % 100 <= pct
{
if r.osm_tags.is("oneway", "yes") {
r.osm_tags.remove(osm::PARKING_BOTH);
r.osm_tags.insert(osm::PARKING_RIGHT, "parallel");
} else {
r.osm_tags.insert(osm::PARKING_BOTH, "parallel");
}
}
}
}
}
match opts.public_offstreet_parking {
PublicOffstreetParking::None => {}
PublicOffstreetParking::GIS(ref path) => {
use_offstreet_parking(&mut map, path.clone(), timer);
}
}
apply_private_offstreet_parking(&mut map, opts.private_offstreet_parking);
parking::apply_parking(&mut map, &opts, timer);
if let Some(ref path) = opts.elevation {
use_elevation(&mut map, path, timer);
}
map.config = opts.map_config;
map
}
fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
timer.start("apply parking hints");
let shapes: ExtraShapes = abstutil::read_binary(path, timer);
// Match shapes with the nearest road + direction (true for forwards)
let mut closest: FindClosest<(OriginalRoad, bool)> =
FindClosest::new(&map.gps_bounds.to_bounds());
for (id, r) in &map.roads {
if r.is_light_rail() || r.is_footway() {
continue;
}
let center = PolyLine::must_new(r.center_points.clone());
closest.add(
(*id, true),
map.config
.driving_side
.right_shift(center.clone(), DIRECTED_ROAD_THICKNESS)
.points(),
);
closest.add(
(*id, false),
map.config
.driving_side
.left_shift(center, DIRECTED_ROAD_THICKNESS)
.points(),
);
}
for s in shapes.shapes.into_iter() {
let pts = map.gps_bounds.convert(&s.points);
if pts.len() <= 1 {
continue;
}
// The blockface line endpoints will be close to other roads, so match based on the
// middle of the blockface.
// TODO Long blockfaces sometimes cover two roads. Should maybe find ALL matches within
// the threshold distance?
let middle = if let Ok(pl) = PolyLine::new(pts) {
pl.middle()
} else {
// Weird blockface with duplicate points. Shrug.
continue;
};
if let Some(((r, fwds), _)) = closest.closest_pt(middle, DIRECTED_ROAD_THICKNESS * 5.0) {
let tags = &mut map.roads.get_mut(&r).unwrap().osm_tags;
// Skip if the road already has this mapped.
if !tags.contains_key(osm::INFERRED_PARKING) {
continue;
}
let category = s.attributes.get("PARKING_CATEGORY");
let has_parking = category != Some(&"None".to_string())
&& category != Some(&"No Parking Allowed".to_string());
let definitely_no_parking =
tags.is_any(osm::HIGHWAY, vec!["motorway", "motorway_link"]);
if has_parking && definitely_no_parking {
timer.warn(format!(
"Blockface says there's parking along motorway {}, ignoring",
r
));
continue;
}
if let Some(both) = tags.remove(osm::PARKING_BOTH) {
tags.insert(osm::PARKING_LEFT, both.clone());
tags.insert(osm::PARKING_RIGHT, both);
}
tags.insert(
if fwds {
osm::PARKING_RIGHT
} else {
osm::PARKING_LEFT
},
if has_parking {
"parallel"
} else {
"no_parking"
},
);
// Maybe fold back into "both"
if tags.contains_key(osm::PARKING_LEFT)
&& tags.get(osm::PARKING_LEFT) == tags.get(osm::PARKING_RIGHT)
{
let value = tags.remove(osm::PARKING_LEFT).unwrap();
tags.remove(osm::PARKING_RIGHT).unwrap();
tags.insert(osm::PARKING_BOTH, value);
}
}
}
timer.stop("apply parking hints");
}
fn use_offstreet_parking(map: &mut RawMap, path: String, timer: &mut Timer) {
timer.start("match offstreet parking points");
let shapes: ExtraShapes = abstutil::read_binary(path, timer);
let mut closest: FindClosest<OriginalBuilding> = FindClosest::new(&map.gps_bounds.to_bounds());
for (id, b) in &map.buildings {
closest.add(*id, b.polygon.points());
}
// TODO Another function just to use ?. Try blocks would rock.
let mut handle_shape: Box<dyn FnMut(kml::ExtraShape) -> Option<()>> = Box::new(|s| {
assert_eq!(s.points.len(), 1);
let pt = Pt2D::from_gps(s.points[0], &map.gps_bounds);
let (id, _) = closest.closest_pt(pt, Distance::meters(50.0))?;
// TODO Handle parking lots.
if !map.buildings[&id].polygon.contains_pt(pt) {
return None;
}
let name = s.attributes.get("DEA_FACILITY_NAME")?.to_string();
let num_stalls = s.attributes.get("DEA_STALLS")?.parse::<usize>().ok()?;
// Well that's silly. Why's it listed?
if num_stalls == 0 {
return None;
}
let bldg = map.buildings.get_mut(&id).unwrap();
if bldg.num_parking_spots > 0 {
// TODO Can't use timer inside this closure
let old_name = bldg.public_garage_name.take().unwrap();
println!(
"Two offstreet parking hints apply to {}: {} @ {}, and {} @ {}",
id, bldg.num_parking_spots, old_name, num_stalls, name
);
bldg.public_garage_name = Some(format!("{} and {}", old_name, name));
bldg.num_parking_spots += num_stalls;
} else {
bldg.public_garage_name = Some(name);
bldg.num_parking_spots = num_stalls;
}
None
});
for s in shapes.shapes.into_iter() {
handle_shape(s);
}
timer.stop("match offstreet parking points");
}
fn apply_private_offstreet_parking(map: &mut RawMap, policy: PrivateOffstreetParking) {
match policy {
PrivateOffstreetParking::FixedPerBldg(n) => {
for b in map.buildings.values_mut() {
if b.public_garage_name.is_none() {
assert_eq!(b.num_parking_spots, 0);
b.num_parking_spots = n;
}
}
}
}
}
fn use_amenities(map: &mut RawMap, amenities: Vec<(Pt2D, String, String)>, timer: &mut Timer) {
let mut closest: FindClosest<OriginalBuilding> = FindClosest::new(&map.gps_bounds.to_bounds());
for (id, b) in &map.buildings {

199
convert_osm/src/parking.rs Normal file
View File

@ -0,0 +1,199 @@
use crate::{OnstreetParking, Options, PrivateOffstreetParking, PublicOffstreetParking};
use abstutil::Timer;
use geom::{Distance, FindClosest, PolyLine, Pt2D};
use kml::ExtraShapes;
use map_model::osm;
use map_model::raw::{OriginalBuilding, OriginalRoad, RawMap};
// Just used for matching hints to different sides of a road.
const DIRECTED_ROAD_THICKNESS: Distance = Distance::const_meters(2.5);
pub fn apply_parking(map: &mut RawMap, opts: &Options, timer: &mut Timer) {
match opts.onstreet_parking {
OnstreetParking::JustOSM => {}
OnstreetParking::Blockface(ref path) => {
use_parking_hints(map, path.clone(), timer);
}
OnstreetParking::SomeAdditionalWhereNoData { pct } => {
let pct = pct as i64;
for (id, r) in map.roads.iter_mut() {
if r.osm_tags.contains_key(osm::INFERRED_PARKING)
&& r.osm_tags
.is_any(osm::HIGHWAY, vec!["residential", "tertiary"])
&& id.osm_way_id % 100 <= pct
{
if r.osm_tags.is("oneway", "yes") {
r.osm_tags.remove(osm::PARKING_BOTH);
r.osm_tags.insert(osm::PARKING_RIGHT, "parallel");
} else {
r.osm_tags.insert(osm::PARKING_BOTH, "parallel");
}
}
}
}
}
match opts.public_offstreet_parking {
PublicOffstreetParking::None => {}
PublicOffstreetParking::GIS(ref path) => {
use_offstreet_parking(map, path.clone(), timer);
}
}
apply_private_offstreet_parking(map, &opts.private_offstreet_parking);
}
fn use_parking_hints(map: &mut RawMap, path: String, timer: &mut Timer) {
timer.start("apply parking hints");
let shapes: ExtraShapes = abstutil::read_binary(path, timer);
// Match shapes with the nearest road + direction (true for forwards)
let mut closest: FindClosest<(OriginalRoad, bool)> =
FindClosest::new(&map.gps_bounds.to_bounds());
for (id, r) in &map.roads {
if r.is_light_rail() || r.is_footway() {
continue;
}
let center = PolyLine::must_new(r.center_points.clone());
closest.add(
(*id, true),
map.config
.driving_side
.right_shift(center.clone(), DIRECTED_ROAD_THICKNESS)
.points(),
);
closest.add(
(*id, false),
map.config
.driving_side
.left_shift(center, DIRECTED_ROAD_THICKNESS)
.points(),
);
}
for s in shapes.shapes.into_iter() {
let pts = map.gps_bounds.convert(&s.points);
if pts.len() <= 1 {
continue;
}
// The blockface line endpoints will be close to other roads, so match based on the
// middle of the blockface.
// TODO Long blockfaces sometimes cover two roads. Should maybe find ALL matches within
// the threshold distance?
let middle = if let Ok(pl) = PolyLine::new(pts) {
pl.middle()
} else {
// Weird blockface with duplicate points. Shrug.
continue;
};
if let Some(((r, fwds), _)) = closest.closest_pt(middle, DIRECTED_ROAD_THICKNESS * 5.0) {
let tags = &mut map.roads.get_mut(&r).unwrap().osm_tags;
// Skip if the road already has this mapped.
if !tags.contains_key(osm::INFERRED_PARKING) {
continue;
}
let category = s.attributes.get("PARKING_CATEGORY");
let has_parking = category != Some(&"None".to_string())
&& category != Some(&"No Parking Allowed".to_string());
let definitely_no_parking =
tags.is_any(osm::HIGHWAY, vec!["motorway", "motorway_link"]);
if has_parking && definitely_no_parking {
timer.warn(format!(
"Blockface says there's parking along motorway {}, ignoring",
r
));
continue;
}
if let Some(both) = tags.remove(osm::PARKING_BOTH) {
tags.insert(osm::PARKING_LEFT, both.clone());
tags.insert(osm::PARKING_RIGHT, both);
}
tags.insert(
if fwds {
osm::PARKING_RIGHT
} else {
osm::PARKING_LEFT
},
if has_parking {
"parallel"
} else {
"no_parking"
},
);
// Maybe fold back into "both"
if tags.contains_key(osm::PARKING_LEFT)
&& tags.get(osm::PARKING_LEFT) == tags.get(osm::PARKING_RIGHT)
{
let value = tags.remove(osm::PARKING_LEFT).unwrap();
tags.remove(osm::PARKING_RIGHT).unwrap();
tags.insert(osm::PARKING_BOTH, value);
}
}
}
timer.stop("apply parking hints");
}
fn use_offstreet_parking(map: &mut RawMap, path: String, timer: &mut Timer) {
timer.start("match offstreet parking points");
let shapes: ExtraShapes = abstutil::read_binary(path, timer);
let mut closest: FindClosest<OriginalBuilding> = FindClosest::new(&map.gps_bounds.to_bounds());
for (id, b) in &map.buildings {
closest.add(*id, b.polygon.points());
}
// TODO Another function just to use ?. Try blocks would rock.
let mut handle_shape: Box<dyn FnMut(kml::ExtraShape) -> Option<()>> = Box::new(|s| {
assert_eq!(s.points.len(), 1);
let pt = Pt2D::from_gps(s.points[0], &map.gps_bounds);
let (id, _) = closest.closest_pt(pt, Distance::meters(50.0))?;
// TODO Handle parking lots.
if !map.buildings[&id].polygon.contains_pt(pt) {
return None;
}
let name = s.attributes.get("DEA_FACILITY_NAME")?.to_string();
let num_stalls = s.attributes.get("DEA_STALLS")?.parse::<usize>().ok()?;
// Well that's silly. Why's it listed?
if num_stalls == 0 {
return None;
}
let bldg = map.buildings.get_mut(&id).unwrap();
if bldg.num_parking_spots > 0 {
// TODO Can't use timer inside this closure
let old_name = bldg.public_garage_name.take().unwrap();
println!(
"Two offstreet parking hints apply to {}: {} @ {}, and {} @ {}",
id, bldg.num_parking_spots, old_name, num_stalls, name
);
bldg.public_garage_name = Some(format!("{} and {}", old_name, name));
bldg.num_parking_spots += num_stalls;
} else {
bldg.public_garage_name = Some(name);
bldg.num_parking_spots = num_stalls;
}
None
});
for s in shapes.shapes.into_iter() {
handle_shape(s);
}
timer.stop("match offstreet parking points");
}
fn apply_private_offstreet_parking(map: &mut RawMap, policy: &PrivateOffstreetParking) {
match policy {
PrivateOffstreetParking::FixedPerBldg(n) => {
for b in map.buildings.values_mut() {
if b.public_garage_name.is_none() {
assert_eq!(b.num_parking_spots, 0);
b.num_parking_spots = *n;
}
}
}
}
}