Skip to content

Commit 7312436

Browse files
committed
Anchor: have two classes NamedAnchor and UnnamedAnchor and an enum
Signed-off-by: Tristram Gräbener <[email protected]>
1 parent f60ae68 commit 7312436

File tree

6 files changed

+152
-74
lines changed

6 files changed

+152
-74
lines changed

python/src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,11 +301,16 @@ pub struct LrmProjection {
301301

302302
impl From<&liblrs::lrm_scale::Anchor> for Anchor {
303303
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+
304309
Self {
305-
name: value.clone().id.unwrap_or_else(|| "-".to_owned()),
306-
position: value.point.map(|p| p.into()),
307-
curve_position: value.curve_position,
308-
scale_position: value.scale_position,
310+
name,
311+
position: value.point().map(|p| p.into()),
312+
curve_position: value.curve_position(),
313+
scale_position: value.scale_position(),
309314
}
310315
}
311316
}

src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ fn read_and_write_lrs() {
5050

5151
let buffer = builder.build_data(properties!("source" => "example"));
5252
let lrs = lrs::Lrs::<SphericalLineStringCurve>::from_bytes(buffer).unwrap();
53-
let anchor = &lrs.lrms[0].scale.anchors[0];
54-
assert_eq!(anchor.id.as_ref().unwrap(), "12");
53+
match &lrs.lrms[0].scale.anchors[0] {
54+
lrm_scale::Anchor::Named(anchor) => assert_eq!(anchor.name, "12"),
55+
_ => unreachable!(),
56+
}
5557
}

src/lrm_scale.rs

Lines changed: 128 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,36 @@ pub enum LrmScaleError {
2929
NoAnchorFound,
3030
}
3131

32-
/// An `Anchor` is a reference point that is well known from which the location is computed.
32+
/// An unnamed anchor is an anchor that is not a landmark, and no point will be referenced from that anchor.
33+
///
34+
/// It is used to match a scale position with a `Curve` position.
35+
/// This can happen when an object between two `NamedAnchor` has a bad offset:
36+
/// if it is said to be 300m away from the previous named anchor, but in reality it is 322m.
37+
/// This makes sure that an object label at +310m is located after the one at +300m.
3338
#[derive(PartialEq, Debug, Clone)]
34-
pub struct Anchor {
35-
/// Some `Anchor` objects might not be named,
36-
/// e.g. the first anchor of the LRM.
37-
pub id: Option<String>,
39+
pub struct UnnamedAnchor {
40+
/// Distance from the start of the scale in the scale space, can be negative.
41+
pub scale_position: ScalePosition,
42+
43+
/// Real distance from the start of the `Curve`.
44+
/// The `Curve` might not start at the same 0 (e.g. the `Curve` is longer than the scale),
45+
/// or the `Curve` might not progress at the same rate (e.g. the `Curve` is a schematic representation that distorts distances).
46+
pub curve_position: CurvePosition,
47+
48+
/// Position of the anchor on the `Curve`.
49+
pub point: Option<Point>,
3850

51+
/// Metadata to describe the node
52+
pub properties: Properties,
53+
}
54+
55+
/// A named anchor is an anchor that is a known reference point.
56+
///
57+
/// It often is a milestone (such a km 42), but it can be any landmark (a bridge, a notable building…)
58+
#[derive(PartialEq, Debug, Clone)]
59+
pub struct NamedAnchor {
60+
/// The name that identifies the anchor. It must be unique within the `LrmScale`.
61+
pub name: String,
3962
/// Distance from the start of the scale in the scale space, can be negative.
4063
pub scale_position: ScalePosition,
4164

@@ -51,54 +74,90 @@ pub struct Anchor {
5174
pub properties: Properties,
5275
}
5376

77+
/// An anchor is a reference point on a `LrmScale`.
78+
///
79+
/// It can either be a `NamedAnchor` or an `UnnamedAnchor`.
80+
#[derive(Debug, PartialEq, Clone)]
81+
pub enum Anchor {
82+
/// A `NamedAnchor`
83+
Named(NamedAnchor),
84+
/// An `UnnamedAnchor`
85+
Unnamed(UnnamedAnchor),
86+
}
87+
5488
impl Anchor {
5589
/// Builds a named `Anchor`.
56-
pub fn new(
90+
pub fn new_named(
5791
name: &str,
5892
scale_position: ScalePosition,
5993
curve_position: CurvePosition,
6094
point: Option<Point>,
6195
properties: Properties,
6296
) -> Self {
63-
Self {
64-
id: Some(name.to_owned()),
97+
Self::Named(NamedAnchor {
98+
name: name.to_owned(),
6599
scale_position,
66100
curve_position,
67101
point,
68102
properties,
69-
}
103+
})
70104
}
71105

72-
/// Builds an unnamed `Anchor`.
106+
/// Create an unnamed anchor.
73107
pub fn new_unnamed(
74108
scale_position: ScalePosition,
75109
curve_position: CurvePosition,
76110
point: Option<Point>,
77111
properties: Properties,
78112
) -> Self {
79-
Self {
80-
id: None,
113+
Self::Unnamed(UnnamedAnchor {
81114
scale_position,
82115
curve_position,
83116
point,
84117
properties,
118+
})
119+
}
120+
121+
/// Position of the anchor on the scale.
122+
///
123+
/// This value is arbitrary. It typically is expressed in meters, but can be any unit.
124+
/// It is common, but not necessary that the scale starts at 0 at the start of the curve.
125+
pub fn scale_position(&self) -> ScalePosition {
126+
match self {
127+
Anchor::Named(anchor) => anchor.scale_position,
128+
Anchor::Unnamed(anchor) => anchor.scale_position,
85129
}
86130
}
87131

88-
fn as_named(&self) -> Option<NamedAnchor> {
89-
self.id.as_ref().map(|id| NamedAnchor {
90-
id: id.to_owned(),
91-
scale_position: self.scale_position,
92-
curve_position: self.curve_position,
93-
})
132+
/// Position of the anchor on the curve.
133+
///
134+
/// The value is between 0 and 1 and represents a fraction of the curve.
135+
/// The can sometimes be negative to represent an anchor located before the curve starts.
136+
pub fn curve_position(&self) -> ScalePosition {
137+
match self {
138+
Anchor::Named(anchor) => anchor.curve_position,
139+
Anchor::Unnamed(anchor) => anchor.curve_position,
140+
}
141+
}
142+
143+
/// Properties of the anchor
144+
pub fn properties(&self) -> &Properties {
145+
match self {
146+
Anchor::Named(anchor) => &anchor.properties,
147+
Anchor::Unnamed(anchor) => &anchor.properties,
148+
}
94149
}
95-
}
96150

97-
// Private struct to be used when we only deal with Anchor that has name.
98-
struct NamedAnchor {
99-
id: String,
100-
scale_position: ScalePosition,
101-
curve_position: CurvePosition,
151+
/// Geographical position of the anchor
152+
///
153+
/// The location can be outside of the curve (a landmark visible from the curve)
154+
/// It is optional as it might be defined only with the curve position
155+
pub fn point(&self) -> Option<Point> {
156+
match self {
157+
Anchor::Named(anchor) => anchor.point,
158+
Anchor::Unnamed(anchor) => anchor.point,
159+
}
160+
}
102161
}
103162

104163
/// A measure defines a location on the [LrmScale].
@@ -125,7 +184,7 @@ impl LrmScaleMeasure {
125184
}
126185

127186
/// Represents an `LrmScale` and allows to map [Measure] to a position along a `Curve`.
128-
#[derive(PartialEq, Debug)]
187+
#[derive(PartialEq, Debug, Clone)]
129188
pub struct LrmScale {
130189
/// Unique identifier.
131190
pub id: String,
@@ -140,21 +199,21 @@ impl LrmScale {
140199
pub fn locate_point(&self, measure: &LrmScaleMeasure) -> Result<CurvePosition, LrmScaleError> {
141200
let named_anchor = self
142201
.iter_named()
143-
.find(|anchor| anchor.id == measure.anchor_name)
202+
.find(|anchor| anchor.name == measure.anchor_name)
144203
.ok_or(LrmScaleError::UnknownAnchorName)?;
145204

146205
let scale_position = named_anchor.scale_position + measure.scale_offset;
147206
let anchors = self
148207
.anchors
149208
.windows(2)
150-
.find(|window| window[1].scale_position >= scale_position)
209+
.find(|window| window[1].scale_position() >= scale_position)
151210
.or_else(|| self.anchors.windows(2).last())
152211
.ok_or(LrmScaleError::NoAnchorFound)?;
153212

154-
let scale_interval = anchors[0].scale_position - anchors[1].scale_position;
155-
let curve_interval = anchors[0].curve_position - anchors[1].curve_position;
156-
Ok(anchors[0].curve_position
157-
+ curve_interval * (scale_position - anchors[0].scale_position) / scale_interval)
213+
let scale_interval = anchors[0].scale_position() - anchors[1].scale_position();
214+
let curve_interval = anchors[0].curve_position() - anchors[1].curve_position();
215+
Ok(anchors[0].curve_position()
216+
+ curve_interval * (scale_position - anchors[0].scale_position()) / scale_interval)
158217
}
159218

160219
/// Returns a measure given a distance along the `Curve`.
@@ -172,19 +231,19 @@ impl LrmScale {
172231
// Then we search the nearest Anchor that will be the reference
173232
// to convert from Curve units to scale units.
174233
let nearest_anchor = if named_anchor.curve_position < curve_position {
175-
self.next_anchor(&named_anchor.id)
176-
.or(self.previous_anchor(&named_anchor.id))
234+
self.next_anchor(&named_anchor.name)
235+
.or(self.previous_anchor(&named_anchor.name))
177236
} else {
178-
self.previous_anchor(&named_anchor.id)
179-
.or(self.next_anchor(&named_anchor.id))
237+
self.previous_anchor(&named_anchor.name)
238+
.or(self.next_anchor(&named_anchor.name))
180239
}
181240
.ok_or(LrmScaleError::NoAnchorFound)?;
182241

183-
let ratio = (nearest_anchor.scale_position - named_anchor.scale_position)
184-
/ (nearest_anchor.curve_position - named_anchor.curve_position);
242+
let ratio = (nearest_anchor.scale_position() - named_anchor.scale_position)
243+
/ (nearest_anchor.curve_position() - named_anchor.curve_position);
185244

186245
Ok(LrmScaleMeasure {
187-
anchor_name: named_anchor.id,
246+
anchor_name: named_anchor.name.clone(),
188247
scale_offset: (curve_position - named_anchor.curve_position) * ratio,
189248
})
190249
}
@@ -201,7 +260,7 @@ impl LrmScale {
201260
.ok_or(LrmScaleError::NoAnchorFound)?;
202261

203262
Ok(LrmScaleMeasure {
204-
anchor_name: named_anchor.id,
263+
anchor_name: named_anchor.name.clone(),
205264
scale_offset: scale_position - named_anchor.scale_position,
206265
})
207266
}
@@ -211,13 +270,13 @@ impl LrmScale {
211270
pub fn get_position(&self, measure: LrmScaleMeasure) -> Result<ScalePosition, LrmScaleError> {
212271
let named_anchor = self
213272
.iter_named()
214-
.find(|anchor| anchor.id == measure.anchor_name)
273+
.find(|anchor| anchor.name == measure.anchor_name)
215274
.ok_or(LrmScaleError::UnknownAnchorName)?;
216275

217276
Ok(named_anchor.scale_position + measure.scale_offset)
218277
}
219278

220-
fn nearest_named(&self, curve_position: CurvePosition) -> Option<NamedAnchor> {
279+
fn nearest_named(&self, curve_position: CurvePosition) -> Option<&NamedAnchor> {
221280
// Tries to find the Anchor whose curve_position is the biggest possible, yet smaller than Curve position
222281
// Otherwise take the first named
223282
// Anchor names ----A----B----
@@ -232,7 +291,7 @@ impl LrmScale {
232291
.or_else(|| self.iter_named().next())
233292
}
234293

235-
fn scale_nearest_named(&self, scale_position: ScalePosition) -> Option<NamedAnchor> {
294+
fn scale_nearest_named(&self, scale_position: ScalePosition) -> Option<&NamedAnchor> {
236295
// Like nearest_named, but our position is along the scale
237296
self.iter_named()
238297
.rev()
@@ -245,28 +304,35 @@ impl LrmScale {
245304
self.anchors
246305
.iter()
247306
.rev()
248-
.skip_while(|anchor| anchor.id.as_deref() != Some(name))
307+
.skip_while(|anchor| match anchor {
308+
Anchor::Named(anchor) => anchor.name != name,
309+
Anchor::Unnamed(_) => true,
310+
})
249311
.nth(1)
250312
}
251313

252314
// Finds the closest Anchor after the Anchor having the name `name`
253315
fn next_anchor(&self, name: &str) -> Option<&Anchor> {
254316
self.anchors
255317
.iter()
256-
.skip_while(|anchor| anchor.id.as_deref() != Some(name))
318+
.skip_while(|anchor| match anchor {
319+
Anchor::Named(anchor) => anchor.name != name,
320+
Anchor::Unnamed(_) => true,
321+
})
257322
.nth(1)
258323
}
259324

260325
// Iterates only on named Anchor objects
261-
fn iter_named(&self) -> impl DoubleEndedIterator<Item = NamedAnchor> + '_ {
262-
self.anchors.iter().filter_map(|anchor| anchor.as_named())
326+
fn iter_named(&self) -> impl DoubleEndedIterator<Item = &NamedAnchor> + '_ {
327+
self.anchors.iter().filter_map(|anchor| match anchor {
328+
Anchor::Named(anchor) => Some(anchor),
329+
Anchor::Unnamed(_) => None,
330+
})
263331
}
264332
}
265333

266334
#[cfg(test)]
267335
pub(crate) mod tests {
268-
use geo::point;
269-
270336
use crate::properties;
271337

272338
use super::*;
@@ -275,8 +341,8 @@ pub(crate) mod tests {
275341
LrmScale {
276342
id: "id".to_owned(),
277343
anchors: vec![
278-
Anchor::new("a", 0., 0., Some(point! { x: 0., y: 0. }), properties!()),
279-
Anchor::new("b", 10., 0.5, Some(point! { x: 0., y: 0. }), properties!()),
344+
Anchor::new_named("a", 0., 0., None, properties!()),
345+
Anchor::new_named("b", 10., 0.5, None, properties!()),
280346
],
281347
}
282348
}
@@ -311,15 +377,15 @@ pub(crate) mod tests {
311377
let scale = LrmScale {
312378
id: "id".to_owned(),
313379
anchors: vec![
314-
Anchor::new("a", 0., 2., Some(point! { x: 0., y: 0. }), properties!()),
315-
Anchor::new("b", 10., 3., Some(point! { x: 0., y: 0. }), properties!()),
380+
Anchor::new_named("a", 0., 2., None, properties!()),
381+
Anchor::new_named("b", 10., 3., None, properties!()),
316382
],
317383
};
318384

319-
assert_eq!(scale.nearest_named(2.1).unwrap().id, "a");
320-
assert_eq!(scale.nearest_named(2.9).unwrap().id, "a");
321-
assert_eq!(scale.nearest_named(1.5).unwrap().id, "a");
322-
assert_eq!(scale.nearest_named(3.5).unwrap().id, "b");
385+
assert_eq!(scale.nearest_named(2.1).unwrap().name, "a");
386+
assert_eq!(scale.nearest_named(2.9).unwrap().name, "a");
387+
assert_eq!(scale.nearest_named(1.5).unwrap().name, "a");
388+
assert_eq!(scale.nearest_named(3.5).unwrap().name, "b");
323389
}
324390

325391
#[test]
@@ -343,10 +409,10 @@ pub(crate) mod tests {
343409
let scale = LrmScale {
344410
id: "id".to_owned(),
345411
anchors: vec![
346-
Anchor::new_unnamed(0., 100., Some(point! { x: 0., y: 0. }), properties!()),
347-
Anchor::new("a", 1., 200., Some(point! { x: 0., y: 0. }), properties!()),
348-
Anchor::new("b", 3., 300., Some(point! { x: 0., y: 0. }), properties!()),
349-
Anchor::new_unnamed(4., 400., Some(point! { x: 0., y: 0. }), properties!()),
412+
Anchor::new_unnamed(0., 100., None, properties!()),
413+
Anchor::new_named("a", 1., 200., None, properties!()),
414+
Anchor::new_named("b", 3., 300., None, properties!()),
415+
Anchor::new_unnamed(4., 400., None, properties!()),
350416
],
351417
};
352418

@@ -414,7 +480,7 @@ pub(crate) mod tests {
414480
let scale = LrmScale {
415481
id: "id".to_owned(),
416482
anchors: vec![
417-
Anchor::new("a", 1000. + 0., -2., None, properties!()),
483+
Anchor::new_named("a", 1000. + 0., -2., None, properties!()),
418484
Anchor::new_unnamed(1000. + 300., 1., None, properties!()),
419485
],
420486
};
@@ -437,7 +503,7 @@ pub(crate) mod tests {
437503
let scale = LrmScale {
438504
id: "id".to_owned(),
439505
anchors: vec![
440-
Anchor::new("a", 0., 0., None, properties!()),
506+
Anchor::new_named("a", 0., 0., None, properties!()),
441507
Anchor::new_unnamed(1., 0.4, None, properties!()),
442508
Anchor::new_unnamed(9., 0.6, None, properties!()),
443509
],

src/lrs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ impl<CurveImpl: Curve> Lrs<CurveImpl> {
295295
.unwrap_or_else(|| project(&anchor, curve));
296296

297297
match anchor.name() {
298-
Some(name) => Anchor::new(
298+
Some(name) => Anchor::new_named(
299299
name,
300300
scale_position,
301301
curve_position,

0 commit comments

Comments
 (0)