ovtile/mapbox/
vector_feature.rs

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