diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-07 14:58:02 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-07 14:58:02 +0200 |
| commit | 22a58e96dddd326f4c331c2a1bdea46f808ae3ad (patch) | |
| tree | 08222960e60a9e6db843a0efe37e93b7f67913da /ride/ride.go | |
| parent | 4b90740c41afb43d11e4dd2c0c9ed91654b7ce43 (diff) | |
refactor: Store rides in columns
Diffstat (limited to 'ride/ride.go')
| -rw-r--r-- | ride/ride.go | 131 |
1 files changed, 74 insertions, 57 deletions
diff --git a/ride/ride.go b/ride/ride.go index 9ba644e..311e933 100644 --- a/ride/ride.go +++ b/ride/ride.go @@ -16,25 +16,32 @@ import ( "gonum.org/v1/plot/vg" ) -type Point struct { - DistanceM float64 - ElevationM float64 - Coord geodist.Coord - Timestamp time.Time -} - type Ride struct { - points []Point + distances []float64 + elevations []float64 + coords []geodist.Coord + timestamps []time.Time } func (r *Ride) check() { - kcore.Assert(len(r.points) > 0, "no points in ride") + 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.timestamps), "ride columns have different lengths") } -func (r *Ride) Points() []Point { - return r.points +func (r Ride) Len() int { + return len(r.distances) } +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) Timestamp(i int) time.Time { return r.timestamps[i] } + type RideParser interface { Parse(reader io.Reader) (Ride, error) } @@ -62,13 +69,19 @@ func (p GPXRideParser) Parse(reader io.Reader) (Ride, error) { if len(segment.Points) == 0 { return Ride{}, errors.New("ride has no track points") } - points := make([]Point, 0, len(segment.Points)) + distances := make([]float64, 0, len(segment.Points)) + elevations := make([]float64, 0, len(segment.Points)) + coords := make([]geodist.Coord, 0, len(segment.Points)) + timestamps := make([]time.Time, 0, len(segment.Points)) distance := 0.0 previous := segment.Points[0] if previous.Elevation.Null() { return Ride{}, errors.New("points without elevation") } - points = append(points, Point{DistanceM: 0, ElevationM: previous.Elevation.Value(), Coord: geodist.Coord{Lat: previous.Latitude, Lon: previous.Longitude}, Timestamp: previous.Timestamp}) + distances = append(distances, 0) + elevations = append(elevations, previous.Elevation.Value()) + coords = append(coords, geodist.Coord{Lat: previous.Latitude, Lon: previous.Longitude}) + timestamps = append(timestamps, previous.Timestamp) for i := 1; i < len(segment.Points); i++ { p := segment.Points[i] distance += p.Distance2D(&previous) @@ -79,18 +92,21 @@ func (p GPXRideParser) Parse(reader io.Reader) (Ride, error) { if p.Elevation.Null() { return Ride{}, errors.New("points without elevation") } - points = append(points, Point{DistanceM: distance, ElevationM: p.Elevation.Value(), Coord: geodist.Coord{Lat: p.Latitude, Lon: p.Longitude}, Timestamp: p.Timestamp}) + distances = append(distances, distance) + elevations = append(elevations, p.Elevation.Value()) + coords = append(coords, geodist.Coord{Lat: p.Latitude, Lon: p.Longitude}) + timestamps = append(timestamps, p.Timestamp) } - if len(points) < 2 { + if len(distances) < 2 { return Ride{}, errors.New("zero distance") } - ride := Ride{points} + ride := Ride{distances: distances, elevations: elevations, coords: coords, timestamps: timestamps} ride.check() return ride, nil } -func FromPoints(points []Point) Ride { - ride := Ride{points} +func FromColumns(distances []float64, elevations []float64, coords []geodist.Coord, timestamps []time.Time) Ride { + ride := Ride{distances: distances, elevations: elevations, coords: coords, timestamps: timestamps} ride.check() return ride } @@ -98,42 +114,43 @@ func FromPoints(points []Point) Ride { func (r *Ride) ScoreFromKm(start, end float64) float64 { i := 0 j := 0 - for k, p := range r.points { - if i == 0 && p.DistanceM >= start*1000 { + for k, distance := range r.distances { + if i == 0 && distance >= start*1000 { i = k } - if j == 0 && p.DistanceM >= end*1000 { + if j == 0 && distance >= end*1000 { j = k break } } - return Score(r.points, i, j) + return Score(*r, i, j) } func (r *Ride) DifficultyScore() float64 { - return difficultyScore(r.points) + return difficultyScore(*r, 0, r.Len()-1) } -func difficultyScore(points []Point) float64 { - if len(points) < 2 { +func difficultyScore(r Ride, startIndex, endIndex int) float64 { + if endIndex-startIndex < 1 { return 0 } - last := points[len(points)-1] - if last.DistanceM <= 0 { + startDistance := r.DistanceM(startIndex) + lastDistance := r.DistanceM(endIndex) + if lastDistance <= startDistance { return 0 } score := 0.0 - i := 0 - for start := points[0].DistanceM; start < last.DistanceM; start += 100 { - end := math.Min(start+100, last.DistanceM) - for i < len(points)-1 && points[i+1].DistanceM <= start { + i := startIndex + for start := startDistance; start < lastDistance; start += 100 { + end := math.Min(start+100, lastDistance) + for i < endIndex && r.DistanceM(i+1) <= start { i++ } - startElevation := interpolateElevation(points, i, start) - for i < len(points)-1 && points[i+1].DistanceM < end { + startElevation := interpolateElevation(r, i, start) + for i < endIndex && r.DistanceM(i+1) < end { i++ } - endElevation := interpolateElevation(points, i, end) + endElevation := interpolateElevation(r, i, end) slope := (endElevation - startElevation) / (end - start) if slope > 0 { score += (end - start) / 1000 * (slope * 100) * (slope * 100) @@ -142,37 +159,37 @@ func difficultyScore(points []Point) float64 { return score } -func interpolateElevation(points []Point, i int, distance float64) float64 { - if i+1 >= len(points) { - return points[i].ElevationM +func interpolateElevation(r Ride, i int, distance float64) float64 { + if i+1 >= r.Len() { + return r.ElevationM(i) } - start := points[i] - end := points[i+1] - t := (distance - start.DistanceM) / (end.DistanceM - start.DistanceM) - return start.ElevationM + t*(end.ElevationM-start.ElevationM) + startDistance := r.DistanceM(i) + endDistance := r.DistanceM(i + 1) + t := (distance - startDistance) / (endDistance - startDistance) + return r.ElevationM(i) + t*(r.ElevationM(i+1)-r.ElevationM(i)) } func (r *Ride) ClimbFromDist(startDist, endDist float64) Climb { start, end := 0, 0 - for i, p := range r.points { - if start == 0 && p.DistanceM >= startDist { + for i, distance := range r.distances { + if start == 0 && distance >= startDist { start = i } - if end == 0 && p.DistanceM >= endDist { + if end == 0 && distance >= endDist { end = i break } } - return Climb{rideStart: start, rideEnd: end, points: r.points[start : end+1]} + return Climb{ride: *r, rideStart: start, rideEnd: end} } func Plot(r *Ride, outputFile string) { r.check() - pts := make(plotter.XYs, len(r.points)) - for i, p := range r.points { - pts[i].X = p.DistanceM / 1000 // Convert distance to kilometers - pts[i].Y = p.ElevationM + pts := make(plotter.XYs, r.Len()) + for i := 0; i < r.Len(); i++ { + pts[i].X = r.DistanceM(i) / 1000 // Convert distance to kilometers + pts[i].Y = r.ElevationM(i) } p := plot.New() @@ -192,8 +209,8 @@ func PlotScore(r *Ride, startKm, endKm float64, outputFile string) { r.check() startIndex := 0 - for i, p := range r.points { - if p.DistanceM >= startKm*1000 { + for i := 0; i < r.Len(); i++ { + if r.DistanceM(i) >= startKm*1000 { startIndex = i break } @@ -201,22 +218,22 @@ func PlotScore(r *Ride, startKm, endKm float64, outputFile string) { pts1 := make(plotter.XYs, 0) endIndex := 0 - for i := startIndex + 1; i < len(r.points); i++ { - if r.points[i].DistanceM > endKm*1000 { + for i := startIndex + 1; i < r.Len(); i++ { + if r.DistanceM(i) > endKm*1000 { endIndex = i break } - score1 := Score(r.points, startIndex, i) + score1 := Score(*r, startIndex, i) pts1 = append(pts1, plotter.XY{ - X: r.points[i].DistanceM / 1000, // Convert distance to kilometers + X: r.DistanceM(i) / 1000, // Convert distance to kilometers Y: score1, }) } pts2 := make(plotter.XYs, 0) for i := startIndex; i < endIndex; i++ { - score2 := Score(r.points, i, endIndex) + score2 := Score(*r, i, endIndex) pts2 = append(pts2, plotter.XY{ - X: r.points[i].DistanceM / 1000, // Convert distance to kilometers + X: r.DistanceM(i) / 1000, // Convert distance to kilometers Y: score2, }) } |