use ezu_features::{Feature, Polygon, Value};
use ezu_style::{FeatureFilter, FilterAtom, FilterMatch, NotInner};
pub fn collect_polygons(
features: &[Feature],
filter: &Option<FeatureFilter>,
min_zoom_field: &Option<String>,
z: u8,
) -> Vec<Polygon> {
let mut out = Vec::new();
for f in features {
if !feature_passes(f, filter, min_zoom_field, z) {
continue;
}
out.extend(f.geometry.polygons.iter().cloned());
}
out
}
pub fn collect_points(
features: &[Feature],
filter: &Option<FeatureFilter>,
min_zoom_field: &Option<String>,
z: u8,
) -> Vec<(i32, i32)> {
let mut out = Vec::new();
for f in features {
if !feature_passes(f, filter, min_zoom_field, z) {
continue;
}
out.extend(f.geometry.points.iter().copied());
}
out
}
pub fn collect_lines(
features: &[Feature],
filter: &Option<FeatureFilter>,
min_zoom_field: &Option<String>,
z: u8,
) -> Vec<Vec<(i32, i32)>> {
let mut out = Vec::new();
for f in features {
if !feature_passes(f, filter, min_zoom_field, z) {
continue;
}
out.extend(f.geometry.lines.iter().cloned());
}
out
}
fn feature_passes(
f: &Feature,
filter: &Option<FeatureFilter>,
min_zoom_field: &Option<String>,
z: u8,
) -> bool {
if let Some(field) = min_zoom_field.as_ref() {
let ok = f
.properties
.get(field)
.and_then(value_as_i64)
.map(|mz| mz <= z as i64)
.unwrap_or(true); if !ok {
return false;
}
}
if let Some(filter) = filter.as_ref() {
for (k, expected) in filter {
match f.properties.get(k) {
Some(actual) => {
if !match_value(actual, expected) {
return false;
}
}
None => {
if !matches!(expected, FilterMatch::Not(_)) {
return false;
}
}
}
}
}
true
}
fn match_value(actual: &Value, expected: &FilterMatch) -> bool {
match expected {
FilterMatch::One(atom) => atom_equals(actual, atom),
FilterMatch::Any(atoms) => atoms.iter().any(|a| atom_equals(actual, a)),
FilterMatch::Not(n) => match &n.not {
NotInner::One(atom) => !atom_equals(actual, atom),
NotInner::Any(atoms) => !atoms.iter().any(|a| atom_equals(actual, a)),
},
}
}
fn atom_equals(actual: &Value, expected: &FilterAtom) -> bool {
match (actual, expected) {
(Value::String(a), FilterAtom::Str(b)) => a == b,
(Value::Bool(a), FilterAtom::Bool(b)) => a == b,
(Value::Int(a), FilterAtom::Int(b)) | (Value::SInt(a), FilterAtom::Int(b)) => a == b,
(Value::UInt(a), FilterAtom::Int(b)) => (*a as i64) == *b,
(Value::Float(a), FilterAtom::Float(b)) => (*a as f64) == *b,
(Value::Double(a), FilterAtom::Float(b)) => a == b,
(Value::Int(a), FilterAtom::Float(b)) | (Value::SInt(a), FilterAtom::Float(b)) => {
(*a as f64) == *b
}
_ => false,
}
}
fn value_as_i64(v: &Value) -> Option<i64> {
match v {
Value::Int(n) | Value::SInt(n) => Some(*n),
Value::UInt(n) => Some(*n as i64),
Value::Float(n) => Some(*n as i64),
Value::Double(n) => Some(*n as i64),
_ => None,
}
}