open_vector_tile/mapbox/
vector_feature.rs

1use crate::{
2    base::{BaseVectorFeature, TessellationWrapper},
3    command_encode,
4    open::FeatureType as OpenFeatureType,
5    zigzag, Point, VectorFeatureMethods, VectorGeometry, VectorLineWithOffset,
6    VectorLines3DWithOffset, VectorLinesWithOffset, VectorPoints, VectorPoints3D,
7};
8use alloc::{collections::BTreeMap, rc::Rc, string::String, vec, vec::Vec};
9use core::cell::RefCell;
10use pbf::{BitCast, ProtoRead, Protobuf};
11use s2json::{MapboxProperties, PrimitiveValue, Properties, BBOX};
12
13/// Mapbox specification for a Feature
14#[derive(Debug)]
15pub struct MapboxVectorFeature {
16    /// the id of the feature
17    pub id: Option<u64>,
18    /// the version of the vector tile
19    pub version: u16,
20    /// the properties
21    pub properties: MapboxProperties,
22    /// the extent
23    pub extent: usize,
24    /// the feature type
25    pub r#type: FeatureType,
26    /// whether the feature is using the S2 spec. This isn't used by most tooling and was replaced by
27    /// the open spec
28    pub is_s2: bool,
29    indices_index: Option<usize>,
30    indices: Option<Vec<u32>>,
31    geometry_index: usize,
32    geometry: Option<VectorGeometry>,
33    tessellation_index: Option<usize>,
34    keys: Rc<RefCell<Vec<String>>>,
35    values: Rc<RefCell<Vec<PrimitiveValue>>>,
36    pbf: Rc<RefCell<Protobuf>>,
37}
38impl MapboxVectorFeature {
39    /// Create a new MapboxVectorFeature
40    pub fn new(
41        pbf: Rc<RefCell<Protobuf>>,
42        is_s2: bool,
43        extent: usize,
44        version: u16,
45        keys: Rc<RefCell<Vec<String>>>,
46        values: Rc<RefCell<Vec<PrimitiveValue>>>,
47    ) -> MapboxVectorFeature {
48        MapboxVectorFeature {
49            id: None,
50            version,
51            properties: MapboxProperties::new(),
52            extent,
53            r#type: FeatureType::Point,
54            is_s2,
55            // tmp pbf until after reading in attributes
56            indices_index: None,
57            indices: None,
58            geometry_index: 0,
59            geometry: None,
60            tessellation_index: None,
61            keys,
62            values,
63            pbf,
64        }
65    }
66}
67impl VectorFeatureMethods for MapboxVectorFeature {
68    /// get the feature id
69    fn id(&self) -> Option<u64> {
70        self.id
71    }
72
73    /// get the feature version
74    fn version(&self) -> u16 {
75        self.version
76    }
77
78    /// get the feature properties
79    fn properties(&self) -> Properties {
80        (&self.properties).into()
81    }
82
83    /// get the feature extent
84    fn extent(&self) -> usize {
85        self.extent
86    }
87
88    /// get the feature type
89    fn get_type(&self) -> OpenFeatureType {
90        (&self.r#type).into()
91    }
92
93    /// get the bbox
94    fn bbox(&self) -> Option<BBOX> {
95        None
96    }
97
98    /// whether the feature has m values
99    fn has_m_values(&self) -> bool {
100        false
101    }
102
103    /// whether the feature is a points type
104    fn is_points(&self) -> bool {
105        self.r#type == FeatureType::Point
106    }
107
108    /// whether the feature is a line type
109    fn is_lines(&self) -> bool {
110        self.r#type == FeatureType::Line
111    }
112
113    /// whether the feature is a polygon type
114    fn is_polygons(&self) -> bool {
115        self.r#type == FeatureType::Polygon || self.r#type == FeatureType::MultiPolygon
116    }
117
118    /// whether the feature is a points 3D type
119    fn is_points_3d(&self) -> bool {
120        false
121    }
122
123    /// whether the feature is a line 3D type
124    fn is_lines_3d(&self) -> bool {
125        false
126    }
127
128    /// whether the feature is a polygon 3D type
129    fn is_polygons_3d(&self) -> bool {
130        false
131    }
132
133    /// regardless of the type, we return a flattend point array
134    fn load_points(&mut self) -> VectorPoints {
135        match self.load_geometry() {
136            VectorGeometry::VectorPoints(p) => p,
137            VectorGeometry::VectorLines(lines) => {
138                lines.iter().flat_map(|p| p.geometry.clone()).collect()
139            }
140            VectorGeometry::VectorPolys(polys) => polys
141                .iter()
142                .flat_map(|p| p.iter().flat_map(|p| p.geometry[..p.geometry.len() - 1].to_vec()))
143                .collect(),
144            #[tarpaulin::skip]
145            _ => panic!("unexpected geometry type"),
146        }
147    }
148
149    #[tarpaulin::skip]
150    fn load_points_3d(&mut self) -> VectorPoints3D {
151        panic!("unexpected geometry type")
152    }
153
154    /// an array of lines. The offsets will be set to 0
155    fn load_lines(&mut self) -> VectorLinesWithOffset {
156        match self.load_geometry() {
157            VectorGeometry::VectorLines(lines) => lines,
158            VectorGeometry::VectorPolys(polys) => polys.iter().flat_map(|p| p.clone()).collect(),
159            #[tarpaulin::skip]
160            _ => panic!("unexpected geometry type"),
161        }
162    }
163
164    /// an array of 3D lines. The offsets will be set to 0
165    #[tarpaulin::skip]
166    fn load_lines_3d(&mut self) -> VectorLines3DWithOffset {
167        panic!("unexpected geometry type")
168    }
169
170    /// (flattened geometry & tesslation if applicable, indices)
171    fn load_geometry_flat(&mut self) -> (Vec<f64>, Vec<u32>) {
172        // build a multiplier
173        let multiplier: f64 = 1.0 / self.extent as f64;
174        // grab the geometry, flatten it, and mutate to an f64
175        let mut geometry: Vec<f64> = match self.load_geometry() {
176            VectorGeometry::VectorPolys(polys) => polys
177                .iter()
178                .flat_map(|p| {
179                    p.iter().flat_map(|p| {
180                        p.geometry
181                            .clone()
182                            .into_iter()
183                            .flat_map(|p| vec![p.x as f64 * multiplier, p.y as f64 * multiplier])
184                    })
185                })
186                .collect(),
187            #[tarpaulin::skip]
188            _ => panic!("unexpected geometry type"),
189        };
190        // if a poly, check if we should load indices
191        let indices = self.read_indices();
192        // if a poly, check if we should load tessellation
193        self.add_tessellation(&mut geometry, multiplier);
194
195        (geometry, indices)
196    }
197
198    /// load the geometry
199    fn load_geometry(&mut self) -> VectorGeometry {
200        if let Some(geometry) = &self.geometry {
201            return geometry.clone();
202        }
203
204        let mut pbf = self.pbf.borrow_mut();
205        pbf.set_pos(self.geometry_index);
206
207        let end: usize = pbf.read_varint::<usize>() + pbf.get_pos();
208        let mut cmd: usize = 1;
209        let mut length: isize = 0;
210        let mut x: i32 = 0;
211        let mut y: i32 = 0;
212
213        let mut points: VectorPoints = vec![];
214        let mut lines: VectorLinesWithOffset = vec![];
215        let mut polys: Vec<VectorLinesWithOffset> = vec![];
216
217        while pbf.get_pos() < end {
218            if length <= 0 {
219                let cmd_len: usize = pbf.read_varint();
220                cmd = cmd_len & 0x7;
221                length = (cmd_len >> 3) as isize;
222            }
223
224            length -= 1;
225
226            if cmd == 1 || cmd == 2 {
227                x += pbf.read_s_varint::<i32>();
228                y += pbf.read_s_varint::<i32>();
229
230                if cmd == 1 {
231                    // moveTo
232                    if !points.is_empty() && self.r#type != FeatureType::Point {
233                        lines.push((&points[..]).into());
234                        points = vec![];
235                    }
236                }
237                points.push(Point::new(x, y));
238            } else if cmd == 4 {
239                // close poly
240                if !points.is_empty() {
241                    lines.push((&points[..]).into());
242                }
243                polys.push(lines);
244                lines = vec![];
245                points = vec![];
246            } else if cmd == 7 {
247                // close path
248                if !points.is_empty() {
249                    points.push(points[0].clone());
250                    lines.push((&points[..]).into());
251                    points = vec![];
252                }
253            } else {
254                #[tarpaulin::skip]
255                panic!("unknown cmd: {}", cmd);
256            }
257        }
258
259        let geometry = if self.r#type == FeatureType::Point {
260            VectorGeometry::VectorPoints(points)
261        } else {
262            if !points.is_empty() {
263                lines.push(VectorLineWithOffset::new(0.0, points.clone()));
264            }
265            if self.r#type == FeatureType::Line {
266                VectorGeometry::VectorLines(lines)
267            } else if (self.r#type == FeatureType::MultiPolygon
268                || self.r#type == FeatureType::Polygon)
269                && !self.is_s2
270            {
271                VectorGeometry::VectorPolys(classify_rings(&lines))
272            } else {
273                VectorGeometry::VectorPolys(polys)
274            }
275        };
276
277        self.geometry = Some(geometry.clone());
278        geometry
279    }
280
281    /// load the indices
282    fn read_indices(&mut self) -> Vec<u32> {
283        if let Some(indices) = &self.indices {
284            return indices.clone();
285        } else if self.indices_index.is_none() {
286            return vec![];
287        }
288
289        let mut pbf = self.pbf.borrow_mut();
290        pbf.set_pos(self.indices_index.unwrap());
291
292        let mut curr: i32 = 0;
293        let end = pbf.read_varint::<usize>() + pbf.get_pos();
294        // build indices
295        let mut indices: Vec<u32> = vec![];
296        while pbf.get_pos() < end {
297            curr += pbf.read_s_varint::<i32>();
298            indices.push(curr as u32);
299        }
300
301        self.indices = Some(indices.clone());
302        indices
303    }
304
305    /// Add tessellation data to the geometry
306    fn add_tessellation(&mut self, geometry: &mut Vec<f64>, multiplier: f64) {
307        if self.tessellation_index.is_none() {
308            return;
309        }
310
311        let mut pbf = self.pbf.borrow_mut();
312        pbf.set_pos(self.tessellation_index.unwrap());
313
314        let end = pbf.read_varint::<usize>() + pbf.get_pos();
315        let mut x = 0;
316        let mut y = 0;
317        while pbf.get_pos() < end {
318            x += pbf.read_s_varint::<i32>();
319            y += pbf.read_s_varint::<i32>();
320            geometry.push(x as f64 * multiplier);
321            geometry.push(y as f64 * multiplier);
322        }
323    }
324
325    /// Add 3D tessellation data to the geometry
326    #[tarpaulin::skip]
327    fn add_tessellation_3d(&mut self, _geometry: &mut Vec<f64>, _multiplier: f64) {
328        panic!("unexpected geometry type")
329    }
330}
331impl ProtoRead for MapboxVectorFeature {
332    fn read(&mut self, tag: u64, pb: &mut Protobuf) {
333        if self.is_s2 {
334            match tag {
335                15 => self.id = Some(pb.read_varint::<u64>()),
336                1 => {
337                    let end = pb.get_pos() + pb.read_varint::<usize>();
338
339                    while pb.get_pos() < end {
340                        let key = &self.keys.borrow()[pb.read_varint::<usize>()];
341                        let value = &self.values.borrow()[pb.read_varint::<usize>()];
342
343                        self.properties.insert(key.clone(), value.clone());
344                    }
345                }
346                2 => self.r#type = pb.read_varint::<FeatureType>(),
347                3 => self.geometry_index = pb.get_pos(),
348                4 => self.indices_index = Some(pb.get_pos()),
349                5 => self.tessellation_index = Some(pb.get_pos()),
350                #[tarpaulin::skip]
351                _ => panic!("unknown tag: {}", tag),
352            }
353        } else {
354            match tag {
355                1 => self.id = Some(pb.read_varint::<u64>()),
356                2 => {
357                    let end = pb.get_pos() + pb.read_varint::<usize>();
358
359                    while pb.get_pos() < end {
360                        let key = &self.keys.borrow()[pb.read_varint::<usize>()];
361                        let value = &self.values.borrow()[pb.read_varint::<usize>()];
362
363                        self.properties.insert(key.clone(), value.clone());
364                    }
365                }
366                3 => self.r#type = pb.read_varint::<FeatureType>(),
367                4 => self.geometry_index = pb.get_pos(),
368                5 => self.indices_index = Some(pb.get_pos()),
369                6 => self.tessellation_index = Some(pb.get_pos()),
370                #[tarpaulin::skip]
371                _ => panic!("unknown tag: {}", tag),
372            }
373        }
374    }
375}
376
377fn classify_rings(rings: &VectorLinesWithOffset) -> Vec<VectorLinesWithOffset> {
378    let mut polygons: Vec<VectorLinesWithOffset> = vec![];
379    let mut polygon: VectorLinesWithOffset = vec![];
380    let mut ccw: Option<bool> = None;
381
382    let mut i: usize = 0;
383    while i < rings.len() {
384        let area = signed_area(&rings[i].geometry);
385        if area == 0 {
386            continue;
387        }
388        if ccw.is_none() {
389            ccw = Some(area < 0);
390        }
391
392        if ccw.is_some() && ccw.unwrap() == (area < 0) {
393            // outer poly ring
394            if !polygon.is_empty() {
395                polygons.push(polygon.clone());
396                polygon = vec![];
397            }
398            polygon.push(rings[i].clone());
399        } else {
400            // inner poly ring (hole)
401            polygon.push(rings[i].clone());
402        }
403
404        i += 1
405    }
406    if !polygon.is_empty() {
407        polygons.push(polygon.clone());
408    }
409
410    polygons
411}
412
413fn signed_area(ring: &[Point]) -> i32 {
414    let mut sum: i32 = 0;
415    let mut i: usize = 0;
416    let mut j = ring.len() - 1;
417    while i < ring.len() {
418        let p1 = &ring[i];
419        let p2 = &ring[j];
420        sum += (p2.x - p1.x) * (p1.y + p2.y);
421
422        j = i;
423        i += 1;
424    }
425
426    sum
427}
428
429/// Mapbox Vector Feature types.
430#[derive(Debug, Clone, PartialEq)]
431pub enum FeatureType {
432    /// Point Feature
433    Point = 1,
434    /// Line Feature
435    Line = 2,
436    /// Polygon Feature
437    Polygon = 3,
438    /// MultiPolygon Feature
439    MultiPolygon = 4,
440}
441impl From<OpenFeatureType> for FeatureType {
442    fn from(value: OpenFeatureType) -> Self {
443        match value {
444            OpenFeatureType::Points => FeatureType::Point,
445            OpenFeatureType::Lines => FeatureType::Line,
446            OpenFeatureType::Polygons => FeatureType::MultiPolygon,
447            #[tarpaulin::skip]
448            _ => panic!("unknown value: {:?}", value),
449        }
450    }
451}
452impl BitCast for FeatureType {
453    fn to_u64(&self) -> u64 {
454        (*self).clone() as u64
455    }
456    fn from_u64(value: u64) -> Self {
457        match value {
458            1 => FeatureType::Point,
459            2 => FeatureType::Line,
460            3 => FeatureType::Polygon,
461            4 => FeatureType::MultiPolygon,
462            #[tarpaulin::skip]
463            _ => panic!("unknown value: {}", value),
464        }
465    }
466}
467
468/// Write a feature to a protobuffer using the S2 Specification
469pub fn write_feature(
470    feature: &BaseVectorFeature,
471    keys: &mut BTreeMap<String, usize>,
472    values: &mut BTreeMap<PrimitiveValue, usize>,
473    mapbox_support: bool,
474) -> Vec<u8> {
475    let mut pbf = Protobuf::new();
476
477    let properties: MapboxProperties = feature.properties().clone().into();
478    if let Some(id) = feature.id() {
479        pbf.write_varint_field(if mapbox_support { 1 } else { 15 }, id);
480    }
481    pbf.write_bytes_field(
482        if mapbox_support { 2 } else { 1 },
483        &write_properties(&properties, keys, values),
484    );
485    let _type: FeatureType = feature.get_type().into();
486    pbf.write_varint_field(if mapbox_support { 3 } else { 2 }, _type);
487    // Geometry
488    let written = write_geometry(feature, mapbox_support);
489    pbf.write_bytes_field(if mapbox_support { 4 } else { 3 }, &written);
490    // Indices
491    if let Some(indices) = feature.indices() {
492        pbf.write_bytes_field(if mapbox_support { 5 } else { 4 }, &write_indices(&indices));
493    }
494    // Tessellation
495    if let Some(TessellationWrapper::Tessellation(tess)) = feature.tessellation() {
496        pbf.write_bytes_field(if mapbox_support { 6 } else { 5 }, &write_tessellation(&tess));
497    }
498
499    pbf.take()
500}
501
502/// Write a properties to a protobuffer using the S2 Specification
503fn write_properties(
504    properties: &MapboxProperties,
505    keys: &mut BTreeMap<String, usize>,
506    values: &mut BTreeMap<PrimitiveValue, usize>,
507) -> Vec<u8> {
508    let mut pbf = Protobuf::new();
509
510    for (key, value) in properties.iter() {
511        let key_length = keys.len();
512        let key_index = keys.entry(key.clone()).or_insert(key_length);
513        pbf.write_varint(*key_index);
514        let value_length = values.len();
515        let value_index = values.entry(value.clone()).or_insert(value_length);
516        pbf.write_varint(*value_index);
517    }
518
519    pbf.take()
520}
521
522/// write the indices to a protobuffer using the S2 Specification
523fn write_indices(indices: &[u32]) -> Vec<u8> {
524    let mut pbf = Protobuf::new();
525
526    let mut curr: i32 = 0;
527    for index in indices {
528        let d_curr = (*index as i32) - curr;
529        pbf.write_varint(zigzag(d_curr));
530        curr += d_curr;
531    }
532
533    pbf.take()
534}
535
536/// write the tessellation to a protobuffer using the S2 Specification
537fn write_tessellation(geometry: &[Point]) -> Vec<u8> {
538    let mut pbf = Protobuf::new();
539    let mut x = 0;
540    let mut y = 0;
541    for point in geometry {
542        let dx = point.x - x;
543        let dy = point.y - y;
544        pbf.write_varint(zigzag(dx));
545        pbf.write_varint(zigzag(dy));
546        x += dx;
547        y += dy;
548    }
549
550    pbf.take()
551}
552
553/// write the geometry to a protobuffer using the S2 Specification
554fn write_geometry(feature: &BaseVectorFeature, mapbox_support: bool) -> Vec<u8> {
555    use BaseVectorFeature::*;
556    let mut pbf = Protobuf::new();
557    match feature {
558        BaseVectorPointsFeature(points) => write_geometry_points(&points.geometry, &mut pbf),
559        BaseVectorLinesFeature(lines) => write_geometry_lines(&lines.geometry, &mut pbf),
560        BaseVectorPolysFeature(polys) => {
561            write_geometry_polys(&polys.geometry, &mut pbf, mapbox_support)
562        }
563        #[tarpaulin::skip]
564        _ => panic!("unknown feature type: {:?}", feature.get_type()),
565    };
566    pbf.take()
567}
568
569/// write the points geometry to a protobuffer using the S2 Specification
570fn write_geometry_points(points: &[Point], pbf: &mut Protobuf) {
571    let mut x = 0;
572    let mut y = 0;
573
574    for point in points {
575        // move
576        pbf.write_varint(command_encode(1, 1)); // moveto
577                                                // store
578        let dx = point.x - x;
579        let dy = point.y - y;
580        pbf.write_varint(zigzag(dx));
581        pbf.write_varint(zigzag(dy));
582        // update position
583        x += dx;
584        y += dy;
585    }
586}
587
588/// write the lines geometry to a protobuffer using the S2 Specification
589fn write_geometry_lines(lines: &[VectorLineWithOffset], pbf: &mut Protobuf) {
590    let mut x = 0;
591    let mut y = 0;
592
593    for line in lines {
594        let line_geo = &line.geometry;
595        pbf.write_varint(command_encode(1, 1)); // moveto
596                                                // do not write polygon closing path as lineto
597        let line_count = line_geo.len();
598        let mut i = 0;
599        while i < line_count {
600            if i == 1 {
601                pbf.write_varint(command_encode(2, (line_count - 1).try_into().unwrap()));
602                // lineto
603            }
604
605            let point = &line_geo[i];
606            let dx = point.x - x;
607            let dy = point.y - y;
608            pbf.write_varint(zigzag(dx));
609            pbf.write_varint(zigzag(dy));
610            x += dx;
611            y += dy;
612
613            i += 1;
614        }
615    }
616}
617
618/// write the polys geometry to a protobuffer using the S2 Specification
619fn write_geometry_polys(
620    polys: &[Vec<VectorLineWithOffset>],
621    pbf: &mut Protobuf,
622    mapbox_support: bool,
623) {
624    let mut x = 0;
625    let mut y = 0;
626
627    for poly in polys {
628        for ring in poly {
629            let ring_geo = &ring.geometry;
630            pbf.write_varint(command_encode(1, 1)); // moveto
631            let line_count = ring_geo.len() - 1;
632            let mut i = 0;
633            while i < line_count {
634                if i == 1 {
635                    pbf.write_varint(command_encode(2, (line_count - 1).try_into().unwrap()));
636                    // lineto
637                }
638
639                let point = &ring_geo[i];
640                let dx = point.x - x;
641                let dy = point.y - y;
642                pbf.write_varint(zigzag(dx));
643                pbf.write_varint(zigzag(dy));
644                x += dx;
645                y += dy;
646
647                i += 1;
648            }
649            pbf.write_varint(command_encode(7, 1)); // ClosePath
650        }
651        // ClosePolygon (Mapbox does not support so close path if not supported)
652        pbf.write_varint(command_encode(if mapbox_support { 7 } else { 4 }, 1));
653    }
654}