diff --git a/libs/vector_tile/Cargo.toml b/libs/vector_tile/Cargo.toml new file mode 100644 index 00000000..1fc4a15c --- /dev/null +++ b/libs/vector_tile/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "vector-tile" +version = "0.1.0" +description = "A library decoding vector tiles" +readme = "README.md" +categories = ["encoding"] +edition = "2021" +build = "build.rs" + +[dependencies] +log = "0.4" +num-traits = "0.2" +pointy = "0.3" +protobuf = "2.25" + +[build-dependencies] +protobuf-codegen-pure = "2.25" + + diff --git a/libs/vector_tile/build.rs b/libs/vector_tile/build.rs new file mode 100644 index 00000000..8f055319 --- /dev/null +++ b/libs/vector_tile/build.rs @@ -0,0 +1,13 @@ +extern crate protobuf_codegen_pure; + +use std::path::PathBuf; + +fn main() { + let out_path = PathBuf::from("src/protos"); + protobuf_codegen_pure::Codegen::new() + .out_dir(out_path) + .inputs(&["spec/2.1/vector_tile.proto"]) + .include("spec/2.1") + .run() + .expect("Codegen failed."); +} \ No newline at end of file diff --git a/libs/vector_tile/spec b/libs/vector_tile/spec new file mode 160000 index 00000000..c8d178da --- /dev/null +++ b/libs/vector_tile/spec @@ -0,0 +1 @@ +Subproject commit c8d178da65cce0ea474e3d99e3368970ce217093 diff --git a/libs/vector_tile/src/encoding.rs b/libs/vector_tile/src/encoding.rs new file mode 100644 index 00000000..56fbe4e4 --- /dev/null +++ b/libs/vector_tile/src/encoding.rs @@ -0,0 +1,185 @@ +use std::collections::HashMap; + +use crate::geometry::{ + Geometry, GeometryLineString, GeometryPoint, GeometryPolygon, MultiPoint, Point, +}; +use crate::protos::vector_tile::{ + Tile as ProtoTile, Tile_Feature as ProtoFeature, Tile_GeomType as ProtoGeomType, Tile_GeomType, + Tile_Layer as ProtoLayer, Tile_Value as ProtoValue, +}; +use crate::tile::{Feature, Layer, PropertyValue, Tile}; + +pub trait Decode { + fn decode(self) -> T; +} + +/// Decode a PropertyValue +impl Decode for ProtoValue { + fn decode(self) -> PropertyValue { + if self.has_bool_value() { + PropertyValue::BoolValue(self.get_bool_value()) + } else if self.has_string_value() { + PropertyValue::StringValue(String::from(self.get_string_value())) + } else if self.has_float_value() { + PropertyValue::FloatValue(self.get_float_value()) + } else if self.has_int_value() { + PropertyValue::IntValue(self.get_int_value()) + } else if self.has_sint_value() { + PropertyValue::SIntValue(self.get_sint_value()) + } else if self.has_uint_value() { + PropertyValue::UIntValue(self.get_uint_value()) + } else if self.has_double_value() { + PropertyValue::DoubleValue(self.get_double_value()) + } else { + PropertyValue::Unknown + } + } +} + +/// Decode a list of PropertyValues +impl Decode> for Vec { + fn decode(self) -> Vec { + self.into_iter().map(|value| value.decode()).collect() + } +} + +const CMD_MOVE_TO: u32 = 1; +const CMD_LINE_TO: u32 = 2; +const CMD_CLOSE_PATH: u32 = 7; + +const CMD_MOVE_TO_PARAMETERS: usize = 2; +const CMD_LINE_TO_PARAMETERS: usize = 2; +const CMD_CLOSE_PATH_PARAMETERS: usize = 0; + +trait ZigZag { + /// Encodes a value to zigzag + fn zigzag(self) -> i32; + /// Decodes a value from zigzag encoding + fn zagzig(self) -> i32; +} + +impl ZigZag for u32 { + fn zigzag(self) -> i32 { + ((self << 1) ^ (self >> 31)) as i32 + } + + fn zagzig(self) -> i32 { + ((self >> 1) as i32 ^ (-((self & 1) as i32))) + } +} + +impl Decode for Vec { + fn decode(self) -> GeometryPoint { + let mut points = vec![]; + let mut i = 0; + + while i < self.len() - 1 { + let command = self[i] & 0x7; + + if command != CMD_MOVE_TO { + // FIXME: ERROR + } + + let count = (self[i] >> 3) as usize; + i += 1; + + for parameter in 0..count { + points.push(Point::new(self[i + parameter].zagzig(), self[i + parameter + 1].zagzig())); + } + + i += count * CMD_MOVE_TO_PARAMETERS; + } + + if points.len() == 1 { + GeometryPoint::Point(points.remove(0)) + } else if points.len() > 1 { + GeometryPoint::MultiPoint(MultiPoint::new(points)) + } else { + GeometryPoint::Point(Point::new(0, 0)); // point is at the origin + } + } +} + +impl Decode for Vec { + fn decode(self) -> GeometryLineString { + + + i += count * match command { + CMD_MOVE_TO => CMD_MOVE_TO_PARAMETERS, + CMD_LINE_TO => CMD_LINE_TO_PARAMETERS, + CMD_CLOSE_PATH => CMD_CLOSE_PATH_PARAMETERS, + _ => 0, + }; + } +} + +impl Decode for Vec { + fn decode(self) -> GeometryPolygon { + GeometryPolygon::Unknown + } +} + +/// Decode a Geometry +impl Decode for ProtoFeature { + fn decode(self) -> Geometry { + match &self.get_field_type() { + Tile_GeomType::UNKNOWN => Geometry::Unknown, + Tile_GeomType::POINT => Geometry::GeometryPoint(self.geometry.decode()), + Tile_GeomType::LINESTRING => Geometry::GeometryLineString(self.geometry.decode()), + Tile_GeomType::POLYGON => Geometry::GeometryPolygon(self.geometry.decode()), + } + } +} + +/// Decode a Feature +impl Decode for (&ProtoLayer, ProtoFeature) { + fn decode(self) -> Feature { + let (layer, feature) = self; + + let mut properties = HashMap::new(); + + for chunk in feature.tags.chunks(2) { + let key = chunk[0]; + let value = chunk[1]; + + let keys = &layer.keys; + if let Some(actualKey) = keys.get(key as usize) { + let values = &layer.values; + if let Some(actualValue) = values.get(value as usize) { + properties.insert(actualKey.clone(), actualValue.clone().decode()); + } + } + } + let geometry = feature.clone().decode(); // FIXME: Inefficient clone + + Feature::new(feature, geometry, properties) + } +} + +/// Decode a Layer +impl Decode for ProtoLayer { + fn decode(mut self) -> Layer { + // FIXME: Order of features is changed here + let mut features = Vec::new(); + + while let Some(feature) = self.features.pop() { + features.push((&self, feature).decode()) + } + + Layer::new(self, features) + } +} + +/// Decode a whole Tile +impl Decode for ProtoTile { + fn decode(mut self) -> Tile { + // FIXME: Order of layers is changed here + let mut layers = Vec::new(); + + while let Some(layer) = self.layers.pop() { + layers.push(layer.decode()) + } + + Tile::new(self, layers) + } +} diff --git a/libs/vector_tile/src/geometry.rs b/libs/vector_tile/src/geometry.rs new file mode 100644 index 00000000..cc8a72cb --- /dev/null +++ b/libs/vector_tile/src/geometry.rs @@ -0,0 +1,99 @@ +use num_traits::Num; + +type Number = i32; + +#[derive(Debug)] +pub enum GeometryPoint { + Point(Point), + MultiPoint(MultiPoint), + Unknown, +} + +#[derive(Debug)] +pub struct MultiPoint { + points: Vec, +} + +#[derive(Debug)] +pub struct Point { + x: Number, + y: Number, +} + +#[derive(Debug)] +pub enum GeometryLineString { + LineString(LineString), + MultiLineString(MultiLineString), + Unknown, +} + +#[derive(Debug)] +pub struct MultiLineString { + lines: Vec, +} + +#[derive(Debug)] +pub struct LineString { + points: Vec, +} + +#[derive(Debug)] +pub enum GeometryPolygon { + Polygon(Polygon), + MultiLineString(MultiPolygon), + Unknown, +} + +#[derive(Debug)] +pub struct Polygon { + points: Vec, +} + +#[derive(Debug)] +pub struct MultiPolygon { + polygons: Vec, +} + +#[derive(Debug)] +pub enum Geometry { + GeometryPoint(GeometryPoint), + GeometryLineString(GeometryLineString), + GeometryPolygon(GeometryPolygon), + Unknown, +} + +impl Point { + pub(crate) fn new(x: Number, y: Number) -> Self { + Self { x, y } + } +} + +impl MultiPoint { + pub(crate) fn new(points: Vec) -> Self { + Self { points } + } +} + +impl LineString { + pub(crate) fn new(points: Vec) -> Self { + Self { points } + } +} + +impl MultiLineString { + pub(crate) fn new(lines: Vec) -> Self { + Self { lines } + } +} + +impl Polygon { + pub(crate) fn new(points: Vec) -> Self { + Self { points } + } +} + +impl MultiPolygon { + pub(crate) fn new(polygons: Vec) -> Self { + Self { polygons } + } +} diff --git a/libs/vector_tile/src/lib.rs b/libs/vector_tile/src/lib.rs new file mode 100644 index 00000000..89a192ac --- /dev/null +++ b/libs/vector_tile/src/lib.rs @@ -0,0 +1,9 @@ + +mod protos; +mod encoding; + +#[cfg(test)] +mod tests; + +pub mod tile; +pub mod geometry; \ No newline at end of file diff --git a/libs/vector_tile/src/protos/.gitignore b/libs/vector_tile/src/protos/.gitignore new file mode 100644 index 00000000..1efd6264 --- /dev/null +++ b/libs/vector_tile/src/protos/.gitignore @@ -0,0 +1,2 @@ +*.rs +!mod.rs \ No newline at end of file diff --git a/libs/vector_tile/src/protos/mod.rs b/libs/vector_tile/src/protos/mod.rs new file mode 100644 index 00000000..cc69ffdd --- /dev/null +++ b/libs/vector_tile/src/protos/mod.rs @@ -0,0 +1,2 @@ +#[path = "vector_tile.rs"] +pub mod vector_tile; \ No newline at end of file diff --git a/libs/vector_tile/src/tests.rs b/libs/vector_tile/src/tests.rs new file mode 100644 index 00000000..3af1e620 --- /dev/null +++ b/libs/vector_tile/src/tests.rs @@ -0,0 +1,16 @@ +use std::fs::File; +use std::io::BufReader; +use crate::encoding::Decode; + +use protobuf::Message; + +use crate::protos::vector_tile::Tile; + +#[test] +fn it_works() { + let mut f = File::open("libs/vector_tile/test_data/europe.pbf").expect("no file found"); + //let mut f = File::open("test_data/europe.pbf").expect("no file found"); + let mut reader = BufReader::new(f); + let x = Tile::parse_from_reader(&mut reader).unwrap().decode(); + println!("{:#?}", x); +} diff --git a/libs/vector_tile/src/tile.rs b/libs/vector_tile/src/tile.rs new file mode 100644 index 00000000..ef36e5a5 --- /dev/null +++ b/libs/vector_tile/src/tile.rs @@ -0,0 +1,79 @@ +use std::collections::HashMap; + +use crate::geometry::Geometry; +use crate::protos::vector_tile::{ + Tile as ProtoTile, Tile_Feature, Tile_Feature as ProtoFeature, Tile_Layer as ProtoLayer, +}; + +#[derive(Debug)] +pub struct Tile { + internal: ProtoTile, + layers: Vec, +} + +#[derive(Debug)] +pub struct Layer { + internal: ProtoLayer, + features: Vec, +} + +#[derive(Debug)] +pub struct Feature { + internal: ProtoFeature, + geometry: Geometry, + properties: HashMap, +} + +#[derive(Debug)] +pub enum PropertyValue { + StringValue(String), + FloatValue(f32), + DoubleValue(f64), + IntValue(i64), + UIntValue(u64), + SIntValue(i64), + BoolValue(bool), + Unknown, +} + +impl Feature { + pub(crate) fn new( + internal: ProtoFeature, + geometry: Geometry, + properties: HashMap, + ) -> Self { + Feature { + internal, + geometry, + properties, + } + } + + pub fn id(&self) -> u64 { + self.internal.get_id() + } +} + +impl Layer { + pub(crate) fn new(internal: ProtoLayer, features: Vec) -> Self { + Layer { internal, features } + } + + pub fn extend(&self) -> u32 { + self.internal.get_extent() + } + + pub fn version(&self) -> u32 { + self.internal.get_version() + } + + pub fn name(&self) -> &str { + self.internal.get_name() + } +} + +impl Tile { + pub(crate) fn new(internal: ProtoTile, layers: Vec) -> Self { + Tile { internal, layers } + } +} diff --git a/libs/vector_tile/test_data/europe.pbf b/libs/vector_tile/test_data/europe.pbf new file mode 100644 index 00000000..bd48bf59 Binary files /dev/null and b/libs/vector_tile/test_data/europe.pbf differ