extern crate flatbuffers;
use std::cmp::Ordering;
use flatbuffers::{ForwardsUOffset, Vector};
use geo::orient::Direction;
use geo_index::rtree::{RTreeIndex, RTreeRef};
use thiserror::Error;
use crate::curves::{Curve, CurveError};
use crate::lrm_scale::{
Anchor, CurvePosition, LrmScale, LrmScaleError, LrmScaleMeasure, ScalePosition,
};
use crate::lrs_generated;
use geo::{Contains, LineString, Point, coord, point};
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct LrmHandle(pub usize);
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct TraversalHandle(pub usize);
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
pub struct NodeHandle(pub usize);
pub struct Lrm {
pub scale: LrmScale,
pub traversal: TraversalHandle,
pub properties: Properties,
}
pub struct Traversal<CurveImpl: Curve> {
pub id: String,
pub curve: CurveImpl,
pub lrms: Vec<LrmHandle>,
}
pub struct Lrs<CurveImpl: Curve> {
pub lrms: Vec<Lrm>,
pub traversals: Vec<Traversal<CurveImpl>>,
pub properties: Properties,
pub nodes: Vec<Node>,
pub segments: Vec<Segment>,
pub rtree_data: Option<Vec<u8>>,
}
pub struct Node {
pub id: String,
pub geometry: Option<Point>,
pub properties: Properties,
}
impl From<&lrs_generated::Point> for Point {
fn from(fb_point: &lrs_generated::Point) -> Self {
point! {
x: fb_point.x(),
y: fb_point.y(),
}
}
}
impl From<lrs_generated::Node<'_>> for Node {
fn from(fb_node: lrs_generated::Node) -> Self {
Self {
id: fb_node.id().to_owned(),
geometry: fb_node.geometry().map(Point::from),
properties: from_fb(fb_node.properties()),
}
}
}
pub struct Segment {
pub id: String,
pub properties: Properties,
pub start_node: NodeHandle,
pub end_node: NodeHandle,
}
impl From<lrs_generated::Segment<'_>> for Segment {
fn from(fb_segment: lrs_generated::Segment) -> Self {
Self {
id: fb_segment.id().to_owned(),
properties: from_fb(fb_segment.properties()),
start_node: NodeHandle(fb_segment.start_node_index() as usize),
end_node: NodeHandle(fb_segment.end_node_index() as usize),
}
}
}
pub struct LrmProjection {
pub measure: LrmMeasure,
pub orthogonal_offset: f64,
}
#[derive(Clone, Copy, Debug)]
pub struct LrmPosition {
pub distance_from_start: ScalePosition,
pub lrm: LrmHandle,
}
#[derive(Clone, Debug)]
pub struct LrmMeasure {
pub measure: LrmScaleMeasure,
pub lrm: LrmHandle,
}
#[derive(Clone, Copy, Debug)]
pub struct TraversalProjection {
pub distance_from_start: CurvePosition,
pub orthogonal_offset: f64,
pub traversal: TraversalHandle,
}
#[derive(Clone, Copy, Debug)]
pub struct TraversalPosition {
pub curve_position: CurvePosition,
pub traversal: TraversalHandle,
}
pub struct TraversalRange {
pub traversal: TraversalHandle,
pub begin: CurvePosition,
pub end: CurvePosition,
pub direction: Direction,
}
pub struct LrmRange {
pub lrm: LrmHandle,
pub begin: LrmScaleMeasure,
pub end: LrmScaleMeasure,
pub direction: Direction,
}
fn project<CurveImpl: Curve>(
anchor: &lrs_generated::Anchor,
curve: &CurveImpl,
) -> (f64, Option<Point>) {
let p = anchor.geometry().map(|p| point! {x: p.x(), y: p.y()});
let distance_along_curve = curve
.project(p.unwrap())
.expect("Could not project anchor on the curve")
.distance_along_curve;
(distance_along_curve, p)
}
impl<CurveImpl: Curve> Lrs<CurveImpl> {
pub fn lrm_len(&self) -> usize {
self.lrms.len()
}
pub fn from_bytes(buf: &[u8]) -> Result<Self, LrsError> {
let lrs = lrs_generated::root_as_lrs(buf).map_err(LrsError::InvalidArchive)?;
let rtree_data = lrs
.lrm_spatial_index()
.map(|buffer| buffer.bytes().to_vec());
let mut result = Self {
lrms: vec![],
traversals: vec![],
properties: from_fb(lrs.properties()),
nodes: vec![],
segments: vec![],
rtree_data,
};
let source_anchors = lrs
.anchors()
.ok_or(LrsError::IncompleteArchive("anchors".to_owned()))?;
for traversal in lrs.traversals().unwrap_or_default() {
let mut coords = vec![];
for segment in traversal.segments() {
let mut geom: Vec<_> = lrs
.segments()
.expect("Bad index")
.get(segment.segment_index() as usize)
.geometry()
.iter()
.map(|p| coord! {x: p.x(),y: p.y()})
.collect();
if segment.direction() == lrs_generated::Direction::Decreasing {
geom.reverse();
}
coords.append(&mut geom);
}
let line_string = geo::LineString::new(coords);
result.traversals.push(Traversal {
id: traversal.id().to_owned(),
curve: CurveImpl::new(line_string, 1000.),
lrms: vec![],
});
}
for (lrm_idx, raw_lrm) in lrs
.linear_referencing_methods()
.unwrap_or_default()
.iter()
.enumerate()
{
let traversal_idx = raw_lrm.traversal_index() as usize;
let curve = &result
.traversals
.get(traversal_idx)
.ok_or(LrsError::IncompleteArchive(format!(
"traversal {traversal_idx} from lrm {lrm_idx}"
)))?
.curve;
let anchors: Vec<_> = raw_lrm
.anchor_indices()
.iter()
.enumerate()
.map(|(idx, anchor_idx)| {
let anchor = source_anchors.get(anchor_idx as usize);
let scale_position = raw_lrm.distances().get(idx);
let (curve_position, coord) = raw_lrm
.projected_anchors()
.map(|anchors| {
let projected_anchor = anchors.get(idx);
let geometry = projected_anchor.geometry().map(Point::from);
(projected_anchor.distance_along_curve(), geometry)
})
.unwrap_or_else(|| project(&anchor, curve));
match anchor.name() {
Some(name) => Anchor::new_named(
name,
scale_position,
curve_position,
coord,
from_fb(anchor.properties()),
),
None => Anchor::new_unnamed(
scale_position,
curve_position,
coord,
from_fb(anchor.properties()),
),
}
})
.collect();
let lrm = Lrm {
scale: LrmScale {
id: raw_lrm.id().to_owned(),
anchors,
},
traversal: TraversalHandle(traversal_idx),
properties: from_fb(raw_lrm.properties()),
};
result.traversals[traversal_idx]
.lrms
.push(LrmHandle(lrm_idx));
result.lrms.push(lrm);
}
for raw_node in lrs.nodes().unwrap_or_default().iter() {
result.nodes.push(Node::from(raw_node));
}
for raw_segment in lrs.segments().unwrap_or_default().iter() {
result.segments.push(Segment::from(raw_segment))
}
Ok(result)
}
pub fn new<P: AsRef<std::path::Path>>(filename: P) -> Result<Self, LrsError> {
use std::io::Read;
let mut f = std::fs::File::open(filename).map_err(|_| LrsError::OpenFileError)?;
let mut buf = Vec::new();
f.read_to_end(&mut buf)
.map_err(|_| LrsError::ReadFileError)?;
Self::from_bytes(&buf)
}
}
#[derive(Error, Debug, PartialEq)]
pub enum LrsError {
#[error("invalid handle")]
InvalidHandle,
#[error("curve error")]
CurveError(#[from] CurveError),
#[error("curve error")]
LrmScaleError(#[from] LrmScaleError),
#[error("open file error")]
OpenFileError,
#[error("read file error")]
ReadFileError,
#[error("invalid flatbuffer content {0}")]
InvalidArchive(#[from] flatbuffers::InvalidFlatbuffer),
#[error("the archive does not have all the required data: {0} is missing")]
IncompleteArchive(String),
}
pub trait LrsBase {
fn get_lrm(&self, lrm_id: &str) -> Option<LrmHandle>;
fn get_traversal(&self, traversal_id: &str) -> Option<TraversalHandle>;
fn get_linestring(&self, traversal: TraversalHandle) -> Result<LineString, LrsError>;
fn lookup(&self, point: Point, lrm: LrmHandle) -> Result<LrmProjection, LrsError>;
fn lookup_lrms(&self, point: Point) -> Vec<LrmProjection>;
fn traversals_containing(&self, point: Point) -> Vec<TraversalHandle>;
fn locate_traversal(&self, position: TraversalPosition) -> Result<Point, LrsError>;
fn get_lrm_traversal(&self, lrm: LrmHandle) -> TraversalHandle;
fn get_traversal_lrms(&self, traversal: TraversalHandle) -> &[LrmHandle];
fn traversal_project(
&self,
position: TraversalPosition,
onto: TraversalHandle,
) -> Result<TraversalProjection, LrsError>;
fn traversal_project_range(
&self,
range: TraversalRange,
onto: TraversalHandle,
) -> Result<TraversalRange, LrsError>;
fn lrm_project(
&self,
position: TraversalPosition,
onto: LrmHandle,
) -> Result<LrmProjection, LrsError>;
fn lrm_project_range(
&self,
range: TraversalRange,
onto: LrmHandle,
) -> Result<LrmRange, LrsError>;
fn lrm_get_measure(&self, position: LrmPosition) -> Result<LrmMeasure, LrsError>;
fn lrm_get_position(&self, measure: LrmMeasure) -> Result<LrmPosition, LrsError>;
}
impl<CurveImpl: Curve> LrsBase for Lrs<CurveImpl> {
fn get_lrm(&self, lrm_id: &str) -> Option<LrmHandle> {
self.lrms
.iter()
.position(|lrm| lrm.scale.id == lrm_id)
.map(LrmHandle)
}
fn get_traversal(&self, traversal_id: &str) -> Option<TraversalHandle> {
self.traversals
.iter()
.position(|traversal| traversal.id == traversal_id)
.map(TraversalHandle)
}
fn lookup(&self, point: Point, lrm_handle: LrmHandle) -> Result<LrmProjection, LrsError> {
let lrm = &self.lrms[lrm_handle.0];
let traversal = &self.traversals[lrm.traversal.0];
let projection = traversal.curve.project(point)?;
let measure = lrm.scale.locate_anchor(projection.distance_along_curve)?;
Ok(LrmProjection {
measure: LrmMeasure {
lrm: lrm_handle,
measure,
},
orthogonal_offset: projection.offset,
})
}
fn traversals_containing(&self, point: Point) -> Vec<TraversalHandle> {
let rtree = self
.rtree_data
.as_ref()
.and_then(|buf| RTreeRef::try_new(buf).ok());
rtree
.map(|tree| {
tree.search(point.x(), point.y(), point.x(), point.y())
.iter()
.map(|idx| TraversalHandle(*idx as usize))
.collect()
})
.unwrap_or(
self.traversals
.iter()
.enumerate()
.filter(|(_idx, traversal)| traversal.curve.bbox().contains(&point))
.map(|(idx, _traversal)| TraversalHandle(idx))
.collect(),
)
}
fn lookup_lrms(&self, point: Point) -> Vec<LrmProjection> {
let mut result: Vec<_> = self
.traversals_containing(point)
.iter()
.flat_map(|traversal_handle| &self.traversals[traversal_handle.0].lrms)
.flat_map(|&lrm_handle| self.lookup(point, lrm_handle))
.collect();
result.sort_by(|a, b| {
a.orthogonal_offset
.abs()
.partial_cmp(&b.orthogonal_offset.abs())
.unwrap_or(Ordering::Equal)
});
result
}
fn locate_traversal(&self, position: TraversalPosition) -> Result<Point, LrsError> {
Ok(self
.get_curve(position.traversal)?
.resolve(position.curve_position)?)
}
fn get_lrm_traversal(&self, lrm: LrmHandle) -> TraversalHandle {
self.lrms[lrm.0].traversal
}
fn get_traversal_lrms(&self, traversal: TraversalHandle) -> &[LrmHandle] {
&self.traversals[traversal.0].lrms
}
fn traversal_project(
&self,
position: TraversalPosition,
onto: TraversalHandle,
) -> Result<TraversalProjection, LrsError> {
let segment = self.orthogonal_segment(position.traversal, position.curve_position)?;
let onto_curve = self.get_curve(onto)?;
let point = onto_curve
.intersect_segment(segment)
.ok_or(CurveError::NotOnTheCurve)?;
let projected = onto_curve.project(point)?;
Ok(TraversalProjection {
distance_from_start: projected.distance_along_curve,
orthogonal_offset: projected.offset,
traversal: onto,
})
}
fn traversal_project_range(
&self,
range: TraversalRange,
onto: TraversalHandle,
) -> Result<TraversalRange, LrsError> {
let begin_pos = TraversalPosition {
traversal: range.traversal,
curve_position: range.begin,
};
let end_pos = TraversalPosition {
traversal: range.traversal,
curve_position: range.end,
};
let begin_projection = self.traversal_project(begin_pos, onto)?;
let end_position = self.traversal_project(end_pos, onto)?;
Ok(TraversalRange {
traversal: onto,
begin: begin_projection.distance_from_start,
end: end_position.distance_from_start,
direction: range.direction,
})
}
fn lrm_project(
&self,
position: TraversalPosition,
onto: LrmHandle,
) -> Result<LrmProjection, LrsError> {
let lrm = self.lrms.get(onto.0).ok_or(LrsError::InvalidHandle)?;
let measure = lrm.scale.locate_anchor(position.curve_position)?;
Ok(LrmProjection {
measure: LrmMeasure { lrm: onto, measure },
orthogonal_offset: 0.,
})
}
fn lrm_project_range(
&self,
range: TraversalRange,
onto: LrmHandle,
) -> Result<LrmRange, LrsError> {
let begin_pos = TraversalPosition {
traversal: range.traversal,
curve_position: range.begin,
};
let end_pos = TraversalPosition {
traversal: range.traversal,
curve_position: range.end,
};
let begin_projection = self.lrm_project(begin_pos, onto)?;
let end_projection = self.lrm_project(end_pos, onto)?;
Ok(LrmRange {
lrm: onto,
begin: begin_projection.measure.measure,
end: end_projection.measure.measure,
direction: range.direction,
})
}
fn lrm_get_measure(&self, position: LrmPosition) -> Result<LrmMeasure, LrsError> {
let scale = self.get_lrm_by_handle(position.lrm)?;
Ok(LrmMeasure {
measure: scale.get_measure(position.distance_from_start)?,
lrm: position.lrm,
})
}
fn lrm_get_position(&self, measure: LrmMeasure) -> Result<LrmPosition, LrsError> {
let scale = self.get_lrm_by_handle(measure.lrm)?;
Ok(LrmPosition {
distance_from_start: scale.locate_point(&measure.measure)?,
lrm: measure.lrm,
})
}
fn get_linestring(&self, traversal: TraversalHandle) -> Result<LineString, LrsError> {
self.get_curve(traversal).map(|c| c.as_linestring())
}
}
impl<CurveImpl: Curve> Lrs<CurveImpl> {
fn get_curve(&self, handle: TraversalHandle) -> Result<&CurveImpl, LrsError> {
self.traversals
.get(handle.0)
.map(|traversal| &traversal.curve)
.ok_or(LrsError::InvalidHandle)
}
fn get_lrm_by_handle(&self, handle: LrmHandle) -> Result<&LrmScale, LrsError> {
self.lrms
.get(handle.0)
.map(|lrm| &lrm.scale)
.ok_or(LrsError::InvalidHandle)
}
fn orthogonal_segment(
&self,
handle: TraversalHandle,
from_start: CurvePosition,
) -> Result<geo::Line, LrsError> {
let from_curve = self.get_curve(handle)?;
let normal = from_curve.get_normal(from_start)?;
let position = from_curve.resolve(from_start)?;
let start = geo::coord! {
x: position.x() + normal.0 * from_curve.max_extent(),
y: position.y() + normal.1 * from_curve.max_extent()
};
let end = geo::coord! {
x: position.x() - normal.0 * from_curve.max_extent(),
y: position.y() - normal.1 * from_curve.max_extent()
};
Ok(geo::Line::new(start, end))
}
}
pub type Properties = std::collections::HashMap<String, String>;
#[macro_export]
macro_rules! properties {
($($k:expr => $v:expr),* $(,)?) => {{
core::convert::From::from([$(($k.to_owned(), $v.to_owned()),)*])
}};
}
pub fn from_fb(properties: Option<Vector<ForwardsUOffset<lrs_generated::Property>>>) -> Properties {
properties
.unwrap_or_default()
.iter()
.map(|property| (property.key().to_string(), property.value().to_string()))
.collect()
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use geo::line_string;
use crate::curves::PlanarLineStringCurve;
use super::*;
fn lrs() -> Lrs<PlanarLineStringCurve> {
let traversal = Traversal {
curve: PlanarLineStringCurve::new(line_string![(x: 0., y:0.), (x: 200., y:0.)], 1.),
id: "curve".to_owned(),
lrms: vec![LrmHandle(0), LrmHandle(1)],
};
let traversal2 = Traversal {
curve: PlanarLineStringCurve::new(line_string![(x: 0., y:-1.), (x: 200., y:-1.)], 1.),
id: "curve".to_owned(),
lrms: vec![LrmHandle(1)],
};
let lrm = Lrm {
scale: crate::lrm_scale::tests::scale(),
traversal: TraversalHandle(0),
properties: properties!("some key" => "some value"),
};
let mut lrm2 = Lrm {
traversal: TraversalHandle(1),
scale: crate::lrm_scale::tests::scale(),
properties: properties!(),
};
"id2".clone_into(&mut lrm2.scale.id);
Lrs {
lrms: vec![lrm, lrm2],
traversals: vec![traversal, traversal2],
properties: properties!("source" => "test"),
nodes: vec![],
segments: vec![],
rtree_data: None,
}
}
#[test]
fn read_properties() {
assert_eq!(lrs().properties["source"], "test");
}
#[test]
fn get_lrm() {
assert!(lrs().get_lrm("id").is_some());
assert!(lrs().get_lrm("ideology").is_none());
}
#[test]
fn get_traversal() {
assert!(lrs().get_traversal("curve").is_some());
assert!(lrs().get_traversal("Achtung, die Kurve!").is_none());
}
#[test]
fn lookup_single_lrm() {
let lrs = lrs();
let result = lrs
.lookup(point! {x: 50., y:0.5}, lrs.get_lrm("id").unwrap())
.unwrap();
assert_eq!(result.orthogonal_offset, 0.5);
assert_eq!(result.measure.measure.scale_offset, 5.);
}
#[test]
fn lookup_multiple_lrm() {
let lrs = lrs();
let result = lrs
.lookup(point! {x: 50., y:0.5}, lrs.get_lrm("id2").unwrap())
.unwrap();
assert_eq!(result.orthogonal_offset, 1.5);
assert_eq!(result.measure.measure.scale_offset, 5.);
assert_eq!(result.measure.measure.anchor_name, "a");
assert_eq!(result.orthogonal_offset, 1.5);
assert_eq!(result.measure.measure.scale_offset, 5.);
}
#[test]
fn lookup_lrms() {
let result = lrs().lookup_lrms(point! {x: 50., y:0.5});
assert_eq!(result.len(), 2);
assert_eq!(result[0].orthogonal_offset, 0.5);
assert_eq!(result[0].measure.measure.scale_offset, 5.);
assert_eq!(result[1].orthogonal_offset, 1.5);
assert_eq!(result[1].measure.measure.scale_offset, 5.);
}
#[test]
fn locate_traversal() {
let result = lrs()
.locate_traversal(TraversalPosition {
curve_position: 0.05,
traversal: TraversalHandle(0),
})
.unwrap();
assert_eq!(result, point! {x: 10., y: 0.});
let result = lrs()
.locate_traversal(TraversalPosition {
curve_position: 0.05,
traversal: TraversalHandle(1),
})
.unwrap();
assert_eq!(result, point! {x: 10., y: -1.});
}
#[test]
fn get_lrm_traversal() {
let result = lrs().get_lrm_traversal(LrmHandle(0));
assert_eq!(TraversalHandle(0), result);
let result = lrs().get_lrm_traversal(LrmHandle(1));
assert_eq!(TraversalHandle(1), result);
}
#[test]
fn get_traversal_lrms() {
let lrs = lrs();
let result = lrs.get_traversal_lrms(TraversalHandle(0));
assert_eq!(result, &[LrmHandle(0), LrmHandle(1)]);
let result = lrs.get_traversal_lrms(TraversalHandle(1));
assert_eq!(result, &[LrmHandle(1)]);
}
#[test]
fn traversal_project() {
let position = TraversalPosition {
curve_position: 0.1,
traversal: TraversalHandle(0),
};
let result = lrs()
.traversal_project(position, TraversalHandle(0))
.unwrap();
assert_eq!(result.distance_from_start, 0.1);
}
#[test]
fn traversal_project_range() {
let range = TraversalRange {
begin: 0.1,
end: 0.2,
direction: Direction::Default,
traversal: TraversalHandle(0),
};
let result = lrs()
.traversal_project_range(range, TraversalHandle(0))
.unwrap();
assert_eq!(result.begin, 0.1);
assert_eq!(result.end, 0.2);
}
#[test]
fn lrm_project() {
let mut position = TraversalPosition {
curve_position: 0.25,
traversal: TraversalHandle(0),
};
let result = lrs().lrm_project(position, LrmHandle(0)).unwrap();
assert_eq!(result.orthogonal_offset, 0.);
assert_eq!(result.measure.measure.scale_offset, 5.);
assert_eq!(result.measure.measure.anchor_name, "a");
position.curve_position = 0.65;
let result = lrs().lrm_project(position, LrmHandle(0)).unwrap();
assert_eq!(result.orthogonal_offset, 0.);
assert_relative_eq!(result.measure.measure.scale_offset, 3.);
assert_eq!(result.measure.measure.anchor_name, "b");
}
#[test]
fn lrm_project_range() {
let range = TraversalRange {
begin: 0.25,
end: 0.7,
direction: Direction::Default,
traversal: TraversalHandle(0),
};
let result = lrs().lrm_project_range(range, LrmHandle(0)).unwrap();
assert_eq!(result.begin.scale_offset, 5.);
assert_eq!(result.begin.anchor_name, "a");
assert_relative_eq!(result.end.scale_offset, 4.);
assert_eq!(result.end.anchor_name, "b");
}
#[test]
fn lrm_get_measure() {
let position = LrmPosition {
distance_from_start: 5.,
lrm: LrmHandle(0),
};
let result = lrs().lrm_get_measure(position).unwrap();
assert_eq!(result.measure.anchor_name, "a");
assert_eq!(result.measure.scale_offset, 5.);
let position = LrmPosition {
distance_from_start: 25.,
lrm: LrmHandle(0),
};
let result = lrs().lrm_get_measure(position).unwrap();
assert_eq!(result.measure.anchor_name, "b");
assert_eq!(result.measure.scale_offset, 15.);
}
#[test]
fn lrm_get_position() {
let measure = LrmMeasure {
lrm: LrmHandle(0),
measure: LrmScaleMeasure {
anchor_name: "a".to_owned(),
scale_offset: 5.,
},
};
let result = lrs().lrm_get_position(measure).unwrap();
assert_eq!(result.distance_from_start, 0.25);
}
}