Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/prepare-build-env/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
11 changes: 5 additions & 6 deletions src/feature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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<T: CoordNum = f32> {
/// The geometry of the feature.
pub geometry: Geometry<f32>,
pub geometry: Geometry<T>,

/// Optional identifier for the feature.
pub id: Option<u64>,
Expand All @@ -38,7 +37,7 @@ pub struct Feature {
pub properties: Option<HashMap<String, Value>>,
}

impl Feature {
impl<T: CoordNum> Feature<T> {
/// Retrieves the geometry of the feature.
///
/// # Returns
Expand All @@ -60,7 +59,7 @@ impl Feature {
/// let geometry = feature.get_geometry();
/// println!("{:?}", geometry);
/// ```
pub fn get_geometry(&self) -> &Geometry<f32> {
pub fn get_geometry(&self) -> &Geometry<T> {
&self.geometry
}
}
62 changes: 51 additions & 11 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -210,6 +211,41 @@ impl Reader {
/// }
/// ```
pub fn get_features(&self, layer_index: usize) -> Result<Vec<Feature>, error::ParserError> {
self.get_features_as::<f32>(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::<i32>(0);
///
/// // Get features with i16 coordinates
/// let features = reader.get_features_as::<i16>(0);
/// ```
pub fn get_features_as<T: CoordNum>(&self, layer_index: usize) -> Result<Vec<Feature<T>>, error::ParserError> {
let layer = self.tile.layers.get(layer_index);
match layer {
Some(layer) => {
Expand All @@ -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::<T>(&feature.geometry, geom_type) {
Ok(parsed_geometry) => parsed_geometry,
Err(error) => {
return Err(error);
Expand Down Expand Up @@ -321,29 +357,33 @@ fn map_value(value: vector_tile::tile::Value) -> Value {
Value::Null
}

fn shoelace_formula(points: &[Point<f32>]) -> f32 {
fn shoelace_formula<T: CoordNum>(points: &[Point<T>]) -> 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<T: CoordNum>(
geometry_data: &[u32],
geom_type: GeomType,
) -> Result<Geometry<f32>, error::ParserError> {
) -> Result<Geometry<T>, 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<Coord<f32>> = Vec::with_capacity(geometry_data.len());
let mut polygons: Vec<Polygon<f32>> = Vec::new();
let mut linestrings: Vec<LineString<f32>> = Vec::new();
let mut coordinates: Vec<Coord<T>> = Vec::with_capacity(geometry_data.len());
let mut polygons: Vec<Polygon<T>> = Vec::new();
let mut linestrings: Vec<LineString<T>> = Vec::new();

let mut cursor: [i32; 2] = [0, 0];
let mut parameter_count: u32 = 0;
Expand Down Expand Up @@ -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;
Expand Down
45 changes: 25 additions & 20 deletions tests/real_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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>("f32")
}

#[test]
fn read_all_fixtures_i32() -> Result<(), io::Error> {
assert_all_fixtures_readable::<i32>("i32")
}

#[test]
fn read_all_fixtures_i16() -> Result<(), io::Error> {
assert_all_fixtures_readable::<i16>("i16")
}

fn assert_all_fixtures_readable<T: CoordNum>(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::<T>(i);
assert!(!features.unwrap().is_empty());
}
println!("found layer names: {:?}", layer_names);
}
Ok(())
}
Expand Down