diff --git a/Cargo.toml b/Cargo.toml index f02b353d..79b32852 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ criterion = "0.5.1" csv = "1.2.2" dbase = "0.4" diesel = { version = "2.1.0", default-features = false, features = ["postgres"] } -dup-indexer = "0.3" +dup-indexer = "0.4" env_logger = "0.10.0" flatgeobuf = "3.27.0" futures-util = "0.3.28" diff --git a/geozero/src/mvt/mod.rs b/geozero/src/mvt/mod.rs index f0044d0d..a8baf2eb 100644 --- a/geozero/src/mvt/mod.rs +++ b/geozero/src/mvt/mod.rs @@ -21,7 +21,7 @@ pub(crate) mod conversion { use crate::error::Result; use crate::mvt::vector_tile::tile; use crate::mvt::MvtWriter; - use crate::GeozeroGeometry; + use crate::GeozeroDatasource; /// Convert to MVT geometry. pub trait ToMvt { @@ -31,7 +31,7 @@ pub(crate) mod conversion { /// * `extent` - Size of MVT tile in tile coordinate space (e.g. 4096). /// * `left`, `bottom`, `right`, `top` - Bounds of tile in map coordinate space, with no buffer. fn to_mvt( - &self, + &mut self, extent: u32, left: f64, bottom: f64, @@ -40,12 +40,12 @@ pub(crate) mod conversion { ) -> Result; /// Convert to MVT geometry with geometries in unmodified tile coordinate space. - fn to_mvt_unscaled(&self) -> Result; + fn to_mvt_unscaled(&mut self) -> Result; } - impl ToMvt for T { + impl ToMvt for T { fn to_mvt( - &self, + &mut self, extent: u32, left: f64, bottom: f64, @@ -53,19 +53,20 @@ pub(crate) mod conversion { top: f64, ) -> Result { let mut mvt = MvtWriter::new(extent, left, bottom, right, top); - self.process_geom(&mut mvt)?; + self.process(&mut mvt)?; Ok(mvt.feature) } - fn to_mvt_unscaled(&self) -> Result { + fn to_mvt_unscaled(&mut self) -> Result { let mut mvt = MvtWriter::default(); - self.process_geom(&mut mvt)?; + self.process(&mut mvt)?; Ok(mvt.feature) } } } mod mvt_error; + pub use mvt_error::MvtError; #[cfg(feature = "with-wkb")] diff --git a/geozero/src/mvt/mvt_writer.rs b/geozero/src/mvt/mvt_writer.rs index dc032392..7287c8d6 100644 --- a/geozero/src/mvt/mvt_writer.rs +++ b/geozero/src/mvt/mvt_writer.rs @@ -4,12 +4,13 @@ use crate::error::Result; use crate::mvt::mvt_commands::{Command, CommandInteger, ParameterInteger}; use crate::mvt::vector_tile::{tile, tile::GeomType}; -use crate::GeomProcessor; +use crate::mvt::TagsBuilder; +use crate::{ColumnValue, FeatureProcessor, GeomProcessor, PropertyProcessor}; use super::mvt_error::MvtError; /// Generator for MVT geometry type. -#[derive(Default, Debug)] +#[derive(Debug)] pub struct MvtWriter { pub(crate) feature: tile::Feature, // Extent, 0 for unscaled @@ -24,6 +25,25 @@ pub struct MvtWriter { last_y: i32, line_state: LineState, is_multiline: bool, + tags_builder: TagsBuilder, +} + +impl Default for MvtWriter { + fn default() -> Self { + Self { + feature: tile::Feature::default(), + extent: 4096, + left: 0.0, + bottom: 0.0, + x_multiplier: 1.0, + y_multiplier: 1.0, + last_x: 0, + last_y: 0, + line_state: LineState::None, + is_multiline: false, + tags_builder: TagsBuilder::new(), + } + } } #[derive(Default, Debug, PartialEq)] @@ -175,6 +195,38 @@ impl GeomProcessor for MvtWriter { } } +impl PropertyProcessor for MvtWriter { + fn property(&mut self, _idx: usize, name: &str, value: &ColumnValue) -> Result { + self.tags_builder.insert_ref( + name, + value + .try_into() + .map_err(|_| MvtError::UnsupportedKeyValueType(name.to_string()))?, + ); + // match colval { + // ColumnValue::Byte(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::UByte(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::Bool(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::Short(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::UShort(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::Int(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::UInt(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::Long(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::ULong(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::Float(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::Double(v) => write_num_prop(self.out, colname, &v)?, + // ColumnValue::String(v) | ColumnValue::DateTime(v) => { + // write_str_prop(self.out, colname, v)?; + // } + // ColumnValue::Json(_v) => (), + // ColumnValue::Binary(_v) => (), + // }; + Ok(false) + } +} + +impl FeatureProcessor for MvtWriter {} + #[cfg(test)] mod test_mvt { use super::*; @@ -400,28 +452,29 @@ mod test { #[test] fn point_geom() { - let geojson = GeoJson(r#"{"type": "Point", "coordinates": [25, 17]}"#); + let mut geojson = GeoJson(r#"{"type": "Point", "coordinates": [25, 17]}"#); let mvt = geojson.to_mvt_unscaled().unwrap(); assert_eq!(mvt.geometry, [9, 50, 34]); } #[test] fn multipoint_geom() { - let geojson = GeoJson(r#"{"type": "MultiPoint", "coordinates": [[5, 7], [3, 2]]}"#); + let mut geojson = GeoJson(r#"{"type": "MultiPoint", "coordinates": [[5, 7], [3, 2]]}"#); let mvt = geojson.to_mvt_unscaled().unwrap(); assert_eq!(mvt.geometry, [17, 10, 14, 3, 9]); } #[test] fn line_geom() { - let geojson = GeoJson(r#"{"type": "LineString", "coordinates": [[2,2], [2,10], [10,10]]}"#); + let mut geojson = + GeoJson(r#"{"type": "LineString", "coordinates": [[2,2], [2,10], [10,10]]}"#); let mvt = geojson.to_mvt_unscaled().unwrap(); assert_eq!(mvt.geometry, [9, 4, 4, 18, 0, 16, 16, 0]); } #[test] fn multiline_geom() { - let geojson = GeoJson( + let mut geojson = GeoJson( r#"{"type": "MultiLineString", "coordinates": [[[2,2], [2,10], [10,10]],[[1,1],[3,5]]]}"#, ); let mvt = geojson.to_mvt_unscaled().unwrap(); @@ -433,7 +486,7 @@ mod test { #[test] fn polygon_geom() { - let geojson = + let mut geojson = GeoJson(r#"{"type": "Polygon", "coordinates": [[[3, 6], [8, 12], [20, 34], [3, 6]]]}"#); let mvt = geojson.to_mvt_unscaled().unwrap(); assert_eq!(mvt.geometry, [9, 6, 12, 18, 10, 12, 24, 44, 15]); @@ -457,7 +510,7 @@ mod test { ] ] }"#; - let geojson = GeoJson(geojson); + let mut geojson = GeoJson(geojson); let mvt = geojson.to_mvt_unscaled().unwrap(); assert_eq!( mvt.geometry, @@ -473,7 +526,7 @@ mod test { "type": "Polygon", "coordinates": [[[34876,37618],[37047,39028],[37756,39484],[38779,40151],[39247,40451],[39601,40672],[40431,41182],[41010,41525],[41834,41995],[42190,42193],[42547,42387],[42540,42402],[42479,42516],[42420,42627],[42356,42749],[42344,42770],[42337,42784],[41729,42461],[40755,41926],[40118,41563],[39435,41161],[38968,40882],[38498,40595],[37200,39786],[36547,39382],[34547,38135],[34555,38122],[34595,38059],[34655,37964],[34726,37855],[34795,37745],[34863,37638],[34876,37618]]] }"#; - let geojson = GeoJson(geojson); + let mut geojson = GeoJson(geojson); let mvt = geojson.to_mvt_unscaled().unwrap(); assert_eq!( mvt.geometry, @@ -487,19 +540,10 @@ mod test { ); } - #[test] - #[cfg(feature = "with-geo")] - fn geo_screen_coords_to_mvt() -> Result<()> { - let geo: geo_types::Geometry = geo_types::Point::new(25.0, 17.0).into(); - let mvt = geo.to_mvt_unscaled()?; - assert_eq!(mvt.geometry, [9, 50, 34]); - Ok(()) - } - #[test] #[cfg(feature = "with-geo")] fn geo_to_mvt() -> Result<()> { - let geo: geo_types::Geometry = geo_types::Point::new(960000.0, 6002729.0).into(); + let mut geo = GeoJson(r#"{"type": "Point", "coordinates": [960000.0, 6002729.0]}"#); let mvt = geo.to_mvt(256, 958826.08, 5987771.04, 978393.96, 6007338.92)?; assert_eq!(mvt.geometry, [9, 30, 122]); let geojson = mvt.to_json()?; diff --git a/geozero/src/mvt/tag_builder.rs b/geozero/src/mvt/tag_builder.rs index ab1e0f30..5f1daebc 100644 --- a/geozero/src/mvt/tag_builder.rs +++ b/geozero/src/mvt/tag_builder.rs @@ -1,6 +1,5 @@ use crate::mvt::tile_value::TileValue; -use dup_indexer::{DupIndexer, PtrRead}; -use std::hash::Hash; +use dup_indexer::{DupIndexer, DupIndexerRefs, PtrRead}; /// A builder for key-value pairs, where the key is a `String` or `&str`, and the value is a /// [`TileValue`] enum which can hold any of the MVT value types. @@ -17,8 +16,8 @@ use std::hash::Hash; /// let (keys, values) = builder.into_tags(); /// # } #[derive(Debug)] -pub struct TagsBuilder { - keys: DupIndexer, +pub struct TagsBuilder { + keys: DupIndexerRefs, values: DupIndexer, } @@ -26,28 +25,35 @@ pub struct TagsBuilder { /// both of which are safe for `PtrRead`. unsafe impl PtrRead for TileValue {} -impl Default for TagsBuilder { +impl Default for TagsBuilder { fn default() -> Self { Self::new() } } -impl TagsBuilder { +impl TagsBuilder { pub fn new() -> Self { Self { - keys: DupIndexer::new(), + keys: DupIndexerRefs::new(), values: DupIndexer::new(), } } - pub fn insert(&mut self, key: K, value: TileValue) -> (u32, u32) { + pub fn insert(&mut self, key: String, value: TileValue) -> (u32, u32) { ( - self.keys.insert(key) as u32, + self.keys.insert_owned(key) as u32, self.values.insert(value) as u32, ) } - pub fn into_tags(self) -> (Vec, Vec) { + pub fn insert_ref(&mut self, key: &str, value: TileValue) -> (u32, u32) { + ( + self.keys.insert_ref(key) as u32, + self.values.insert(value) as u32, + ) + } + + pub fn into_tags(self) -> (Vec, Vec) { (self.keys.into_vec(), self.values.into_vec()) } } diff --git a/geozero/src/mvt/tile_value.rs b/geozero/src/mvt/tile_value.rs index 8d206a8f..99f286ee 100644 --- a/geozero/src/mvt/tile_value.rs +++ b/geozero/src/mvt/tile_value.rs @@ -1,4 +1,5 @@ use crate::mvt::tile::Value; +use crate::ColumnValue; use std::hash::Hash; /// A wrapper for the MVT value types. @@ -72,6 +73,38 @@ impl TryFrom for TileValue { } } +impl TryFrom<&ColumnValue<'_>> for TileValue { + type Error = (); + + fn try_from(v: &ColumnValue) -> Result { + // string_value - ColumnValue::String + // float_value - ColumnValue::Float + // double_value - ColumnValue::Double + // int_value - ColumnValue::Long + // uint_value - ColumnValue::ULong + // sint_value - ColumnValue::Long + // bool_value - ColumnValue::Bool + + Ok(match v { + ColumnValue::Byte(v) => TileValue::Sint(*v as i64), + ColumnValue::UByte(v) => TileValue::Uint(*v as u64), + ColumnValue::Bool(v) => TileValue::Bool(*v), + ColumnValue::Short(v) => TileValue::Sint(*v as i64), + ColumnValue::UShort(v) => TileValue::Uint(*v as u64), + ColumnValue::Int(v) => TileValue::Sint(*v as i64), + ColumnValue::UInt(v) => TileValue::Uint(*v as u64), + ColumnValue::Long(v) => TileValue::Sint(*v), + ColumnValue::ULong(v) => TileValue::Uint(*v), + ColumnValue::Float(v) => TileValue::Float(*v), + ColumnValue::Double(v) => TileValue::Double(*v), + ColumnValue::String(v) => TileValue::Str(v.to_string()), + ColumnValue::Json(v) => TileValue::Str(v.to_string()), + ColumnValue::DateTime(v) => TileValue::Str(v.to_string()), + ColumnValue::Binary(_) => Err(())?, + }) + } +} + // Treat floats as bits so that we can use as keys. // It is up to the users to ensure that the bits are not NaNs, or are consistent. diff --git a/geozero/tests/mvt.rs b/geozero/tests/mvt.rs index 490e2c0b..877d9aef 100644 --- a/geozero/tests/mvt.rs +++ b/geozero/tests/mvt.rs @@ -1,37 +1,12 @@ use geozero::mvt::{Message, Tile}; use geozero::{ ColumnValue, CoordDimensions, FeatureProcessor, GeomProcessor, GeozeroDatasource, - PropertyProcessor, ToJson, ToMvt, + PropertyProcessor, }; -use serde_json::json; use std::env; use std::fmt::Write; use std::sync::Mutex; -#[test] -fn geo_screen_coords_to_mvt() { - let geo: geo_types::Geometry = geo_types::Point::new(25.0, 17.0).into(); - let mvt = geo.to_mvt_unscaled().unwrap(); - assert_eq!(mvt.geometry, [9, 50, 34]); -} - -#[test] -fn geo_to_mvt() { - let geo: geo_types::Geometry = geo_types::Point::new(960000.0, 6002729.0).into(); - let mvt = geo - .to_mvt(256, 958826.08, 5987771.04, 978393.96, 6007338.92) - .unwrap(); - assert_eq!(mvt.geometry, [9, 30, 122]); - let geojson = mvt.to_json().unwrap(); - assert_eq!( - serde_json::from_str::(&geojson).unwrap(), - json!({ - "type": "Point", - "coordinates": [15,61] - }) - ); -} - type GzResult = geozero::error::Result<()>; struct Proc {