cartography 0.10.0

Cartography is a map rendering library for Geographic features expressed using [georust](https://georust.org/) libraries.
Documentation
use crate::{
  Feature as _, GeometryType,
  osm::OsmFeature,
  styling::{Checker, RenderingState},
};

use super::{TagValue, YamlFilter, YamlGeometry};

pub(super) fn compile_filter(filter: &YamlFilter) -> Checker<OsmFeature>
{
  let geometry = filter.geometry;
  let min_zoom = filter.min_zoom;
  let max_zoom = filter.max_zoom;
  let tag_filters: Vec<(String, Vec<String>)> = filter
    .tag
    .as_ref()
    .map(|tags| {
      tags
        .iter()
        .map(|(key, value)| {
          let values = match value
          {
            TagValue::String(v) => vec![v.clone()],
            TagValue::Sequence(vs) => vs.clone(),
          };
          (key.clone(), values)
        })
        .collect()
    })
    .unwrap_or_default();

  Box::new(
    move |rendering_state: &RenderingState, feature: &OsmFeature| {
      if let Some(geometry) = geometry
      {
        let expected = match geometry
        {
          YamlGeometry::Point => GeometryType::Point,
          YamlGeometry::Linestring => GeometryType::LineString,
          YamlGeometry::Polygon => GeometryType::Polygon,
          YamlGeometry::Multipolygon => GeometryType::Collection,
        };

        if feature.geometry_type() != expected
          && (feature.geometry_type() != GeometryType::Collection
            || feature.element_geometry_type() != expected)
        {
          return false;
        }
      }

      if let Some(min_zoom) = min_zoom
        && rendering_state.zoom_level < min_zoom
      {
        return false;
      }
      if let Some(max_zoom) = max_zoom
        && rendering_state.zoom_level > max_zoom
      {
        return false;
      }

      tag_filters.iter().all(|(key, expected_values)| {
        feature
          .tag(key)
          .is_some_and(|actual_value| expected_values.iter().any(|v| v == actual_value))
      })
    },
  )
}

#[cfg(test)]
mod tests
{
  use std::collections::HashMap;

  use super::*;

  fn make_feature(tags: Vec<(&str, &str)>, geometry: geo::Geometry) -> OsmFeature
  {
    OsmFeature {
      id: 1,
      geometry,
      tags: tags
        .into_iter()
        .map(|(k, v)| (k.to_owned(), v.to_owned()))
        .collect(),
    }
  }

  fn point() -> geo::Geometry
  {
    geo::Geometry::Point(geo::Point::new(0.0, 0.0))
  }

  fn linestring() -> geo::Geometry
  {
    geo::Geometry::LineString(geo::LineString::from(vec![(0.0, 0.0), (1.0, 1.0)]))
  }

  fn polygon() -> geo::Geometry
  {
    geo::Geometry::Polygon(geo::Polygon::new(
      geo::LineString::from(vec![(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)]),
      vec![],
    ))
  }

  fn rs(zoom_level: f64) -> RenderingState
  {
    RenderingState { zoom_level }
  }

  #[test]
  fn geometry_only_filter()
  {
    let filter = YamlFilter {
      geometry: Some(YamlGeometry::Polygon),
      tag: None,
      min_zoom: None,
      max_zoom: None,
    };
    let checker = compile_filter(&filter);
    assert!(checker(&rs(10.0), &make_feature(vec![], polygon())));
    assert!(!checker(&rs(10.0), &make_feature(vec![], linestring())));
  }

  #[test]
  fn single_value_tag_filter()
  {
    let filter = YamlFilter {
      geometry: None,
      tag: Some(HashMap::from([(
        "natural".to_string(),
        TagValue::String("water".to_string()),
      )])),
      min_zoom: None,
      max_zoom: None,
    };
    let checker = compile_filter(&filter);
    assert!(checker(
      &rs(10.0),
      &make_feature(vec![("natural", "water")], polygon())
    ));
    assert!(!checker(
      &rs(10.0),
      &make_feature(vec![("natural", "wood")], polygon())
    ));
  }

  #[test]
  fn list_value_tag_filter()
  {
    let filter = YamlFilter {
      geometry: None,
      tag: Some(HashMap::from([(
        "highway".to_string(),
        TagValue::Sequence(vec!["primary".to_string(), "trunk".to_string()]),
      )])),
      min_zoom: None,
      max_zoom: None,
    };
    let checker = compile_filter(&filter);
    assert!(checker(
      &rs(10.0),
      &make_feature(vec![("highway", "primary")], linestring())
    ));
    assert!(!checker(
      &rs(10.0),
      &make_feature(vec![("highway", "service")], linestring())
    ));
  }

  #[test]
  fn zoom_range_filter()
  {
    let filter = YamlFilter {
      geometry: None,
      tag: None,
      min_zoom: Some(8.0),
      max_zoom: Some(12.0),
    };
    let checker = compile_filter(&filter);
    let feature = make_feature(vec![], point());
    assert!(checker(&rs(10.0), &feature));
    assert!(!checker(&rs(7.0), &feature));
    assert!(!checker(&rs(13.0), &feature));
  }

  #[test]
  fn combined_filter()
  {
    let filter = YamlFilter {
      geometry: Some(YamlGeometry::Polygon),
      tag: Some(HashMap::from([(
        "natural".to_string(),
        TagValue::String("water".to_string()),
      )])),
      min_zoom: Some(8.0),
      max_zoom: Some(12.0),
    };
    let checker = compile_filter(&filter);
    assert!(checker(
      &rs(10.0),
      &make_feature(vec![("natural", "water")], polygon())
    ));
    assert!(!checker(
      &rs(10.0),
      &make_feature(vec![("natural", "water")], linestring())
    ));
    assert!(!checker(
      &rs(10.0),
      &make_feature(vec![("natural", "wood")], polygon())
    ));
  }
}