Skip to content

Commit 1a516ae

Browse files
committed
[course] Refactor CourseBuilder/SegmentedCourseBuilder
Splits the old CourseBuilderImpl into two types to allow for a two-stage build. The first stage (segmentation) does the work of solving the indirect problem between adjacent points and geocentric (XYZ) forward if necessary, while the second converts into a Course, after the CourseSetBuilderImpl has had the chance to run process_waypoint and append course points to the SegmentedCourseBuilder.
1 parent fc3c53e commit 1a516ae

3 files changed

Lines changed: 121 additions & 77 deletions

File tree

integration-stub/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ fn write_fit(spec: PathBuf, out: PathBuf) -> Result<()> {
7474
point.lat * DEG,
7575
point.lon * DEG,
7676
None,
77-
)?)?;
77+
)?);
7878
}
7979
let course_set = builder.build()?;
8080
let course = course_set.courses.first().unwrap();

src/course.rs

Lines changed: 119 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ where
112112
CourseError: From<<P as TryFrom<GeoPoint>>::Error>,
113113
{
114114
options: CourseOptions,
115-
courses: Vec<CourseBuilderImpl<P>>,
115+
course_builders: Vec<CourseBuilder>,
116+
segmented_courses: Vec<SegmentedCourseBuilder<P>>,
116117
waypoints: Vec<Waypoint<P>>,
117118
}
118119

@@ -126,18 +127,19 @@ where
126127
pub fn new(options: CourseOptions) -> Self {
127128
Self {
128129
options,
129-
courses: Vec::new(),
130+
course_builders: Vec::new(),
131+
segmented_courses: Vec::new(),
130132
waypoints: Vec::new(),
131133
}
132134
}
133135

134-
pub fn add_course(&mut self) -> &mut CourseBuilderImpl<P> {
135-
self.courses.push(CourseBuilderImpl::new());
136+
pub fn add_course(&mut self) -> &mut CourseBuilder {
137+
self.course_builders.push(CourseBuilder::new());
136138
self.last_course_mut().unwrap()
137139
}
138140

139-
pub fn last_course_mut(&mut self) -> Result<&mut CourseBuilderImpl<P>> {
140-
match self.courses.last_mut() {
141+
pub fn last_course_mut(&mut self) -> Result<&mut CourseBuilder> {
142+
match self.course_builders.last_mut() {
141143
Some(course) => Ok(course),
142144
None => Err(CourseError::MissingCourse),
143145
}
@@ -149,17 +151,21 @@ where
149151
}
150152

151153
pub fn num_courses(&self) -> usize {
152-
self.courses.len()
154+
self.course_builders.len()
153155
}
154156

157+
/// Build the courses.
158+
///
159+
/// The geodesic calculations happen in here.
155160
pub fn build(mut self) -> Result<CourseSet> {
156-
let mut courses = vec![];
157-
for course_builder in &mut self.courses {
158-
course_builder.calculate_segments()?;
159-
}
161+
let mut courses = Vec::new();
162+
self.segmented_courses = std::mem::take(&mut self.course_builders)
163+
.into_iter()
164+
.map(|c| c.segment())
165+
.collect::<Result<Vec<_>>>()?;
160166
self.process_waypoints()?;
161-
for course_builder in self.courses {
162-
courses.push(course_builder.build()?);
167+
for segmented_course in self.segmented_courses {
168+
courses.push(segmented_course.build()?);
163169
}
164170
Ok(CourseSet { courses })
165171
}
@@ -183,13 +189,13 @@ where
183189
}))
184190
}
185191

186-
fn process_waypoint(
192+
fn process_single_waypoint(
187193
waypoint: &Waypoint<P>,
188-
course: &CourseBuilderImpl<P>,
194+
course: &SegmentedCourseBuilder<P>,
189195
threshold: Meter<f64>,
190196
) -> Result<Vec<NearIntercept>> {
191197
let mut slns = Vec::new();
192-
for (segment, start_distance) in course.segments.as_ref().unwrap() {
198+
for (segment, start_distance) in &course.segments_and_distances {
193199
slns.push(<Self as SolveIntercept<P>>::solve_intercept(
194200
segment,
195201
waypoint,
@@ -221,11 +227,14 @@ where
221227

222228
#[tracing::instrument(level = "debug", name = "process_waypoints", skip_all)]
223229
fn process_waypoints(&mut self) -> Result<()> {
224-
for course in &mut self.courses {
230+
for segmented_course in &mut self.segmented_courses {
225231
let waypoints_and_intercepts = iter_work!(&self.waypoints)
226232
.map(|waypoint| {
227-
let near_intercepts =
228-
Self::process_waypoint(waypoint, course, self.options.threshold)?;
233+
let near_intercepts = Self::process_single_waypoint(
234+
waypoint,
235+
segmented_course,
236+
self.options.threshold,
237+
)?;
229238
Ok((waypoint, near_intercepts))
230239
})
231240
.collect::<Result<Vec<_>>>()?;
@@ -241,23 +250,27 @@ where
241250
.unwrap()
242251
});
243252
Self::add_course_point(
244-
&mut course.course_points,
253+
&mut segmented_course.course_points,
245254
&near_sorted[0],
246255
waypoint,
247256
);
248257
}
249258

250259
InterceptStrategy::First => {
251260
Self::add_course_point(
252-
&mut course.course_points,
261+
&mut segmented_course.course_points,
253262
&near_intercepts[0],
254263
waypoint,
255264
);
256265
}
257266

258267
InterceptStrategy::All => {
259268
for sln in near_intercepts {
260-
Self::add_course_point(&mut course.course_points, sln, waypoint);
269+
Self::add_course_point(
270+
&mut segmented_course.course_points,
271+
sln,
272+
waypoint,
273+
);
261274
}
262275
}
263276
}
@@ -409,53 +422,59 @@ impl Course {
409422
}
410423
}
411424

412-
pub struct CourseBuilderImpl<P>
413-
where
414-
P: HasGeoPoint + Send + Sync,
415-
<P as TryFrom<GeoPoint>>::Error: Send,
416-
CourseError: From<<P as TryFrom<GeoPoint>>::Error>,
417-
{
425+
/// Builder for [`Course`].
426+
///
427+
/// Used as part of [`CourseSetBuilderImpl`] to allow composing a course.
428+
/// Within that context, obtain a new instance with
429+
/// [`CourseSetBuilderImpl::add_course`].
430+
pub struct CourseBuilder {
418431
route_points: Vec<GeoPoint>,
419-
segments: Option<Vec<(GeoSegment<P>, Meter<f64>)>>,
420432
name: Option<String>,
421-
course_points: Vec<CoursePoint>,
422433
num_repeated_points_skipped: usize,
423434
}
424435

425436
#[allow(clippy::new_without_default)]
426-
impl<P> CourseBuilderImpl<P>
427-
where
428-
P: HasGeoPoint + Send + Sync,
429-
<P as TryFrom<GeoPoint>>::Error: Send,
430-
CourseError: From<<P as TryFrom<GeoPoint>>::Error>,
431-
{
437+
impl CourseBuilder {
432438
fn new() -> Self {
433439
Self {
434440
route_points: Vec::new(),
435-
segments: None,
436441
name: None,
437-
course_points: Vec::new(),
438442
num_repeated_points_skipped: 0,
439443
}
440444
}
441445

446+
/// Set the course's name.
442447
pub fn with_name(&mut self, name: String) -> &mut Self {
443448
self.name = Some(name);
444449
self
445450
}
446451

447-
pub fn with_route_point(&mut self, point: GeoPoint) -> Result<&mut Self> {
452+
/// Add a route point to the course.
453+
///
454+
/// Adds GPX route points (or equivalently, track points) to the course in
455+
/// order of traversal.
456+
pub fn with_route_point(&mut self, point: GeoPoint) -> &mut Self {
448457
if let Some(prev) = self.route_points.last() {
449458
if *prev == point {
450459
self.num_repeated_points_skipped += 1;
451-
return Ok(self);
460+
return self;
452461
}
453462
}
454463
self.route_points.push(point);
455-
Ok(self)
464+
self
456465
}
457466

458-
fn calculate_segments(&mut self) -> Result<()> {
467+
/// Segment the course.
468+
///
469+
/// Does the initial geodesic calculations of solving the indirect problem
470+
/// between adjacent points, and lifting points into instances the type
471+
/// parameter `P` (such as [`XyzPoint`]).
472+
fn segment<P>(self) -> Result<SegmentedCourseBuilder<P>>
473+
where
474+
P: HasGeoPoint + Send + Sync,
475+
<P as TryFrom<GeoPoint>>::Error: Send,
476+
CourseError: From<<P as TryFrom<GeoPoint>>::Error>,
477+
{
459478
let ps = iter_work!(self.route_points)
460479
.map(|p| P::try_from(*p))
461480
.collect::<std::result::Result<Vec<_>, _>>()?;
@@ -476,29 +495,58 @@ where
476495
})
477496
.collect::<Vec<_>>();
478497

479-
self.segments = Some(segments_and_distances);
480-
Ok(())
498+
Ok(SegmentedCourseBuilder {
499+
route_points: self.route_points,
500+
segments_and_distances,
501+
name: self.name,
502+
course_points: Vec::new(),
503+
num_repeated_points_skipped: self.num_repeated_points_skipped,
504+
})
481505
}
506+
}
482507

508+
/// A segmented [`Course`] builder.
509+
///
510+
/// This represents an intermediate stage of building a [`CourseBuilder`]: The
511+
/// initial work of processing route points into geodesic segments along with
512+
/// distance information, as well as possibly [`XyPoint`] values, has been done.
513+
///
514+
/// This builder is used internally within [`CourseSetBuilderImpl`] to gather
515+
/// course points.
516+
struct SegmentedCourseBuilder<P>
517+
where
518+
P: HasGeoPoint + Send + Sync,
519+
<P as TryFrom<GeoPoint>>::Error: Send,
520+
CourseError: From<<P as TryFrom<GeoPoint>>::Error>,
521+
{
522+
route_points: Vec<GeoPoint>,
523+
segments_and_distances: Vec<(GeoSegment<P>, Meter<f64>)>,
524+
name: Option<String>,
525+
course_points: Vec<CoursePoint>,
526+
num_repeated_points_skipped: usize,
527+
}
528+
529+
impl<P> SegmentedCourseBuilder<P>
530+
where
531+
P: HasGeoPoint + Send + Sync,
532+
<P as TryFrom<GeoPoint>>::Error: Send,
533+
CourseError: From<<P as TryFrom<GeoPoint>>::Error>,
534+
{
483535
fn build(mut self) -> Result<Course> {
484-
if self.segments.is_none() {
485-
self.calculate_segments()?;
486-
}
487536
match &self.name {
488537
Some(name) => info!("Building course {}", name),
489538
None => info!("Building untitled course"),
490539
}
491540
let mut records = Vec::new();
492-
let segments = self.segments.as_ref().unwrap();
493-
let num_segments = segments.len();
494-
let total_distance = match segments.last() {
541+
let num_segments = self.segments_and_distances.len();
542+
let total_distance = match self.segments_and_distances.last() {
495543
Some((s, start_distance)) => *start_distance + s.geo_length,
496544
None => 0.0 * M,
497545
};
498-
for (segment, start_distance) in segments {
546+
for (segment, start_distance) in self.segments_and_distances {
499547
records.push(Record {
500548
point: *segment.start.geo(),
501-
cumulative_distance: *start_distance,
549+
cumulative_distance: start_distance,
502550
})
503551
}
504552
if let Some(rp) = self.route_points.last() {
@@ -586,30 +634,25 @@ mod tests {
586634
use dimensioned::si::{M, Meter};
587635

588636
use crate::course::{
589-
CourseBuilderImpl, CourseSetBuilder, InterceptSolution, NearIntercept, Waypoint,
637+
CourseBuilder, CourseSetBuilder, InterceptSolution, NearIntercept, Waypoint,
590638
};
591639
use crate::fit::CoursePointType;
592640
use crate::types::{GeoAndXyzPoint, GeoPoint};
593641
use crate::{CourseOptions, geo_point, geo_points};
594642

595-
#[cfg(feature = "floor")]
596-
type CourseBuilder = CourseBuilderImpl<GeoAndXyzPoint>;
597-
598-
#[cfg(not(feature = "floor"))]
599-
type CourseBuilder = CourseBuilderImpl<GeoPoint>;
600-
601643
#[test]
602644
fn test_course_builder_empty() -> Result<()> {
603-
let course = CourseBuilder::new().build()?;
645+
let course = CourseBuilder::new().segment::<GeoAndXyzPoint>()?.build()?;
604646
assert_eq!(course.records, vec![]);
605647
Ok(())
606648
}
607649

608650
#[test]
609651
fn test_course_builder_single_point() -> Result<()> {
610652
let mut builder = CourseBuilder::new();
611-
builder.with_route_point(geo_point!(1.0, 2.0))?;
653+
builder.with_route_point(geo_point!(1.0, 2.0));
612654
let record_points = builder
655+
.segment::<GeoAndXyzPoint>()?
613656
.build()?
614657
.records
615658
.iter()
@@ -626,9 +669,10 @@ mod tests {
626669
fn test_course_builder_two_points() -> Result<()> {
627670
let mut builder = CourseBuilder::new();
628671
builder
629-
.with_route_point(geo_point!(1.0, 2.0))?
630-
.with_route_point(geo_point!(1.1, 2.2))?;
672+
.with_route_point(geo_point!(1.0, 2.0))
673+
.with_route_point(geo_point!(1.1, 2.2));
631674
let record_points = builder
675+
.segment::<GeoAndXyzPoint>()?
632676
.build()?
633677
.records
634678
.iter()
@@ -645,15 +689,15 @@ mod tests {
645689
fn test_repeated_points() -> Result<()> {
646690
let mut builder = CourseBuilder::new();
647691
builder
648-
.with_route_point(geo_point!(1.0, 2.0))?
649-
.with_route_point(geo_point!(1.0, 2.0))?
650-
.with_route_point(geo_point!(1.1, 2.2))?
651-
.with_route_point(geo_point!(1.1, 2.2))?
652-
.with_route_point(geo_point!(1.2, 2.1))?
653-
.with_route_point(geo_point!(1.1, 2.2))?
654-
.with_route_point(geo_point!(1.1, 2.2))?;
655-
656-
let course = builder.build()?;
692+
.with_route_point(geo_point!(1.0, 2.0))
693+
.with_route_point(geo_point!(1.0, 2.0))
694+
.with_route_point(geo_point!(1.1, 2.2))
695+
.with_route_point(geo_point!(1.1, 2.2))
696+
.with_route_point(geo_point!(1.2, 2.1))
697+
.with_route_point(geo_point!(1.1, 2.2))
698+
.with_route_point(geo_point!(1.1, 2.2));
699+
700+
let course = builder.segment::<GeoAndXyzPoint>()?.build()?;
657701
let record_points = course.records.iter().map(|r| r.point).collect::<Vec<_>>();
658702

659703
let expected_points = geo_points![(1.0, 2.0), (1.1, 2.2), (1.2, 2.1), (1.1, 2.2)];
@@ -667,9 +711,9 @@ mod tests {
667711
let mut builder = CourseSetBuilder::new(CourseOptions::default());
668712
builder
669713
.add_course()
670-
.with_route_point(geo_point!(35.5252717091331, -101.2856451853322))?
671-
.with_route_point(geo_point!(36.05200980326534, -90.02610043506964))?
672-
.with_route_point(geo_point!(38.13369722302025, -78.51238236506529))?;
714+
.with_route_point(geo_point!(35.5252717091331, -101.2856451853322))
715+
.with_route_point(geo_point!(36.05200980326534, -90.02610043506964))
716+
.with_route_point(geo_point!(38.13369722302025, -78.51238236506529));
673717

674718
builder.add_waypoint(Waypoint {
675719
name: "MyWaypoint".to_owned(),

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ pub fn convert_gpx<R: BufRead, W: Write>(
129129
}
130130

131131
GpxItem::TrackOrRoutePoint(p) => {
132-
builder.last_course_mut()?.with_route_point(p)?;
132+
builder.last_course_mut()?.with_route_point(p);
133133
}
134134

135135
GpxItem::Waypoint(wpt) => {

0 commit comments

Comments
 (0)