#![allow(dead_code)]
use super::{geometry_type::GeomType, layer::VectorTileLayer};
use crate::geo::{
CompositeGeometryTrait, Coordinates, GeoFeature, GeoProperties, GeoValue, Geometry, GeometryTrait,
MultiLineStringGeometry, MultiPointGeometry, MultiPolygonGeometry, RingGeometry, SingleGeometryTrait,
};
use anyhow::{Context, Result, bail, ensure};
use byteorder::LE;
use versatiles_core::{
Blob,
io::{ValueReader, ValueReaderSlice, ValueWriter, ValueWriterBlob},
utils::float_to_int,
};
#[derive(Clone, Debug, PartialEq)]
pub struct VectorTileFeature {
pub id: Option<u64>,
pub tag_ids: Vec<u32>,
pub geom_type: GeomType,
pub geom_data: Blob,
}
impl Default for VectorTileFeature {
fn default() -> Self {
VectorTileFeature {
id: None,
tag_ids: Vec::new(),
geom_type: GeomType::Unknown,
geom_data: Blob::new_empty(),
}
}
}
impl VectorTileFeature {
pub fn read(reader: &mut dyn ValueReader<'_, LE>) -> Result<VectorTileFeature> {
let mut f = VectorTileFeature::default();
while reader.has_remaining() {
match reader.read_pbf_key().context("Failed to read PBF key")? {
(1, 0) => f.id = Some(reader.read_varint().context("Failed to read feature ID")?),
(2, 2) => f.tag_ids = reader.read_pbf_packed_uint32().context("Failed to read tag IDs")?,
(3, 0) => f.geom_type = GeomType::from(reader.read_varint().context("Failed to read geometry type")?),
(4, 2) => f.geom_data = reader.read_pbf_blob().context("Failed to read geometry data")?,
(f, w) => bail!("Unexpected combination of field number ({f}) and wire type ({w})"),
}
}
Ok(f)
}
pub fn to_blob(&self) -> Result<Blob> {
let mut writer = ValueWriterBlob::new_le();
if let Some(id) = self.id {
writer
.write_pbf_key(1, 0)
.context("Failed to write PBF key for feature ID")?;
writer.write_varint(id).context("Failed to write feature ID")?;
}
if !self.tag_ids.is_empty() {
writer
.write_pbf_key(2, 2)
.context("Failed to write PBF key for tag IDs")?;
writer
.write_pbf_packed_uint32(&self.tag_ids)
.context("Failed to write tag IDs")?;
}
writer
.write_pbf_key(3, 0)
.context("Failed to write PBF key for geometry type")?;
writer
.write_varint(self.geom_type.as_u64())
.context("Failed to write geometry type")?;
if !self.geom_data.is_empty() {
writer
.write_pbf_key(4, 2)
.context("Failed to write PBF key for geometry data")?;
writer
.write_pbf_blob(&self.geom_data)
.context("Failed to write geometry data")?;
}
Ok(writer.into_blob())
}
pub fn to_geometry(&self) -> Result<Geometry> {
let coordinates = {
let mut reader = ValueReaderSlice::new_le(self.geom_data.as_slice());
let mut lines: Vec<Vec<Coordinates>> = Vec::new();
let mut line: Vec<Coordinates> = Vec::new();
let mut x = 0;
let mut y = 0;
while reader.has_remaining() {
let value = reader
.read_varint()
.context("Failed to read varint for geometry command")?;
let command = value & 0x7;
let count = value >> 3;
match command {
1 | 2 => {
for _ in 0..count {
if command == 1 && !line.is_empty() {
lines.push(line);
line = Vec::new();
}
x += reader.read_svarint().context("Failed to read x coordinate")?;
y += reader.read_svarint().context("Failed to read y coordinate")?;
line.push(Coordinates::new(x as f64, y as f64));
}
}
7 => {
ensure!(!line.is_empty(), "ClosePath command found on an empty linestring");
line.push(line[0].clone());
}
_ => bail!("Unknown command {command}"),
}
}
if !line.is_empty() {
lines.push(line);
}
lines
};
match self.geom_type {
GeomType::Unknown => bail!("Unknown geometry type"),
GeomType::MultiPoint => {
ensure!(!coordinates.is_empty(), "(Multi)Points must not be empty");
Ok(Geometry::new_multi_point(
coordinates
.into_iter()
.map(|mut point| {
ensure!(point.len() == 1, "(Multi)Point entries must have exactly one entry");
Ok(point.pop().unwrap())
})
.collect::<Result<Vec<Coordinates>>>()?,
))
}
GeomType::MultiLineString => {
ensure!(!coordinates.is_empty(), "MultiLineStrings must have at least one entry");
let f = Geometry::new_multi_line_string(coordinates);
f.verify().context("Invalid MultiLineString")?;
Ok(f)
}
GeomType::MultiPolygon => {
ensure!(!coordinates.is_empty(), "Polygons must have at least one entry");
let mut current_polygon = Vec::new();
let mut polygons = Vec::new();
for ring in coordinates {
let ring = RingGeometry(ring);
ring.verify().context("Invalid ring in Polygon")?;
let area = ring.area();
if area > 1e-14 {
if !current_polygon.is_empty() {
polygons.push(current_polygon);
current_polygon = Vec::new();
}
current_polygon.push(ring);
} else if area < -1e-14 {
if current_polygon.is_empty() {
log::trace!("An outer ring must precede inner rings");
} else {
current_polygon.push(ring);
}
} else {
log::trace!("Error: Ring with zero area");
}
}
if !current_polygon.is_empty() {
polygons.push(current_polygon);
}
Ok(Geometry::new_multi_polygon(polygons))
}
}
}
pub fn decode_properties(&self, layer: &VectorTileLayer) -> Result<GeoProperties> {
layer.decode_tag_ids(&self.tag_ids)
}
pub fn to_feature(&self, layer: &VectorTileLayer) -> Result<GeoFeature> {
let mut feature = GeoFeature::new(self.to_geometry().context("Failed to convert to geometry")?);
if let Some(id) = self.id {
feature.set_id(GeoValue::from(id));
}
feature.properties = layer.decode_tag_ids(&self.tag_ids)?;
Ok(feature)
}
pub fn from_geometry(id: Option<u64>, tag_ids: Vec<u32>, geometry: Geometry) -> Result<VectorTileFeature> {
fn write_coord(writer: &mut ValueWriterBlob<LE>, coord0: &mut (i64, i64), coord: &Coordinates) -> Result<()> {
let x = float_to_int(coord.x())?;
let y = float_to_int(coord.y())?;
writer.write_svarint(x - coord0.0)?;
writer.write_svarint(y - coord0.1)?;
coord0.0 = x;
coord0.1 = y;
Ok(())
}
fn write_points(points: MultiPointGeometry) -> Result<Blob> {
let mut writer = ValueWriterBlob::new_le();
let point0 = &mut (0i64, 0i64);
writer.write_varint(((points.len() as u64) << 3) | 0x1)?;
for point in points.into_iter() {
write_coord(&mut writer, point0, point.as_coord())?;
}
Ok(writer.into_blob())
}
fn write_line_strings(line_strings: MultiLineStringGeometry) -> Result<Blob> {
let mut writer = ValueWriterBlob::new_le();
let point0 = &mut (0i64, 0i64);
for line_string in line_strings.into_iter() {
if line_string.is_empty() {
continue;
}
let (first, rest) = line_string.into_first_and_rest().unwrap();
writer.write_varint((1 << 3) | 0x1)?; write_coord(&mut writer, point0, &first)?;
if !rest.is_empty() {
writer.write_varint(((rest.len() as u64) << 3) | 0x2)?; for point in rest {
write_coord(&mut writer, point0, &point)?;
}
}
}
Ok(writer.into_blob())
}
fn write_polygons(polygons: MultiPolygonGeometry) -> Result<Blob> {
let mut writer = ValueWriterBlob::new_le();
let point0 = &mut (0i64, 0i64);
for polygon in polygons.into_iter() {
for ring in polygon.into_iter() {
if ring.len() < 4 {
continue;
}
let (first, mut rest) = ring.into_first_and_rest().unwrap();
rest.pop();
writer.write_varint((1 << 3) | 0x1)?; write_coord(&mut writer, point0, &first)?;
if !rest.is_empty() {
writer.write_varint(((rest.len() as u64) << 3) | 0x2)?; for point in rest {
write_coord(&mut writer, point0, &point)?;
}
}
writer.write_varint(7)?; }
}
Ok(writer.into_blob())
}
fn m<T>(g: &[T]) -> Vec<&T> {
g.iter().collect()
}
use crate::geo::Geometry::{LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon};
let (geom_type, geom_data) = match geometry {
Point(g) => (GeomType::MultiPoint, write_points(g.into_multi())?),
MultiPoint(g) => (GeomType::MultiPoint, write_points(g)?),
LineString(g) => (GeomType::MultiLineString, write_line_strings(g.into_multi())?),
MultiLineString(g) => (GeomType::MultiLineString, write_line_strings(g)?),
Polygon(g) => (GeomType::MultiPolygon, write_polygons(g.into_multi())?),
MultiPolygon(g) => (GeomType::MultiPolygon, write_polygons(g)?),
};
Ok(VectorTileFeature {
id,
tag_ids,
geom_type,
geom_data,
})
}
#[cfg(test)]
pub fn new_example() -> Self {
VectorTileFeature::from_geometry(Some(3), vec![1, 2], Geometry::new_example()).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round_trip_feature(geometry: Geometry) -> Result<()> {
let vector_tile_feature = VectorTileFeature::from_geometry(None, vec![], geometry.clone())?;
assert_eq!(geometry.into_multi_geometry(), vector_tile_feature.to_geometry()?);
Ok(())
}
#[test]
fn point_geometry_round_trip() -> Result<()> {
let geometry = Geometry::new_point(&[1, 2]);
round_trip_feature(geometry)
}
#[test]
fn line_string_geometry_round_trip() -> Result<()> {
let geometry = Geometry::new_line_string(&[[0, 1], [0, 3]]);
round_trip_feature(geometry)
}
#[test]
fn polygon_geometry_round_trip() -> Result<()> {
let geometry = Geometry::new_polygon(&[
vec![[0, 0], [3, 0], [3, 3], [0, 3], [0, 0]],
vec![[1, 1], [1, 2], [2, 2], [1, 1]],
]);
round_trip_feature(geometry)
}
#[test]
fn multi_point_geometry_round_trip() -> Result<()> {
let geometry = Geometry::new_multi_point(&[[2, 3], [4, 5]]);
round_trip_feature(geometry)
}
#[test]
fn multi_line_string_geometry_round_trip() -> Result<()> {
let geometry = Geometry::new_multi_line_string(&[vec![[0, 0], [1, 1], [2, 0]], vec![[0, 2], [1, 1], [2, 2]]]);
round_trip_feature(geometry)
}
#[test]
fn multi_polygon_geometry_round_trip() -> Result<()> {
let geometry = Geometry::new_multi_polygon(&[
vec![
vec![[0, 0], [3, 0], [3, 3], [0, 3], [0, 0]],
vec![[1, 1], [1, 2], [2, 2], [1, 1]],
],
vec![
vec![[4, 0], [7, 0], [7, 3], [4, 3], [4, 0]],
vec![[5, 1], [5, 2], [6, 2], [5, 1]],
],
]);
round_trip_feature(geometry)
}
}