#![allow(dead_code)]
use super::layer::VectorTileLayer;
use anyhow::{Result, bail};
use versatiles_core::{
Blob,
io::{ValueReader, ValueReaderSlice, ValueWriter, ValueWriterBlob},
};
use versatiles_derive::context;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct VectorTile {
pub layers: Vec<VectorTileLayer>,
}
impl VectorTile {
#[must_use]
pub fn new(layers: Vec<VectorTileLayer>) -> VectorTile {
VectorTile { layers }
}
#[context("parsing VectorTile from Blob ({} bytes)", blob.len())]
pub fn from_blob(blob: &Blob) -> Result<VectorTile> {
let mut reader = ValueReaderSlice::new_le(blob.as_slice());
let mut tile = VectorTile::default();
while reader.has_remaining()? {
match reader.read_pbf_key().context("Failed to read PBF key")? {
(3, 2) => {
tile.layers.push(
VectorTileLayer::read(
reader
.get_pbf_sub_reader()
.context("Failed to get PBF sub-reader")?
.as_mut(),
)
.context("Failed to read VectorTileLayer")?,
);
}
(f, w) => bail!("Unexpected combination of field number ({f}) and wire type ({w})"),
}
}
Ok(tile)
}
#[context("serializing VectorTile to Blob")]
pub fn to_blob(&self) -> Result<Blob> {
let mut writer = ValueWriterBlob::new_le();
for layer in &self.layers {
writer.write_pbf_key(3, 2).context("Failed to write PBF key")?;
writer
.write_pbf_blob(&layer.to_blob().context("Failed to convert VectorTileLayer to blob")?)
.context("Failed to write PBF blob")?;
}
Ok(writer.into_blob())
}
#[must_use]
pub fn find_layer(&self, name: &str) -> Option<&VectorTileLayer> {
self.layers.iter().find(|layer| layer.name == name)
}
pub fn find_layer_mut(&mut self, name: &str) -> Option<&mut VectorTileLayer> {
self.layers.iter_mut().find(|layer| layer.name == name)
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::Context;
use std::env::current_dir;
use versatiles_core::io::{DataReaderFile, DataReaderTrait};
async fn get_pbf() -> Result<Blob> {
DataReaderFile::open(¤t_dir().unwrap().join("../testdata/shortbread-tile.pbf"))
.context("Failed to open PBF file")?
.read_all()
.await
.context("Failed to read all data from PBF file")
}
async fn tile() -> Result<VectorTile> {
VectorTile::from_blob(&get_pbf().await?).context("Failed to convert blob to VectorTile")
}
#[tokio::test]
async fn from_to_blob() -> Result<()> {
let tile1 = tile().await.context("Failed to get initial VectorTile")?;
let blob2 = tile1.to_blob().context("Failed to convert VectorTile to blob")?;
let tile2 = VectorTile::from_blob(&blob2).context("Failed to convert blob back to VectorTile")?;
assert_eq!(tile1, tile2);
Ok(())
}
#[tokio::test]
async fn find_layer_returns_first_match() -> Result<()> {
let tile = tile().await?;
let first_name = tile.layers.first().map(|l| l.name.clone()).expect("fixture has layers");
let found = tile.find_layer(&first_name).expect("layer present");
assert_eq!(found.name, first_name);
assert!(tile.find_layer("definitely-not-a-layer-name").is_none());
Ok(())
}
#[tokio::test]
async fn find_layer_mut_allows_modification() -> Result<()> {
let mut tile = tile().await?;
let first_name = tile.layers.first().map(|l| l.name.clone()).expect("fixture has layers");
let original_extent = tile.find_layer(&first_name).unwrap().extent;
tile.find_layer_mut(&first_name).unwrap().extent = original_extent + 1;
assert_eq!(tile.find_layer(&first_name).unwrap().extent, original_extent + 1);
Ok(())
}
#[test]
fn from_blob_rejects_unknown_top_level_field() {
let mut writer = ValueWriterBlob::new_le();
writer.write_pbf_key(1, 0).unwrap();
writer.write_varint(123).unwrap();
let blob = writer.into_blob();
let err = VectorTile::from_blob(&blob).unwrap_err();
let msg = format!("{err:#}");
assert!(msg.contains("field number"), "{msg}");
}
#[test]
fn empty_blob_decodes_to_empty_tile() {
let tile = VectorTile::from_blob(&Blob::new_empty()).unwrap();
assert!(tile.layers.is_empty());
}
#[test]
fn empty_tile_round_trips_via_blob() {
let tile = VectorTile::default();
let blob = tile.to_blob().unwrap();
assert!(blob.is_empty());
let decoded = VectorTile::from_blob(&blob).unwrap();
assert_eq!(tile, decoded);
}
}