Skip to content

Commit c081ec3

Browse files
committed
Add a function to use the index to find the nearest lrms of a coord
Signed-off-by: Tristram Gräbener <tristram+git@tristramg.eu>
1 parent 220021b commit c081ec3

5 files changed

Lines changed: 160 additions & 30 deletions

File tree

python/src/lib.rs

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,27 @@ impl Anchor {
288288
}
289289
}
290290

291+
impl From<&liblrs::lrm_scale::Anchor> for Anchor {
292+
fn from(value: &liblrs::lrm_scale::Anchor) -> Self {
293+
let name = match value {
294+
liblrs::lrm_scale::Anchor::Named(value) => value.name.clone(),
295+
liblrs::lrm_scale::Anchor::Unnamed(_) => "-".to_owned(),
296+
};
297+
Self {
298+
name,
299+
position: value.point().map(|p| p.into()),
300+
curve_position: value.curve_position(),
301+
scale_position: value.scale_position(),
302+
}
303+
}
304+
}
305+
291306
#[pyclass]
292307
/// The result of a projection onto an [`LrmScale`].
293308
pub struct LrmProjection {
309+
#[pyo3(get, set)]
310+
/// Handle of the [`Lrm`] where this measurement applies to
311+
lrm_handle: usize,
294312
/// Contains `measure` ([`LrmScaleMeasure`]) and `lrm` ([`LrmHandle`]).
295313
#[pyo3(get, set)]
296314
pub measure: LrmScaleMeasure,
@@ -299,18 +317,15 @@ pub struct LrmProjection {
299317
pub orthogonal_offset: f64,
300318
}
301319

302-
impl From<&liblrs::lrm_scale::Anchor> for Anchor {
303-
fn from(value: &liblrs::lrm_scale::Anchor) -> Self {
304-
let name = match value {
305-
liblrs::lrm_scale::Anchor::Named(value) => value.name.clone(),
306-
liblrs::lrm_scale::Anchor::Unnamed(_) => "-".to_owned(),
307-
};
308-
320+
impl From<&liblrs::lrs::LrmProjection> for LrmProjection {
321+
fn from(value: &liblrs::lrs::LrmProjection) -> Self {
309322
Self {
310-
name,
311-
position: value.point().map(|p| p.into()),
312-
curve_position: value.curve_position(),
313-
scale_position: value.scale_position(),
323+
lrm_handle: value.measure.lrm.0,
324+
measure: LrmScaleMeasure {
325+
anchor_name: value.measure.measure.anchor_name.to_owned(),
326+
scale_offset: value.measure.measure.scale_offset,
327+
},
328+
orthogonal_offset: value.orthogonal_offset,
314329
}
315330
}
316331
}
@@ -395,13 +410,19 @@ impl Lrs {
395410
.lrs
396411
.lookup(point.into(), LrmHandle(lrm_handle))
397412
.iter()
398-
.map(|p| LrmProjection {
399-
measure: LrmScaleMeasure {
400-
anchor_name: p.measure.measure.anchor_name.to_owned(),
401-
scale_offset: p.measure.measure.scale_offset,
402-
},
403-
orthogonal_offset: p.orthogonal_offset,
404-
})
413+
.map(LrmProjection::from)
414+
.collect()
415+
}
416+
417+
/// Projects a [`Point`] on all applicable [`Traversal`]s nearby.
418+
/// The [`Point`] must be in the bounding box of the [`Curve`] of the [`Traversal`].
419+
/// The result is sorted by `orthogonal_offset`: the nearest [`Lrm`] to the [`Point`] is the first item.
420+
fn lookup_lrms(&self, point: Point) -> Vec<LrmProjection> {
421+
self.lrs
422+
.lrs
423+
.lookup_lrms(point.into())
424+
.iter()
425+
.map(LrmProjection::from)
405426
.collect()
406427
}
407428

src/builder.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ struct TempTraversal {
6565
id: String,
6666
curve: SphericalLineStringCurve,
6767
segments: Vec<SegmentOfTraversal>,
68+
lrms: Vec<usize>,
6869
}
6970

7071
impl TempTraversal {
@@ -248,6 +249,7 @@ impl<'fbb> Builder<'fbb> {
248249
id: traversal_id.to_owned(),
249250
curve: SphericalLineStringCurve::new(geo::LineString::new(coords), 100.),
250251
segments: segments.to_vec(),
252+
lrms: vec![],
251253
});
252254
self.nodes_of_traversal.push(nodes_of_traversal);
253255

@@ -279,6 +281,9 @@ impl<'fbb> Builder<'fbb> {
279281
projected_anchors: Some(self.project_anchors(&anchors, traversal_index)),
280282
..Default::default()
281283
};
284+
self.temp_traversal[traversal_index]
285+
.lrms
286+
.push(self.lrms.len());
282287
self.lrms
283288
.push(LinearReferencingMethod::create(&mut self.fbb, &args));
284289
}
@@ -544,14 +549,14 @@ impl<'fbb> Builder<'fbb> {
544549
}
545550

546551
#[cfg(test)]
547-
mod tests {
552+
pub(crate) mod tests {
548553
use crate::lrm_scale::LrmScaleMeasure;
549554

550555
use super::*;
551556
use approx::assert_relative_eq;
552557
use geo::{coord, point};
553558

554-
fn build_traversal(builder: &mut Builder) -> usize {
559+
pub fn build_traversal(builder: &mut Builder) -> usize {
555560
let s1 = builder.add_segment("s1", &[coord! {x: 0., y: 0.}, coord! {x:1., y: 0.}], 0, 1);
556561
let s2 = builder.add_segment("s2", &[coord! {x: 1., y: 0.}, coord! {x:2., y: 0.}], 1, 2);
557562
let sot1 = super::SegmentOfTraversal {

src/curves.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ use thiserror::Error;
1616
/// A [`Curve`] can be part of a larger [`Curve`] (e.g. for optimisation purposes and to have better bounding boxes).
1717
/// The [`Curve`] can be implemented.
1818
pub trait Curve {
19+
/// Is the curve represented in spherical coordinates (like longitude;latitude)
20+
///
21+
/// This has an influence on how to compute distances and bearings
22+
const IS_SPHERICAL: bool;
23+
1924
/// Builds a new [`Curve`] from a [`LineString`].
2025
/// `max_extent` is the maximum distance that is considered to be “on the curve”.
2126
/// `max_extent` plays a role in the bounding box.
@@ -101,6 +106,8 @@ pub struct PlanarLineStringCurve {
101106
}
102107

103108
impl Curve for PlanarLineStringCurve {
109+
const IS_SPHERICAL: bool = false;
110+
104111
fn new(geom: LineString, max_extent: f64) -> Self {
105112
let length = geom.length(&Euclidean);
106113
Self {
@@ -357,6 +364,8 @@ impl SphericalLineStringCurve {
357364
}
358365

359366
impl Curve for SphericalLineStringCurve {
367+
const IS_SPHERICAL: bool = true;
368+
360369
fn new(geom: LineString, max_extent: f64) -> Self {
361370
let length = geom.length(&Geodesic);
362371
Self {

src/lrs.rs

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@ use std::cmp::Ordering;
1010

1111
use flatbuffers::{ForwardsUOffset, Vector};
1212
use geo::orient::Direction;
13+
use geo_index::rtree::{RTreeIndex, RTreeRef};
1314
use thiserror::Error;
1415

1516
use crate::curves::{Curve, CurveError};
1617
use crate::lrm_scale::{
1718
Anchor, CurvePosition, LrmScale, LrmScaleError, LrmScaleMeasure, ScalePosition,
1819
};
1920
use crate::lrs_generated;
20-
use geo::{LineString, Point, coord, point};
21+
use geo::{Contains, LineString, Point, coord, point};
2122

2223
/// Used as handle to identify a [`LrmScale`] within a specific [`Lrs`].
2324
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
@@ -66,7 +67,7 @@ pub struct Lrs<CurveImpl: Curve> {
6667
/// All the [`Segment`] of this Lrs
6768
pub segments: Vec<Segment>,
6869
/// An RTree spatial index of the LRM extents
69-
rtree_data: Option<Vec<u8>>,
70+
pub rtree_data: Option<Vec<u8>>,
7071
}
7172

7273
/// A Node is a topological element of the [`Lrs`] that represents a intersection (or an extremity) of an [`Lrm`]
@@ -400,6 +401,10 @@ pub trait LrsBase {
400401
/// Projects a [`Point`] on all [`Lrm`] where the [`Point`] is in the bounding box.
401402
/// The result is sorted by `orthogonal_offset`: the nearest [`Lrm`] to the [`Point`] is the first item.
402403
fn lookup_lrms(&self, point: Point) -> Vec<LrmProjection>;
404+
/// Returns all the traversals whose bounding box include the given point
405+
///
406+
/// The function will use the spatial index if it is defined
407+
fn traversals_containing(&self, point: Point) -> Vec<TraversalHandle>;
403408

404409
/// Given a [`TraversalPosition`], returns it geographical position ([`Point`]).
405410
fn locate_traversal(&self, position: TraversalPosition) -> Result<Point, LrsError>;
@@ -482,12 +487,37 @@ impl<CurveImpl: Curve> LrsBase for Lrs<CurveImpl> {
482487
})
483488
}
484489

490+
fn traversals_containing(&self, point: Point) -> Vec<TraversalHandle> {
491+
let rtree = self
492+
.rtree_data
493+
.as_ref()
494+
.and_then(|buf| RTreeRef::try_new(buf).ok());
495+
496+
rtree
497+
.map(|tree| {
498+
// The rectangles inserted in the rtree are already expanded by the margin
499+
// That is why the min and max values are the same.
500+
tree.search(point.x(), point.y(), point.x(), point.y())
501+
.iter()
502+
.map(|idx| TraversalHandle(*idx as usize))
503+
.collect()
504+
})
505+
.unwrap_or(
506+
self.traversals
507+
.iter()
508+
.enumerate()
509+
.filter(|(_idx, traversal)| traversal.curve.bbox().contains(&point))
510+
.map(|(idx, _traversal)| TraversalHandle(idx))
511+
.collect(),
512+
)
513+
}
514+
485515
fn lookup_lrms(&self, point: Point) -> Vec<LrmProjection> {
486516
let mut result: Vec<_> = self
487-
.lrms
517+
.traversals_containing(point)
488518
.iter()
489-
.enumerate()
490-
.flat_map(|(lrm_idx, _lrm)| self.lookup(point, LrmHandle(lrm_idx)))
519+
.flat_map(|traversal_handle| &self.traversals[traversal_handle.0].lrms)
520+
.flat_map(|&lrm_handle| self.lookup(point, lrm_handle))
491521
.collect();
492522
result.sort_by(|a, b| {
493523
a.orthogonal_offset

src/lrs_ext.rs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44
use geo::{Coord, Point};
55

66
use crate::curves::{Curve, SphericalLineStringCurve};
7-
use crate::lrm_scale::Anchor;
8-
use crate::lrm_scale::LrmScaleMeasure;
9-
use crate::lrs::Properties;
10-
use crate::lrs::{self, TraversalPosition};
11-
use crate::lrs::{LrsBase, LrsError};
7+
use crate::lrm_scale::{Anchor, LrmScaleMeasure};
8+
use crate::lrs::{self, LrmProjection, LrsBase, LrsError, Properties, TraversalPosition};
129

1310
type Lrs = lrs::Lrs<SphericalLineStringCurve>;
1411

@@ -101,4 +98,72 @@ impl ExtLrs {
10198
pub fn anchor_properties(&self, lrm_index: usize, anchor_index: usize) -> &Properties {
10299
self.lrs.lrms[lrm_index].scale.anchors[anchor_index].properties()
103100
}
101+
102+
/// Projects a [`Point`] on all [`Lrm`] where the [`Point`] is in the bounding box.
103+
/// The result is sorted by `orthogonal_offset`: the nearest [`Lrm`] to the [`Point`] is the first item.
104+
pub fn lookup_lrms(&self, point: Point) -> Vec<LrmProjection> {
105+
self.lrs.lookup_lrms(point)
106+
}
107+
}
108+
109+
#[cfg(test)]
110+
pub(crate) mod tests {
111+
use geo::{Coord, coord, point};
112+
113+
use crate::builder::{AnchorOnLrm, Builder, SegmentOfTraversal};
114+
use crate::{lrs, properties};
115+
116+
fn build_lrm(builder: &mut Builder, name: &str, coords: &[Coord]) {
117+
let segment_index = builder.add_segment("name", coords, 0, 1);
118+
let sot = SegmentOfTraversal {
119+
segment_index,
120+
reversed: false,
121+
};
122+
let traversal_index = builder.add_traversal(name, &[sot]);
123+
let start_anchor = AnchorOnLrm {
124+
anchor_index: builder.add_anchor("start", Some("start"), coords[0], properties!()),
125+
distance_along_lrm: 0.0,
126+
};
127+
let end_anchor = AnchorOnLrm {
128+
anchor_index: builder.add_anchor(
129+
"end",
130+
Some("end"),
131+
*coords.last().unwrap(),
132+
properties!(),
133+
),
134+
distance_along_lrm: 1.0,
135+
};
136+
builder.add_lrm(
137+
name,
138+
traversal_index,
139+
&[start_anchor, end_anchor],
140+
properties!(),
141+
);
142+
}
143+
144+
#[test]
145+
fn test() {
146+
let mut b = Builder::new();
147+
build_lrm(&mut b, "lrm1", &[coord! {x:0., y:0.}, coord! {x:2., y:0.}]);
148+
build_lrm(&mut b, "lrm2", &[coord! {x:0., y:1.}, coord! {x:2., y:1.}]);
149+
build_lrm(&mut b, "lrm3", &[coord! {x:1., y:1.}, coord! {x:1., y:-1.}]);
150+
let lrs = b.build_lrs(properties!()).unwrap();
151+
152+
let nearest0 = lrs.lookup_lrms(point! {x: 1.0001, y:0.0});
153+
assert_eq!(nearest0.len(), 2);
154+
assert_eq!(nearest0[0].measure.lrm, lrs::LrmHandle(0));
155+
assert_eq!(nearest0[1].measure.lrm, lrs::LrmHandle(2));
156+
157+
let nearest1 = lrs.lookup_lrms(point! {x: 0.5, y:1.0});
158+
assert_eq!(nearest1.len(), 1);
159+
assert_eq!(nearest1[0].measure.lrm, lrs::LrmHandle(1));
160+
161+
let nearest2 = lrs.lookup_lrms(point! {x:1., y:-0.0001});
162+
assert_eq!(nearest2.len(), 2);
163+
assert_eq!(nearest2[0].measure.lrm, lrs::LrmHandle(2));
164+
assert_eq!(nearest2[1].measure.lrm, lrs::LrmHandle(0));
165+
166+
let nearest3 = lrs.lookup_lrms(point! {x:2.35, y:48.98});
167+
assert!(nearest3.is_empty());
168+
}
104169
}

0 commit comments

Comments
 (0)