Skip to content

Commit

Permalink
lrs: expose planar coordinate system
Browse files Browse the repository at this point in the history
Signed-off-by: Baptiste Prevot <pro.baptiste.prevot@gmail.com>
  • Loading branch information
Castavo committed Oct 25, 2024
1 parent a83b3d9 commit 6d8bd3e
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 62 deletions.
51 changes: 26 additions & 25 deletions python/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
use std::path::PathBuf;

use liblrs::lrs::LrmHandle;
use liblrs::builder::Properties;
use liblrs::lrs::{self, LrmHandle};
use liblrs::lrs_ext::*;
use liblrs::{builder::Properties, lrs::LrsBase};
use pyo3::{exceptions::PyTypeError, prelude::*};

/// Holds the whole Linear Referencing System.
Expand Down Expand Up @@ -230,6 +230,17 @@ impl Anchor {
}
}

impl From<&liblrs::lrm_scale::Anchor> for Anchor {
fn from(value: &liblrs::lrm_scale::Anchor) -> Self {
Self {
name: value.clone().id.unwrap_or_else(|| "-".to_owned()),
position: value.point.map(|p| p.into()),
curve_position: value.curve_position,
scale_position: value.scale_position,
}
}
}

#[pyclass]
/// The result of a projection onto an [`LrmScale`].
pub struct LrmProjection {
Expand All @@ -241,13 +252,11 @@ pub struct LrmProjection {
pub orthogonal_offset: f64,
}

impl From<&liblrs::lrm_scale::Anchor> for Anchor {
fn from(value: &liblrs::lrm_scale::Anchor) -> Self {
impl From<lrs::LrmProjection> for LrmProjection {
fn from(value: lrs::LrmProjection) -> Self {
Self {
name: value.clone().id.unwrap_or_else(|| "-".to_owned()),
position: value.point.map(|p| p.into()),
curve_position: value.curve_position,
scale_position: value.scale_position,
measure: (&value.measure.measure).into(),
orthogonal_offset: value.orthogonal_offset,
}
}
}
Expand All @@ -256,8 +265,8 @@ impl From<&liblrs::lrm_scale::Anchor> for Anchor {
impl Lrs {
/// Load the data.
#[new]
pub fn load(data: &[u8]) -> PyResult<Lrs> {
ExtLrs::load(data)
pub fn load(data: &[u8], planar: bool) -> PyResult<Lrs> {
ExtLrs::load(data, planar)
.map(|lrs| Self { lrs })
.map_err(|e| PyTypeError::new_err(e.to_string()))
}
Expand Down Expand Up @@ -300,9 +309,8 @@ impl Lrs {
/// Get the positon along the curve given a [`LrmScaleMeasure`]
/// The value will be between 0.0 and 1.0, both included
pub fn locate_point(&self, lrm_index: usize, measure: &LrmScaleMeasure) -> PyResult<f64> {
self.lrs.lrs.lrms[lrm_index]
.scale
.locate_point(&measure.into())
self.lrs
.locate_point(lrm_index, &(measure.into()))
.map_err(|e| PyTypeError::new_err(e.to_string()))
}

Expand All @@ -321,24 +329,17 @@ impl Lrs {

/// Given a ID returns the corresponding lrs index (or None if not found)
pub fn find_lrm(&self, lrm_id: &str) -> Option<usize> {
self.lrs.lrs.get_lrm(lrm_id).map(|handle| handle.0)
self.lrs.find_lrm(lrm_id)
}

/// Projects a [`Point`] on all applicable [`Traversal`]s to a given [`Lrm`].
/// The [`Point`] must be in the bounding box of the [`Curve`] of the [`Traversal`].
/// The result is sorted by `orthogonal_offset`: the nearest [`Lrm`] to the [`Point`] is the first item.
fn lookup(&self, point: Point, lrm_handle: usize) -> Vec<LrmProjection> {
self.lrs
.lrs
.lookup(point.into(), LrmHandle(lrm_handle))
.iter()
.map(|p| LrmProjection {
measure: LrmScaleMeasure {
anchor_name: p.measure.measure.anchor_name.to_owned(),
scale_offset: p.measure.measure.scale_offset,
},
orthogonal_offset: p.orthogonal_offset,
})
.into_iter()
.map(|p| p.into())
.collect()
}
}
Expand Down Expand Up @@ -457,10 +458,10 @@ impl Builder {
}

/// Builds the lrs to be used directly
pub fn build_lrs(&mut self, properties: Properties) -> PyResult<Lrs> {
pub fn build_lrs(&mut self, properties: Properties, planar: bool) -> PyResult<Lrs> {
let lrs = self
.inner
.build_lrs(properties)
.build_lrs(properties, planar)
.map_err(|e| PyTypeError::new_err(e.to_string()))?;
Ok(Lrs { lrs })
}
Expand Down
6 changes: 3 additions & 3 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,8 @@ impl<'fbb> Builder<'fbb> {
}

/// Builds the LRS from the data.
pub fn build_lrs(&mut self, properties: Properties) -> Result<ExtLrs, String> {
ExtLrs::load(self.build_data(properties))
pub fn build_lrs(&mut self, properties: Properties, planar: bool) -> Result<ExtLrs, String> {
ExtLrs::load(self.build_data(properties), planar)
}

/// Return the mapping between a traversal id and its index in the builder.
Expand Down Expand Up @@ -643,7 +643,7 @@ mod tests {
};
b.add_lrm("lrm", traversal, &[aol, aol2], properties!());

let lrs = b.build_lrs(properties!()).unwrap();
let lrs = b.build_lrs(properties!(), true).unwrap();
let lrm = lrs
.resolve(
0,
Expand Down
105 changes: 79 additions & 26 deletions src/lrs_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,62 +3,88 @@
use geo::{Coord, Point};

use crate::curves::{Curve, SphericalLineStringCurve};
use crate::lrm_scale::Anchor;
use crate::curves::{Curve, PlanarLineStringCurve, SphericalLineStringCurve};
use crate::lrm_scale::LrmScaleMeasure;
use crate::lrs::{self, TraversalPosition};
use crate::lrs::{LrsBase, LrsError};

type Lrs = lrs::Lrs<SphericalLineStringCurve>;
use crate::lrm_scale::{Anchor, LrmScaleError};
use crate::lrs::{Lrm, LrmHandle, LrmProjection, Lrs, LrsError};
use crate::lrs::{LrsBase, TraversalPosition};

/// Struct exposed to js.
pub struct ExtLrs {
/// The linear referencing system
pub lrs: Lrs,
pub enum ExtLrs {
/// LRS with spherical coordinates.
Spherical(Lrs<SphericalLineStringCurve>),
/// LRS with planar coordinates.
Planar(Lrs<PlanarLineStringCurve>),
}

impl ExtLrs {
/// Load the data.
pub fn load(data: &[u8]) -> Result<ExtLrs, String> {
Lrs::from_bytes(data)
.map(|lrs| Self { lrs })
.map_err(|err| err.to_string())
pub fn load(data: &[u8], planar: bool) -> Result<ExtLrs, String> {
if planar {
Lrs::<PlanarLineStringCurve>::from_bytes(data).map(ExtLrs::Planar)
} else {
Lrs::<SphericalLineStringCurve>::from_bytes(data).map(ExtLrs::Spherical)
}
.map_err(|err| err.to_string())
}

/// How many LRMs compose the LRS.
pub fn lrm_len(&self) -> usize {
self.lrs.lrm_len()
match self {
ExtLrs::Spherical(lrs) => lrs.lrm_len(),
ExtLrs::Planar(lrs) => lrs.lrm_len(),
}
}

/// Given a ID returns the corresponding lrs index (or None if not found)
pub fn find_lrm(&self, lrm_id: &str) -> Option<usize> {
match self {
ExtLrs::Spherical(lrs) => lrs.get_lrm(lrm_id).map(|handle| handle.0),
ExtLrs::Planar(lrs) => lrs.get_lrm(lrm_id).map(|handle| handle.0),
}
}

fn get_lrm(&self, index: usize) -> &Lrm {
match self {
ExtLrs::Spherical(lrs) => &lrs.lrms[index],
ExtLrs::Planar(lrs) => &lrs.lrms[index],
}
}

/// Return the geometry of the LRM.
pub fn get_lrm_geom(&self, index: usize) -> Result<Vec<geo::Coord>, String> {
let lrm = self.lrs.lrms.get(index).ok_or("Invalid index")?;
self.lrs
.get_linestring(lrm.reference_traversal)
.map_err(|err| err.to_string())
.map(|linestring| linestring.0)
let lrm = self.get_lrm(index);
match self {
ExtLrs::Spherical(lrs) => lrs.get_linestring(lrm.reference_traversal),
ExtLrs::Planar(lrs) => lrs.get_linestring(lrm.reference_traversal),
}
.map_err(|err| err.to_string())
.map(|linestring| linestring.0)
}

/// `id` of the [`LrmScale`].
pub fn get_lrm_scale_id(&self, index: usize) -> String {
self.lrs.lrms[index].scale.id.clone()
self.get_lrm(index).scale.id.clone()
}

/// All the [`Anchor`]s of a LRM.
pub fn get_anchors(&self, lrm_index: usize) -> Vec<Anchor> {
self.lrs.lrms[lrm_index].scale.anchors.to_vec()
self.get_lrm(lrm_index).scale.anchors.to_vec()
}

/// Get the position given a [`LrmScaleMeasure`].
pub fn resolve(&self, lrm_index: usize, measure: &LrmScaleMeasure) -> Result<Point, LrsError> {
let lrm = &self.lrs.lrms[lrm_index];
let lrm = self.get_lrm(lrm_index);
let curve_position = lrm.scale.locate_point(measure)?.clamp(0., 1.0);

let traversal_position = TraversalPosition {
curve_position,
traversal: lrm.reference_traversal,
};
self.lrs.locate_traversal(traversal_position)
match self {
ExtLrs::Spherical(lrs) => lrs.locate_traversal(traversal_position),
ExtLrs::Planar(lrs) => lrs.locate_traversal(traversal_position),
}
}

/// Given two [`LrmScaleMeasure`]s, return a range of [`LineString`].
Expand All @@ -68,9 +94,8 @@ impl ExtLrs {
from: &LrmScaleMeasure,
to: &LrmScaleMeasure,
) -> Result<Vec<Coord>, String> {
let lrm = &self.lrs.lrms[lrm_index];
let lrm = self.get_lrm(lrm_index);
let scale = &lrm.scale;
let curve = &self.lrs.traversals[lrm.reference_traversal.0].curve;
let from = scale
.locate_point(from)
.map_err(|e| e.to_string())?
Expand All @@ -80,9 +105,37 @@ impl ExtLrs {
.map_err(|e| e.to_string())?
.clamp(0., 1.);

match curve.sublinestring(from, to) {
let sublinestring = match self {
ExtLrs::Spherical(lrs) => lrs.traversals[lrm.reference_traversal.0]
.curve
.sublinestring(from, to),
ExtLrs::Planar(lrs) => lrs.traversals[lrm.reference_traversal.0]
.curve
.sublinestring(from, to),
};

match sublinestring {
Some(linestring) => Ok(linestring.0),
None => Err("Could not find sublinestring".to_string()),
}
}

/// Given a point, return the [`LrmProjection`]s.
pub fn lookup(&self, point: Point, lrm_handle: LrmHandle) -> Vec<LrmProjection> {
match self {
ExtLrs::Spherical(lrs) => lrs.lookup(point, lrm_handle),
ExtLrs::Planar(lrs) => lrs.lookup(point, lrm_handle),
}
}

/// Get the positon along the curve given a [`LrmScaleMeasure`]
/// The value will be between 0.0 and 1.0, both included
pub fn locate_point(
&self,
lrm_index: usize,
measure: &LrmScaleMeasure,
) -> Result<f64, LrmScaleError> {
let lrm = self.get_lrm(lrm_index);
lrm.scale.locate_point(measure)
}
}
2 changes: 1 addition & 1 deletion wasm/html_demo/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ set_panic_hook()
async function file_selected(el) {
const [file] = el.target.files;
const data = await file.arrayBuffer()
const lrs = await Lrs.load(new Uint8Array(data));
const lrs = await Lrs.load(new Uint8Array(data), planar=false);

const curves_features = []
const anchors_features = []
Expand Down
10 changes: 3 additions & 7 deletions wasm/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
//! High level extensions meant for an easy usage
//! Those functions are exposed in wasm-bindings
use liblrs::{
lrs::{LrmHandle, LrsBase},
lrs_ext::*,
};
use liblrs::{lrs::LrmHandle, lrs_ext::*};
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
Expand Down Expand Up @@ -135,8 +132,8 @@ pub struct LrmProjection {
#[wasm_bindgen]
impl Lrs {
/// Load the data.
pub fn load(data: &[u8]) -> Result<Lrs, String> {
ExtLrs::load(data).map(|lrs| Self { lrs })
pub fn load(data: &[u8], planar: bool) -> Result<Lrs, String> {
ExtLrs::load(data, planar).map(|lrs| Self { lrs })
}

/// How many LRMs compose the LRS.
Expand Down Expand Up @@ -191,7 +188,6 @@ impl Lrs {
/// The result is sorted by `orthogonal_offset`: the nearest [`Lrm`] to the [`Point`] is the first item.
pub fn lookup(&self, point: Point, lrm_handle: usize) -> Vec<LrmProjection> {
self.lrs
.lrs
.lookup(point.into(), LrmHandle(lrm_handle))
.iter()
.map(|p| LrmProjection {
Expand Down

0 comments on commit 6d8bd3e

Please sign in to comment.