1 use abstutil::{prettyprint_usize, slurp_file, Tags, Timer};
2 use geom::{GPSBounds, LonLat, Pt2D};
3 use map_model::osm::{NodeID, OsmID, RelationID, WayID};
4 use std::collections::BTreeMap;
5 use std::error::Error;
6 
7 // References to missing objects are just filtered out.
8 // Per https://wiki.openstreetmap.org/wiki/OSM_XML#Certainties_and_Uncertainties, we assume
9 // elements come in order: nodes, ways, then relations.
10 //
11 // TODO Filter out visible=false
12 // TODO NodeID, WayID, RelationID are nice. Plumb forward through map_model.
13 // TODO Replicate IDs in each object, and change members to just hold a reference to the object
14 // (which is guaranteed to exist).
15 
16 pub struct Document {
17     pub gps_bounds: GPSBounds,
18     pub nodes: BTreeMap<NodeID, Node>,
19     pub ways: BTreeMap<WayID, Way>,
20     pub relations: BTreeMap<RelationID, Relation>,
21 }
22 
23 pub struct Node {
24     pub pt: Pt2D,
25     pub tags: Tags,
26 }
27 
28 pub struct Way {
29     // Duplicates geometry, because it's convenient
30     pub nodes: Vec<NodeID>,
31     pub pts: Vec<Pt2D>,
32     pub tags: Tags,
33 }
34 
35 pub struct Relation {
36     pub tags: Tags,
37     // Role, member
38     pub members: Vec<(String, OsmID)>,
39 }
40 
read( path: &str, input_gps_bounds: &GPSBounds, timer: &mut Timer, ) -> Result<Document, Box<dyn Error>>41 pub fn read(
42     path: &str,
43     input_gps_bounds: &GPSBounds,
44     timer: &mut Timer,
45 ) -> Result<Document, Box<dyn Error>> {
46     timer.start(format!("read {}", path));
47     let bytes = slurp_file(path)?;
48     let raw_string = std::str::from_utf8(&bytes)?;
49     let tree = roxmltree::Document::parse(raw_string)?;
50     timer.stop(format!("read {}", path));
51 
52     let mut doc = Document {
53         gps_bounds: input_gps_bounds.clone(),
54         nodes: BTreeMap::new(),
55         ways: BTreeMap::new(),
56         relations: BTreeMap::new(),
57     };
58 
59     timer.start("scrape objects");
60     for obj in tree.descendants() {
61         if !obj.is_element() {
62             continue;
63         }
64         match obj.tag_name().name() {
65             "bounds" => {
66                 // If we weren't provided with GPSBounds, use this.
67                 if doc.gps_bounds != GPSBounds::new() {
68                     continue;
69                 }
70                 doc.gps_bounds.update(LonLat::new(
71                     obj.attribute("minlon").unwrap().parse::<f64>().unwrap(),
72                     obj.attribute("minlat").unwrap().parse::<f64>().unwrap(),
73                 ));
74                 doc.gps_bounds.update(LonLat::new(
75                     obj.attribute("maxlon").unwrap().parse::<f64>().unwrap(),
76                     obj.attribute("maxlat").unwrap().parse::<f64>().unwrap(),
77                 ));
78             }
79             "node" => {
80                 if doc.gps_bounds == GPSBounds::new() {
81                     timer.warn(
82                         "No clipping polygon provided and the .osm is missing a <bounds> element, \
83                          so figuring out the bounds manually."
84                             .to_string(),
85                     );
86                     doc.gps_bounds = scrape_bounds(&tree);
87                 }
88 
89                 let id = NodeID(obj.attribute("id").unwrap().parse::<i64>().unwrap());
90                 if doc.nodes.contains_key(&id) {
91                     return Err(format!("Duplicate {}, your .osm is corrupt", id).into());
92                 }
93                 let pt = Pt2D::from_gps(
94                     LonLat::new(
95                         obj.attribute("lon").unwrap().parse::<f64>().unwrap(),
96                         obj.attribute("lat").unwrap().parse::<f64>().unwrap(),
97                     ),
98                     &doc.gps_bounds,
99                 );
100                 let tags = read_tags(obj);
101                 doc.nodes.insert(id, Node { pt, tags });
102             }
103             "way" => {
104                 let id = WayID(obj.attribute("id").unwrap().parse::<i64>().unwrap());
105                 if doc.ways.contains_key(&id) {
106                     return Err(format!("Duplicate {}, your .osm is corrupt", id).into());
107                 }
108                 let tags = read_tags(obj);
109 
110                 let mut nodes = Vec::new();
111                 let mut pts = Vec::new();
112                 for child in obj.children() {
113                     if child.tag_name().name() == "nd" {
114                         let n = NodeID(child.attribute("ref").unwrap().parse::<i64>().unwrap());
115                         // Just skip missing nodes
116                         if let Some(ref node) = doc.nodes.get(&n) {
117                             nodes.push(n);
118                             pts.push(node.pt);
119                         }
120                     }
121                 }
122                 if !nodes.is_empty() {
123                     doc.ways.insert(id, Way { tags, nodes, pts });
124                 }
125             }
126             "relation" => {
127                 let id = RelationID(obj.attribute("id").unwrap().parse::<i64>().unwrap());
128                 if doc.relations.contains_key(&id) {
129                     return Err(format!("Duplicate {}, your .osm is corrupt", id).into());
130                 }
131                 let tags = read_tags(obj);
132                 let mut members = Vec::new();
133                 for child in obj.children() {
134                     if child.tag_name().name() == "member" {
135                         let member = match child.attribute("type").unwrap() {
136                             "node" => {
137                                 let n =
138                                     NodeID(child.attribute("ref").unwrap().parse::<i64>().unwrap());
139                                 if !doc.nodes.contains_key(&n) {
140                                     continue;
141                                 }
142                                 OsmID::Node(n)
143                             }
144                             "way" => {
145                                 let w =
146                                     WayID(child.attribute("ref").unwrap().parse::<i64>().unwrap());
147                                 if !doc.ways.contains_key(&w) {
148                                     continue;
149                                 }
150                                 OsmID::Way(w)
151                             }
152                             "relation" => {
153                                 let r = RelationID(
154                                     child.attribute("ref").unwrap().parse::<i64>().unwrap(),
155                                 );
156                                 if !doc.relations.contains_key(&r) {
157                                     continue;
158                                 }
159                                 OsmID::Relation(r)
160                             }
161                             _ => continue,
162                         };
163                         members.push((child.attribute("role").unwrap().to_string(), member));
164                     }
165                 }
166                 doc.relations.insert(id, Relation { tags, members });
167             }
168             _ => {}
169         }
170     }
171     timer.stop("scrape objects");
172     timer.note(format!(
173         "Found {} nodes, {} ways, {} relations",
174         prettyprint_usize(doc.nodes.len()),
175         prettyprint_usize(doc.ways.len()),
176         prettyprint_usize(doc.relations.len())
177     ));
178 
179     Ok(doc)
180 }
181 
read_tags(obj: roxmltree::Node) -> Tags182 fn read_tags(obj: roxmltree::Node) -> Tags {
183     let mut tags = Tags::new(BTreeMap::new());
184     for child in obj.children() {
185         if child.tag_name().name() == "tag" {
186             let key = child.attribute("k").unwrap();
187             // Filter out really useless data
188             if key.starts_with("tiger:") || key.starts_with("old_name:") {
189                 continue;
190             }
191             tags.insert(key, child.attribute("v").unwrap());
192         }
193     }
194     tags
195 }
196 
scrape_bounds(doc: &roxmltree::Document) -> GPSBounds197 fn scrape_bounds(doc: &roxmltree::Document) -> GPSBounds {
198     let mut b = GPSBounds::new();
199     for obj in doc.descendants() {
200         if obj.is_element() && obj.tag_name().name() == "node" {
201             b.update(LonLat::new(
202                 obj.attribute("lon").unwrap().parse::<f64>().unwrap(),
203                 obj.attribute("lat").unwrap().parse::<f64>().unwrap(),
204             ));
205         }
206     }
207     b
208 }
209