use geo::Point;
use thiserror::Error;
use crate::lrs::Properties;
pub type CurvePosition = f64;
pub type ScalePosition = f64;
#[derive(Error, Debug, PartialEq)]
pub enum LrmScaleError {
#[error("a scale needs at least two named anchor")]
NoEnoughNamedAnchor,
#[error("duplicated anchor: {0}")]
DuplicatedAnchorName(String),
#[error("anchor is unknown in the LrmScale")]
UnknownAnchorName,
#[error("no anchor found")]
NoAnchorFound,
}
#[derive(PartialEq, Debug, Clone)]
pub struct UnnamedAnchor {
pub scale_position: ScalePosition,
pub curve_position: CurvePosition,
pub point: Option<Point>,
pub properties: Properties,
}
#[derive(PartialEq, Debug, Clone)]
pub struct NamedAnchor {
pub name: String,
pub scale_position: ScalePosition,
pub curve_position: CurvePosition,
pub point: Option<Point>,
pub properties: Properties,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Anchor {
Named(NamedAnchor),
Unnamed(UnnamedAnchor),
}
impl Anchor {
pub fn new_named(
name: &str,
scale_position: ScalePosition,
curve_position: CurvePosition,
point: Option<Point>,
properties: Properties,
) -> Self {
Self::Named(NamedAnchor {
name: name.to_owned(),
scale_position,
curve_position,
point,
properties,
})
}
pub fn new_unnamed(
scale_position: ScalePosition,
curve_position: CurvePosition,
point: Option<Point>,
properties: Properties,
) -> Self {
Self::Unnamed(UnnamedAnchor {
scale_position,
curve_position,
point,
properties,
})
}
pub fn scale_position(&self) -> ScalePosition {
match self {
Anchor::Named(anchor) => anchor.scale_position,
Anchor::Unnamed(anchor) => anchor.scale_position,
}
}
pub fn curve_position(&self) -> ScalePosition {
match self {
Anchor::Named(anchor) => anchor.curve_position,
Anchor::Unnamed(anchor) => anchor.curve_position,
}
}
pub fn properties(&self) -> &Properties {
match self {
Anchor::Named(anchor) => &anchor.properties,
Anchor::Unnamed(anchor) => &anchor.properties,
}
}
pub fn point(&self) -> Option<Point> {
match self {
Anchor::Named(anchor) => anchor.point,
Anchor::Unnamed(anchor) => anchor.point,
}
}
}
#[derive(Clone, Debug)]
pub struct LrmScaleMeasure {
pub anchor_name: String,
pub scale_offset: ScalePosition,
}
impl LrmScaleMeasure {
pub fn new(anchor_name: &str, scale_offset: ScalePosition) -> Self {
Self {
anchor_name: anchor_name.to_owned(),
scale_offset,
}
}
}
#[derive(PartialEq, Debug, Clone)]
pub struct LrmScale {
pub id: String,
pub anchors: Vec<Anchor>,
}
impl LrmScale {
pub fn locate_point(&self, measure: &LrmScaleMeasure) -> Result<CurvePosition, LrmScaleError> {
let named_anchor = self
.iter_named()
.find(|anchor| anchor.name == measure.anchor_name)
.ok_or(LrmScaleError::UnknownAnchorName)?;
let scale_position = named_anchor.scale_position + measure.scale_offset;
let anchors = self
.anchors
.windows(2)
.find(|window| window[1].scale_position() >= scale_position)
.or_else(|| self.anchors.windows(2).last())
.ok_or(LrmScaleError::NoAnchorFound)?;
let scale_interval = anchors[0].scale_position() - anchors[1].scale_position();
let curve_interval = anchors[0].curve_position() - anchors[1].curve_position();
Ok(anchors[0].curve_position()
+ curve_interval * (scale_position - anchors[0].scale_position()) / scale_interval)
}
pub fn locate_anchor(
&self,
curve_position: CurvePosition,
) -> Result<LrmScaleMeasure, LrmScaleError> {
let named_anchor = self
.nearest_named(curve_position)
.ok_or(LrmScaleError::NoAnchorFound)?;
let anchors = self
.anchors
.windows(2)
.find(|window| window[1].curve_position() >= curve_position)
.or_else(|| self.anchors.windows(2).last())
.ok_or(LrmScaleError::NoAnchorFound)?;
let ratio = (anchors[0].scale_position() - anchors[1].scale_position())
/ (anchors[0].curve_position() - anchors[1].curve_position());
Ok(LrmScaleMeasure {
anchor_name: named_anchor.name.clone(),
scale_offset: (anchors[0].scale_position() - named_anchor.scale_position)
+ (curve_position - anchors[0].curve_position()) * ratio,
})
}
pub fn get_measure(
&self,
scale_position: ScalePosition,
) -> Result<LrmScaleMeasure, LrmScaleError> {
let named_anchor = self
.scale_nearest_named(scale_position)
.ok_or(LrmScaleError::NoAnchorFound)?;
Ok(LrmScaleMeasure {
anchor_name: named_anchor.name.clone(),
scale_offset: scale_position - named_anchor.scale_position,
})
}
pub fn get_position(&self, measure: LrmScaleMeasure) -> Result<ScalePosition, LrmScaleError> {
let named_anchor = self
.iter_named()
.find(|anchor| anchor.name == measure.anchor_name)
.ok_or(LrmScaleError::UnknownAnchorName)?;
Ok(named_anchor.scale_position + measure.scale_offset)
}
fn nearest_named(&self, curve_position: CurvePosition) -> Option<&NamedAnchor> {
self.iter_named()
.rev()
.find(|anchor| anchor.curve_position <= curve_position)
.or_else(|| self.iter_named().next())
}
fn scale_nearest_named(&self, scale_position: ScalePosition) -> Option<&NamedAnchor> {
self.iter_named()
.rev()
.find(|anchor| anchor.scale_position <= scale_position)
.or_else(|| self.iter_named().next())
}
fn iter_named(&self) -> impl DoubleEndedIterator<Item = &NamedAnchor> + '_ {
self.anchors.iter().filter_map(|anchor| match anchor {
Anchor::Named(anchor) => Some(anchor),
Anchor::Unnamed(_) => None,
})
}
}
#[cfg(test)]
pub(crate) mod tests {
use crate::properties;
use super::*;
pub(crate) fn scale() -> LrmScale {
LrmScale {
id: "id".to_owned(),
anchors: vec![
Anchor::new_named("a", 0., 0., None, properties!()),
Anchor::new_named("b", 10., 0.5, None, properties!()),
],
}
}
#[test]
fn locate_point() {
assert_eq!(
scale().locate_point(&LrmScaleMeasure::new("a", 5.)),
Ok(0.25)
);
assert_eq!(
scale().locate_point(&LrmScaleMeasure::new("b", 5.)),
Ok(0.75)
);
assert_eq!(
scale().locate_point(&LrmScaleMeasure::new("a", -5.)),
Ok(-0.25)
);
assert_eq!(
scale().locate_point(&LrmScaleMeasure::new("c", 5.)),
Err(LrmScaleError::UnknownAnchorName)
);
}
#[test]
fn nearest_named() {
let scale = LrmScale {
id: "id".to_owned(),
anchors: vec![
Anchor::new_named("a", 0., 2., None, properties!()),
Anchor::new_named("b", 10., 3., None, properties!()),
],
};
assert_eq!(scale.nearest_named(2.1).unwrap().name, "a");
assert_eq!(scale.nearest_named(2.9).unwrap().name, "a");
assert_eq!(scale.nearest_named(1.5).unwrap().name, "a");
assert_eq!(scale.nearest_named(3.5).unwrap().name, "b");
}
#[test]
fn locate_anchor() {
let measure = scale().locate_anchor(0.2).unwrap();
assert_eq!(measure.anchor_name, "a");
assert_eq!(measure.scale_offset, 4.);
let measure = scale().locate_anchor(0.75).unwrap();
assert_eq!(measure.anchor_name, "b");
assert_eq!(measure.scale_offset, 5.);
let measure = scale().locate_anchor(-0.05).unwrap();
assert_eq!(measure.anchor_name, "a");
assert_eq!(measure.scale_offset, -1.);
}
#[test]
fn locate_anchor_with_unnamed() {
let scale = LrmScale {
id: "id".to_owned(),
anchors: vec![
Anchor::new_unnamed(0., 100., None, properties!()),
Anchor::new_named("a", 1., 200., None, properties!()),
Anchor::new_unnamed(1.2, 250., None, properties!()), Anchor::new_named("b", 3., 300., None, properties!()),
Anchor::new_unnamed(4., 400., None, properties!()),
],
};
let measure = scale.locate_anchor(150.).unwrap();
assert_eq!(measure.anchor_name, "a");
assert_eq!(measure.scale_offset, -0.5);
let measure = scale.locate_anchor(50.).unwrap();
assert_eq!(measure.anchor_name, "a");
assert_eq!(measure.scale_offset, -1.5);
let measure = scale.locate_anchor(275.).unwrap();
assert_eq!(measure.anchor_name, "a");
assert_eq!(measure.scale_offset, 0.2 + 0.9);
let measure = scale.locate_anchor(350.).unwrap();
assert_eq!(measure.anchor_name, "b");
assert_eq!(measure.scale_offset, 0.5);
let measure = scale.locate_anchor(500.).unwrap();
assert_eq!(measure.anchor_name, "b");
assert_eq!(measure.scale_offset, 2.);
}
#[test]
fn get_measure() {
let measure = scale().get_measure(5.).unwrap();
assert_eq!(measure.anchor_name, "a");
assert_eq!(measure.scale_offset, 5.);
let measure = scale().get_measure(25.).unwrap();
assert_eq!(measure.anchor_name, "b");
assert_eq!(measure.scale_offset, 15.);
}
#[test]
fn get_position() {
let position = scale()
.get_position(LrmScaleMeasure {
anchor_name: "a".to_string(),
scale_offset: 5.,
})
.unwrap();
assert_eq!(position, 5.);
let position = scale()
.get_position(LrmScaleMeasure {
anchor_name: "b".to_string(),
scale_offset: 15.,
})
.unwrap();
assert_eq!(position, 25.);
}
#[test]
fn single_anchor() {
let scale = LrmScale {
id: "id".to_owned(),
anchors: vec![
Anchor::new_named("a", 1000. + 0., -2., None, properties!()),
Anchor::new_unnamed(1000. + 300., 1., None, properties!()),
],
};
let position = scale
.locate_point(&LrmScaleMeasure {
anchor_name: "a".to_string(),
scale_offset: 200.,
})
.unwrap();
assert_eq!(position, 0.);
}
#[test]
fn irregular_scale() {
let scale = LrmScale {
id: "id".to_owned(),
anchors: vec![
Anchor::new_named("a", 0., 0., None, properties!()),
Anchor::new_unnamed(1., 0.4, None, properties!()),
Anchor::new_unnamed(9., 0.6, None, properties!()),
],
};
let position = scale
.locate_point(&LrmScaleMeasure {
anchor_name: "a".to_string(),
scale_offset: 5.,
})
.unwrap();
assert_eq!(position, 0.5);
}
}