use std::collections::{BTreeSet, HashMap};
use osm::{NodeID, OsmID, RelationID, WayID};
use abstio::MapName;
use abstutil::{Tags, Timer};
use geom::{Distance, FindClosest, HashablePt2D, Polygon, Pt2D, Ring};
use kml::{ExtraShape, ExtraShapes};
use map_model::raw::{RawArea, RawBuilding, RawMap, RawParkingLot, RawRoad, RestrictionType};
use map_model::{osm, Amenity, AreaType, Direction, DrivingSide, NamePerLanguage};
use crate::osm_geom::{get_multipolygon_members, glue_multipolygon, multipoly_geometry};
use crate::{transit, Options};
pub struct OsmExtract {
pub roads: Vec<(WayID, RawRoad)>,
pub traffic_signals: HashMap<HashablePt2D, Direction>,
pub osm_node_ids: HashMap<HashablePt2D, NodeID>,
pub simple_turn_restrictions: Vec<(RestrictionType, WayID, NodeID, WayID)>,
pub complicated_turn_restrictions: Vec<(RelationID, WayID, WayID, WayID)>,
pub amenities: Vec<(Pt2D, Amenity)>,
}
pub fn extract_osm(map: &mut RawMap, opts: &Options, timer: &mut Timer) -> OsmExtract {
let mut doc = crate::reader::read(&opts.osm_input, &map.gps_bounds, timer).unwrap();
if false {
let ways: BTreeSet<WayID> = abstio::read_json("osm_ways.json".to_string(), timer);
for id in ways {
doc.ways
.get_mut(&id)
.unwrap()
.tags
.insert("junction", "intersection");
}
}
if let Some(way) = doc.ways.get_mut(&WayID(881403608)) {
way.tags.insert("highway", "construction");
}
if opts.clip.is_none() {
map.gps_bounds = doc.gps_bounds.clone();
map.boundary_polygon = map.gps_bounds.to_bounds().get_rectangle();
}
let mut out = OsmExtract {
roads: Vec::new(),
traffic_signals: HashMap::new(),
osm_node_ids: HashMap::new(),
simple_turn_restrictions: Vec::new(),
complicated_turn_restrictions: Vec::new(),
amenities: Vec::new(),
};
timer.start_iter("processing OSM nodes", doc.nodes.len());
for (id, node) in &doc.nodes {
timer.next();
out.osm_node_ids.insert(node.pt.to_hashable(), *id);
if node.tags.is(osm::HIGHWAY, "traffic_signals") {
let dir = if node.tags.is("traffic_signals:direction", "backward") {
Direction::Back
} else {
Direction::Fwd
};
out.traffic_signals.insert(node.pt.to_hashable(), dir);
}
for amenity in get_bldg_amenities(&node.tags) {
out.amenities.push((node.pt, amenity));
}
}
let mut extra_footways = ExtraShapes { shapes: Vec::new() };
let mut coastline_groups: Vec<(WayID, Vec<Pt2D>)> = Vec::new();
let mut memorial_areas: Vec<Polygon> = Vec::new();
timer.start_iter("processing OSM ways", doc.ways.len());
for (id, way) in &mut doc.ways {
timer.next();
let id = *id;
way.tags.insert(osm::OSM_WAY_ID, id.0.to_string());
if is_road(&mut way.tags, opts) {
if id == WayID(332060260) || id == WayID(332060236) {
way.tags.insert(osm::SIDEWALK, "right");
}
out.roads.push((
id,
RawRoad {
center_points: way.pts.clone(),
osm_tags: way.tags.clone(),
turn_restrictions: Vec::new(),
complicated_turn_restrictions: Vec::new(),
percent_incline: 0.0,
},
));
continue;
} else if way.tags.is(osm::HIGHWAY, "service") {
map.parking_aisles.push((id, way.pts.clone()));
} else if way
.tags
.is_any(osm::HIGHWAY, vec!["cycleway", "footway", "path"])
{
extra_footways.shapes.push(ExtraShape {
points: map.gps_bounds.convert_back(&way.pts),
attributes: way.tags.inner().clone(),
});
} else if way.tags.is("natural", "coastline") && !way.tags.is("place", "island") {
coastline_groups.push((id, way.pts.clone()));
continue;
}
let mut deduped = way.pts.clone();
deduped.dedup();
let polygon = if let Ok(ring) = Ring::new(deduped) {
ring.into_polygon()
} else {
continue;
};
if is_bldg(&way.tags) {
map.buildings.insert(
OsmID::Way(id),
RawBuilding {
polygon,
public_garage_name: None,
num_parking_spots: 0,
amenities: get_bldg_amenities(&way.tags),
osm_tags: way.tags.clone(),
},
);
} else if let Some(at) = get_area_type(&way.tags) {
map.areas.push(RawArea {
area_type: at,
osm_id: OsmID::Way(id),
polygon,
osm_tags: way.tags.clone(),
});
} else if way.tags.is("amenity", "parking") {
map.parking_lots.push(RawParkingLot {
osm_id: OsmID::Way(id),
polygon,
osm_tags: way.tags.clone(),
});
} else if way.tags.is("historic", "memorial") {
memorial_areas.push(polygon);
}
}
if map.name == MapName::seattle("huge_seattle") {
abstio::write_binary(map.name.city.input_path("footways.bin"), &extra_footways);
}
let boundary = map.boundary_polygon.clone().into_ring();
let mut amenity_areas: Vec<(Polygon, Amenity)> = Vec::new();
let mut stop_areas: Vec<((osm::NodeID, Pt2D), Pt2D)> = Vec::new();
for (id, rel) in &mut doc.relations {
rel.tags.insert(osm::OSM_REL_ID, id.0.to_string());
}
timer.start_iter("processing OSM relations", doc.relations.len());
for (id, rel) in &doc.relations {
timer.next();
let id = *id;
if let Some(area_type) = get_area_type(&rel.tags) {
if rel.tags.is("type", "multipolygon") {
for polygon in
glue_multipolygon(id, get_multipolygon_members(id, rel, &doc), Some(&boundary))
{
map.areas.push(RawArea {
area_type,
osm_id: OsmID::Relation(id),
polygon,
osm_tags: rel.tags.clone(),
});
}
}
} else if rel.tags.is("type", "restriction") {
let mut from_way_id: Option<WayID> = None;
let mut via_node_id: Option<NodeID> = None;
let mut via_way_id: Option<WayID> = None;
let mut to_way_id: Option<WayID> = None;
for (role, member) in &rel.members {
match member {
OsmID::Way(w) => {
if role == "from" {
from_way_id = Some(*w);
} else if role == "to" {
to_way_id = Some(*w);
} else if role == "via" {
via_way_id = Some(*w);
}
}
OsmID::Node(n) => {
if role == "via" {
via_node_id = Some(*n);
}
}
OsmID::Relation(r) => {
warn!("{} contains {} as {}", id, r, role);
}
}
}
if let Some(restriction) = rel.tags.get("restriction") {
if let Some(rt) = RestrictionType::new(restriction) {
if let (Some(from), Some(via), Some(to)) = (from_way_id, via_node_id, to_way_id)
{
out.simple_turn_restrictions.push((rt, from, via, to));
} else if let (Some(from), Some(via), Some(to)) =
(from_way_id, via_way_id, to_way_id)
{
if rt == RestrictionType::BanTurns {
out.complicated_turn_restrictions.push((id, from, via, to));
} else {
warn!(
"Weird complicated turn restriction \"{}\" from {} to {} via {}: \
{}",
restriction, from, to, via, id
);
}
}
}
}
} else if is_bldg(&rel.tags) {
match multipoly_geometry(id, rel, &doc) {
Ok(polygon) => {
map.buildings.insert(
OsmID::Relation(id),
RawBuilding {
polygon,
public_garage_name: None,
num_parking_spots: 0,
amenities: get_bldg_amenities(&rel.tags),
osm_tags: rel.tags.clone(),
},
);
}
Err(err) => println!("Skipping building {}: {}", id, err),
}
} else if rel.tags.is("amenity", "parking") {
for polygon in
glue_multipolygon(id, get_multipolygon_members(id, rel, &doc), Some(&boundary))
{
map.parking_lots.push(RawParkingLot {
osm_id: OsmID::Relation(id),
polygon,
osm_tags: rel.tags.clone(),
});
}
} else if rel.tags.is("type", "route") {
map.bus_routes
.extend(transit::extract_route(id, rel, &doc, &map.boundary_polygon));
} else if rel.tags.is("type", "multipolygon") && rel.tags.contains_key("amenity") {
let amenity = Amenity {
names: NamePerLanguage::new(&rel.tags).unwrap_or_else(NamePerLanguage::unnamed),
amenity_type: rel.tags.get("amenity").unwrap().clone(),
osm_tags: rel.tags.clone(),
};
for (role, member) in &rel.members {
if role != "outer" {
continue;
}
if let OsmID::Way(w) = member {
if let Ok(ring) = Ring::new(doc.ways[w].pts.clone()) {
amenity_areas.push((ring.into_polygon(), amenity.clone()));
}
}
}
} else if rel.tags.is("public_transport", "stop_area") {
let mut stops = Vec::new();
let mut platform: Option<Pt2D> = None;
for (role, member) in &rel.members {
if let OsmID::Node(n) = member {
let pt = doc.nodes[n].pt;
if role == "stop" {
stops.push((*n, pt));
} else if role == "platform" {
platform = Some(pt);
}
} else if let OsmID::Way(w) = member {
if role == "platform" {
platform = Some(Pt2D::center(&doc.ways[w].pts));
}
}
}
if let Some(ped_pos) = platform {
for vehicle_pos in stops {
stop_areas.push((vehicle_pos, ped_pos));
}
}
}
}
println!("{} ways of coastline", coastline_groups.len());
for polygon in glue_multipolygon(RelationID(-1), coastline_groups, Some(&boundary)) {
let mut osm_tags = Tags::empty();
osm_tags.insert("water", "ocean");
map.areas.insert(
0,
RawArea {
area_type: AreaType::Water,
osm_id: OsmID::Relation(RelationID(-1)),
polygon,
osm_tags,
},
);
}
timer.start_iter("match buildings to memorial areas", memorial_areas.len());
for area in memorial_areas {
timer.next();
map.buildings
.retain(|_, b| !area.contains_pt(b.polygon.center()));
}
timer.start_iter("match buildings to amenity areas", amenity_areas.len());
for (poly, amenity) in amenity_areas {
timer.next();
for b in map.buildings.values_mut() {
if poly.contains_pt(b.polygon.center()) {
b.amenities.push(amenity.clone());
}
}
}
for (vehicle_pos, ped_pos) in stop_areas {
for route in &mut map.bus_routes {
for stop in &mut route.stops {
if stop.vehicle_pos == vehicle_pos {
stop.ped_pos = Some(ped_pos);
}
}
}
}
map.areas.sort_by_key(|a| match a.area_type {
AreaType::Island => 2,
AreaType::Water => 1,
_ => 0,
});
timer.start("find service roads crossing parking lots");
find_parking_aisles(map, &mut out.roads);
timer.stop("find service roads crossing parking lots");
out
}
fn is_road(tags: &mut Tags, opts: &Options) -> bool {
if tags.is("area", "yes") {
return false;
}
if tags.is("railway", "light_rail") {
return true;
}
if tags.is("railway", "rail") && opts.include_railroads {
return true;
}
if tags.is("railway", "tram") {
return false;
}
let highway = if let Some(x) = tags.get(osm::HIGHWAY) {
if x == "construction" {
if let Some(x) = tags.get("construction") {
x
} else {
return false;
}
} else {
x
}
} else {
return false;
};
if !vec![
"cycleway",
"footway",
"living_street",
"motorway",
"motorway_link",
"path",
"pedestrian",
"primary",
"primary_link",
"residential",
"secondary",
"secondary_link",
"service",
"steps",
"tertiary",
"tertiary_link",
"track",
"trunk",
"trunk_link",
"unclassified",
]
.contains(&highway.as_ref())
{
return false;
}
if highway == "track" && tags.is("bicycle", "no") {
return false;
}
#[allow(clippy::collapsible_if)]
if (highway == "footway" || highway == "path" || highway == "steps")
&& opts.map_config.inferred_sidewalks
{
if !tags.is_any("bicycle", vec!["designated", "yes"]) {
return false;
}
}
if highway == "pedestrian"
&& tags.is("bicycle", "dismount")
&& opts.map_config.inferred_sidewalks
{
return false;
}
if highway == "service" && tags.is_any("service", vec!["driveway", "parking_aisle"]) {
return false;
}
if highway == "service" && tags.is("golf", "cartpath") {
return false;
}
if tags.is("lanes", "0") {
return false;
}
if opts.skip_local_roads && osm::RoadRank::from_highway(highway) == osm::RoadRank::Local {
return false;
}
if !tags.contains_key(osm::PARKING_LEFT)
&& !tags.contains_key(osm::PARKING_RIGHT)
&& !tags.contains_key(osm::PARKING_BOTH)
&& !tags.is_any(osm::HIGHWAY, vec!["motorway", "motorway_link", "service"])
&& !tags.is("junction", "roundabout")
{
tags.insert(osm::PARKING_BOTH, "no_parking");
tags.insert(osm::INFERRED_PARKING, "true");
}
if !tags.contains_key(osm::SIDEWALK) && opts.map_config.inferred_sidewalks {
tags.insert(osm::INFERRED_SIDEWALKS, "true");
if tags.contains_key("sidewalk:left") || tags.contains_key("sidewalk:right") {
let right = !tags.is("sidewalk:right", "no");
let left = !tags.is("sidewalk:left", "no");
let value = match (right, left) {
(true, true) => "both",
(true, false) => "right",
(false, true) => "left",
(false, false) => "none",
};
tags.insert(osm::SIDEWALK, value);
} else if tags.is_any(osm::HIGHWAY, vec!["motorway", "motorway_link"])
|| tags.is_any("junction", vec!["intersection", "roundabout"])
|| tags.is("foot", "no")
|| tags.is(osm::HIGHWAY, "service")
|| tags.is_any(osm::HIGHWAY, vec!["cycleway", "pedestrian", "track"])
{
tags.insert(osm::SIDEWALK, "none");
} else if tags.is("oneway", "yes") {
if opts.map_config.driving_side == DrivingSide::Right {
tags.insert(osm::SIDEWALK, "right");
} else {
tags.insert(osm::SIDEWALK, "left");
}
if tags.is_any(osm::HIGHWAY, vec!["residential", "living_street"])
&& !tags.is("dual_carriageway", "yes")
{
tags.insert(osm::SIDEWALK, "both");
}
} else {
tags.insert(osm::SIDEWALK, "both");
}
}
true
}
fn is_bldg(tags: &Tags) -> bool {
tags.contains_key("building") && !tags.contains_key("abandoned:man_made")
}
fn get_bldg_amenities(tags: &Tags) -> Vec<Amenity> {
let mut amenities = Vec::new();
for key in ["amenity", "shop", "craft", "office", "tourism", "leisure"] {
if let Some(amenity) = tags.get(key) {
amenities.push(Amenity {
names: NamePerLanguage::new(tags).unwrap_or_else(NamePerLanguage::unnamed),
amenity_type: amenity.clone(),
osm_tags: tags.clone(),
});
}
}
amenities
}
fn get_area_type(tags: &Tags) -> Option<AreaType> {
if tags.is_any("leisure", vec!["garden", "park", "golf_course"]) {
return Some(AreaType::Park);
}
if tags.is_any("natural", vec!["wood", "scrub"]) {
return Some(AreaType::Park);
}
if tags.is_any(
"landuse",
vec![
"cemetery",
"flowerbed",
"forest",
"grass",
"meadow",
"recreation_ground",
"village_green",
],
) || tags.is("amenity", "graveyard")
{
return Some(AreaType::Park);
}
if tags.is("natural", "water") || tags.is("waterway", "riverbank") {
return Some(AreaType::Water);
}
if tags.is("place", "island") {
return Some(AreaType::Island);
}
if tags.is(osm::HIGHWAY, "pedestrian") {
return Some(AreaType::PedestrianPlaza);
}
None
}
fn find_parking_aisles(map: &mut RawMap, roads: &mut Vec<(WayID, RawRoad)>) {
let mut closest: FindClosest<usize> = FindClosest::new(&map.gps_bounds.to_bounds());
for (idx, lot) in map.parking_lots.iter().enumerate() {
closest.add(idx, lot.polygon.points());
}
let mut keep_roads = Vec::new();
let mut parking_aisles = Vec::new();
for (id, road) in roads.drain(..) {
if !road.osm_tags.is(osm::HIGHWAY, "service") {
keep_roads.push((id, road));
continue;
}
let candidates: Vec<usize> = closest
.all_close_pts(Pt2D::center(&road.center_points), Distance::meters(500.0))
.into_iter()
.map(|(idx, _, _)| idx)
.collect();
if service_road_crosses_parking_lot(map, &road, candidates) {
parking_aisles.push((id, road.center_points.clone()));
} else {
keep_roads.push((id, road));
}
}
roads.extend(keep_roads);
for (id, pts) in parking_aisles {
map.parking_aisles.push((id, pts));
}
}
fn service_road_crosses_parking_lot(map: &RawMap, road: &RawRoad, candidates: Vec<usize>) -> bool {
if let Ok((polylines, rings)) = Ring::split_points(&road.center_points) {
for pl in polylines {
for idx in &candidates {
if map.parking_lots[*idx].polygon.clip_polyline(&pl).is_some() {
return true;
}
}
}
for ring in rings {
for idx in &candidates {
if map.parking_lots[*idx].polygon.clip_ring(&ring).is_some() {
return true;
}
}
}
}
false
}