mzpeaks/coordinate/
range.rs

1use std::{
2    error::Error,
3    fmt::Display,
4    marker::PhantomData,
5    num::ParseFloatError,
6    ops::{Bound, Range, RangeBounds, RangeTo},
7    str::FromStr,
8};
9
10use super::{CoordinateLike, HasProximity};
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14/// An interval within a single dimension
15#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17pub struct CoordinateRange<C> {
18    pub start: Option<f64>,
19    pub end: Option<f64>,
20    #[cfg_attr(feature = "serde", serde(skip))]
21    coord: PhantomData<C>,
22}
23
24impl<C> CoordinateRange<C> {
25    pub fn new(start: Option<f64>, end: Option<f64>) -> Self {
26        Self {
27            start,
28            end,
29            coord: PhantomData,
30        }
31    }
32
33    pub fn contains<T: CoordinateLike<C>>(&self, point: &T) -> bool {
34        let x = CoordinateLike::<C>::coordinate(point);
35        RangeBounds::<f64>::contains(&self, &x)
36    }
37
38    pub fn contains_raw(&self, x: &f64) -> bool {
39        RangeBounds::<f64>::contains(&self, x)
40    }
41
42    pub fn overlaps<T: RangeBounds<f64>>(&self, interval: &T) -> bool {
43        let interval_start = match interval.start_bound() {
44            Bound::Included(x) => *x,
45            Bound::Excluded(x) => *x,
46            Bound::Unbounded => 0.0,
47        };
48
49        let interval_end = match interval.end_bound() {
50            Bound::Included(y) => *y,
51            Bound::Excluded(y) => *y,
52            Bound::Unbounded => f64::INFINITY,
53        };
54        (self.end.unwrap_or(f64::INFINITY) >= interval_start
55            && interval_end >= self.start.unwrap_or(0.0))
56            || (self.end.is_close(&Some(interval_end))
57                && self.start.is_close(&Some(interval_start)))
58    }
59}
60
61impl<C> Default for CoordinateRange<C> {
62    fn default() -> Self {
63        Self {
64            start: None,
65            end: None,
66            coord: PhantomData,
67        }
68    }
69}
70
71/** An inclusive interval over a single dimension
72*/
73pub trait Span1D {
74    type DimType: HasProximity;
75
76    fn start(&self) -> Self::DimType;
77    fn end(&self) -> Self::DimType;
78
79    fn contains(&self, i: &Self::DimType) -> bool {
80        (self.start() <= *i && *i <= self.end()) || (self.start().is_close(i) || self.end().is_close(i))
81    }
82
83    fn is_close<T: Span1D<DimType = Self::DimType>>(&self, interval: &T) -> bool {
84        self.start().is_close(&interval.start()) && self.end().is_close(&interval.end())
85    }
86
87    fn overlaps<T: Span1D<DimType = Self::DimType>>(&self, interval: &T) -> bool {
88        (self.end() >= interval.start() && interval.end() >= self.start())
89            || self.is_close(&interval)
90    }
91
92    fn is_contained_in_interval<T: Span1D<DimType = Self::DimType>>(&self, interval: &T) -> bool {
93        (self.start() >= interval.start() && self.end() <= interval.end())
94            || self.is_close(&interval)
95    }
96
97    fn contains_interval<T: Span1D<DimType = Self::DimType>>(&self, interval: &T) -> bool {
98        (self.start() <= interval.start() && self.end() >= interval.end())
99            || self.is_close(&interval)
100    }
101}
102
103impl<T: Span1D> Span1D for &T {
104    type DimType = T::DimType;
105
106    fn start(&self) -> Self::DimType {
107        (*self).start()
108    }
109
110    fn end(&self) -> Self::DimType {
111        (*self).end()
112    }
113}
114
115impl<C> Span1D for CoordinateRange<C> {
116    type DimType = Option<f64>;
117
118    fn start(&self) -> Self::DimType {
119        self.start
120    }
121
122    fn end(&self) -> Self::DimType {
123        self.end
124    }
125}
126
127impl<T: HasProximity> Span1D for Range<T> {
128    type DimType = T;
129
130    fn start(&self) -> Self::DimType {
131        self.start
132    }
133
134    fn end(&self) -> Self::DimType {
135        self.end
136    }
137}
138
139/// A basic [`Span1D`] implementation
140#[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)]
141#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
142pub struct SimpleInterval<V: PartialOrd> {
143    pub start: V,
144    pub end: V,
145}
146
147impl<V: PartialOrd> SimpleInterval<V> {
148    pub fn new(start: V, end: V) -> SimpleInterval<V> {
149        SimpleInterval { start, end }
150    }
151}
152
153impl<V: HasProximity> Span1D for SimpleInterval<V> {
154    type DimType = V;
155
156    fn start(&self) -> Self::DimType {
157        self.start
158    }
159
160    fn end(&self) -> Self::DimType {
161        self.end
162    }
163}
164
165impl<V: PartialOrd> From<(V, V)> for SimpleInterval<V> {
166    fn from(value: (V, V)) -> Self {
167        Self::new(value.0, value.1)
168    }
169}
170
171impl<V: PartialOrd> From<Range<V>> for SimpleInterval<V> {
172    fn from(value: Range<V>) -> Self {
173        Self::new(value.start, value.end)
174    }
175}
176
177#[derive(Debug)]
178pub enum CoordinateRangeParseError {
179    MalformedStart(ParseFloatError),
180    MalformedEnd(ParseFloatError),
181}
182
183impl Display for CoordinateRangeParseError {
184    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185        match self {
186            CoordinateRangeParseError::MalformedStart(e) => {
187                write!(f, "Failed to parse range start {e}")
188            }
189            CoordinateRangeParseError::MalformedEnd(e) => {
190                write!(f, "Failed to parse range end {e}")
191            }
192        }
193    }
194}
195
196impl Error for CoordinateRangeParseError {}
197
198impl<C> FromStr for CoordinateRange<C> {
199    type Err = CoordinateRangeParseError;
200
201    fn from_str(s: &str) -> Result<Self, Self::Err> {
202        let mut tokens = if s.contains(' ') {
203            s.split(' ')
204        } else if s.contains(':') {
205            s.split(':')
206        } else if s.contains('-') {
207            s.split('-')
208        } else {
209            s.split(' ')
210        };
211        let start_s = tokens.next().unwrap();
212        let start_t = if start_s.is_empty() {
213            None
214        } else {
215            match start_s.parse() {
216                Ok(val) => Some(val),
217                Err(e) => return Err(CoordinateRangeParseError::MalformedStart(e)),
218            }
219        };
220        let end_s = tokens.next().unwrap();
221        let end_t = if end_s.is_empty() {
222            None
223        } else {
224            match end_s.parse() {
225                Ok(val) => Some(val),
226                Err(e) => return Err(CoordinateRangeParseError::MalformedEnd(e)),
227            }
228        };
229        Ok(CoordinateRange {
230            start: start_t,
231            end: end_t,
232            coord: PhantomData,
233        })
234    }
235}
236
237impl<C> From<RangeTo<f64>> for CoordinateRange<C> {
238    fn from(value: RangeTo<f64>) -> Self {
239        Self::new(None, Some(value.end))
240    }
241}
242
243impl<C> From<Range<f64>> for CoordinateRange<C> {
244    fn from(value: Range<f64>) -> Self {
245        Self::new(Some(value.start), Some(value.end))
246    }
247}
248
249impl<C> RangeBounds<f64> for CoordinateRange<C> {
250    fn start_bound(&self) -> Bound<&f64> {
251        if let Some(start) = self.start.as_ref() {
252            Bound::Included(start)
253        } else {
254            Bound::Unbounded
255        }
256    }
257
258    fn end_bound(&self) -> Bound<&f64> {
259        if let Some(end) = self.end.as_ref() {
260            Bound::Included(end)
261        } else {
262            Bound::Unbounded
263        }
264    }
265}
266
267impl<C> RangeBounds<f64> for &CoordinateRange<C> {
268    fn start_bound(&self) -> Bound<&f64> {
269        (*self).start_bound()
270    }
271
272    fn end_bound(&self) -> Bound<&f64> {
273        (*self).end_bound()
274    }
275}
276
277impl<C> From<(f64, f64)> for CoordinateRange<C> {
278    fn from(value: (f64, f64)) -> Self {
279        Self::new(Some(value.0), Some(value.1))
280    }
281}
282
283impl<C> From<CoordinateRange<C>> for Range<f64> {
284    fn from(value: CoordinateRange<C>) -> Self {
285        let start = value.start.unwrap_or(0.0);
286        let end = value.end.unwrap_or(f64::INFINITY);
287
288        start..end
289    }
290}
291
292#[cfg(test)]
293mod test {
294    use crate::Time;
295
296    use super::*;
297
298    #[test]
299    fn test_conversion() {
300        let time_range: CoordinateRange<Time> = (..5.0).into();
301        assert_eq!(time_range.start(), None);
302        assert_eq!(time_range.end(), Some(5.0));
303
304        let time_range: CoordinateRange<Time> = (5.0..10.0).into();
305        assert_eq!(time_range.start(), Some(5.0));
306        assert_eq!(time_range.end(), Some(10.0));
307
308        let time_range: CoordinateRange<Time> = ":5.0".parse().unwrap();
309        assert_eq!(time_range.start(), None);
310        assert_eq!(time_range.end(), Some(5.0));
311
312        let time_range: CoordinateRange<Time> = "-5.0".parse().unwrap();
313        assert_eq!(time_range.start(), None);
314        assert_eq!(time_range.end(), Some(5.0));
315
316        let time_range: CoordinateRange<Time> = " 5.0".parse().unwrap();
317        assert_eq!(time_range.start(), None);
318        assert_eq!(time_range.end(), Some(5.0));
319
320        // let time_range: CoordinateRange<Time> = "5.0".parse().unwrap();
321        // assert_eq!(time_range.start(), None);
322        // assert_eq!(time_range.end(), Some(5.0));
323
324        let time_range: CoordinateRange<Time> = (0.0, 5.0).into();
325        assert_eq!(time_range.start(), Some(0.0));
326        assert_eq!(time_range.end(), Some(5.0));
327
328        let time_range: SimpleInterval<f64> = (0.0, 5.0).into();
329        assert_eq!(time_range.start(), 0.0);
330        assert_eq!(time_range.end(), 5.0);
331
332        let time_range: SimpleInterval<f64> = (5.0..10.0).into();
333        assert_eq!(time_range.start(), 5.0);
334        assert_eq!(time_range.end(), 10.0);
335
336        let empty = CoordinateRange::<Time>::default();
337        assert_eq!(empty.start(), None);
338        assert_eq!(empty.end(), None);
339
340        assert_eq!(empty.start_bound(), Bound::Unbounded);
341        assert_eq!(empty.end_bound(), Bound::Unbounded);
342        let t: Range<f64> = empty.into();
343
344        assert_eq!(t.start, 0.0);
345        assert_eq!(t.end, f64::INFINITY);
346    }
347
348    #[test]
349    fn test_interval() {
350        let time_range: CoordinateRange<Time> = (..5.0).into();
351        assert!(time_range.contains_raw(&2.5));
352        assert!(!time_range.contains_raw(&7.5));
353
354        let time_range2: CoordinateRange<Time> = (3.0..10.0).into();
355        assert!(time_range.overlaps(&time_range2));
356
357        assert!((5.0..7.0).is_contained_in_interval(&(3.0..10.0)));
358        assert!((3.0..10.0).contains_interval(&(5.0..7.0)));
359    }
360}