cartography 0.11.1

Cartography is a map rendering library for Geographic features expressed using [georust](https://georust.org/) libraries.
Documentation
use std::fmt::Debug;

use geo::{BoundingRect as _, Distance, MapCoords, geometry};

use crate::{
  BoxedImageDataRef, ImageFeature, Projection, Result, YAxisDirection, map,
  styling::{self},
  transform,
};

mod rect;

/// View controller, handles conversion between map coordinate and display coordinate
#[derive(Debug)]
pub struct ViewController
{
  projection: Projection,
  /// Surface of the view in projection
  map_rect: geo::Rect,
  /// Surface of the view in pixel
  view_rect: geo::Rect,
}

impl ViewController
{
  /// Set the projection used by the view.
  pub fn set_projection(&mut self, proj: Projection) -> Result<()>
  {
    self.map_rect = transform(&self.projection, &proj, &self.map_rect)?;
    self.projection = proj;
    Ok(())
  }
  /// Show the extent of the map
  pub fn show_extent(map_rect: geo::Rect, view_rect: geo::Rect) -> Self
  {
    Self {
      projection: Projection::wgs84(),
      map_rect,
      view_rect,
    }
  }
  fn coord_to_view(&self, pt: &geo::Coord, y_axis_direction: YAxisDirection) -> geo::Coord
  {
    let x = pt.x;
    let y = pt.y;

    let x = self.view_rect.min().x
      + (x - self.map_rect.min().x) / self.map_rect.width() * self.view_rect.width();
    let y = match y_axis_direction
    {
      YAxisDirection::South =>
      {
        self.view_rect.min().y
          + (y - self.map_rect.min().y) / self.map_rect.height() * self.view_rect.height()
      }
      YAxisDirection::North =>
      {
        self.view_rect.max().y
          - (y - self.map_rect.min().y) / self.map_rect.height() * self.view_rect.height()
      }
    };

    geo::Coord { x, y }
  }
  pub(crate) fn map_rect_ref(&self) -> &geo::Rect
  {
    &self.map_rect
  }
  /// Return the rect view of the map, a.k.a, the part of the map that is displayed
  pub fn map_rect(&self) -> geo::Rect
  {
    self.map_rect
  }
  /// Return the rect view of the map, a.k.a, the part of the map that is displayed
  pub fn set_map_rect(&mut self, rect: geo::Rect)
  {
    self.map_rect = rect
  }
  #[allow(dead_code)]
  pub(crate) fn view_to_coord(&self, pt: &geo::Coord) -> geo::Coord
  {
    let motion = self.motion_view_to_coord(pt);
    geo::Coord {
      x: self.map_rect.min().x + motion.x,
      y: self.map_rect.min().y + motion.y,
    }
  }
  /// Return the motion in geographic coordinate from an input in pixels
  #[allow(dead_code)]
  pub(crate) fn motion_view_to_coord(&self, m: &geo::Coord) -> geo::Coord
  {
    let x = m.x;
    let y = m.y;

    geo::Coord {
      x: (x - self.view_rect.min().x) / (self.view_rect.width()) * (self.map_rect.width()),
      y: (y - self.view_rect.min().y) / (self.view_rect.height()) * (self.map_rect.height()),
    }
  }
  #[allow(dead_code)]
  pub(crate) fn set_view(&mut self, view_rect: geo::Rect)
  {
    self.view_rect = view_rect;
  }
  fn geometry_to_view(
    &self,
    projection: &Projection,
    geo: geometry::Geometry,
  ) -> crate::Result<geometry::Geometry>
  {
    geo.try_map_coords(|coord| {
      let coord = {
        if projection == &self.projection
        {
          coord
        }
        else
        {
          transform(projection, &self.projection, &coord)?
        }
      };
      Ok(self.coord_to_view(&coord, projection.y_axis_direction()))
    })
  }
  fn rect_to_view(&self, rect: geo::Rect, y_axis_direction: YAxisDirection) -> geo::Rect
  {
    let top_left = self.coord_to_view(&rect.min(), y_axis_direction);
    let bottom_right = self.coord_to_view(&rect.max(), y_axis_direction);
    geo::Rect::new(top_left, bottom_right)
  }

  #[allow(dead_code)]
  pub(crate) fn zoom(&mut self, factor: f64)
  {
    let (x, y) = self.map_rect.center().x_y();
    let w = self.map_rect.width();
    let h = self.map_rect.height();

    self.map_rect = rect::from_center_size(x, y, factor * w, factor * h);
  }
  #[allow(dead_code)]
  pub(crate) fn pan(&mut self, x: f64, y: f64)
  {
    self.map_rect = match self.projection.y_axis_direction()
    {
      YAxisDirection::North => rect::pan(&self.map_rect, x, -y),
      YAxisDirection::South => rect::pan(&self.map_rect, x, y),
    }
  }
  pub(crate) fn view_rect(&self) -> geo::Rect
  {
    self.view_rect.to_owned()
  }
  /// Return the zoom level, as calculated for OpenStreetMap.
  /// See https://wiki.openstreetmap.org/wiki/Zoom_levels
  pub fn zoom_level(&self) -> f64
  {
    let map_rect_wgs = transform(&self.projection, &Projection::wgs84(), &self.map_rect).unwrap();
    let c = 40075016.686;
    let left_right_distance = if map_rect_wgs.min().x == -180.0 && map_rect_wgs.max().x == 180.0
    {
      c
    }
    else
    {
      geo::Haversine.distance(
        geo::Point::new(map_rect_wgs.min().x, 0.0),
        geo::Point::new(map_rect_wgs.max().x, 0.0),
      )
    };
    let spixel = left_right_distance / (self.view_rect.max().x - self.view_rect.min().x);
    (c / spixel).ln() / 2.0f64.ln() - 8.0
  }
}

/// Trait for rendering maps
pub trait MapRenderer<TFeature: crate::Feature>
{
  /// Draw a point feature.
  fn draw_point(
    &mut self,
    rendering_state: &styling::RenderingState,
    feature: &TFeature,
    point: &geo::Point,
    symbol: &styling::Symbol<TFeature>,
  );
  /// Draw a polygon feature.
  fn draw_polygon(
    &mut self,
    rendering_state: &styling::RenderingState,
    feature: &TFeature,
    polygon: &geo::Polygon,
    symbol: &styling::Symbol<TFeature>,
  );
  /// Draw a line-string feature.
  fn draw_line_string(
    &mut self,
    rendering_state: &styling::RenderingState,
    feature: &TFeature,
    line: &geo::LineString,
    symbol: &styling::Symbol<TFeature>,
  )
  {
    let _ = (rendering_state, feature, line, symbol);
  }
  /// draw the image.
  /// `target_rect` is the rectangle where to draw the entire image, it might go
  /// out of canvas and it is the responsability of render to clip
  fn draw_image(
    &mut self,
    rendering_state: &styling::RenderingState,
    feature: &TFeature,
    image: ImageFeature<BoxedImageDataRef<'_>>,
    target_rect: geo::Rect,
  );
  /// Draw a label at the provided anchor point.
  fn draw_label(
    &mut self,
    rendering_state: &styling::RenderingState,
    feature: &TFeature,
    anchor: geo::Point,
    config: &styling::LabelConfig<TFeature>,
  );
  /// Dispatch drawing for a geometry by variant.
  fn draw_geometry(
    &mut self,
    rendering_state: &styling::RenderingState,
    feature: &TFeature,
    geometry: &geo::Geometry,
    symbol: &styling::Symbol<TFeature>,
  )
  {
    match geometry
    {
      geo::Geometry::Point(point) => self.draw_point(rendering_state, feature, point, symbol),
      geo::Geometry::Line(_) => log::error!("Line is not supported."),
      geo::Geometry::LineString(line) =>
      {
        self.draw_line_string(rendering_state, feature, line, symbol)
      }
      geo::Geometry::Polygon(poly) => self.draw_polygon(rendering_state, feature, poly, symbol),
      geo::Geometry::MultiPoint(mp) =>
      {
        for point in mp.iter()
        {
          self.draw_point(rendering_state, feature, point, symbol);
        }
      }
      geo::Geometry::MultiLineString(mls) =>
      {
        for line in mls.iter()
        {
          self.draw_line_string(rendering_state, feature, line, symbol);
        }
      }
      geo::Geometry::MultiPolygon(mp) =>
      {
        for poly in mp.iter()
        {
          self.draw_polygon(rendering_state, feature, poly, symbol);
        }
      }
      geo::Geometry::GeometryCollection(_) => log::error!("GeometryCollection is not supported."),
      geo::Geometry::Rect(_) => log::error!("Rect is not supported."),
      geo::Geometry::Triangle(_) => log::error!("Triangle is not supported."),
    }
  }
  /// Hook called once before any map drawing occurs.
  fn start_map(&mut self) {}
  /// Draw the map background.
  fn draw_background(&mut self, background_color: &styling::Rgba, size: (f64, f64));
  /// Draw all visible layers and features for the given style and view.
  fn draw_map(
    &mut self,
    map: &map::Map<TFeature>,
    view_controller: &ViewController,
    style: &styling::Style<TFeature>,
  ) where
    TFeature: map::Feature,
  {
    self.start_map();
    self.draw_background(
      &style.background_color(),
      (
        view_controller.view_rect().width(),
        view_controller.view_rect().height(),
      ),
    );
    let rendering_state = styling::RenderingState {
      zoom_level: view_controller.zoom_level(),
    };
    for layer in map.visible_layers()
    {
      let map_rect = transform(
        &view_controller.projection,
        layer.projection(),
        view_controller.map_rect_ref(),
      )
      .unwrap();

      for feat in layer.features(map_rect, rendering_state.zoom_level)
      {
        if let Some(image) = feat.image()
        {
          let image_bbox = image.image_data().bounding_box();
          let image_rect_view =
            transform(layer.projection(), &view_controller.projection, &image_bbox).unwrap();
          let target_rect = view_controller.rect_to_view(
            image_rect_view,
            view_controller.projection.y_axis_direction(),
          );

          self.draw_image(&rendering_state, &feat, image, target_rect);
        }
      }

      for r in style.rules()
      {
        for feat in layer.features(map_rect, rendering_state.zoom_level)
        {
          if (r.check)(&rendering_state, &feat)
            && let Some(geometry) = feat.geometry()
            && let Ok(geometry) = view_controller.geometry_to_view(layer.projection(), geometry)
          {
            self.draw_geometry(&rendering_state, &feat, &geometry, &r.symbol);
            if let Some(label) = &r.symbol.label
              && let Some(anchor) = geometry_label_anchor(&geometry)
            {
              self.draw_label(&rendering_state, &feat, anchor, label);
            }
          }
        }
      }
    }
  }
}

fn geometry_label_anchor(geometry: &geo::Geometry) -> Option<geo::Point>
{
  geometry
    .bounding_rect()
    .map(|rect| geo::Point::from(rect.center()))
}

#[cfg(test)]
mod tests
{
  use super::*;

  #[test]
  fn test_view_controller()
  {
    let controller = ViewController::show_extent(
      geo::Rect::new(
        geo::Coord {
          x: -180.0,
          y: -80.0,
        },
        geo::Coord { x: 180.0, y: 80.0 },
      ),
      geo::Rect::new(
        geo::Coord { x: 0.0, y: 0.0 },
        geo::Coord { x: 512.0, y: 512.0 },
      ),
    );
    let p = controller.view_to_coord(&(256.0, 256.0).into());
    assert!(p.x == 0.0);
    assert!(p.y == 0.0);
  }
}