cartography 0.11.0

Cartography is a map rendering library for Geographic features expressed using [georust](https://georust.org/) libraries.
Documentation
use crate::Result;
use geo::{CoordNum, coord};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum YAxisDirection
{
  North,
  South,
}

/// Represnt a CRS Projection
#[derive(Debug)]
pub struct Projection
{
  pub(crate) proj: proj4rs::proj::Proj,
  pub(crate) unit: Unit,
}

/// Unit for the values
#[derive(Debug)]
pub enum Unit
{
  /// Radian
  Radian,
  /// Degree
  Degree,
  /// Meter
  Meter,
}

impl Projection
{
  /// Create a new WGS 84 projection
  pub fn wgs84() -> Self
  {
    Self::from_proj_string(
      "+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs",
      Unit::Degree,
    )
    .expect("Internal error, invalid WGS84 string.")
  }
  /// Create a web mercator projection, to be used with OpenStreetMap tiles.
  pub fn web_mercator() -> Self
  {
    Self::from_proj_string(
        "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"
,        Unit::Meter
    )
        .expect("Internal error, invalid web mercator string.")
  }
  /// Create a new projection from a `proj` string
  pub fn from_proj_string(proj: &str, unit: Unit) -> crate::Result<Self>
  {
    Ok(Self {
      proj: proj4rs::proj::Proj::from_proj_string(proj)?,
      unit,
    })
  }
  /// Y-Axis
  pub(crate) fn y_axis_direction(&self) -> YAxisDirection
  {
    if self.proj.axis()[1] == b'n'
    {
      YAxisDirection::North
    }
    else
    {
      YAxisDirection::South
    }
  }
}

impl PartialEq for &Projection
{
  fn eq(&self, other: &Self) -> bool
  {
    self.proj.projname() == other.proj.projname()
  }
}

impl Eq for &Projection {}

/// Trait to implement
pub trait Transformable: Sized
{
  fn transform(&self, from: &Projection, to: &Projection) -> Result<Self>;
}

impl<T> Transformable for geo::Coord<T>
where
  T: num_traits::Float + num_traits::FloatConst + CoordNum,
  (T, T): proj4rs::transform::Transform,
{
  fn transform(&self, from: &Projection, to: &Projection) -> Result<Self>
  {
    let mut coord = self.x_y();
    if matches!(from.unit, Unit::Degree)
    {
      coord.0 = coord.0 * T::PI() / T::from(180.0).unwrap();
      coord.1 = coord.1 * T::PI() / T::from(180.0).unwrap();
    }
    proj4rs::transform::transform(&from.proj, &to.proj, &mut coord)?;
    if matches!(to.unit, Unit::Degree)
    {
      coord.0 = coord.0 * T::from(180.0).unwrap() / T::PI();
      coord.1 = coord.1 * T::from(180.0).unwrap() / T::PI();
    }
    Ok(coord! { x: coord.0, y: coord.1})
  }
}

impl<T> Transformable for geo::Rect<T>
where
  T: num_traits::Float + num_traits::FloatConst + CoordNum,
  (T, T): proj4rs::transform::Transform,
{
  fn transform(&self, from: &Projection, to: &Projection) -> Result<Self>
  {
    let min = transform(from, to, &self.min())?;
    let max = transform(from, to, &self.max())?;
    Ok(Self::new(min, max))
  }
}

/// Transform coordinate from one projection to an other
pub fn transform<T: Transformable>(from: &Projection, to: &Projection, value: &T) -> Result<T>
{
  value.transform(from, to)
}

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

  #[test]
  fn test_projection_creation()
  {
    let wgs = Projection::wgs84();
    let web = Projection::web_mercator();

    assert_eq!(wgs.y_axis_direction(), YAxisDirection::North);
    assert_eq!(web.y_axis_direction(), YAxisDirection::North);

    assert_eq!(&wgs, &Projection::wgs84());
    assert_eq!(&web, &Projection::web_mercator());
  }

  #[test]
  fn test_coord_transform_wgs_to_webmercator()
  {
    let wgs = Projection::wgs84();
    let web = Projection::web_mercator();

    let paris = geo::coord! { x: 2.3522, y: 48.8566 };

    let paris_web = paris.transform(&wgs, &web).unwrap();

    assert_relative_eq!(paris_web.x, 261845.0, epsilon = 100.0);
    assert_relative_eq!(paris_web.y, 6250560.0, epsilon = 100.0);

    let paris_wgs_back = paris_web.transform(&web, &wgs).unwrap();
    assert_relative_eq!(paris_wgs_back.x, paris.x, epsilon = 1e-5);
    assert_relative_eq!(paris_wgs_back.y, paris.y, epsilon = 1e-5);
  }

  #[test]
  fn test_rect_transform_wgs_to_webmercator()
  {
    let wgs = Projection::wgs84();
    let web = Projection::web_mercator();

    let rect_wgs = geo::Rect::new(
      geo::coord! { x: 2.3, y: 48.85 },
      geo::coord! { x: 2.4, y: 48.87 },
    );

    let rect_web = rect_wgs.transform(&wgs, &web).unwrap();

    assert!(rect_web.min().x > 200_000.0 && rect_web.max().x < 300_000.0);
    assert!(rect_web.min().y > 6_200_000.0 && rect_web.max().y < 6_300_000.0);

    let rect_wgs_back = rect_web.transform(&web, &wgs).unwrap();
    assert_relative_eq!(rect_wgs_back.min().x, rect_wgs.min().x, epsilon = 1e-5);
    assert_relative_eq!(rect_wgs_back.min().y, rect_wgs.min().y, epsilon = 1e-5);
    assert_relative_eq!(rect_wgs_back.max().x, rect_wgs.max().x, epsilon = 1e-5);
    assert_relative_eq!(rect_wgs_back.max().y, rect_wgs.max().y, epsilon = 1e-5);
  }
}