use super::{VectorTile, VectorTileLayer, validate_tile};
use anyhow::Result;
use std::collections::HashSet;
pub fn repair_tile(tile: VectorTile, drop_offenders: bool) -> Result<VectorTile> {
let issues = validate_tile(&tile);
if issues.is_empty() {
return Ok(tile);
}
let mut seen_names: HashSet<String> = HashSet::new();
let mut new_layers: Vec<VectorTileLayer> = Vec::with_capacity(tile.layers.len());
for mut layer in tile.layers {
if !seen_names.insert(layer.name.clone()) {
continue;
}
if layer.extent.is_none() {
layer.extent = Some(4096);
}
if layer.version.is_none() {
layer.version = Some(1);
}
let has_feature_issues = issues
.iter()
.any(|i| i.layer == layer.name && i.feature_index.is_some());
if has_feature_issues {
let extent = layer.extent.unwrap_or(4096);
let version = layer.version.unwrap_or(1);
let name = layer.name.clone();
let mut geo_features = Vec::with_capacity(layer.features.len());
let mut decode_failed = false;
for feature in &layer.features {
match feature.to_feature_lenient(&layer) {
Ok(gf) => geo_features.push(gf),
Err(_) if drop_offenders => {
}
Err(_) => {
decode_failed = true;
break;
}
}
}
if !decode_failed {
layer = VectorTileLayer::from_features(name, geo_features, extent, version)?;
}
}
new_layers.push(layer);
}
Ok(VectorTile::new(new_layers))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::vector_tile::feature::VectorTileFeature;
use crate::vector_tile::geometry_type::GeomType;
use crate::vector_tile::{IssueKind, VectorTileLayer, validate_tile};
use versatiles_core::Blob;
use versatiles_core::io::{ValueWriter, ValueWriterBlob};
fn raw_polygon_feature(rings: &[Vec<(i32, i32)>]) -> VectorTileFeature {
let mut writer = ValueWriterBlob::new_le();
let mut prev = (0i64, 0i64);
for ring in rings {
assert!(!ring.is_empty());
let (fx, fy) = ring[0];
let (ix, iy) = (i64::from(fx), i64::from(fy));
writer.write_varint((1 << 3) | 0x1).unwrap();
writer.write_svarint(ix - prev.0).unwrap();
writer.write_svarint(iy - prev.1).unwrap();
prev = (ix, iy);
let rest = ring.len() - 1;
if rest > 0 {
writer.write_varint(((rest as u64) << 3) | 0x2).unwrap();
for &(fx, fy) in &ring[1..] {
let (ix, iy) = (i64::from(fx), i64::from(fy));
writer.write_svarint(ix - prev.0).unwrap();
writer.write_svarint(iy - prev.1).unwrap();
prev = (ix, iy);
}
}
writer.write_varint(7).unwrap(); }
VectorTileFeature {
id: None,
tag_ids: vec![],
geom_type: GeomType::MultiPolygon,
geom_data: writer.into_blob(),
}
}
fn malformed_feature() -> VectorTileFeature {
VectorTileFeature {
id: None,
tag_ids: vec![],
geom_type: GeomType::MultiPolygon,
geom_data: Blob::from(vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
}
}
fn layer_with(name: &str, features: Vec<VectorTileFeature>) -> VectorTileLayer {
let mut layer = VectorTileLayer::new(name.to_string(), 4096, 1);
layer.features = features;
layer
}
#[test]
fn clean_tile_is_returned_unchanged() -> Result<()> {
let outer = vec![(0, 0), (4, 0), (4, 4), (0, 4)];
let feature = raw_polygon_feature(&[outer]);
let layer = layer_with("roads", vec![feature]);
let tile = VectorTile::new(vec![layer]);
let repaired = repair_tile(tile, false)?;
let issues = validate_tile(&repaired);
assert!(issues.is_empty(), "repaired tile should be clean: {issues:?}");
Ok(())
}
#[test]
fn repairs_missing_extent() -> Result<()> {
let mut layer = VectorTileLayer::new("l".to_string(), 4096, 1);
layer.extent = None;
let tile = VectorTile::new(vec![layer]);
let repaired = repair_tile(tile, false)?;
assert_eq!(repaired.layers[0].extent, Some(4096));
assert!(validate_tile(&repaired).is_empty());
Ok(())
}
#[test]
fn repairs_missing_version() -> Result<()> {
let mut layer = VectorTileLayer::new("l".to_string(), 4096, 1);
layer.version = None;
let tile = VectorTile::new(vec![layer]);
let repaired = repair_tile(tile, false)?;
assert_eq!(repaired.layers[0].version, Some(1));
assert!(validate_tile(&repaired).is_empty());
Ok(())
}
#[test]
fn drops_duplicate_layer_names() -> Result<()> {
let a = VectorTileLayer::new("roads".to_string(), 4096, 1);
let b = VectorTileLayer::new("roads".to_string(), 4096, 1);
let tile = VectorTile::new(vec![a, b]);
let repaired = repair_tile(tile, false)?;
assert_eq!(repaired.layers.len(), 1);
assert_eq!(repaired.layers[0].name, "roads");
assert!(validate_tile(&repaired).is_empty());
Ok(())
}
#[test]
fn repairs_orphan_inner_ring() -> Result<()> {
let orphan = vec![(0, 0), (0, 4), (4, 4), (4, 0)];
let feature = raw_polygon_feature(&[orphan]);
let layer = layer_with("l", vec![feature]);
let tile = VectorTile::new(vec![layer]);
let pre = validate_tile(&tile);
assert!(pre.iter().any(|i| i.kind == IssueKind::OrphanInnerRing));
let repaired = repair_tile(tile, false)?;
let post = validate_tile(&repaired);
assert!(
post.iter().all(|i| i.kind != IssueKind::OrphanInnerRing),
"orphan inner ring should be gone after repair: {post:?}"
);
Ok(())
}
#[test]
fn drop_offenders_true_removes_malformed_feature() -> Result<()> {
let good = raw_polygon_feature(&[vec![(0, 0), (4, 0), (4, 4), (0, 4)]]);
let layer = layer_with("l", vec![good, malformed_feature()]);
let tile = VectorTile::new(vec![layer]);
let repaired = repair_tile(tile, true)?;
assert_eq!(
repaired.layers[0].features.len(),
1,
"malformed feature should be dropped"
);
Ok(())
}
#[test]
fn drop_offenders_false_keeps_layer_geometry_on_decode_failure() -> Result<()> {
let good = raw_polygon_feature(&[vec![(0, 0), (4, 0), (4, 4), (0, 4)]]);
let layer = layer_with("l", vec![good, malformed_feature()]);
let original_feature_count = 2;
let tile = VectorTile::new(vec![layer]);
let repaired = repair_tile(tile, false)?;
assert_eq!(
repaired.layers[0].features.len(),
original_feature_count,
"features should be unchanged when rebuild is skipped"
);
Ok(())
}
#[test]
fn structural_fixes_applied_even_when_geometry_rebuild_skipped() -> Result<()> {
let mut layer = VectorTileLayer::new("l".to_string(), 4096, 1);
layer.extent = None;
layer.version = None;
layer.features = vec![malformed_feature()];
let tile = VectorTile::new(vec![layer]);
let repaired = repair_tile(tile, false)?;
assert_eq!(repaired.layers[0].extent, Some(4096));
assert_eq!(repaired.layers[0].version, Some(1));
Ok(())
}
}