use std::{
error::Error,
fmt::Display,
marker::PhantomData,
num::ParseFloatError,
ops::{Bound, Range, RangeBounds, RangeTo},
str::FromStr,
};
use super::CoordinateLike;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct CoordinateRange<C> {
pub start: Option<f64>,
pub end: Option<f64>,
coord: PhantomData<C>,
}
impl<C> CoordinateRange<C> {
pub fn new(start: Option<f64>, end: Option<f64>) -> Self {
Self {
start,
end,
coord: PhantomData,
}
}
pub fn contains<T: CoordinateLike<C>>(&self, point: &T) -> bool {
let x = CoordinateLike::<C>::coordinate(point);
RangeBounds::<f64>::contains(&self, &x)
}
pub fn contains_raw(&self, x: &f64) -> bool {
RangeBounds::<f64>::contains(&self, x)
}
pub fn overlaps<T: RangeBounds<f64>>(&self, interval: &T) -> bool {
let interval_start = match interval.start_bound() {
Bound::Included(x) => *x,
Bound::Excluded(x) => *x,
Bound::Unbounded => 0.0,
};
let interval_end = match interval.end_bound() {
Bound::Included(y) => *y,
Bound::Excluded(y) => *y,
Bound::Unbounded => f64::INFINITY,
};
self.end.unwrap_or(f64::INFINITY) >= interval_start
&& interval_end >= self.start.unwrap_or(0.0)
}
}
impl<C> Default for CoordinateRange<C> {
fn default() -> Self {
Self {
start: None,
end: None,
coord: PhantomData,
}
}
}
pub trait Span1D {
type DimType: PartialOrd + Copy;
fn start(&self) -> Self::DimType;
fn end(&self) -> Self::DimType;
fn contains(&self, i: &Self::DimType) -> bool {
self.start() <= *i && *i <= self.end()
}
fn overlaps<T: Span1D<DimType = Self::DimType>>(&self, interval: &T) -> bool {
self.end() >= interval.start() && interval.end() >= self.start()
}
fn is_contained_in_interval<T: Span1D<DimType = Self::DimType>>(&self, interval: &T) -> bool {
self.start() >= interval.start() && self.end() <= interval.end()
}
fn contains_interval<T: Span1D<DimType = Self::DimType>>(&self, interval: &T) -> bool {
self.start() <= interval.start() && self.end() >= interval.end()
}
}
impl<T: Span1D> Span1D for &T {
type DimType = T::DimType;
fn start(&self) -> Self::DimType {
(*self).start()
}
fn end(&self) -> Self::DimType {
(*self).end()
}
}
impl<C> Span1D for CoordinateRange<C> {
type DimType = Option<f64>;
fn start(&self) -> Self::DimType {
self.start
}
fn end(&self) -> Self::DimType {
self.end
}
}
impl<T: Copy + PartialOrd> Span1D for Range<T> {
type DimType = T;
fn start(&self) -> Self::DimType {
self.start
}
fn end(&self) -> Self::DimType {
self.end
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
pub struct SimpleInterval<V: PartialOrd> {
pub start: V,
pub end: V,
}
impl<V: PartialOrd> SimpleInterval<V> {
pub fn new(start: V, end: V) -> SimpleInterval<V> {
SimpleInterval { start, end }
}
}
impl<V: PartialOrd + Copy> Span1D for SimpleInterval<V> {
type DimType = V;
fn start(&self) -> Self::DimType {
self.start
}
fn end(&self) -> Self::DimType {
self.end
}
}
impl<V: PartialOrd> From<(V, V)> for SimpleInterval<V> {
fn from(value: (V, V)) -> Self {
Self::new(value.0, value.1)
}
}
impl<V: PartialOrd> From<Range<V>> for SimpleInterval<V> {
fn from(value: Range<V>) -> Self {
Self::new(value.start, value.end)
}
}
#[derive(Debug)]
pub enum CoordinateRangeParseError {
MalformedStart(ParseFloatError),
MalformedEnd(ParseFloatError),
}
impl Display for CoordinateRangeParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CoordinateRangeParseError::MalformedStart(e) => {
write!(f, "Failed to parse range start {e}")
}
CoordinateRangeParseError::MalformedEnd(e) => {
write!(f, "Failed to parse range end {e}")
}
}
}
}
impl Error for CoordinateRangeParseError {}
impl<C> FromStr for CoordinateRange<C> {
type Err = CoordinateRangeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut tokens = if s.contains(' ') {
s.split(' ')
} else if s.contains(':') {
s.split(':')
} else if s.contains('-') {
s.split('-')
} else {
s.split(' ')
};
let start_s = tokens.next().unwrap();
let start_t = if start_s.is_empty() {
None
} else {
match start_s.parse() {
Ok(val) => Some(val),
Err(e) => return Err(CoordinateRangeParseError::MalformedStart(e)),
}
};
let end_s = tokens.next().unwrap();
let end_t = if end_s.is_empty() {
None
} else {
match end_s.parse() {
Ok(val) => Some(val),
Err(e) => return Err(CoordinateRangeParseError::MalformedEnd(e)),
}
};
Ok(CoordinateRange {
start: start_t,
end: end_t,
coord: PhantomData,
})
}
}
impl<C> From<RangeTo<f64>> for CoordinateRange<C> {
fn from(value: RangeTo<f64>) -> Self {
Self::new(None, Some(value.end))
}
}
impl<C> From<Range<f64>> for CoordinateRange<C> {
fn from(value: Range<f64>) -> Self {
Self::new(Some(value.start), Some(value.end))
}
}
impl<C> RangeBounds<f64> for CoordinateRange<C> {
fn start_bound(&self) -> Bound<&f64> {
if let Some(start) = self.start.as_ref() {
Bound::Included(start)
} else {
Bound::Unbounded
}
}
fn end_bound(&self) -> Bound<&f64> {
if let Some(end) = self.end.as_ref() {
Bound::Included(end)
} else {
Bound::Unbounded
}
}
}
impl<C> RangeBounds<f64> for &CoordinateRange<C> {
fn start_bound(&self) -> Bound<&f64> {
(*self).start_bound()
}
fn end_bound(&self) -> Bound<&f64> {
(*self).end_bound()
}
}
impl<C> From<(f64, f64)> for CoordinateRange<C> {
fn from(value: (f64, f64)) -> Self {
Self::new(Some(value.0), Some(value.1))
}
}
impl<C> From<CoordinateRange<C>> for Range<f64> {
fn from(value: CoordinateRange<C>) -> Self {
let start = value.start.unwrap_or(0.0);
let end = value.end.unwrap_or(f64::INFINITY);
start..end
}
}