1- //! Abstract representation of courses and waypoints
1+ //! Types for composing courses that contain course points
22//!
3- //! Provides [`Course`], an abstract representation of a course with its records
4- //! and course points (if any). Courses are created by obtaining a
5- //! [`CourseSetBuilder`] and adding data to it.
3+ //! # Course points and courses, waypoints and routes
64//!
7- //! Once all the data has been added (for example, by parsing it from a GPX
8- //! file), [`CourseSetBuilder::build`] returns a [`CourseSet`].
5+ //! This module borrows terminology from GPX and Garmin FIT. Here a route, like
6+ //! a GPX route, consists of a sequence of route points with latitude,
7+ //! longitude, and optionally elevation. And a waypoint is just a named
8+ //! location, which may or may not be associated with any given route.
9+ //!
10+ //! (GPX also has a notion of tracks and track points, but here they are
11+ //! represented as routes and route points. This module has no concept
12+ //! corresponding to a GPX track segment.)
13+ //!
14+ //! These GPX-based concepts have FIT analogues, which contain additional
15+ //! distance information:
16+ //!
17+ //! | GPX term | FIT term | FIT additional data |
18+ //! | ----------- | ------------ | ------------------------------------------ |
19+ //! | route | course | Total distance |
20+ //! | route point | record | Distance along the course |
21+ //! | waypoint | course point | Association with and distance along course |
22+ //!
23+ //! The role of this module is to calculate this additional information needed
24+ //! to turn a set of routes and waypoints into courses and associated course
25+ //! points.
26+ //!
27+ //! Unlike in a Garmin FIT course file, here course records do not contain
28+ //! timestamps, but those can be trivially computed from records' distances
29+ //! based on a given start time and speed.
30+ //!
31+ //! # Module overview
32+ //!
33+ //! This module provides [`CourseSetBuilder`], which is used to build a set of
34+ //! routes and waypoints into a set of courses and their now-associated course
35+ //! points.
36+ //!
37+ //! Individual routes are added with [`CourseSetBuilder::add_route`], and their
38+ //! [`RouteBuilder::with_name`] and [`RouteBuilder::with_route_point`] methods
39+ //! are used to add names and route points to them.
40+ //!
41+ //! Waypoints are added to the set (not to a particular route) with
42+ //! [`CourseSetBuilder::add_waypoint`].
43+ //!
44+ //! Once your routes and waypoints have been assembled, running
45+ //! [`CourseSetBuilder::build`] does the work needed to determine which
46+ //! waypoints qualify as course points along any of the given routes. It
47+ //! returns a [`CourseSet`] containing [`Course`] instances, which in turn will
48+ //! contain any identified [`CoursePoint`] instances.
49+ //!
50+ //! If route points are given optional elevation data, this will simply be
51+ //! passed through to the corresponding course points. No additional geoid data
52+ //! is applied by this module, and elevations are not used in distance
53+ //! calculations.
954
1055use std:: cmp:: Ordering ;
1156use std:: convert:: Infallible ;
1257
13- use dimensioned:: si:: { M , Meter } ;
58+ use dimensioned:: si:: { Meter , M } ;
1459#[ cfg( feature = "rayon" ) ]
1560use rayon:: prelude:: * ;
1661use thiserror:: Error ;
1762use tracing:: { debug, error, info} ;
1863
1964use crate :: algorithm:: {
20- AlgorithmError , FromGeoPoints , NearbySegment , find_nearby_segments , intercept_distance_floor ,
21- karney_interception ,
65+ find_nearby_segments , intercept_distance_floor , karney_interception , AlgorithmError ,
66+ FromGeoPoints , NearbySegment ,
2267} ;
2368use crate :: fit:: CoursePointType ;
24- use crate :: geographic:: { GeographicError , geodesic_inverse } ;
69+ use crate :: geographic:: { geodesic_inverse , GeographicError } ;
2570use crate :: types:: { GeoAndXyzPoint , GeoPoint , GeoSegment , HasGeoPoint } ;
2671
2772#[ derive( Error , Debug ) ]
@@ -54,7 +99,7 @@ macro_rules! iter_work {
5499 } ;
55100}
56101
57- /// Strategy for handling duplicate intercepts from a waypoint.
102+ /// A strategy for handling duplicate intercepts from a waypoint.
58103///
59104/// Duplicate interception can happen in an out-and-back course, for example.
60105/// This strategy determines what to do in the case that duplicate intercepts
@@ -74,19 +119,19 @@ pub enum InterceptStrategy {
74119 All ,
75120}
76121
77- /// Options for building a course.
78- pub struct CourseOptions {
79- /// The threshold distance of a waypoint from the segments of a course,
80- /// below which the course will be considered "intercepted" by the waypoint,
81- /// turning it into a course point .
122+ /// Options for building a course set
123+ pub struct CourseSetOptions {
124+ /// The maximum distance between a waypoint and a route, within which the
125+ /// waypoint will be considered to be a course point along the corresponding
126+ /// course.
82127 pub threshold : Meter < f64 > ,
83128
84- /// What strategy should be applied in the case of duplicate course
85- /// intercepts from a waypoint .
129+ /// What strategy to apply when a waypoint intercepts a single route
130+ /// multiple times .
86131 pub strategy : InterceptStrategy ,
87132}
88133
89- impl Default for CourseOptions {
134+ impl Default for CourseSetOptions {
90135 fn default ( ) -> Self {
91136 Self {
92137 threshold : 35.0 * M ,
@@ -95,7 +140,7 @@ impl Default for CourseOptions {
95140 }
96141}
97142
98- impl CourseOptions {
143+ impl CourseSetOptions {
99144 pub fn with_threshold ( self , threshold : Meter < f64 > ) -> Self {
100145 Self {
101146 threshold,
@@ -115,27 +160,30 @@ pub struct CourseSet {
115160 pub courses : Vec < Course > ,
116161}
117162
163+ /// Builds routes and waypoints into courses with associated course points
118164pub struct CourseSetBuilder {
119- options : CourseOptions ,
120- course_builders : Vec < CourseBuilder > ,
165+ options : CourseSetOptions ,
166+ course_builders : Vec < RouteBuilder > ,
121167 waypoints : Vec < Waypoint > ,
122168}
123169
124170impl CourseSetBuilder {
125- pub fn new ( options : CourseOptions ) -> Self {
171+ /// Returns a new builder using the given [`CourseSetOptions`].
172+ pub fn new ( options : CourseSetOptions ) -> Self {
126173 Self {
127174 options,
128175 course_builders : Vec :: new ( ) ,
129176 waypoints : Vec :: new ( ) ,
130177 }
131178 }
132179
133- pub fn add_course ( & mut self ) -> & mut CourseBuilder {
134- self . course_builders . push ( CourseBuilder :: new ( ) ) ;
180+ /// Adds a new [`RouteBuilder`] to this set builder.
181+ pub fn add_route ( & mut self ) -> & mut RouteBuilder {
182+ self . course_builders . push ( RouteBuilder :: new ( ) ) ;
135183 self . last_course_mut ( ) . unwrap ( )
136184 }
137185
138- pub fn last_course_mut ( & mut self ) -> Result < & mut CourseBuilder > {
186+ pub fn last_course_mut ( & mut self ) -> Result < & mut RouteBuilder > {
139187 match self . course_builders . last_mut ( ) {
140188 Some ( course) => Ok ( course) ,
141189 None => Err ( CourseError :: MissingCourse ) ,
@@ -294,7 +342,7 @@ impl CourseSetBuilder {
294342}
295343
296344#[ derive( Clone , Copy , Debug ) ]
297- pub struct NearIntercept {
345+ struct NearIntercept {
298346 /// The point of interception.
299347 intercept_point : GeoPoint ,
300348
@@ -320,7 +368,7 @@ impl PartialOrd for NearIntercept {
320368}
321369
322370#[ derive( Clone , Copy , PartialEq , Debug ) ]
323- pub enum InterceptSolution {
371+ enum InterceptSolution {
324372 Near ( NearIntercept ) ,
325373 Far ,
326374}
@@ -349,7 +397,7 @@ impl NearbySegment<Meter<f64>> for &InterceptSolution {
349397 }
350398}
351399
352- /// A course for navigation.
400+ /// A navigation course
353401///
354402/// This contains the distance data needed as input for a FIT course file, but
355403/// it does not represent speeds or timestamps.
@@ -358,9 +406,9 @@ pub struct Course {
358406 /// course, in order of physical traversal.
359407 pub records : Vec < Record > ,
360408
361- /// The course points and their cumulative distances. These are derived from
362- /// the subset of waypoints provided to [`CourseSetBuilder`] that are
363- /// located near the course.
409+ /// The course points and their course distances. These are derived from the
410+ /// subset of waypoints provided to [`CourseSetBuilder`] that are located
411+ /// near the course.
364412 pub course_points : Vec < CoursePoint > ,
365413
366414 /// The name of the course, if given.
@@ -386,16 +434,16 @@ impl Course {
386434///
387435/// Used as part of [`CourseSetBuilder`] to allow composing a course.
388436/// Within that context, obtain a new instance with
389- /// [`CourseSetBuilder::add_course `].
390- pub struct CourseBuilder {
437+ /// [`CourseSetBuilder::add_route `].
438+ pub struct RouteBuilder {
391439 route_points : Vec < GeoPoint > ,
392440 xyz_points : Vec < GeoAndXyzPoint > ,
393441 name : Option < String > ,
394442 num_repeated_points_skipped : usize ,
395443}
396444
397445#[ allow( clippy:: new_without_default) ]
398- impl CourseBuilder {
446+ impl RouteBuilder {
399447 fn new ( ) -> Self {
400448 Self {
401449 route_points : Vec :: new ( ) ,
@@ -464,7 +512,7 @@ impl CourseBuilder {
464512
465513/// A segmented [`Course`] builder.
466514///
467- /// This represents an intermediate stage of building a [`CourseBuilder `]: The
515+ /// This represents an intermediate stage of building a [`RouteBuilder `]: The
468516/// initial work of processing route points into geodesic segments along with
469517/// distance information, as well as possibly [`XyPoint`] values, has been done.
470518///
@@ -574,25 +622,25 @@ pub struct CoursePoint {
574622mod tests {
575623 use anyhow:: Result ;
576624 use approx:: assert_relative_eq;
577- use dimensioned:: si:: { M , Meter } ;
625+ use dimensioned:: si:: { Meter , M } ;
578626
579627 use crate :: course:: {
580- CourseBuilder , CourseSetBuilder , InterceptSolution , NearIntercept , Waypoint ,
628+ CourseSetBuilder , InterceptSolution , NearIntercept , RouteBuilder , Waypoint ,
581629 } ;
582630 use crate :: fit:: CoursePointType ;
583631 use crate :: types:: GeoPoint ;
584- use crate :: { CourseOptions , geo_point, geo_points} ;
632+ use crate :: { geo_point, geo_points, CourseSetOptions } ;
585633
586634 #[ test]
587- fn test_course_builder_empty ( ) -> Result < ( ) > {
588- let course = CourseBuilder :: new ( ) . segment ( ) ?. build ( ) ?;
635+ fn test_route_builder_empty ( ) -> Result < ( ) > {
636+ let course = RouteBuilder :: new ( ) . segment ( ) ?. build ( ) ?;
589637 assert_eq ! ( course. records, vec![ ] ) ;
590638 Ok ( ( ) )
591639 }
592640
593641 #[ test]
594- fn test_course_builder_single_point ( ) -> Result < ( ) > {
595- let mut builder = CourseBuilder :: new ( ) ;
642+ fn test_route_builder_single_point ( ) -> Result < ( ) > {
643+ let mut builder = RouteBuilder :: new ( ) ;
596644 builder. with_route_point ( geo_point ! ( 1.0 , 2.0 ) ) ;
597645 let record_points = builder
598646 . segment ( ) ?
@@ -609,8 +657,8 @@ mod tests {
609657 }
610658
611659 #[ test]
612- fn test_course_builder_two_points ( ) -> Result < ( ) > {
613- let mut builder = CourseBuilder :: new ( ) ;
660+ fn test_route_builder_two_points ( ) -> Result < ( ) > {
661+ let mut builder = RouteBuilder :: new ( ) ;
614662 builder
615663 . with_route_point ( geo_point ! ( 1.0 , 2.0 ) )
616664 . with_route_point ( geo_point ! ( 1.1 , 2.2 ) ) ;
@@ -630,7 +678,7 @@ mod tests {
630678
631679 #[ test]
632680 fn test_repeated_points ( ) -> Result < ( ) > {
633- let mut builder = CourseBuilder :: new ( ) ;
681+ let mut builder = RouteBuilder :: new ( ) ;
634682 builder
635683 . with_route_point ( geo_point ! ( 1.0 , 2.0 ) )
636684 . with_route_point ( geo_point ! ( 1.0 , 2.0 ) )
@@ -651,9 +699,9 @@ mod tests {
651699
652700 #[ test]
653701 fn test_intercept_long_segments ( ) -> Result < ( ) > {
654- let mut builder = CourseSetBuilder :: new ( CourseOptions :: default ( ) ) ;
702+ let mut builder = CourseSetBuilder :: new ( CourseSetOptions :: default ( ) ) ;
655703 builder
656- . add_course ( )
704+ . add_route ( )
657705 . with_route_point ( geo_point ! ( 35.5252717091331 , -101.2856451853322 ) )
658706 . with_route_point ( geo_point ! ( 36.05200980326534 , -90.02610043506964 ) )
659707 . with_route_point ( geo_point ! ( 38.13369722302025 , -78.51238236506529 ) ) ;
0 commit comments