Skip to content

Commit

Permalink
[breaking] Implement screen coord translation for MVT writer (#150)
Browse files Browse the repository at this point in the history
* Rename `to_mvt()` to `to_mvt_unscaled()`
* Add a new `to_mvt(...)`
---------

Co-authored-by: Yuri Astrakhan <[email protected]>
  • Loading branch information
pka and nyurik committed Aug 9, 2023
1 parent 78672a0 commit 1023c28
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 40 deletions.
2 changes: 1 addition & 1 deletion geozero/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ required-features = ["with-geojson"]
[[test]]
name = "mvt"
path = "tests/mvt.rs"
required-features = ["with-mvt"]
required-features = ["with-mvt", "with-geo", "with-geojson"]

[[test]]
name = "polylabel"
Expand Down
35 changes: 31 additions & 4 deletions geozero/src/mvt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,39 @@ pub(crate) mod conversion {
/// Convert to MVT geometry.
pub trait ToMvt {
/// Convert to MVT geometry.
fn to_mvt(&self) -> Result<tile::Feature>;
///
/// # Arguments
/// * `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,
extent: u32,
left: f64,
bottom: f64,
right: f64,
top: f64,
) -> Result<tile::Feature>;

/// Convert to MVT geometry with geometries in unmodified tile coordinate space.
fn to_mvt_unscaled(&self) -> Result<tile::Feature>;
}

impl<T: GeozeroGeometry> ToMvt for T {
fn to_mvt(&self) -> Result<tile::Feature> {
let mut mvt = MvtWriter::new();
fn to_mvt(
&self,
extent: u32,
left: f64,
bottom: f64,
right: f64,
top: f64,
) -> Result<tile::Feature> {
let mut mvt = MvtWriter::new(extent, left, bottom, right, top);
self.process_geom(&mut mvt)?;
Ok(mvt.feature)
}

fn to_mvt_unscaled(&self) -> Result<tile::Feature> {
let mut mvt = MvtWriter::default();
self.process_geom(&mut mvt)?;
Ok(mvt.feature)
}
Expand All @@ -51,7 +78,7 @@ mod wkb {

impl FromWkb for tile::Feature {
fn from_wkb<R: Read>(rdr: &mut R, dialect: WkbDialect) -> Result<Self> {
let mut mvt = MvtWriter::new();
let mut mvt = MvtWriter::default();
crate::wkb::process_wkb_type_geom(rdr, &mut mvt, dialect)?;
Ok(mvt.feature)
}
Expand Down
93 changes: 61 additions & 32 deletions geozero/src/mvt/mvt_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,43 @@ use crate::GeomProcessor;
use super::mvt_error::MvtError;

/// Generator for MVT geometry type.
#[derive(Default, Debug)]
pub struct MvtWriter {
pub(crate) feature: tile::Feature,
// Extent, 0 for unscaled
extent: i32,
// Scale geometry to bounds
left: f64,
bottom: f64,
x_multiplier: f64,
y_multiplier: f64,
// Writer state
last_x: i32,
last_y: i32,
line_state: LineState,
is_multiline: bool,
}

#[derive(PartialEq)]
#[derive(Default, Debug, PartialEq)]
enum LineState {
#[default]
None,
// Issue LineTo command after first point
Line(usize),
Ring(usize),
}

impl MvtWriter {
pub fn new() -> MvtWriter {
Self::default()
pub fn new(extent: u32, left: f64, bottom: f64, right: f64, top: f64) -> MvtWriter {
assert_ne!(extent, 0);
MvtWriter {
extent: extent as i32,
left,
bottom,
x_multiplier: (extent as f64) / (right - left),
y_multiplier: (extent as f64) / (top - bottom),
..Default::default()
}
}

pub fn geometry(&self) -> &tile::Feature {
Expand All @@ -44,20 +62,8 @@ impl MvtWriter {
}
}

impl Default for MvtWriter {
fn default() -> Self {
Self {
feature: tile::Feature::default(),
last_x: 0,
last_y: 0,
line_state: LineState::None,
is_multiline: false,
}
}
}

impl GeomProcessor for MvtWriter {
fn xy(&mut self, x: f64, y: f64, idx: usize) -> Result<()> {
fn xy(&mut self, x_coord: f64, y_coord: f64, idx: usize) -> Result<()> {
// Omit last coord of ring (emit ClosePath instead)
let last_ring_coord = if let LineState::Ring(size) = self.line_state {
idx == size - 1
Expand All @@ -66,8 +72,16 @@ impl GeomProcessor for MvtWriter {
};

if !last_ring_coord {
let x = x as i32;
let y = y as i32;
let (x, y) = if self.extent != 0 {
// scale to tile coordinate space
let x = ((x_coord - self.left) * self.x_multiplier) as i32;
let y = ((y_coord - self.bottom) * self.y_multiplier) as i32;
// Y is stored as reversed
(x, self.extent.saturating_sub(y))
} else {
// unscaled
(x_coord as i32, y_coord as i32)
};
self.feature
.geometry
.push(ParameterInteger::from(x.saturating_sub(self.last_x)));
Expand Down Expand Up @@ -377,30 +391,31 @@ mod test_mvt {
#[cfg(feature = "with-geojson")]
mod test {
use super::*;
use crate::geojson::conversion::ToJson;
use crate::geojson::GeoJson;
use crate::ToMvt;
use std::convert::TryFrom;
use serde_json::json;

// https://github.com/mapbox/vector-tile-spec/tree/master/2.1#435-example-geometry-encodings

#[test]
fn point_geom() {
let geojson = GeoJson(r#"{"type": "Point", "coordinates": [25, 17]}"#);
let mvt = geojson.to_mvt().unwrap();
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 mvt = geojson.to_mvt().unwrap();
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 mvt = geojson.to_mvt().unwrap();
let mvt = geojson.to_mvt_unscaled().unwrap();
assert_eq!(mvt.geometry, [9, 4, 4, 18, 0, 16, 16, 0]);
}

Expand All @@ -409,7 +424,7 @@ mod test {
let geojson = GeoJson(
r#"{"type": "MultiLineString", "coordinates": [[[2,2], [2,10], [10,10]],[[1,1],[3,5]]]}"#,
);
let mvt = geojson.to_mvt().unwrap();
let mvt = geojson.to_mvt_unscaled().unwrap();
assert_eq!(
mvt.geometry,
[9, 4, 4, 18, 0, 16, 16, 0, 9, 17, 17, 10, 4, 8]
Expand All @@ -420,7 +435,7 @@ mod test {
fn polygon_geom() {
let geojson =
GeoJson(r#"{"type": "Polygon", "coordinates": [[[3, 6], [8, 12], [20, 34], [3, 6]]]}"#);
let mvt = geojson.to_mvt().unwrap();
let mvt = geojson.to_mvt_unscaled().unwrap();
assert_eq!(mvt.geometry, [9, 6, 12, 18, 10, 12, 24, 44, 15]);
}

Expand All @@ -443,7 +458,7 @@ mod test {
]
}"#;
let geojson = GeoJson(geojson);
let mvt = geojson.to_mvt().unwrap();
let mvt = geojson.to_mvt_unscaled().unwrap();
assert_eq!(
mvt.geometry,
[
Expand All @@ -452,15 +467,14 @@ mod test {
]
);
}

#[test]
fn big_number_geom() {
let geojson = r#"{
"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 mvt = geojson.to_mvt().unwrap();
let mvt = geojson.to_mvt_unscaled().unwrap();
assert_eq!(
mvt.geometry,
[
Expand All @@ -475,12 +489,27 @@ mod test {

#[test]
#[cfg(feature = "with-geo")]
fn geo_to_mvt() -> Result<()> {
use std::str::FromStr;
let geo =
geo_types::Geometry::try_from(wkt::Wkt::from_str("POINT (25 17)").unwrap()).unwrap();
let mvt = geo.to_mvt()?;
fn geo_screen_coords_to_mvt() -> Result<()> {
let geo: geo_types::Geometry<f64> = 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<f64> = geo_types::Point::new(960000.0, 6002729.0).into();
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()?;
assert_eq!(
serde_json::from_str::<serde_json::Value>(&geojson).unwrap(),
json!({
"type": "Point",
"coordinates": [15,61]
}) // without reverse_y: [15,195]
);
Ok(())
}
}
30 changes: 27 additions & 3 deletions geozero/tests/mvt.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,37 @@
use geozero::mvt::Message;
use geozero::mvt::Tile;
use geozero::mvt::{Message, Tile};
use geozero::{
ColumnValue, CoordDimensions, FeatureProcessor, GeomProcessor, GeozeroDatasource,
PropertyProcessor,
PropertyProcessor, ToJson, ToMvt,
};
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<f64> = 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<f64> = 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::<serde_json::Value>(&geojson).unwrap(),
json!({
"type": "Point",
"coordinates": [15,61]
})
);
}

type GzResult = geozero::error::Result<()>;

struct Proc {
Expand Down

0 comments on commit 1023c28

Please sign in to comment.