open_vector_tile/mapbox/
vector_feature.rs

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