refactor the osm tags helper

This commit is contained in:
Dustin Carlino 2020-07-14 09:03:37 -07:00
parent 289b3561df
commit a5cf34ff59
6 changed files with 160 additions and 157 deletions

View File

@ -200,3 +200,42 @@ impl<K: Clone + PartialEq, V> VecMap<K, V> {
&mut self.inner.last_mut().unwrap().1 &mut self.inner.last_mut().unwrap().1
} }
} }
// Convenience functions around a string->string map
#[derive(Clone)]
pub struct Tags(BTreeMap<String, String>);
impl Tags {
pub fn new(map: BTreeMap<String, String>) -> Tags {
Tags(map)
}
pub fn get(&self, k: &str) -> Option<&String> {
self.0.get(k)
}
pub fn contains_key(&self, k: &str) -> bool {
self.0.contains_key(k)
}
pub fn is(&self, k: &str, v: &str) -> bool {
self.0.get(k) == Some(&v.to_string())
}
pub fn is_any(&self, k: &str, values: Vec<&str>) -> bool {
if let Some(v) = self.0.get(k) {
values.contains(&v.as_ref())
} else {
false
}
}
pub fn insert<K: Into<String>, V: Into<String>>(&mut self, k: K, v: V) {
self.0.insert(k.into(), v.into());
}
// TODO Maybe store this directly instead.
pub fn take(self) -> BTreeMap<String, String> {
self.0
}
}

View File

@ -8,7 +8,7 @@ mod time;
pub use crate::cli::CmdArgs; pub use crate::cli::CmdArgs;
pub use crate::clone::Cloneable; pub use crate::clone::Cloneable;
pub use crate::collections::{ pub use crate::collections::{
contains_duplicates, retain_btreemap, retain_btreeset, wraparound_get, Counter, MultiMap, contains_duplicates, retain_btreemap, retain_btreeset, wraparound_get, Counter, MultiMap, Tags,
VecMap, VecMap,
}; };
pub use crate::io::{ pub use crate::io::{

View File

@ -1,4 +1,4 @@
use abstutil::{FileWithProgress, Timer}; use abstutil::{FileWithProgress, Tags, Timer};
use geom::{GPSBounds, HashablePt2D, LonLat, PolyLine, Polygon, Pt2D, Ring}; use geom::{GPSBounds, HashablePt2D, LonLat, PolyLine, Polygon, Pt2D, Ring};
use map_model::raw::{ use map_model::raw::{
OriginalBuilding, RawArea, RawBuilding, RawBusRoute, RawBusStop, RawMap, RawParkingLot, OriginalBuilding, RawArea, RawBuilding, RawBusRoute, RawBusStop, RawMap, RawParkingLot,
@ -72,7 +72,7 @@ pub fn extract_osm(
osm_node_ids.insert(pt.to_hashable(), node.id); osm_node_ids.insert(pt.to_hashable(), node.id);
let tags = tags_to_map(&node.tags); let tags = tags_to_map(&node.tags);
if tags.get(osm::HIGHWAY) == Some(&"traffic_signals".to_string()) { if tags.is(osm::HIGHWAY, "traffic_signals") {
traffic_signals.insert(pt.to_hashable()); traffic_signals.insert(pt.to_hashable());
} }
if let Some(amenity) = tags.get("amenity") { if let Some(amenity) = tags.get("amenity") {
@ -118,21 +118,21 @@ pub fn extract_osm(
} }
let pts = map.gps_bounds.convert(&gps_pts); let pts = map.gps_bounds.convert(&gps_pts);
let mut tags = tags_to_map(&way.tags); let mut tags = tags_to_map(&way.tags);
tags.insert(osm::OSM_WAY_ID.to_string(), way.id.to_string()); tags.insert(osm::OSM_WAY_ID, way.id.to_string());
if is_road(&mut tags) { if is_road(&mut tags) {
// TODO Hardcoding these overrides. OSM is correct, these don't have // TODO Hardcoding these overrides. OSM is correct, these don't have
// sidewalks; there's a crosswalk mapped. But until we can snap sidewalks properly, do // sidewalks; there's a crosswalk mapped. But until we can snap sidewalks properly, do
// this to prevent the sidewalks from being disconnected. // this to prevent the sidewalks from being disconnected.
if way.id == 332060260 || way.id == 332060236 { if way.id == 332060260 || way.id == 332060236 {
tags.insert(osm::SIDEWALK.to_string(), "right".to_string()); tags.insert(osm::SIDEWALK, "right");
} }
roads.push(( roads.push((
way.id, way.id,
RawRoad { RawRoad {
center_points: pts, center_points: pts,
osm_tags: tags, osm_tags: tags.take(),
turn_restrictions: Vec::new(), turn_restrictions: Vec::new(),
complicated_turn_restrictions: Vec::new(), complicated_turn_restrictions: Vec::new(),
}, },
@ -151,7 +151,7 @@ pub fn extract_osm(
public_garage_name: None, public_garage_name: None,
num_parking_spots: 0, num_parking_spots: 0,
amenities: get_bldg_amenities(&tags), amenities: get_bldg_amenities(&tags),
osm_tags: tags, osm_tags: tags.take(),
}, },
); );
} else if let Some(at) = get_area_type(&tags) { } else if let Some(at) = get_area_type(&tags) {
@ -162,17 +162,17 @@ pub fn extract_osm(
area_type: at, area_type: at,
osm_id: way.id, osm_id: way.id,
polygon: Polygon::new(&pts), polygon: Polygon::new(&pts),
osm_tags: tags, osm_tags: tags.take(),
}); });
} else if tags.get("natural") == Some(&"coastline".to_string()) { } else if tags.is("natural", "coastline") {
coastline_groups.push((way.id, pts)); coastline_groups.push((way.id, pts));
} else if tags.get("amenity") == Some(&"parking".to_string()) { } else if tags.is("amenity", "parking") {
// TODO Verify parking = surface or handle other cases? // TODO Verify parking = surface or handle other cases?
map.parking_lots.push(RawParkingLot { map.parking_lots.push(RawParkingLot {
polygon: Polygon::new(&pts), polygon: Polygon::new(&pts),
osm_id: way.id, osm_id: way.id,
}); });
} else if tags.get("highway") == Some(&"service".to_string()) { } else if tags.is("highway", "service") {
map.parking_aisles.push(pts); map.parking_aisles.push(pts);
} else { } else {
// The way might be part of a relation later. // The way might be part of a relation later.
@ -188,22 +188,22 @@ pub fn extract_osm(
for rel in doc.relations.values() { for rel in doc.relations.values() {
timer.next(); timer.next();
let mut tags = tags_to_map(&rel.tags); let mut tags = tags_to_map(&rel.tags);
tags.insert(osm::OSM_REL_ID.to_string(), rel.id.to_string()); tags.insert(osm::OSM_REL_ID, rel.id.to_string());
if let Some(area_type) = get_area_type(&tags) { if let Some(area_type) = get_area_type(&tags) {
if tags.get("type") == Some(&"multipolygon".to_string()) { if tags.is("type", "multipolygon") {
if let Some(pts_per_way) = get_multipolygon_members(rel, &id_to_way) { if let Some(pts_per_way) = get_multipolygon_members(rel, &id_to_way) {
for polygon in glue_multipolygon(rel.id, pts_per_way, &boundary) { for polygon in glue_multipolygon(rel.id, pts_per_way, &boundary) {
map.areas.push(RawArea { map.areas.push(RawArea {
area_type, area_type,
osm_id: rel.id, osm_id: rel.id,
polygon, polygon,
osm_tags: tags.clone(), osm_tags: tags.clone().take(),
}); });
} }
} }
} }
} else if tags.get("type") == Some(&"restriction".to_string()) { } else if tags.is("type", "restriction") {
let mut from_way_id: Option<i64> = None; let mut from_way_id: Option<i64> = None;
let mut via_node_id: Option<i64> = None; let mut via_node_id: Option<i64> = None;
let mut via_way_id: Option<i64> = None; let mut via_way_id: Option<i64> = None;
@ -280,11 +280,11 @@ pub fn extract_osm(
public_garage_name: None, public_garage_name: None,
num_parking_spots: 0, num_parking_spots: 0,
amenities: get_bldg_amenities(&tags), amenities: get_bldg_amenities(&tags),
osm_tags: tags, osm_tags: tags.take(),
}, },
); );
} }
} else if tags.get("type") == Some(&"route_master".to_string()) { } else if tags.is("type", "route_master") {
map.bus_routes map.bus_routes
.extend(extract_route(&tags, rel, &doc, &id_to_way, &map.gps_bounds)); .extend(extract_route(&tags, rel, &doc, &id_to_way, &map.gps_bounds));
} }
@ -326,22 +326,24 @@ pub fn extract_osm(
) )
} }
fn tags_to_map(raw_tags: &[osm_xml::Tag]) -> BTreeMap<String, String> { fn tags_to_map(raw_tags: &[osm_xml::Tag]) -> Tags {
raw_tags Tags::new(
.iter() raw_tags
.filter_map(|tag| { .iter()
// Toss out really useless metadata. .filter_map(|tag| {
if tag.key.starts_with("tiger:") || tag.key.starts_with("old_name:") { // Toss out really useless metadata.
None if tag.key.starts_with("tiger:") || tag.key.starts_with("old_name:") {
} else { None
Some((tag.key.clone(), tag.val.clone())) } else {
} Some((tag.key.clone(), tag.val.clone()))
}) }
.collect() })
.collect(),
)
} }
fn is_road(tags: &mut BTreeMap<String, String>) -> bool { fn is_road(tags: &mut Tags) -> bool {
if tags.get("railway") == Some(&"light_rail".to_string()) { if tags.is("railway", "light_rail") {
return true; return true;
} }
@ -351,34 +353,35 @@ fn is_road(tags: &mut BTreeMap<String, String>) -> bool {
// https://github.com/Project-OSRM/osrm-backend/blob/master/profiles/car.lua is another // https://github.com/Project-OSRM/osrm-backend/blob/master/profiles/car.lua is another
// potential reference // potential reference
for value in &[ if tags.is_any(
// List of non-car types from https://wiki.openstreetmap.org/wiki/Key:highway osm::HIGHWAY,
// TODO Footways are very useful, but they need more work to associate with main roads vec![
"footway", // List of non-car types from https://wiki.openstreetmap.org/wiki/Key:highway
"living_street", // TODO Footways are very useful, but they need more work to associate with main roads
"pedestrian", "footway",
"track", "living_street",
"bus_guideway", "pedestrian",
"escape", "track",
"raceway", "bus_guideway",
"bridleway", "escape",
"steps", "raceway",
"path", "bridleway",
"cycleway", "steps",
"proposed", "path",
// This one's debatable. Includes alleys. "cycleway",
"service", "proposed",
// more discovered manually // This one's debatable. Includes alleys.
"abandoned", "service",
"elevator", // more discovered manually
"planned", "abandoned",
"razed", "elevator",
"corridor", "planned",
"junction", "razed",
] { "corridor",
if tags.get(osm::HIGHWAY) == Some(&value.to_string()) { "junction",
return false; ],
} ) {
return false;
} }
// If there's no parking data in OSM already, then assume no parking and mark that it's // If there's no parking data in OSM already, then assume no parking and mark that it's
@ -386,42 +389,41 @@ fn is_road(tags: &mut BTreeMap<String, String>) -> bool {
if !tags.contains_key(osm::PARKING_LEFT) if !tags.contains_key(osm::PARKING_LEFT)
&& !tags.contains_key(osm::PARKING_RIGHT) && !tags.contains_key(osm::PARKING_RIGHT)
&& !tags.contains_key(osm::PARKING_BOTH) && !tags.contains_key(osm::PARKING_BOTH)
&& tags.get(osm::HIGHWAY) != Some(&"motorway".to_string()) && !tags.is(osm::HIGHWAY, "motorway")
&& tags.get(osm::HIGHWAY) != Some(&"motorway_link".to_string()) && !tags.is(osm::HIGHWAY, "motorway_link")
&& tags.get("junction") != Some(&"roundabout".to_string()) && !tags.is("junction", "roundabout")
{ {
tags.insert(osm::PARKING_BOTH.to_string(), "no_parking".to_string()); tags.insert(osm::PARKING_BOTH, "no_parking");
tags.insert(osm::INFERRED_PARKING.to_string(), "true".to_string()); tags.insert(osm::INFERRED_PARKING, "true");
} }
// If there's no sidewalk data in OSM already, then make an assumption and mark that // If there's no sidewalk data in OSM already, then make an assumption and mark that
// it's inferred. // it's inferred.
if !tags.contains_key(osm::SIDEWALK) { if !tags.contains_key(osm::SIDEWALK) {
tags.insert(osm::INFERRED_SIDEWALKS.to_string(), "true".to_string()); tags.insert(osm::INFERRED_SIDEWALKS, "true");
if tags.get(osm::HIGHWAY) == Some(&"motorway".to_string()) if tags.is_any(osm::HIGHWAY, vec!["motorway", "motorway_link"])
|| tags.get(osm::HIGHWAY) == Some(&"motorway_link".to_string()) || tags.is("junction", "roundabout")
|| tags.get("junction") == Some(&"roundabout".to_string())
{ {
tags.insert(osm::SIDEWALK.to_string(), "none".to_string()); tags.insert(osm::SIDEWALK, "none");
} else if tags.get("oneway") == Some(&"yes".to_string()) { } else if tags.is("oneway", "yes") {
tags.insert(osm::SIDEWALK.to_string(), "right".to_string()); tags.insert(osm::SIDEWALK, "right");
if tags.get(osm::HIGHWAY) == Some(&"residential".to_string()) { if tags.is(osm::HIGHWAY, "residential") {
tags.insert(osm::SIDEWALK.to_string(), "both".to_string()); tags.insert(osm::SIDEWALK, "both");
} }
} else { } else {
tags.insert(osm::SIDEWALK.to_string(), "both".to_string()); tags.insert(osm::SIDEWALK, "both");
} }
} }
true true
} }
fn is_bldg(tags: &BTreeMap<String, String>) -> bool { fn is_bldg(tags: &Tags) -> bool {
// Sorry, the towers at Gasworks don't count. :) // Sorry, the towers at Gasworks don't count. :)
tags.contains_key("building") && !tags.contains_key("abandoned:man_made") tags.contains_key("building") && !tags.contains_key("abandoned:man_made")
} }
fn get_bldg_amenities(tags: &BTreeMap<String, String>) -> BTreeSet<(String, String)> { fn get_bldg_amenities(tags: &Tags) -> BTreeSet<(String, String)> {
let mut amenities = BTreeSet::new(); let mut amenities = BTreeSet::new();
if let Some(amenity) = tags.get("amenity") { if let Some(amenity) = tags.get("amenity") {
amenities.insert(( amenities.insert((
@ -442,38 +444,35 @@ fn get_bldg_amenities(tags: &BTreeMap<String, String>) -> BTreeSet<(String, Stri
amenities amenities
} }
fn get_area_type(tags: &BTreeMap<String, String>) -> Option<AreaType> { fn get_area_type(tags: &Tags) -> Option<AreaType> {
if tags.get("leisure") == Some(&"park".to_string()) { if tags.is_any("leisure", vec!["park", "golf_course"]) {
return Some(AreaType::Park); return Some(AreaType::Park);
} }
if tags.get("leisure") == Some(&"golf_course".to_string()) { if tags.is("natural", "wood") {
return Some(AreaType::Park); return Some(AreaType::Park);
} }
if tags.get("natural") == Some(&"wood".to_string()) { if tags.is("landuse", "cemetery") {
return Some(AreaType::Park); return Some(AreaType::Park);
} }
if tags.get("landuse") == Some(&"cemetery".to_string()) {
return Some(AreaType::Park); if tags.is("natural", "water") || tags.is("waterway", "riverbank") {
}
if tags.get("natural") == Some(&"water".to_string())
|| tags.get("waterway") == Some(&"riverbank".to_string())
{
return Some(AreaType::Water); return Some(AreaType::Water);
} }
if tags.get("place") == Some(&"island".to_string()) {
if tags.is("place", "island") {
return Some(AreaType::Island); return Some(AreaType::Island);
} }
// TODO These just cover up poorly inferred road geometry now. Figure out how to use these. // TODO These just cover up poorly inferred road geometry now. Figure out how to use these.
if false { if false {
if tags.get("traffic_calming") == Some(&"island".to_string()) { if tags.is("traffic_calming", "island") {
return Some(AreaType::PedestrianIsland); return Some(AreaType::PedestrianIsland);
} }
if tags.get("highway") == Some(&"pedestrian".to_string()) if tags.is("highway", "pedestrian") && tags.is("area", "yes") {
&& tags.get("area") == Some(&"yes".to_string())
{
return Some(AreaType::PedestrianIsland); return Some(AreaType::PedestrianIsland);
} }
} }
None None
} }
@ -605,7 +604,7 @@ fn glue_to_boundary(result_pl: PolyLine, boundary: &Ring) -> Option<Polygon> {
} }
fn extract_route( fn extract_route(
master_tags: &BTreeMap<String, String>, master_tags: &Tags,
master_rel: &osm_xml::Relation, master_rel: &osm_xml::Relation,
doc: &osm_xml::OSM, doc: &osm_xml::OSM,
id_to_way: &HashMap<i64, Vec<Pt2D>>, id_to_way: &HashMap<i64, Vec<Pt2D>>,

View File

@ -4,7 +4,7 @@ use crate::{
osm, Building, BuildingID, BuildingType, FrontPath, LaneID, LaneType, Map, OffstreetParking, osm, Building, BuildingID, BuildingType, FrontPath, LaneID, LaneType, Map, OffstreetParking,
ParkingLot, ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_LOT_SPOT_LENGTH, ParkingLot, ParkingLotID, Position, NORMAL_LANE_THICKNESS, PARKING_LOT_SPOT_LENGTH,
}; };
use abstutil::Timer; use abstutil::{Tags, Timer};
use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring}; use geom::{Angle, Distance, FindClosest, HashablePt2D, Line, PolyLine, Polygon, Pt2D, Ring};
use rand::{Rng, SeedableRng}; use rand::{Rng, SeedableRng};
use rand_xorshift::XorShiftRng; use rand_xorshift::XorShiftRng;
@ -73,7 +73,12 @@ pub fn make_all_buildings(
amenities: b.amenities.clone(), amenities: b.amenities.clone(),
parking: None, parking: None,
label_center: b.polygon.polylabel(), label_center: b.polygon.polylabel(),
bldg_type: classify_bldg(&b.osm_tags, &b.amenities, b.polygon.area(), &mut rng), bldg_type: classify_bldg(
Tags::new(b.osm_tags.clone()),
&b.amenities,
b.polygon.area(),
&mut rng,
),
}; };
// Can this building have a driveway? If it's not next to a driving lane, then no. // Can this building have a driveway? If it's not next to a driving lane, then no.
@ -360,13 +365,12 @@ fn line_valid(
} }
fn classify_bldg( fn classify_bldg(
tags: &BTreeMap<String, String>, tags: Tags,
amenities: &BTreeSet<(String, String)>, amenities: &BTreeSet<(String, String)>,
area_sq_meters: f64, area_sq_meters: f64,
rng: &mut rand_xorshift::XorShiftRng, rng: &mut rand_xorshift::XorShiftRng,
) -> BuildingType { ) -> BuildingType {
// used: top values from https://taginfo.openstreetmap.org/keys/building#values (>100k uses) // used: top values from https://taginfo.openstreetmap.org/keys/building#values (>100k uses)
let tags = Tags(tags);
let mut commercial = false; let mut commercial = false;
let workers; let workers;
@ -426,7 +430,6 @@ fn classify_bldg(
workers = rng.gen_range(0, 2); workers = rng.gen_range(0, 2);
} else if tags.is_any("building", vec!["apartments", "terrace", "residential"]) { } else if tags.is_any("building", vec!["apartments", "terrace", "residential"]) {
let levels = tags let levels = tags
.0
.get("building:levels") .get("building:levels")
.and_then(|x| x.parse::<usize>().ok()) .and_then(|x| x.parse::<usize>().ok())
.unwrap_or(1); .unwrap_or(1);
@ -445,19 +448,3 @@ fn classify_bldg(
} }
return BuildingType::Residential(workers); return BuildingType::Residential(workers);
} }
// TODO Refactor with lane_specs
struct Tags<'a>(&'a BTreeMap<String, String>);
impl<'a> Tags<'a> {
fn is(&self, k: &str, v: &str) -> bool {
self.0.get(k) == Some(&v.to_string())
}
fn is_any(&self, k: &str, values: Vec<&str>) -> bool {
if let Some(v) = self.0.get(k) {
values.contains(&v.as_ref())
} else {
false
}
}
}

View File

@ -1,4 +1,5 @@
use crate::{osm, LaneType}; use crate::{osm, LaneType};
use abstutil::Tags;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::{fmt, iter}; use std::{fmt, iter};
@ -6,9 +7,9 @@ use std::{fmt, iter};
// TODO This is ripe for unit testing. // TODO This is ripe for unit testing.
// (original direction, reversed direction) // (original direction, reversed direction)
pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Vec<LaneType>) { pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Vec<LaneType>) {
let tags = Tags(osm_tags); let tags = Tags::new(osm_tags.clone());
if let Some(s) = osm_tags.get(osm::SYNTHETIC_LANES) { if let Some(s) = tags.get(osm::SYNTHETIC_LANES) {
if let Some(spec) = RoadSpec::parse(s.to_string()) { if let Some(spec) = RoadSpec::parse(s.to_string()) {
return (spec.fwd, spec.back); return (spec.fwd, spec.back);
} else { } else {
@ -25,20 +26,16 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
} }
// TODO Reversible roads should be handled differently? // TODO Reversible roads should be handled differently?
let oneway = tags.is("oneway", "yes") let oneway =
|| tags.is("oneway", "reversible") tags.is_any("oneway", vec!["yes", "reversible"]) || tags.is("junction", "roundabout");
|| tags.is("junction", "roundabout");
// How many driving lanes in each direction? // How many driving lanes in each direction?
let num_driving_fwd = if let Some(n) = osm_tags let num_driving_fwd = if let Some(n) = tags
.get("lanes:forward") .get("lanes:forward")
.and_then(|num| num.parse::<usize>().ok()) .and_then(|num| num.parse::<usize>().ok())
{ {
n n
} else if let Some(n) = osm_tags } else if let Some(n) = tags.get("lanes").and_then(|num| num.parse::<usize>().ok()) {
.get("lanes")
.and_then(|num| num.parse::<usize>().ok())
{
if oneway { if oneway {
n n
} else if n % 2 == 0 { } else if n % 2 == 0 {
@ -51,15 +48,12 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
// TODO Grrr. // TODO Grrr.
1 1
}; };
let num_driving_back = if let Some(n) = osm_tags let num_driving_back = if let Some(n) = tags
.get("lanes:backward") .get("lanes:backward")
.and_then(|num| num.parse::<usize>().ok()) .and_then(|num| num.parse::<usize>().ok())
{ {
n n
} else if let Some(n) = osm_tags } else if let Some(n) = tags.get("lanes").and_then(|num| num.parse::<usize>().ok()) {
.get("lanes")
.and_then(|num| num.parse::<usize>().ok())
{
if oneway { if oneway {
0 0
} else if n % 2 == 0 { } else if n % 2 == 0 {
@ -80,7 +74,7 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
let driving_lane = if tags.is("access", "no") && tags.is("bus", "yes") { let driving_lane = if tags.is("access", "no") && tags.is("bus", "yes") {
// Sup West Seattle // Sup West Seattle
LaneType::Bus LaneType::Bus
} else if osm_tags } else if tags
.get("motor_vehicle:conditional") .get("motor_vehicle:conditional")
.map(|x| x.starts_with("no")) .map(|x| x.starts_with("no"))
.unwrap_or(false) .unwrap_or(false)
@ -105,14 +99,14 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
return (fwd_side, back_side); return (fwd_side, back_side);
} }
let fwd_bus_spec = if let Some(s) = osm_tags.get("bus:lanes:forward") { let fwd_bus_spec = if let Some(s) = tags.get("bus:lanes:forward") {
s s
} else if let Some(s) = osm_tags.get("psv:lanes:forward") { } else if let Some(s) = tags.get("psv:lanes:forward") {
s s
} else if oneway { } else if oneway {
if let Some(s) = osm_tags.get("bus:lanes") { if let Some(s) = tags.get("bus:lanes") {
s s
} else if let Some(s) = osm_tags.get("psv:lanes") { } else if let Some(s) = tags.get("psv:lanes") {
s s
} else { } else {
"" ""
@ -135,9 +129,9 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
} }
} }
} }
if let Some(spec) = osm_tags if let Some(spec) = tags
.get("bus:lanes:backward") .get("bus:lanes:backward")
.or_else(|| osm_tags.get("psv:lanes:backward")) .or_else(|| tags.get("psv:lanes:backward"))
{ {
let parts: Vec<&str> = spec.split("|").collect(); let parts: Vec<&str> = spec.split("|").collect();
if parts.len() == back_side.len() { if parts.len() == back_side.len() {
@ -161,8 +155,7 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
if tags.is("cycleway:right", "lane") { if tags.is("cycleway:right", "lane") {
fwd_side.push(LaneType::Biking); fwd_side.push(LaneType::Biking);
} }
if tags.is("cycleway:left", "lane") if tags.is_any("cycleway:left", vec!["lane", "opposite_lane"])
|| tags.is("cycleway:left", "opposite_lane")
|| tags.is("cycleway", "opposite_lane") || tags.is("cycleway", "opposite_lane")
{ {
back_side.push(LaneType::Biking); back_side.push(LaneType::Biking);
@ -175,15 +168,11 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
} }
if driving_lane == LaneType::Driving { if driving_lane == LaneType::Driving {
fn has_parking(value: Option<&String>) -> bool { let has_parking = vec!["parallel", "diagonal", "perpendicular"];
value == Some(&"parallel".to_string()) let parking_lane_fwd = tags.is_any(osm::PARKING_RIGHT, has_parking.clone())
|| value == Some(&"diagonal".to_string()) || tags.is_any(osm::PARKING_BOTH, has_parking.clone());
|| value == Some(&"perpendicular".to_string()) let parking_lane_back = tags.is_any(osm::PARKING_LEFT, has_parking.clone())
} || tags.is_any(osm::PARKING_BOTH, has_parking);
let parking_lane_fwd = has_parking(osm_tags.get(osm::PARKING_RIGHT))
|| has_parking(osm_tags.get(osm::PARKING_BOTH));
let parking_lane_back = has_parking(osm_tags.get(osm::PARKING_LEFT))
|| has_parking(osm_tags.get(osm::PARKING_BOTH));
if parking_lane_fwd { if parking_lane_fwd {
fwd_side.push(LaneType::Parking); fwd_side.push(LaneType::Parking);
} }
@ -210,15 +199,6 @@ pub fn get_lane_types(osm_tags: &BTreeMap<String, String>) -> (Vec<LaneType>, Ve
(fwd_side, back_side) (fwd_side, back_side)
} }
// TODO Figure out Rust strings; there's maybe a less verbose way than the old
// osm_tags.get("cycleway:right") == Some(&"lane".to_string()) mess.
struct Tags<'a>(&'a BTreeMap<String, String>);
impl<'a> Tags<'a> {
fn is(&self, k: &str, v: &str) -> bool {
self.0.get(k) == Some(&v.to_string())
}
}
// This is a convenient way for map_editor to plumb instructions here. // This is a convenient way for map_editor to plumb instructions here.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
pub struct RoadSpec { pub struct RoadSpec {

View File

@ -13,8 +13,6 @@ pub const SIDEWALK: &str = "sidewalk";
// The rest of these are all inserted by A/B Street to plumb data between different stages of map // The rest of these are all inserted by A/B Street to plumb data between different stages of map
// construction. They could be plumbed another way, but this is the most convenient. // construction. They could be plumbed another way, but this is the most convenient.
// TODO Comparing to Some(&"true".to_string()) is annoying
// Just a copy of OSM IDs, so that things displaying/searching tags will also pick these up. // Just a copy of OSM IDs, so that things displaying/searching tags will also pick these up.
pub const OSM_WAY_ID: &str = "abst:osm_way_id"; pub const OSM_WAY_ID: &str = "abst:osm_way_id";
pub const OSM_REL_ID: &str = "abst:osm_rel_id"; pub const OSM_REL_ID: &str = "abst:osm_rel_id";