From f0dfb589834c932705dafd35e652f5c3c8e9ebc6 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Sun, 1 Mar 2026 08:52:36 +0100 Subject: [PATCH 1/2] feat: add get_feature_as for i32 and i16 support Use num-traits as direct dependency. geo-types already uses num-traits. Fixes #578 --- Cargo.lock | 1 + Cargo.toml | 3 ++- src/feature.rs | 11 ++++---- src/lib.rs | 62 +++++++++++++++++++++++++++++++++++++-------- tests/real_world.rs | 45 +++++++++++++++++--------------- 5 files changed, 84 insertions(+), 38 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0595a70..9a62981 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -383,6 +383,7 @@ dependencies = [ "geo-types", "geojson", "js-sys", + "num-traits", "prost 0.13.5", "prost-build", "serde", diff --git a/Cargo.toml b/Cargo.toml index 793b969..743a071 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,8 @@ protoc = ["prost-build"] protoc-generated = ["prost-build"] [dependencies] -geo-types = "0.7" +geo-types = { version = "0.7", default-features = false } +num-traits = { version = "0.2", default-features = false, features = ["libm"] } prost = { version = "0.13", default-features = false, features = ["prost-derive", "std"] } wasm-bindgen = { version = "0.2", optional = true } serde-wasm-bindgen = { version = "0.6", optional = true } diff --git a/src/feature.rs b/src/feature.rs index 4cfa344..34e9dc4 100644 --- a/src/feature.rs +++ b/src/feature.rs @@ -9,8 +9,7 @@ //! - `Feature`: Represents a feature with geometry, an optional id and optional properties. use std::collections::HashMap; - -use geo_types::Geometry; +use geo_types::{CoordNum, Geometry}; /// An enumeration representing the value of a property associated with a feature. #[derive(Debug, Clone, PartialEq, PartialOrd)] @@ -27,9 +26,9 @@ pub enum Value { /// A structure representing a feature in a vector tile. #[derive(Debug, Clone)] -pub struct Feature { +pub struct Feature { /// The geometry of the feature. - pub geometry: Geometry, + pub geometry: Geometry, /// Optional identifier for the feature. pub id: Option, @@ -38,7 +37,7 @@ pub struct Feature { pub properties: Option>, } -impl Feature { +impl Feature { /// Retrieves the geometry of the feature. /// /// # Returns @@ -60,7 +59,7 @@ impl Feature { /// let geometry = feature.get_geometry(); /// println!("{:?}", geometry); /// ``` - pub fn get_geometry(&self) -> &Geometry { + pub fn get_geometry(&self) -> &Geometry { &self.geometry } } diff --git a/src/lib.rs b/src/lib.rs index 1ae0c13..9b63b88 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,9 +73,10 @@ mod vector_tile; use feature::{Feature, Value}; use geo_types::{ - Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, + Coord, CoordNum, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon, }; use layer::Layer; +use num_traits::NumCast; use prost::{Message, bytes::Bytes}; use vector_tile::{Tile, tile::GeomType}; @@ -210,6 +211,41 @@ impl Reader { /// } /// ``` pub fn get_features(&self, layer_index: usize) -> Result, error::ParserError> { + self.get_features_as::(layer_index) + } + + /// Retrieves the features of a specific layer with geometry coordinates in the specified numeric type. + /// + /// This is a generic version of [`get_features`](Reader::get_features) that allows you to choose + /// the coordinate type for the geometry. Supported types include `f32` (default), `i32`, and `i16`. + /// + /// # Arguments + /// + /// * `layer_index` - The index of the layer. + /// + /// # Type Parameters + /// + /// * `T` - The numeric type for geometry coordinates (e.g. `f32`, `i32`, `i16`). + /// + /// # Returns + /// + /// A result containing a vector of features if successful, or a `ParserError` if there is an error parsing the tile or accessing the layer. + /// + /// # Examples + /// + /// ``` + /// use mvt_reader::Reader; + /// + /// let data = vec![/* Vector tile data */]; + /// let reader = Reader::new(data).unwrap(); + /// + /// // Get features with i32 coordinates + /// let features = reader.get_features_as::(0); + /// + /// // Get features with i16 coordinates + /// let features = reader.get_features_as::(0); + /// ``` + pub fn get_features_as(&self, layer_index: usize) -> Result>, error::ParserError> { let layer = self.tile.layers.get(layer_index); match layer { Some(layer) => { @@ -218,7 +254,7 @@ impl Reader { if let Some(geom_type) = feature.r#type { match GeomType::try_from(geom_type) { Ok(geom_type) => { - let parsed_geometry = match parse_geometry(&feature.geometry, geom_type) { + let parsed_geometry = match parse_geometry::(&feature.geometry, geom_type) { Ok(parsed_geometry) => parsed_geometry, Err(error) => { return Err(error); @@ -321,29 +357,33 @@ fn map_value(value: vector_tile::tile::Value) -> Value { Value::Null } -fn shoelace_formula(points: &[Point]) -> f32 { +fn shoelace_formula(points: &[Point]) -> f32 { let mut area: f32 = 0.0; let n = points.len(); let mut v1 = points[n - 1]; for v2 in points.iter().take(n) { - area += (v2.y() - v1.y()) * (v2.x() + v1.x()); + let v2y: f32 = NumCast::from(v2.y()).unwrap_or(0.0); + let v1y: f32 = NumCast::from(v1.y()).unwrap_or(0.0); + let v2x: f32 = NumCast::from(v2.x()).unwrap_or(0.0); + let v1x: f32 = NumCast::from(v1.x()).unwrap_or(0.0); + area += (v2y - v1y) * (v2x + v1x); v1 = *v2; } area * 0.5 } -fn parse_geometry( +fn parse_geometry( geometry_data: &[u32], geom_type: GeomType, -) -> Result, error::ParserError> { +) -> Result, error::ParserError> { if geom_type == GeomType::Unknown { return Err(error::ParserError::new(error::GeometryError::new())); } // worst case capacity to prevent reallocation. not needed to be exact. - let mut coordinates: Vec> = Vec::with_capacity(geometry_data.len()); - let mut polygons: Vec> = Vec::new(); - let mut linestrings: Vec> = Vec::new(); + let mut coordinates: Vec> = Vec::with_capacity(geometry_data.len()); + let mut polygons: Vec> = Vec::new(); + let mut linestrings: Vec> = Vec::new(); let mut cursor: [i32; 2] = [0, 0]; let mut parameter_count: u32 = 0; @@ -412,8 +452,8 @@ fn parse_geometry( None => i32::MAX, // clip value }; coordinates.push(Coord { - x: cursor[0] as f32, - y: cursor[1] as f32, + x: NumCast::from(cursor[0]).unwrap_or_else(T::zero), + y: NumCast::from(cursor[1]).unwrap_or_else(T::zero), }); } parameter_count -= 1; diff --git a/tests/real_world.rs b/tests/real_world.rs index 11492ac..2953a00 100644 --- a/tests/real_world.rs +++ b/tests/real_world.rs @@ -3,6 +3,7 @@ use std::fs::{DirEntry, read, read_dir}; use std::path::PathBuf; use std::{io, result::Result}; +use geo_types::CoordNum; use mvt_reader::error::TagsError; use mvt_reader::{Reader, error::GeometryError}; @@ -69,33 +70,37 @@ fn read_corrupted_tags_fixture() -> Result<(), io::Error> { } #[test] -fn read_all_fixtures() -> Result<(), io::Error> { +fn read_all_fixtures_f32() -> Result<(), io::Error> { + assert_all_fixtures_readable::("f32") +} + +#[test] +fn read_all_fixtures_i32() -> Result<(), io::Error> { + assert_all_fixtures_readable::("i32") +} + +#[test] +fn read_all_fixtures_i16() -> Result<(), io::Error> { + assert_all_fixtures_readable::("i16") +} + +fn assert_all_fixtures_readable(label: &str) -> Result<(), io::Error> { for mvt_file in get_all_real_world_fixtures()?.iter() { if !mvt_file.extension().unwrap().eq_ignore_ascii_case("mvt") { println!("Skipped file {:?}", mvt_file); continue; } - println!("Read {:?}", mvt_file); + + println!("Read {:?} ({})", mvt_file, label); + let bytes = read(mvt_file)?; - let reader_result = Reader::new(bytes.to_vec()); - match reader_result { - Ok(reader) => { - let layer_names = match reader.get_layer_names() { - Ok(layer_names) => layer_names, - Err(error) => { - panic!("{}", error); - } - }; - for (i, _) in layer_names.iter().enumerate() { - let features = reader.get_features(i); - assert!(!features.unwrap().is_empty()); - } - println!("found layer names: {:?}", layer_names); - } - Err(_) => { - panic!("Parsing failed unexpectedly") - } + let reader = Reader::new(bytes.to_vec()).expect("Parsing failed unexpectedly"); + let layer_names = reader.get_layer_names().expect("Failed to get layer names"); + for (i, _) in layer_names.iter().enumerate() { + let features = reader.get_features_as::(i); + assert!(!features.unwrap().is_empty()); } + println!("found layer names: {:?}", layer_names); } Ok(()) } From 3c35bc4384134cb522ce4e7e817aeb5ddc68d282 Mon Sep 17 00:00:00 2001 From: Paul Lange Date: Sun, 1 Mar 2026 11:12:01 +0100 Subject: [PATCH 2/2] chore: fix stale apt repository list with update --- .github/actions/prepare-build-env/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/prepare-build-env/action.yml b/.github/actions/prepare-build-env/action.yml index 374bfa7..91ff11a 100644 --- a/.github/actions/prepare-build-env/action.yml +++ b/.github/actions/prepare-build-env/action.yml @@ -14,7 +14,7 @@ runs: steps: - name: Use protobuf-compiler latest shell: bash - run: sudo apt-get install -y protobuf-compiler + run: sudo apt-get update && sudo apt-get install -y protobuf-compiler - name: Use wasm-pack latest shell: bash