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    /// an array of polys
172    fn load_polys(&mut self) -> Vec<VectorLinesWithOffset> {
173        match self.load_geometry() {
174            VectorGeometry::VectorPolys(polys) => polys,
175            #[tarpaulin::skip]
176            _ => panic!("unexpected geometry type"),
177        }
178    }
179
180    /// an array of 3D polys
181    #[tarpaulin::skip]
182    fn load_polys_3d(&mut self) -> Vec<VectorLines3DWithOffset> {
183        panic!("unexpected geometry type")
184    }
185
186    /// (flattened geometry & tesslation if applicable, indices)
187    fn load_geometry_flat(&mut self) -> (Vec<f64>, Vec<u32>) {
188        // build a multiplier
189        let multiplier: f64 = 1.0 / self.extent as f64;
190        // grab the geometry, flatten it, and mutate to an f64
191        let mut geometry: Vec<f64> = match self.load_geometry() {
192            VectorGeometry::VectorPolys(polys) => polys
193                .iter()
194                .flat_map(|p| {
195                    p.iter().flat_map(|p| {
196                        p.geometry
197                            .clone()
198                            .into_iter()
199                            .flat_map(|p| vec![p.x as f64 * multiplier, p.y as f64 * multiplier])
200                    })
201                })
202                .collect(),
203            #[tarpaulin::skip]
204            _ => panic!("unexpected geometry type"),
205        };
206        // if a poly, check if we should load indices
207        let indices = self.read_indices();
208        // if a poly, check if we should load tessellation
209        self.add_tessellation(&mut geometry, multiplier);
210
211        (geometry, indices)
212    }
213
214    /// load the geometry
215    fn load_geometry(&mut self) -> VectorGeometry {
216        if let Some(geometry) = &self.geometry {
217            return geometry.clone();
218        }
219
220        let mut pbf = self.pbf.borrow_mut();
221        pbf.set_pos(self.geometry_index);
222
223        let end: usize = pbf.read_varint::<usize>() + pbf.get_pos();
224        let mut cmd: usize = 1;
225        let mut length: isize = 0;
226        let mut x: i32 = 0;
227        let mut y: i32 = 0;
228
229        let mut points: VectorPoints = vec![];
230        let mut lines: VectorLinesWithOffset = vec![];
231        let mut polys: Vec<VectorLinesWithOffset> = vec![];
232
233        while pbf.get_pos() < end {
234            if length <= 0 {
235                let cmd_len: usize = pbf.read_varint();
236                cmd = cmd_len & 0x7;
237                length = (cmd_len >> 3) as isize;
238            }
239
240            length -= 1;
241
242            if cmd == 1 || cmd == 2 {
243                x += pbf.read_s_varint::<i32>();
244                y += pbf.read_s_varint::<i32>();
245
246                if cmd == 1 {
247                    // moveTo
248                    if !points.is_empty() && self.r#type != FeatureType::Point {
249                        lines.push((&points[..]).into());
250                        points = vec![];
251                    }
252                }
253                points.push(Point::new(x, y));
254            } else if cmd == 4 {
255                // close poly
256                if !points.is_empty() {
257                    lines.push((&points[..]).into());
258                }
259                polys.push(lines);
260                lines = vec![];
261                points = vec![];
262            } else if cmd == 7 {
263                // close path
264                if !points.is_empty() {
265                    points.push(points[0].clone());
266                    lines.push((&points[..]).into());
267                    points = vec![];
268                }
269            } else {
270                #[tarpaulin::skip]
271                panic!("unknown cmd: {}", cmd);
272            }
273        }
274
275        let geometry = if self.r#type == FeatureType::Point {
276            VectorGeometry::VectorPoints(points)
277        } else {
278            if !points.is_empty() {
279                lines.push(VectorLineWithOffset::new(0.0, points.clone()));
280            }
281            if self.r#type == FeatureType::Line {
282                VectorGeometry::VectorLines(lines)
283            } else if (self.r#type == FeatureType::MultiPolygon
284                || self.r#type == FeatureType::Polygon)
285                && !self.is_s2
286            {
287                VectorGeometry::VectorPolys(classify_rings(&lines))
288            } else {
289                VectorGeometry::VectorPolys(polys)
290            }
291        };
292
293        self.geometry = Some(geometry.clone());
294        geometry
295    }
296
297    /// load the indices
298    fn read_indices(&mut self) -> Vec<u32> {
299        if let Some(indices) = &self.indices {
300            return indices.clone();
301        } else if self.indices_index.is_none() {
302            return vec![];
303        }
304
305        let mut pbf = self.pbf.borrow_mut();
306        pbf.set_pos(self.indices_index.unwrap());
307
308        let mut curr: i32 = 0;
309        let end = pbf.read_varint::<usize>() + pbf.get_pos();
310        // build indices
311        let mut indices: Vec<u32> = vec![];
312        while pbf.get_pos() < end {
313            curr += pbf.read_s_varint::<i32>();
314            indices.push(curr as u32);
315        }
316
317        self.indices = Some(indices.clone());
318        indices
319    }
320
321    /// Add tessellation data to the geometry
322    fn add_tessellation(&mut self, geometry: &mut Vec<f64>, multiplier: f64) {
323        if self.tessellation_index.is_none() {
324            return;
325        }
326
327        let mut pbf = self.pbf.borrow_mut();
328        pbf.set_pos(self.tessellation_index.unwrap());
329
330        let end = pbf.read_varint::<usize>() + pbf.get_pos();
331        let mut x = 0;
332        let mut y = 0;
333        while pbf.get_pos() < end {
334            x += pbf.read_s_varint::<i32>();
335            y += pbf.read_s_varint::<i32>();
336            geometry.push(x as f64 * multiplier);
337            geometry.push(y as f64 * multiplier);
338        }
339    }
340
341    /// Add 3D tessellation data to the geometry
342    #[tarpaulin::skip]
343    fn add_tessellation_3d(&mut self, _geometry: &mut Vec<f64>, _multiplier: f64) {
344        panic!("unexpected geometry type")
345    }
346}
347impl ProtoRead for MapboxVectorFeature {
348    fn read(&mut self, tag: u64, pb: &mut Protobuf) {
349        if self.is_s2 {
350            match tag {
351                15 => self.id = Some(pb.read_varint::<u64>()),
352                1 => {
353                    let end = pb.get_pos() + pb.read_varint::<usize>();
354
355                    while pb.get_pos() < end {
356                        let key = &self.keys.borrow()[pb.read_varint::<usize>()];
357                        let value = &self.values.borrow()[pb.read_varint::<usize>()];
358
359                        self.properties.insert(key.clone(), value.clone());
360                    }
361                }
362                2 => self.r#type = pb.read_varint::<FeatureType>(),
363                3 => self.geometry_index = pb.get_pos(),
364                4 => self.indices_index = Some(pb.get_pos()),
365                5 => self.tessellation_index = Some(pb.get_pos()),
366                #[tarpaulin::skip]
367                _ => panic!("unknown tag: {}", tag),
368            }
369        } else {
370            match tag {
371                1 => self.id = Some(pb.read_varint::<u64>()),
372                2 => {
373                    let end = pb.get_pos() + pb.read_varint::<usize>();
374
375                    while pb.get_pos() < end {
376                        let key = &self.keys.borrow()[pb.read_varint::<usize>()];
377                        let value = &self.values.borrow()[pb.read_varint::<usize>()];
378
379                        self.properties.insert(key.clone(), value.clone());
380                    }
381                }
382                3 => self.r#type = pb.read_varint::<FeatureType>(),
383                4 => self.geometry_index = pb.get_pos(),
384                5 => self.indices_index = Some(pb.get_pos()),
385                6 => self.tessellation_index = Some(pb.get_pos()),
386                #[tarpaulin::skip]
387                _ => panic!("unknown tag: {}", tag),
388            }
389        }
390    }
391}
392
393fn classify_rings(rings: &VectorLinesWithOffset) -> Vec<VectorLinesWithOffset> {
394    let mut polygons: Vec<VectorLinesWithOffset> = vec![];
395    let mut polygon: VectorLinesWithOffset = vec![];
396    let mut ccw: Option<bool> = None;
397
398    let mut i: usize = 0;
399    while i < rings.len() {
400        let area = signed_area(&rings[i].geometry);
401        if area == 0 {
402            continue;
403        }
404        if ccw.is_none() {
405            ccw = Some(area < 0);
406        }
407
408        if ccw.is_some() && ccw.unwrap() == (area < 0) {
409            // outer poly ring
410            if !polygon.is_empty() {
411                polygons.push(polygon.clone());
412                polygon = vec![];
413            }
414            polygon.push(rings[i].clone());
415        } else {
416            // inner poly ring (hole)
417            polygon.push(rings[i].clone());
418        }
419
420        i += 1
421    }
422    if !polygon.is_empty() {
423        polygons.push(polygon.clone());
424    }
425
426    polygons
427}
428
429fn signed_area(ring: &[Point]) -> i32 {
430    let mut sum: i32 = 0;
431    let mut i: usize = 0;
432    let mut j = ring.len() - 1;
433    while i < ring.len() {
434        let p1 = &ring[i];
435        let p2 = &ring[j];
436        sum += (p2.x - p1.x) * (p1.y + p2.y);
437
438        j = i;
439        i += 1;
440    }
441
442    sum
443}
444
445/// Mapbox Vector Feature types.
446#[derive(Debug, Clone, PartialEq)]
447pub enum FeatureType {
448    /// Point Feature
449    Point = 1,
450    /// Line Feature
451    Line = 2,
452    /// Polygon Feature
453    Polygon = 3,
454    /// MultiPolygon Feature
455    MultiPolygon = 4,
456}
457impl From<OpenFeatureType> for FeatureType {
458    fn from(value: OpenFeatureType) -> Self {
459        match value {
460            OpenFeatureType::Points => FeatureType::Point,
461            OpenFeatureType::Lines => FeatureType::Line,
462            OpenFeatureType::Polygons => FeatureType::MultiPolygon,
463            #[tarpaulin::skip]
464            _ => panic!("unknown value: {:?}", value),
465        }
466    }
467}
468impl BitCast for FeatureType {
469    fn to_u64(&self) -> u64 {
470        (*self).clone() as u64
471    }
472    fn from_u64(value: u64) -> Self {
473        match value {
474            1 => FeatureType::Point,
475            2 => FeatureType::Line,
476            3 => FeatureType::Polygon,
477            4 => FeatureType::MultiPolygon,
478            #[tarpaulin::skip]
479            _ => panic!("unknown value: {}", value),
480        }
481    }
482}
483
484/// Write a feature to a protobuffer using the S2 Specification
485pub fn write_feature(
486    feature: &BaseVectorFeature,
487    keys: &mut BTreeMap<String, usize>,
488    values: &mut BTreeMap<PrimitiveValue, usize>,
489    mapbox_support: bool,
490) -> Vec<u8> {
491    let mut pbf = Protobuf::new();
492
493    let properties: MapboxProperties = feature.properties().clone().into();
494    if let Some(id) = feature.id() {
495        pbf.write_varint_field(if mapbox_support { 1 } else { 15 }, id);
496    }
497    pbf.write_bytes_field(
498        if mapbox_support { 2 } else { 1 },
499        &write_properties(&properties, keys, values),
500    );
501    let _type: FeatureType = feature.get_type().into();
502    pbf.write_varint_field(if mapbox_support { 3 } else { 2 }, _type);
503    // Geometry
504    let written = write_geometry(feature, mapbox_support);
505    pbf.write_bytes_field(if mapbox_support { 4 } else { 3 }, &written);
506    // Indices
507    if let Some(indices) = feature.indices() {
508        pbf.write_bytes_field(if mapbox_support { 5 } else { 4 }, &write_indices(&indices));
509    }
510    // Tessellation
511    if let Some(TessellationWrapper::Tessellation(tess)) = feature.tessellation() {
512        pbf.write_bytes_field(if mapbox_support { 6 } else { 5 }, &write_tessellation(&tess));
513    }
514
515    pbf.take()
516}
517
518/// Write a properties to a protobuffer using the S2 Specification
519fn write_properties(
520    properties: &MapboxProperties,
521    keys: &mut BTreeMap<String, usize>,
522    values: &mut BTreeMap<PrimitiveValue, usize>,
523) -> Vec<u8> {
524    let mut pbf = Protobuf::new();
525
526    for (key, value) in properties.iter() {
527        let key_length = keys.len();
528        let key_index = keys.entry(key.clone()).or_insert(key_length);
529        pbf.write_varint(*key_index);
530        let value_length = values.len();
531        let value_index = values.entry(value.clone()).or_insert(value_length);
532        pbf.write_varint(*value_index);
533    }
534
535    pbf.take()
536}
537
538/// write the indices to a protobuffer using the S2 Specification
539fn write_indices(indices: &[u32]) -> Vec<u8> {
540    let mut pbf = Protobuf::new();
541
542    let mut curr: i32 = 0;
543    for index in indices {
544        let d_curr = (*index as i32) - curr;
545        pbf.write_varint(zigzag(d_curr));
546        curr += d_curr;
547    }
548
549    pbf.take()
550}
551
552/// write the tessellation to a protobuffer using the S2 Specification
553fn write_tessellation(geometry: &[Point]) -> Vec<u8> {
554    let mut pbf = Protobuf::new();
555    let mut x = 0;
556    let mut y = 0;
557    for point in geometry {
558        let dx = point.x - x;
559        let dy = point.y - y;
560        pbf.write_varint(zigzag(dx));
561        pbf.write_varint(zigzag(dy));
562        x += dx;
563        y += dy;
564    }
565
566    pbf.take()
567}
568
569/// write the geometry to a protobuffer using the S2 Specification
570fn write_geometry(feature: &BaseVectorFeature, mapbox_support: bool) -> Vec<u8> {
571    use BaseVectorFeature::*;
572    let mut pbf = Protobuf::new();
573    match feature {
574        BaseVectorPointsFeature(points) => write_geometry_points(&points.geometry, &mut pbf),
575        BaseVectorLinesFeature(lines) => write_geometry_lines(&lines.geometry, &mut pbf),
576        BaseVectorPolysFeature(polys) => {
577            write_geometry_polys(&polys.geometry, &mut pbf, mapbox_support)
578        }
579        #[tarpaulin::skip]
580        _ => panic!("unknown feature type: {:?}", feature.get_type()),
581    };
582    pbf.take()
583}
584
585/// write the points geometry to a protobuffer using the S2 Specification
586fn write_geometry_points(points: &[Point], pbf: &mut Protobuf) {
587    let mut x = 0;
588    let mut y = 0;
589
590    for point in points {
591        // move
592        pbf.write_varint(command_encode(1, 1)); // moveto
593        // store
594        let dx = point.x - x;
595        let dy = point.y - y;
596        pbf.write_varint(zigzag(dx));
597        pbf.write_varint(zigzag(dy));
598        // update position
599        x += dx;
600        y += dy;
601    }
602}
603
604/// write the lines geometry to a protobuffer using the S2 Specification
605fn write_geometry_lines(lines: &[VectorLineWithOffset], pbf: &mut Protobuf) {
606    let mut x = 0;
607    let mut y = 0;
608
609    for line in lines {
610        let line_geo = &line.geometry;
611        pbf.write_varint(command_encode(1, 1)); // moveto
612        // do not write polygon closing path as lineto
613        let line_count = line_geo.len();
614        let mut i = 0;
615        while i < line_count {
616            if i == 1 {
617                pbf.write_varint(command_encode(2, (line_count - 1).try_into().unwrap()));
618                // lineto
619            }
620
621            let point = &line_geo[i];
622            let dx = point.x - x;
623            let dy = point.y - y;
624            pbf.write_varint(zigzag(dx));
625            pbf.write_varint(zigzag(dy));
626            x += dx;
627            y += dy;
628
629            i += 1;
630        }
631    }
632}
633
634/// write the polys geometry to a protobuffer using the S2 Specification
635fn write_geometry_polys(
636    polys: &[Vec<VectorLineWithOffset>],
637    pbf: &mut Protobuf,
638    mapbox_support: bool,
639) {
640    let mut x = 0;
641    let mut y = 0;
642
643    for poly in polys {
644        for ring in poly {
645            let ring_geo = &ring.geometry;
646            pbf.write_varint(command_encode(1, 1)); // moveto
647            let line_count = ring_geo.len() - 1;
648            let mut i = 0;
649            while i < line_count {
650                if i == 1 {
651                    pbf.write_varint(command_encode(2, (line_count - 1).try_into().unwrap()));
652                    // lineto
653                }
654
655                let point = &ring_geo[i];
656                let dx = point.x - x;
657                let dy = point.y - y;
658                pbf.write_varint(zigzag(dx));
659                pbf.write_varint(zigzag(dy));
660                x += dx;
661                y += dy;
662
663                i += 1;
664            }
665            pbf.write_varint(command_encode(7, 1)); // ClosePath
666        }
667        // ClosePolygon (Mapbox does not support so close path if not supported)
668        pbf.write_varint(command_encode(if mapbox_support { 7 } else { 4 }, 1));
669    }
670}