use serde_json::Value;
fn tolerance_for_zoom(zoom: Option<u8>) -> Option<f64> {
match zoom {
Some(z) if z <= 4 => Some(0.05),
Some(z) if z <= 6 => Some(0.01),
Some(z) if z <= 8 => Some(0.005),
Some(z) if z <= 10 => Some(0.001),
Some(z) if z <= 12 => Some(0.0005),
_ => None, }
}
pub fn simplify_features(features: &mut [Value], zoom: Option<u8>) {
let Some(tolerance) = tolerance_for_zoom(zoom) else {
return;
};
for feature in features.iter_mut() {
if let Some(geometry) = feature.get_mut("geometry") {
simplify_geometry(geometry, tolerance);
}
}
}
fn simplify_geometry(geometry: &mut Value, tolerance: f64) {
let Ok(ir) = geonative_geojson::geometry::from_json(geometry) else {
return;
};
let simplified = geonative_utils::simplify::simplify_geometry(&ir, tolerance);
let result = geonative_geojson::geometry::to_json(&simplified);
if let Some(coords) = result.get("coordinates") {
geometry["coordinates"] = coords.clone();
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn simplify_reduces_polygon_coordinates_at_low_zoom() {
let coords: Vec<Value> = (0..100)
.map(|i| {
let angle = (i as f64) * std::f64::consts::TAU / 100.0;
let r = 1.0 + (i as f64 * 0.001); json!([r * angle.cos(), r * angle.sin()])
})
.collect();
let mut closed = coords.clone();
closed.push(coords[0].clone());
let mut features = vec![json!({
"type": "Feature",
"properties": {},
"geometry": {
"type": "Polygon",
"coordinates": [closed]
}
})];
let original_len = features[0]["geometry"]["coordinates"][0]
.as_array()
.unwrap()
.len();
simplify_features(&mut features, Some(4));
let simplified_len = features[0]["geometry"]["coordinates"][0]
.as_array()
.unwrap()
.len();
assert!(
simplified_len < original_len,
"expected simplification: {simplified_len} should be < {original_len}"
);
assert!(simplified_len >= 4);
}
#[test]
fn simplify_preserves_points() {
let mut features = vec![json!({
"type": "Feature",
"properties": {},
"geometry": {
"type": "Point",
"coordinates": [106.8, -6.2]
}
})];
let before = features[0].clone();
simplify_features(&mut features, Some(4));
assert_eq!(features[0], before);
}
#[test]
fn simplify_skips_at_high_zoom() {
let coords: Vec<Value> = (0..50)
.map(|i| json!([i as f64 * 0.01, i as f64 * 0.01]))
.collect();
let mut features = vec![json!({
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": coords
}
})];
let before = features[0].clone();
simplify_features(&mut features, Some(15));
assert_eq!(features[0], before, "high zoom should not simplify");
}
#[test]
fn simplify_no_zoom_skips() {
let mut features = vec![json!({
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [[0, 0], [1, 1], [2, 0]]
}
})];
let before = features[0].clone();
simplify_features(&mut features, None);
assert_eq!(features[0], before, "no zoom should not simplify");
}
#[test]
fn simplify_reduces_collinear_linestring() {
let mut features = vec![json!({
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [[0.0, 0.0], [0.5, 0.0001], [1.0, 0.0]]
}
})];
simplify_features(&mut features, Some(4)); let coords = features[0]["geometry"]["coordinates"].as_array().unwrap();
assert_eq!(coords.len(), 2);
}
#[test]
fn simplify_keeps_significant_deviation() {
let mut features = vec![json!({
"type": "Feature",
"properties": {},
"geometry": {
"type": "LineString",
"coordinates": [[0.0, 0.0], [0.5, 1.0], [1.0, 0.0]]
}
})];
simplify_features(&mut features, Some(4)); let coords = features[0]["geometry"]["coordinates"].as_array().unwrap();
assert_eq!(coords.len(), 3);
}
}