diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-25 11:24:51 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-25 11:24:51 +0200 |
| commit | ff3cd6248f2b78dc76f1ce8cc2337907188fd401 (patch) | |
| tree | 59cb08531d7a7087a0a71c2306ce73330527c8d4 /ride/ride.go | |
| parent | 9c2337b50a52ee95011028f6193e0c56aa1fc9d4 (diff) | |
Expose ride data as components and make algorithms consume only the slices they read
Ride now exposes its columns as component getters (Distances, Elevations,
Coords, Timestamps, and the optional Powers, Cadences, HeartRates) with the
coords field unexported. difficultyScore, movingTime, Slope and the climb
detection internals are pure functions over the specific slices they use,
and Climb carries only the component slices it needs instead of a whole
Ride.
Diffstat (limited to 'ride/ride.go')
| -rw-r--r-- | ride/ride.go | 72 |
1 files changed, 52 insertions, 20 deletions
diff --git a/ride/ride.go b/ride/ride.go index d5533ae..a694df9 100644 --- a/ride/ride.go +++ b/ride/ride.go @@ -22,7 +22,7 @@ var ErrNoTrackPoints = errors.New("ride has no track points") type Ride struct { distances []float64 elevations []float64 - Coords []geodist.Coord + coords []geodist.Coord timestamps []time.Time heartRates []float64 cadences []float64 @@ -32,7 +32,7 @@ type Ride struct { func (r *Ride) check() { kcore.Assert(r.Len() > 0, "no points in ride") kcore.Assert(len(r.distances) == len(r.elevations), "ride columns have different lengths") - kcore.Assert(len(r.distances) == len(r.Coords), "ride columns have different lengths") + kcore.Assert(len(r.distances) == len(r.coords), "ride columns have different lengths") kcore.Assert(len(r.distances) == len(r.timestamps), "ride columns have different lengths") for _, column := range [][]float64{r.heartRates, r.cadences, r.powers} { if len(column) > 0 { @@ -45,11 +45,25 @@ func (r Ride) Len() int { return len(r.distances) } +func (r Ride) Distances() []float64 { return r.distances } + +func (r Ride) Elevations() []float64 { return r.elevations } + +func (r Ride) Coords() []geodist.Coord { return r.coords } + +func (r Ride) Timestamps() []time.Time { return r.timestamps } + +func (r Ride) Powers() ([]float64, bool) { return optionalComponent(r.powers) } + +func (r Ride) Cadences() ([]float64, bool) { return optionalComponent(r.cadences) } + +func (r Ride) HeartRates() ([]float64, bool) { return optionalComponent(r.heartRates) } + func (r Ride) DistanceM(i int) float64 { return r.distances[i] } func (r Ride) ElevationM(i int) float64 { return r.elevations[i] } -func (r Ride) Coord(i int) geodist.Coord { return r.Coords[i] } +func (r Ride) Coord(i int) geodist.Coord { return r.coords[i] } func (r Ride) Timestamp(i int) time.Time { return r.timestamps[i] } @@ -65,6 +79,13 @@ func (r Ride) PowerW(i int) (float64, bool) { return optionalMetric(r.powers, i) } +func optionalComponent(column []float64) ([]float64, bool) { + if len(column) == 0 { + return nil, false + } + return column, true +} + func optionalMetric(column []float64, i int) (float64, bool) { if len(column) == 0 { return 0, false @@ -171,7 +192,7 @@ func fromColumns(distances []float64, elevations []float64, coords []geodist.Coo ride := Ride{ distances: distances, elevations: elevations, - Coords: coords, + coords: coords, timestamps: timestamps, heartRates: heartRates, cadences: cadences, @@ -235,15 +256,15 @@ const CotacolAlgorithmVersion = "v1" const cotacolSegmentLengthM = 100 func Cotacol(ride Ride) float64 { - return difficultyScore(ride, 0, ride.Len()-1) + return difficultyScore(ride.distances, ride.elevations, 0, ride.Len()-1) } -func difficultyScore(r Ride, startIndex, endIndex int) float64 { +func difficultyScore(distances, elevations []float64, startIndex, endIndex int) float64 { if endIndex-startIndex < 1 { return 0 } - startDistance := r.DistanceM(startIndex) - lastDistance := r.DistanceM(endIndex) + startDistance := distances[startIndex] + lastDistance := distances[endIndex] if lastDistance <= startDistance { return 0 } @@ -251,14 +272,14 @@ func difficultyScore(r Ride, startIndex, endIndex int) float64 { i := startIndex for start := startDistance; start < lastDistance; start += cotacolSegmentLengthM { end := math.Min(start+cotacolSegmentLengthM, lastDistance) - for i < endIndex && r.DistanceM(i+1) <= start { + for i < endIndex && distances[i+1] <= start { i++ } - startElevation := interpolateElevation(r, i, start) - for i < endIndex && r.DistanceM(i+1) < end { + startElevation := interpolateElevation(distances, elevations, i, start) + for i < endIndex && distances[i+1] < end { i++ } - endElevation := interpolateElevation(r, i, end) + endElevation := interpolateElevation(distances, elevations, i, end) slope := (endElevation - startElevation) / (end - start) if slope > 0 { cotacol += (end - start) / 1000 * (slope * 100) * (slope * 100) @@ -267,14 +288,14 @@ func difficultyScore(r Ride, startIndex, endIndex int) float64 { return cotacol } -func interpolateElevation(r Ride, i int, distance float64) float64 { - if i+1 >= r.Len() { - return r.ElevationM(i) +func interpolateElevation(distances, elevations []float64, i int, distance float64) float64 { + if i+1 >= len(distances) { + return elevations[i] } - startDistance := r.DistanceM(i) - endDistance := r.DistanceM(i + 1) + startDistance := distances[i] + endDistance := distances[i+1] t := (distance - startDistance) / (endDistance - startDistance) - return r.ElevationM(i) + t*(r.ElevationM(i+1)-r.ElevationM(i)) + return elevations[i] + t*(elevations[i+1]-elevations[i]) } func (r *Ride) ClimbFromDist(startDist, endDist float64) Climb { @@ -288,10 +309,21 @@ func (r *Ride) ClimbFromDist(startDist, endDist float64) Climb { break } } - return Climb{ride: *r, rideStart: start, rideEnd: end} + return r.climbFrom(start, end) } func (r *Ride) ClimbFromIndexes(startIndex, endIndex int) Climb { kcore.Assert(startIndex >= 0 && endIndex < r.Len() && startIndex < endIndex, "invalid climb indexes") - return Climb{ride: *r, rideStart: startIndex, rideEnd: endIndex} + return r.climbFrom(startIndex, endIndex) +} + +func (r *Ride) climbFrom(start, end int) Climb { + return Climb{ + distances: r.distances, + elevations: r.elevations, + coords: r.coords, + timestamps: r.timestamps, + rideStart: start, + rideEnd: end, + } } |
