summaryrefslogtreecommitdiff
path: root/ride/ride.go
diff options
context:
space:
mode:
authorMartin Kagamino Lehoux <martin@lehoux.net>2025-04-21 11:51:03 +0200
committerMartin Kagamino Lehoux <martin@lehoux.net>2025-04-21 11:51:03 +0200
commitc47470b51075fbac1adea281f3324c1d22b6ff5f (patch)
tree33df3b64d5153ac96f3d7f9086465cf81b7c8fad /ride/ride.go
parentc05cd9cb6305f15e3de985e5040142e0156a1286 (diff)
Cleanup parsing
Diffstat (limited to 'ride/ride.go')
-rw-r--r--ride/ride.go98
1 files changed, 94 insertions, 4 deletions
diff --git a/ride/ride.go b/ride/ride.go
index 094a8f6..f5a92a5 100644
--- a/ride/ride.go
+++ b/ride/ride.go
@@ -1,6 +1,9 @@
package ride
import (
+ "errors"
+ "io"
+ "os"
"time"
"github.com/jftuga/geodist"
@@ -8,6 +11,7 @@ import (
"github.com/tkrajina/gpxgo/gpx"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
+ "gonum.org/v1/plot/plotutil"
"gonum.org/v1/plot/vg"
)
@@ -26,7 +30,26 @@ func (r *Ride) check() {
kcore.Assert(len(r.points) > 0, "no points in ride")
}
-func FromGPX(content *gpx.GPX) Ride {
+type RideParser interface {
+ Parse(reader io.Reader) (Ride, error)
+}
+
+func ParseFile(parser RideParser, filename string) (Ride, error) {
+ f, err := os.Open(filename)
+ if err != nil {
+ return Ride{}, err
+ }
+ defer f.Close()
+ return parser.Parse(f)
+}
+
+type GPXRideParser struct{}
+
+func (p GPXRideParser) Parse(reader io.Reader) (Ride, error) {
+ content, err := gpx.Parse(reader)
+ if err != nil {
+ return Ride{}, err
+ }
segment := content.Tracks[0].Segments[0]
points := make([]Point, len(segment.Points))
distance := 0.0
@@ -34,13 +57,17 @@ func FromGPX(content *gpx.GPX) Ride {
if i != 0 {
distance += p.Distance2D(&segment.Points[i-1])
}
- kcore.Assert(i == 0 || distance > 0, "zero distance")
- kcore.Assert(p.Elevation.NotNull(), "points without elevation")
+ if i != 0 && distance == 0 {
+ return Ride{}, errors.New("zero distance")
+ }
+ if p.Elevation.Null() {
+ return Ride{}, errors.New("points without elevation")
+ }
points[i] = Point{DistanceM: distance, ElevationM: p.Elevation.Value(), Coord: geodist.Coord{Lat: p.Latitude, Lon: p.Longitude}, Timestamp: p.Timestamp}
}
ride := Ride{points}
ride.check()
- return ride
+ return ride, nil
}
func FromPoints(points []Point) Ride {
@@ -99,3 +126,66 @@ func Plot(r *Ride, outputFile string) {
err = p.Save(10*vg.Inch, 4*vg.Inch, outputFile)
kcore.Expect(err, "failed to save plot")
}
+
+func PlotScore(r *Ride, startKm, endKm float64, outputFile string) {
+ r.check()
+
+ startIndex := 0
+ for i, p := range r.points {
+ if p.DistanceM >= startKm*1000 {
+ startIndex = i
+ break
+ }
+ }
+
+ pts1 := make(plotter.XYs, 0)
+ endIndex := 0
+ for i := startIndex + 1; i < len(r.points); i++ {
+ if r.points[i].DistanceM > endKm*1000 {
+ endIndex = i
+ break
+ }
+ score1 := Score(r.points, startIndex, i)
+ pts1 = append(pts1, plotter.XY{
+ X: r.points[i].DistanceM / 1000, // Convert distance to kilometers
+ Y: score1,
+ })
+ }
+ pts2 := make(plotter.XYs, 0)
+ for i := startIndex; i < endIndex; i++ {
+ score2 := Score(r.points, i, endIndex)
+ pts2 = append(pts2, plotter.XY{
+ X: r.points[i].DistanceM / 1000, // Convert distance to kilometers
+ Y: score2,
+ })
+ }
+
+ p := plot.New()
+ p.Title.Text = "Ride Score Profile"
+ p.X.Label.Text = "Distance (km)"
+ p.Y.Label.Text = "Score"
+
+ line1, err := plotter.NewLine(pts1)
+ kcore.Expect(err, "failed to create line plot for score1")
+ line1.Color = plotutil.Color(0) // First line color
+ maxIndex := 0
+ maxValue := pts1[0].Y
+ for i, pt := range pts1 {
+ if pt.Y > maxValue {
+ maxValue = pt.Y
+ maxIndex = i
+ }
+ }
+ println("Max value of line 1 is at distance:", pts1[maxIndex].X, "km")
+
+ line2, err := plotter.NewLine(pts2)
+ kcore.Expect(err, "failed to create line plot for score2")
+ line2.Color = plotutil.Color(1) // Second line color
+
+ p.Add(line1, line2)
+ p.Legend.Add("Score (start to i)", line1)
+ p.Legend.Add("Score (i to end)", line2)
+
+ err = p.Save(10*vg.Inch, 4*vg.Inch, outputFile)
+ kcore.Expect(err, "failed to save plot")
+}