gistools/readers/osm/
relation.rs

1use super::{
2    OSMFilterable, OSMReader, OSMTagFilterType,
3    info::{Info, InfoBlock},
4    node::IntermediateNode,
5    primitive::{OSMMetadata, PrimitiveBlock},
6    way::{IntermediateWay, WayNodes},
7};
8use crate::{data_store::kv::KVStore, parsers::Reader};
9use alloc::{string::String, vec, vec::Vec};
10use pbf::{BitCast, ProtoRead, Protobuf};
11use s2json::{
12    BBox3D, MValue, Properties, VectorFeature, VectorFeatureType, VectorGeometry, VectorLineString,
13    VectorMultiLineString, VectorMultiPolygon, VectorPoint, VectorPolygon,
14};
15use serde::{Deserialize, Serialize};
16
17/// An intermediate vector feature where the way nodes haven't been resolved yet.
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19pub enum IntermediateMember {
20    /// A node member
21    Node(IntermediateNodeMember),
22    /// A way member
23    Way(IntermediateWayMember),
24}
25/// An intermediate vector feature where the way nodes haven't been resolved yet.
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
27pub struct IntermediateNodeMember {
28    /// The role of the node relative to the relation
29    pub role: String,
30    /// The node's id
31    pub node_id: u64,
32}
33/// An intermediate vector feature where the way nodes haven't been resolved yet.
34#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
35pub struct IntermediateWayMember {
36    /// the role of the way relative to the relation
37    pub role: String,
38    /// The way's id
39    pub way_id: u64,
40}
41
42/// An intermediate vector feature where the ways and nodes haven't been resolved yet.
43#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
44pub struct IntermediateRelation {
45    /// The relation's id
46    pub id: u64,
47    /// The relation's properties
48    pub properties: Properties,
49    /// The relation's members
50    pub members: Vec<IntermediateMember>,
51    /// The relation's info block
52    pub info: Option<InfoBlock>,
53}
54impl IntermediateRelation {
55    /// Convert the node to a vector feature
56    pub fn to_vector_feature<_N: KVStore<u64, VectorPoint<MValue>>, _W: KVStore<u64, WayNodes>>(
57        &self,
58        node_geometry: &_N,
59        way_geometry: &_W,
60        add_bbox: bool,
61    ) -> Option<VectorFeature<OSMMetadata, Properties, MValue>> {
62        let mut bbox = BBox3D::default();
63        let IntermediateRelation { id, members, properties, info } = &self;
64        let i_nodes: Vec<IntermediateNodeMember> = members
65            .iter()
66            .filter_map(|m| {
67                if let IntermediateMember::Node(node) = m { Some(node.clone()) } else { None }
68            })
69            .collect();
70        let mut nodes: Vec<NodeMember> = vec![];
71        for IntermediateNodeMember { role, node_id, .. } in &i_nodes {
72            let n = node_geometry.get(*node_id);
73            if let Some(n) = n {
74                nodes.push(NodeMember { id: *node_id, role: role.into(), node: n.clone() });
75            }
76        }
77        let i_ways: Vec<IntermediateWayMember> = members
78            .iter()
79            .filter_map(
80                |m| if let IntermediateMember::Way(way) = m { Some(way.clone()) } else { None },
81            )
82            .collect();
83        let mut ways: Vec<WayMember> = vec![];
84        for IntermediateWayMember { role, way_id } in &i_ways {
85            let w = way_geometry.get(*way_id);
86            if let Some(w) = w {
87                let mut mapped_w: VectorLineString<MValue> = vec![];
88                for node_id in w {
89                    let n = node_geometry.get(*node_id);
90                    if let Some(n) = n {
91                        if add_bbox {
92                            bbox.extend_from_point(n);
93                        }
94                        mapped_w.push(n.clone());
95                    }
96                }
97                ways.push(WayMember { id: *way_id, role: role.into(), way: mapped_w });
98            }
99        }
100        let geo = build_geometry(&mut ways);
101        geo.as_ref()?;
102
103        let mut relation_geo = geo.unwrap();
104        let bbox = if add_bbox { Some(bbox) } else { None };
105        let geometry: VectorGeometry<MValue> = match &mut relation_geo {
106            RelationGeometry::Lines(lines) => {
107                if lines.len() == 1 {
108                    VectorGeometry::new_linestring(core::mem::take(&mut lines[0]), bbox)
109                } else {
110                    VectorGeometry::new_multilinestring(core::mem::take(lines), bbox)
111                }
112            }
113            RelationGeometry::Area(area) => {
114                if area.len() == 1 {
115                    VectorGeometry::new_polygon(core::mem::take(&mut area[0]), bbox)
116                } else {
117                    VectorGeometry::new_multipolygon(core::mem::take(area), bbox)
118                }
119            }
120        };
121        Some(VectorFeature {
122            id: Some(*id),
123            face: 0.into(),
124            _type: VectorFeatureType::VectorFeature,
125            properties: properties.clone(),
126            geometry,
127            metadata: Some(OSMMetadata {
128                osm_type: MemberType::Relation,
129                info: info.clone(),
130                nodes: Some(i_nodes),
131                relation: None,
132            }),
133        })
134    }
135}
136
137/// Member Options. Relations is skipped as it is not supported / has no use.
138#[derive(Debug)]
139pub enum Member {
140    /// Node Member
141    Node(NodeMember),
142    /// Way Member
143    Way(WayMember),
144}
145/// Node Member
146#[derive(Debug)]
147pub struct NodeMember {
148    /// The node's id
149    pub id: u64,
150    /// The role of the node relative to the relation
151    pub role: String,
152    /// The node geometry
153    pub node: VectorPoint<MValue>,
154}
155/// Way Member
156#[derive(Debug)]
157pub struct WayMember {
158    /// The way's id
159    pub id: u64,
160    /// The role of the way relative to the relation
161    pub role: String,
162    /// The way geometry
163    pub way: VectorLineString<MValue>,
164}
165
166/// Relation coordinates from ways with information about node relations.
167#[derive(Debug)]
168pub enum RelationGeometry {
169    /// Lines
170    Lines(VectorMultiLineString<MValue>),
171    /// Area
172    Area(VectorMultiPolygon<MValue>),
173}
174
175/// The expected metadata in the VectorFeature for all types (node, way, relation)
176#[derive(Debug, Default, Copy, Clone, PartialEq, BitCast)]
177pub enum MemberType {
178    /// Node type
179    #[default]
180    Node = 0,
181    /// Way type (lines and polygons)
182    Way = 1,
183    /// Relation type (collection of nodes, ways and relations)
184    Relation = 2,
185}
186
187/// Relation class contains a collection of nodes, ways and relations as members.
188#[derive(Debug, Default, PartialEq)]
189pub struct Relation {
190    /// The relation's id
191    pub id: u64,
192    info: Option<Info>,
193    // Parallel arrays
194    keys: Vec<u32>,
195    vals: Vec<u32>,
196    roles_sid: Vec<i32>, /* This should have been defined as uint32 for consistency, but it is now too late to change it */
197    memids: Vec<i64>,    // DELTA encoded
198    types: Vec<MemberType>,
199}
200impl Relation {
201    /// Get the properties of the node
202    pub fn properties(&self, pb: &PrimitiveBlock) -> Properties {
203        pb.tags(&self.keys, &self.vals)
204    }
205
206    /// Each member can be node, way or relation.
207    pub fn members(&self, pb: &PrimitiveBlock) -> Vec<IntermediateMember> {
208        let mut res = vec![];
209        let mut memid = 0;
210        for i in 0..self.memids.len() {
211            memid += self.memids[i];
212            let role = pb.get_string(self.roles_sid[i] as usize);
213            let cur_type = self.types[i];
214            if cur_type == MemberType::Node {
215                res.push(IntermediateMember::Node(IntermediateNodeMember {
216                    role: role.into(),
217                    node_id: memid as u64,
218                }));
219            } else if cur_type == MemberType::Way {
220                res.push(IntermediateMember::Way(IntermediateWayMember {
221                    role: role.into(),
222                    way_id: memid as u64,
223                }));
224            } else {
225                // Relation -> no-op
226            }
227        }
228        res
229    }
230
231    /// returns the feature in intermediate format to build later
232    pub fn to_intermediate_feature(&self, pb: &PrimitiveBlock) -> Option<IntermediateRelation> {
233        let members = self.members(pb);
234        if members.is_empty() {
235            None
236        } else {
237            Some(IntermediateRelation {
238                id: self.id,
239                properties: self.properties(pb),
240                members,
241                info: self.info.as_ref().map(|info| info.to_block(pb)),
242            })
243        }
244    }
245
246    /// Get the node relation pairs
247    pub fn get_node_relation_pairs(members: &[IntermediateMember]) -> Vec<IntermediateNodeMember> {
248        let mut res = vec![];
249        for member in members {
250            if let IntermediateMember::Node(member) = member
251                && (member.role == "label" || member.role == "admin_centre")
252            {
253                res.push(member.clone());
254            }
255        }
256        res
257    }
258}
259impl OSMFilterable for Relation {
260    fn is_filterable<
261        T: Reader,
262        _N: KVStore<u64, VectorPoint<MValue>>,
263        N: KVStore<u64, IntermediateNode>,
264        _W: KVStore<u64, WayNodes>,
265        W: KVStore<u64, IntermediateWay>,
266        R: KVStore<u64, IntermediateRelation>,
267    >(
268        &self,
269        pb: &PrimitiveBlock,
270        reader: &mut OSMReader<T, _N, N, _W, W, R>,
271    ) -> bool {
272        if reader.skip_relations {
273            return true;
274        }
275        if let Some(tag_filter) = &mut reader.tag_filter {
276            for i in 0..self.keys.len() {
277                let key_str = pb.get_string(self.keys[i] as usize);
278                let val_str = pb.get_string(self.vals[i] as usize);
279                if tag_filter.match_found(OSMTagFilterType::Relation, key_str, val_str) {
280                    return false;
281                }
282            }
283            // if we make it here, we didn't find any matching tags
284            return true;
285        }
286        false
287    }
288}
289/// Read in the contents of the relation
290impl ProtoRead for Relation {
291    fn read(&mut self, tag: u64, pb: &mut Protobuf) {
292        match tag {
293            1 => self.id = pb.read_varint(),
294            2 => self.keys = pb.read_packed(),
295            3 => self.vals = pb.read_packed(),
296            4 => {
297                let mut info = Info::default();
298                pb.read_message(&mut info);
299                self.info = Some(info);
300            }
301            8 => self.roles_sid = pb.read_packed(),
302            9 => self.memids = pb.read_s_packed(),
303            10 => self.types = pb.read_packed(),
304            _ => panic!("unknown tag {}", tag),
305        }
306    }
307}
308
309/// Given a group of Members whose type is "way", build a multilinestring or multipolygon Feature.
310/// If the ways include an 'outer' or 'inner', then we know its an area, otherwise its a line.
311///
312/// ## Parameters
313/// - `ways`: a vector of ways that make up the relation
314///
315/// ## Returns
316/// [`RelationGeometry`] that is either Lines or Area if applicable
317fn build_geometry(ways: &mut [WayMember]) -> Option<RelationGeometry> {
318    // prep variables
319    let mut polygons: VectorMultiPolygon<MValue> = vec![];
320    let mut current_polygon: VectorPolygon<MValue> = vec![];
321    let mut current_ring: VectorLineString<MValue> = vec![];
322    let is_area = ways.iter().any(|m| m.role == "outer" || m.role == "inner");
323
324    // prepare step: members are stored out of order
325    sort_members(ways);
326
327    for member in ways {
328        // Using "isClockwise", depending on whether the ring is outer or inner,
329        // we may need to reverse the order of the points. Every time we find the
330        // first and last point are the same, close out the ring, add it to the current
331        // polygon, and start a new ring. if the current polygon is NOT empty, we store
332        // it in the polygons list and start a new one before adding the completed ring.
333        // NOTE: Due to the nature of OSM data, it is possible that resulting ring is reversed.
334        // Check against the current ring to see if the way needs to be edited.
335        //
336        // grab the geometry from the member and store in current ring, checking current rings order
337        if current_ring.is_empty() {
338            current_ring.extend_from_slice(&member.way);
339        } else {
340            current_ring.extend_from_slice(&member.way[1..]);
341        }
342
343        // if current rings first and last point are the same, close out the ring
344        if current_ring.first() == current_ring.last() {
345            // add the ring to the current polygon. If member role is outer and
346            // current_polygon already has data, we need to store the current poly and
347            // start a new polygon.
348            // If the member role is inner, we can add the ring to the
349            // current polygon.
350            if member.role == "outer" && !current_polygon.is_empty() {
351                polygons.push(current_polygon);
352                current_polygon = vec![];
353            }
354            current_polygon.push(current_ring);
355            current_ring = vec![];
356        }
357    }
358
359    // Last step is to build:
360    // flush ring if it exists
361    if !current_ring.is_empty() {
362        current_polygon.push(current_ring);
363    }
364    //   if (!is_area) return { type: 0, coordinates: current_polygon };
365    if !is_area {
366        return Some(RelationGeometry::Lines(current_polygon));
367    }
368    // flush the current polygon if it exists
369    if !current_polygon.is_empty() {
370        polygons.push(current_polygon);
371    }
372    // grab the polys and return a feature
373    Some(RelationGeometry::Area(polygons))
374}
375
376/// osm throws relation members out of order, so we need to not only sort them
377/// but also check if the first and last points of each way follow the same direction.
378///
379/// ## Parameters
380/// - `members`: the ways to be sorted
381fn sort_members(members: &mut [WayMember]) {
382    let len = members.len();
383    if len < 3 {
384        return;
385    }
386    for i in 0..len - 1 {
387        let cur_first_point = &members[i].way[0];
388        let cur_last_point = &members[i].way[members[i].way.len() - 1];
389        // if current way is already self closing break
390        if cur_first_point == cur_last_point {
391            break;
392        }
393        for j in i + 1..len {
394            let next_first_point = &members[j].way[0];
395            let next_last_point = &members[j].way[members[j].way.len() - 1];
396            // if we find a match between any of the points, swap the member positions
397            // if cur_first_point == next_first_point or cur_last_point == next_last_point
398            // swap the order
399            let equal_first = cur_first_point == next_first_point;
400            let equal_last = cur_last_point == next_last_point;
401            let equal_first_last = cur_first_point == next_last_point;
402            let equal_last_first = cur_last_point == next_first_point;
403            if equal_first || equal_last || equal_first_last || equal_last_first {
404                if equal_first {
405                    members[i].way.reverse();
406                } else if equal_last {
407                    members[j].way.reverse();
408                } else if equal_first_last {
409                    members[i].way.reverse();
410                    members[j].way.reverse();
411                }
412                // we want to move the found member to be next to the current member
413                if i + 1 != j {
414                    members.swap(i + 1, j);
415                }
416                break;
417            }
418        }
419    }
420}