cartography 0.10.0

Cartography is a map rendering library for Geographic features expressed using [georust](https://georust.org/) libraries.
Documentation
use std::{ops::Deref, sync::Arc};

use crate::{geometry, map};

pub struct RenderingState
{
  pub zoom_level: f64,
}

/// Color
#[derive(Clone, Copy)]
pub struct Rgba(color::AlphaColor<color::Srgb>);

impl Deref for Rgba
{
  type Target = color::AlphaColor<color::Srgb>;
  fn deref(&self) -> &Self::Target
  {
    &self.0
  }
}

ccutils::assert_impl_all!(Rgba: Sync, Send);

impl Rgba
{
  /// Create a random color
  pub fn random() -> Self
  {
    rgba(rand::random(), rand::random(), rand::random(), 1.0)
  }
  /// Red
  pub fn red(&self) -> f32
  {
    self.0.components[0]
  }
  /// Green
  pub fn green(&self) -> f32
  {
    self.0.components[1]
  }
  /// Blue
  pub fn blue(&self) -> f32
  {
    self.0.components[2]
  }
  /// Alpha
  pub fn alpha(&self) -> f32
  {
    self.0.components[3]
  }
  /// Transparent color
  pub const TRANSPARENT: Self = rgba(0.0, 0.0, 0.0, 0.0);
  /// Black color
  pub const BLACK: Self = rgb(0.0, 0.0, 0.0);
  /// White color
  pub const WHITE: Self = rgb(1.0, 1.0, 1.0);
  /// Blue background of OSM map
  pub const OSM_BLUE: Self = rgb(170.0 / 255.0, 211.0 / 255.0, 223.0 / 255.0);
  /// "Whitish" background of OSM map
  pub const OSM_WHITE: Self = rgb(213.0 / 255.0, 210.0 / 255.0, 205.0 / 255.0);
}

/// Create a new Rgba color
pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Rgba
{
  Rgba(color::AlphaColor::<color::Srgb>::new([r, g, b, a]))
}
/// Create a new Rgb color (with a = 1.0, fully opaque).
pub const fn rgb(r: f32, g: f32, b: f32) -> Rgba
{
  rgba(r, g, b, 1.0)
}

pub(crate) type Checker<TFeature> =
  Box<dyn Fn(&RenderingState, &TFeature) -> bool + Sync + Send + 'static>;

/// Ruled used for sytling a map
pub struct Rule<TFeature: map::Feature>
{
  pub check: Checker<TFeature>,
  pub symbol: Symbol<TFeature>,
}

ccutils::assert_impl_all!(for(TFeature: map::Feature) Rule<TFeature>: Sync, Send);

/// Ruled builder used for building rules for a map
pub struct RuleBuilder<TFeature: map::Feature>
{
  geom_type: Option<geometry::GeometryType>,
  check: Option<Checker<TFeature>>,
  zoom_range: (Option<f64>, Option<f64>),
  pub symbol: Symbol<TFeature>,
  pub style_builder: StyleBuilder<TFeature>,
}

impl<TFeature: map::Feature + 'static> RuleBuilder<TFeature>
{
  /// Check for the given type
  pub fn geometry_type(mut self, geom_type: geometry::GeometryType) -> Self
  {
    self.geom_type = Some(geom_type);
    self
  }
  /// Add a functional check
  pub fn check<T>(
    mut self,
    check: impl Fn(&RenderingState, &T) -> bool + Sync + Send + 'static,
  ) -> Self
  where
    for<'a> &'a TFeature: TryInto<&'a T>,
  {
    self.check = Some(Box::new(move |rendering_state, feat| {
      let t: Option<&T> = feat.try_into().ok();
      match t
      {
        Some(t) => check(rendering_state, t),
        None => false,
      }
    }));
    self
  }
  /// Set the minimum zoom level
  pub fn min_zoom_level(mut self, level: f64) -> Self
  {
    let zoom_range = self.zoom_range;
    self.zoom_range = (Some(level), zoom_range.1);
    self
  }
  /// Set the minimum zoom level
  pub fn max_zoom_level(mut self, level: f64) -> Self
  {
    let zoom_range = self.zoom_range;
    self.zoom_range = (zoom_range.0, Some(level));
    self
  }
  /// Finish the rule and return the underlying stylebuilder
  pub fn finish_rule(self) -> StyleBuilder<TFeature>
  {
    let mut style_builder = self.style_builder;
    let geom_type = self.geom_type;
    let check = self.check;
    let zoom_range = self.zoom_range;
    style_builder.style.rules.push(Rule {
      check: Box::new(move |rendering_state, feat| {
        if let Some(geom_type) = geom_type
          && feat.geometry_type() != geom_type
          && (feat.geometry_type() != crate::GeometryType::Collection
            || feat.element_geometry_type() != geom_type)
        {
          return false;
        }
        if let Some(min_level) = zoom_range.0
          && rendering_state.zoom_level < min_level
        {
          return false;
        }
        if let Some(max_level) = zoom_range.1
          && rendering_state.zoom_level > max_level
        {
          return false;
        }
        if let Some(check) = &check
        {
          check(rendering_state, feat)
        }
        else
        {
          true
        }
      }),
      symbol: self.symbol,
    });
    style_builder
  }
}

/// Map Style, build using SyleBuilder.
pub struct Style<TFeature: map::Feature>
{
  pub(super) rules: Vec<Rule<TFeature>>,
  pub(super) background_color: Rgba,
}

ccutils::assert_impl_all!(for(TFeature: map::Feature) Style<TFeature>: Sync);
ccutils::assert_impl_all!(for(TFeature: map::Feature) Style<TFeature>: Send);

impl<TFeature: map::Feature> Style<TFeature>
{
  pub(super) fn new() -> Self
  {
    Self {
      rules: vec![],
      background_color: Rgba::TRANSPARENT,
    }
  }
  pub(crate) fn rules(&self) -> impl Iterator<Item = &Rule<TFeature>>
  {
    self.rules.iter()
  }
  pub(crate) fn background_color(&self) -> Rgba
  {
    self.background_color
  }
}

type Colorer<TFeature> = Box<dyn Fn(&RenderingState, &TFeature) -> Rgba + Sync + Send>;

/// Define color for symbols
#[derive(Clone)]
pub struct SymbolColor<TFeature: map::Feature>(Arc<Colorer<TFeature>>);

ccutils::assert_impl_all!(for(TFeature: map::Feature) SymbolColor<TFeature>: Sync);
ccutils::assert_impl_all!(for(TFeature: map::Feature) SymbolColor<TFeature>: Send);

impl<TFeature: map::Feature> Deref for SymbolColor<TFeature>
{
  type Target = Arc<Box<dyn Fn(&RenderingState, &TFeature) -> Rgba + Sync + Send>>;
  fn deref(&self) -> &Self::Target
  {
    &self.0
  }
}

impl<TFeature: map::Feature> SymbolColor<TFeature>
{
  /// Create a new colorization function
  pub fn new_from<T>(
    colorization: impl Fn(&RenderingState, &T) -> Rgba + Sync + Send + 'static,
  ) -> Self
  where
    for<'a> &'a TFeature: TryInto<&'a T>,
  {
    Self::new(move |rs, f| {
      let t = f.try_into().ok();
      match t
      {
        Some(t) => colorization(rs, t),
        None => Rgba::TRANSPARENT,
      }
    })
  }
  /// Create a new colorization function
  pub fn new(
    colorization: impl Fn(&RenderingState, &TFeature) -> Rgba + Sync + Send + 'static,
  ) -> Self
  {
    Self(Arc::new(Box::new(colorization)))
  }
  /// Create a symbol color that will return a random color.
  pub fn random() -> SymbolColor<TFeature>
  {
    Self::new(move |_, _| Rgba::random())
  }
}

impl<TFeature: map::Feature> From<Rgba> for SymbolColor<TFeature>
{
  fn from(val: Rgba) -> Self
  {
    SymbolColor::<TFeature>::new(move |_, _| val)
  }
}

/// Symbol on how to draw the feature
#[derive(Clone)]
pub struct Symbol<TFeature: map::Feature>
{
  /// Width of a stroke
  pub stroke_width: f64,
  /// Function that return the color for the stroke for the given feature
  pub stroke_color: SymbolColor<TFeature>,
  /// Function that return the color for the stroke for the given feature
  pub fill_color: SymbolColor<TFeature>,
  /// Radius of a point
  pub radius: f64,
  /// Optional text label configuration
  pub label: Option<LabelConfig<TFeature>>,
}

ccutils::assert_impl_all!(for(TFeature: map::Feature) Symbol<TFeature>: Sync);
ccutils::assert_impl_all!(for(TFeature: map::Feature) Symbol<TFeature>: Send);

impl<TFeature: map::Feature> Default for Symbol<TFeature>
{
  fn default() -> Self
  {
    Self {
      stroke_width: 0.0,
      stroke_color: Rgba::TRANSPARENT.into(),
      fill_color: Rgba::TRANSPARENT.into(),
      radius: 0.0,
      label: None,
    }
  }
}

pub type TextGetter<TFeature> = Arc<Box<dyn Fn(&TFeature) -> Option<String> + Send + Sync>>;

/// Text label configuration for a symbol.
#[derive(Clone)]
pub struct LabelConfig<TFeature: map::Feature>
{
  /// Function extracting label text from a feature.
  pub text: TextGetter<TFeature>,
  /// Font size in pixels.
  pub font_size: f32,
  /// Text color.
  pub color: SymbolColor<TFeature>,
  /// Halo color painted behind text.
  pub halo_color: SymbolColor<TFeature>,
  /// Minimum zoom level required to draw the label.
  pub min_zoom: f64,
}

ccutils::assert_impl_all!(for(TFeature: map::Feature) LabelConfig<TFeature>: Sync, Send);

/// Used for defining the style of the map
pub struct StyleBuilder<TFeature: map::Feature>
{
  style: Style<TFeature>,
}

impl<TFeature: map::Feature + 'static> Default for StyleBuilder<TFeature>
{
  fn default() -> Self
  {
    Self::new()
  }
}

impl<TFeature: map::Feature + 'static> StyleBuilder<TFeature>
{
  /// New style builder
  pub fn new() -> Self
  {
    Self {
      style: Style::new(),
    }
  }
  /// Add a rule
  pub fn add_rule(self, symbol: Symbol<TFeature>) -> RuleBuilder<TFeature>
  {
    RuleBuilder {
      check: None,
      geom_type: None,
      style_builder: self,
      zoom_range: (None, None),
      symbol,
    }
  }
  /// Add a rule that check the given feature has the given geometry.
  pub fn add_rule_for_type(self, typ: geometry::GeometryType, symbol: Symbol<TFeature>) -> Self
  {
    self.add_rule(symbol).geometry_type(typ).finish_rule()
  }
  /// Add a rule that check the given feature has the given geometry.
  pub fn add_rule_for_type_and<T>(
    self,
    typ: geometry::GeometryType,
    check: impl Fn(&RenderingState, &T) -> bool + Sync + Send + 'static,
    symbol: Symbol<TFeature>,
  ) -> Self
  where
    for<'a> &'a TFeature: TryInto<&'a T>,
  {
    self
      .add_rule(symbol)
      .geometry_type(typ)
      .check(check)
      .finish_rule()
  }
  /// Set the background color
  pub fn set_background_color(mut self, bg: Rgba) -> Self
  {
    self.style.background_color = bg;
    self
  }
}

impl<TFeature: map::Feature> From<StyleBuilder<TFeature>> for Style<TFeature>
{
  fn from(val: StyleBuilder<TFeature>) -> Self
  {
    val.style
  }
}