1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
//! This crate contains implementation to read and write [`Open Street Maps`].
//!
//! The [`Osm`] struct is an abstract representation of an OSM map. You can build your map with this
//! struct by adding nodes, ways and relations. Use the [`OsmBuilder`] if you are working with
//! non OSM data, it lets you work with polygons, poly lines and points instead.
//!
//! The [`osm_io`] module contains io functionality for reading and writing multiple OSM formats.
//! Currently osm and o5m is supported.
//!
//! The [`geo`] module contains some more general geographic abstractions used by this crate.
//!
//! [`Open Street Maps`]: https://wiki.openstreetmap.org/wiki/Main_Page
//! [`Osm`]: struct.Osm.html
//! [`OsmBuilder`]: struct.OsmBuilder.html
//! [`osm_io`]: osm_io/index.html
//! [`geo`]: geo/index.html
mod element;
pub mod geo;
pub mod osm_io;

use crate::geo::{Boundary, Coordinate};
pub use element::*;
use std::cmp::max;
use std::collections::HashMap;

/// `OsmBuilder` makes it easy to build OSM maps from non OSM data. Polygons, multi polygons,
/// poly lines and points are all represented as vectors of coordinates.
///
/// Nodes are automatically added and assigned ids. Ways and relations are automatically created
/// with the correct references.
///
/// When building maps from OSM data the elements should be added directly to an [`Osm`] struct to
/// preserve ids. You can also use the [`osm_io`] module which do exactly that when reading data.
///
/// # Examples
/// ```
/// # use vadeen_osm::OsmBuilder;
/// # use vadeen_osm::geo::Boundary;
/// let mut builder = OsmBuilder::default();
///
/// // Add a point, represented as one node.
/// builder.add_point((2.0, 2.0), vec![("power", "tower")]);
///
/// // Add a poly line, represented as two nodes and a way.
/// builder.add_polyline(
///     vec![(2.0, 2.0), (4.0, 5.0)],
///     vec![("power", "line")]
/// );
///
/// // Add a polygon, which is represented as one way and two nodes in osm.
/// builder.add_polygon(
///     vec![
///         // Outer polygon
///         vec![(1.0, 1.0), (10.0, 10.0), (5.0, 5.0), (1.0, 1.0)],
///         // If you want inner polygons, add them here...
///         // Each inner polygon is represented as a way, the polygons are connected by a relation.
///     ],
///     vec![("natural", "water")]
/// );
///
/// let osm = builder.build();
/// assert_eq!(osm.nodes.len(), 5);
/// assert_eq!(osm.ways.len(), 2);
/// assert_eq!(osm.relations.len(), 0);
///
/// assert_eq!(osm.boundary, Some(Boundary::new((1.0, 1.0), (10.0, 10.0))));
/// ```
///
/// [`Osm`]: struct.Osm.html
/// [`osm_io`]: osm_io/index.html
pub struct OsmBuilder {
    osm: Osm,
}

/// Abstract representation of an OSM map.
///
/// An OSM map contains a boundary, nodes, ways and relations. See the OSM documentation over
/// ['Elements'] for an overview of what each element represents.
///
/// The [`osm`] (xml) and [`o5m`] formats have a very similar structure which corresponds to this
/// struct.
///
/// To build an OSM map, you probably want to read it from file (see [`osm_io`]) or use the
/// [`OsmBuilder`].
///
/// [`osm`]: https://wiki.openstreetmap.org/wiki/OSM_XML
/// [`o5m`]: https://wiki.openstreetmap.org/wiki/O5m
/// [`pbf`]: https://wiki.openstreetmap.org/wiki/O5m
/// [`Elements`]: https://wiki.openstreetmap.org/wiki/Elements
/// [`osm_io`]: osm_io/index.html
/// [`OsmBuilder`]: struct.OsmBuilder.html
#[derive(Debug)]
pub struct Osm {
    pub boundary: Option<Boundary>,
    pub nodes: Vec<Node>,
    pub ways: Vec<Way>,
    pub relations: Vec<Relation>,
    max_id: i64,
    node_id_index: HashMap<Coordinate, i64>,
}

impl OsmBuilder {
    pub fn build(self) -> Osm {
        self.osm
    }

    pub fn add_point<C: Into<Coordinate>, T: Into<Tag>>(&mut self, coordinate: C, tags: Vec<T>) {
        let tags = tags.into_iter().map(T::into).collect();
        self.add_node(coordinate.into(), tags);
    }

    /// First part is the outer polygon, rest of the parts is inner polygons.
    /// `parts` must not be empty or a panic will occur.
    pub fn add_polygon<C, T>(&mut self, mut parts: Vec<Vec<C>>, tags: Vec<T>)
    where
        C: Into<Coordinate>,
        T: Into<Tag>,
    {
        if parts.len() == 1 {
            self.add_polyline(parts.pop().unwrap(), tags);
        } else {
            self.add_multipolygon(parts, tags);
        }
    }

    pub fn add_polyline<C, T>(&mut self, coordinates: Vec<C>, tags: Vec<T>) -> i64
    where
        C: Into<Coordinate>,
        T: Into<Tag>,
    {
        let refs = self.add_nodes(coordinates);
        let id = self.next_id();
        let meta = Meta {
            tags: tags.into_iter().map(|t| t.into()).collect(),
            ..Default::default()
        };
        self.osm.add_way(Way { id, refs, meta });
        id
    }

    fn add_multipolygon<C, T>(&mut self, parts: Vec<Vec<C>>, tags: Vec<T>)
    where
        C: Into<Coordinate>,
        T: Into<Tag>,
    {
        let mut polygon_ids = Vec::new();
        for part in parts {
            let refs = self.add_nodes(part);
            let id = self.next_id();
            let meta = Meta::default();
            self.osm.add_way(Way { id, refs, meta });
            polygon_ids.push(id);
        }

        let mut tags: Vec<Tag> = tags.into_iter().map(T::into).collect();
        tags.push(("type", "multipolygon").into());

        let (outer, inner) = polygon_ids.split_first().unwrap();
        self.add_polygon_relations(*outer, inner, tags);
    }

    fn add_polygon_relations(&mut self, outer: i64, inner: &[i64], tags: Vec<Tag>) {
        let mut members = Vec::new();
        for rel_ref in inner {
            members.push(RelationMember::Way(*rel_ref, "inner".to_owned()));
        }

        members.push(RelationMember::Way(outer, "outer".to_owned()));
        let id = self.next_id();

        let meta = Meta {
            tags: tags.into_iter().map(|t| t).collect(),
            ..Default::default()
        };
        self.osm.add_relation(Relation { id, members, meta });
    }

    fn add_nodes<C: Into<Coordinate>>(&mut self, coordinates: Vec<C>) -> Vec<i64> {
        coordinates
            .into_iter()
            .map(|c| self.add_node(c.into(), vec![]))
            .collect()
    }

    fn add_node(&mut self, coordinate: Coordinate, tags: Vec<Tag>) -> i64 {
        if let Some(id) = self.osm.find_node_id(coordinate) {
            return id;
        }

        let id = self.osm.max_id + 1;
        let meta = Meta {
            tags,
            ..Default::default()
        };
        self.osm.add_node(Node {
            id,
            coordinate,
            meta,
        });
        id
    }

    fn next_id(&mut self) -> i64 {
        self.osm.max_id += 1;
        self.osm.max_id
    }
}

impl Default for OsmBuilder {
    fn default() -> Self {
        OsmBuilder {
            osm: Osm::default(),
        }
    }
}

impl Osm {
    /// Add a node to the map, the boundary is expanded to include the node.
    pub fn add_node(&mut self, node: Node) {
        if let Some(boundary) = &mut self.boundary {
            boundary.expand(node.coordinate);
        }

        self.max_id = max(self.max_id, node.id);
        self.node_id_index.insert(node.coordinate.clone(), node.id);
        self.nodes.push(node);
    }

    /// Add a way to the map.
    pub fn add_way(&mut self, way: Way) {
        self.ways.push(way);
    }

    pub fn add_relation(&mut self, relation: Relation) {
        self.relations.push(relation);
    }

    /// Find node id in an osm map by coordinate.
    pub fn find_node_id(&mut self, coordinate: Coordinate) -> Option<i64> {
        self.node_id_index.get(&coordinate).cloned()
    }
}

impl Default for Osm {
    fn default() -> Self {
        Osm {
            boundary: Some(Boundary::inverted()),
            nodes: Vec::new(),
            ways: Vec::new(),
            relations: Vec::new(),
            max_id: 0,
            node_id_index: HashMap::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::geo::Boundary;
    use crate::{Meta, Node, Osm};

    #[test]
    fn osm_add_node() {
        let mut osm = Osm::default();
        assert_eq!(osm.max_id, 0);

        osm.add_node(Node {
            id: 10,
            coordinate: (65.0, 55.0).into(),
            meta: Meta::default(),
        });

        let expected_boundary = Boundary {
            min: (65.0, 55.0).into(),
            max: (65.0, 55.0).into(),
            freeze: false,
        };
        assert_eq!(osm.max_id, 10);
        assert_eq!(osm.boundary, Some(expected_boundary));
    }
}