h3o 0.3.0

A Rust implementation of the H3 geospatial indexing system.
Documentation
use super::line;
use crate::{error::InvalidGeometry, geom::ToCells, CellIndex, Resolution};
use std::{borrow::Cow, boxed::Box};

/// An ordered collection of two or more [`geo::Coord`]s, representing a
/// path between locations.
///
/// Note that the `ToCells` implementation suffers from the same limitation
/// that [`grid_path_cells`](CellIndex::grid_path_cells), which means that on
/// error `max_cells_count` returns 0 and `to_cells` an empty iterator.
#[derive(Clone, Debug, PartialEq)]
pub struct LineString<'a>(Cow<'a, geo::LineString<f64>>);

impl<'a> LineString<'a> {
    /// Initialize a new line from a line whose coordinates are in radians.
    ///
    /// # Errors
    ///
    /// [`InvalidGeometry`] if the line is invalid (e.g. contains non-finite
    /// coordinates).
    ///
    /// # Example
    ///
    /// ```
    /// use h3o::geom::LineString;
    ///
    /// let line_string = geo::LineString::new(vec![
    ///     geo::coord! { x: -0.009526982062241713, y: 0.8285232894553574 },
    ///     geo::coord! { x: 0.04142734140306332, y: 0.8525145186317127 },
    /// ]);
    /// let line = LineString::from_radians(&line_string)?;
    /// # Ok::<(), h3o::error::InvalidGeometry>(())
    /// ```
    pub fn from_radians(
        line: &'a geo::LineString<f64>,
    ) -> Result<Self, InvalidGeometry> {
        Self::check_coords(line).map(|_| Self(Cow::Borrowed(line)))
    }

    /// Initialize a new line from a line whose coordinates are in degrees.
    ///
    /// # Errors
    ///
    /// [`InvalidGeometry`] if the line is invalid (e.g. contains non-finite
    /// coordinates).
    ///
    /// # Example
    ///
    /// ```
    /// use h3o::geom::LineString;
    ///
    /// let line_string = geo::LineString::new(vec![
    ///     geo::coord! { x: -0.5458558636632915, y: 47.47088771408784 },
    ///     geo::coord! { x: 2.373611818843102,   y: 48.84548389122412 },
    /// ]);
    /// let line = LineString::from_degrees(line_string)?;
    /// # Ok::<(), h3o::error::InvalidGeometry>(())
    /// ```
    pub fn from_degrees(
        mut line: geo::LineString<f64>,
    ) -> Result<Self, InvalidGeometry> {
        for coord in line.coords_mut() {
            coord.x = coord.x.to_radians();
            coord.y = coord.y.to_radians();
        }
        Self::check_coords(&line).map(|_| Self(Cow::Owned(line)))
    }

    // Check that the line's coordinates are finite.
    fn check_coords(
        line: &geo::LineString<f64>,
    ) -> Result<(), InvalidGeometry> {
        if !line.coords().all(|coord| super::coord_is_valid(*coord)) {
            return Err(InvalidGeometry::new(
                "every coordinate of the line must be valid",
            ));
        }
        Ok(())
    }
}

impl From<LineString<'_>> for geo::LineString<f64> {
    fn from(value: LineString<'_>) -> Self {
        value.0.into_owned()
    }
}

impl ToCells for LineString<'_> {
    fn max_cells_count(&self, resolution: Resolution) -> usize {
        self.0
            .lines()
            .map(|line| line::cells_count(line, resolution))
            .sum()
    }

    fn to_cells(
        &self,
        resolution: Resolution,
    ) -> Box<dyn Iterator<Item = CellIndex> + '_> {
        Box::new(
            self.0
                .lines()
                .flat_map(move |line| line::to_cells(line, resolution)),
        )
    }
}