summaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go146
1 files changed, 146 insertions, 0 deletions
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..5a2edc6
--- /dev/null
+++ b/main.go
@@ -0,0 +1,146 @@
+package main
+
+import (
+ "fmt"
+ "math"
+ "os"
+ "slices"
+
+ "github.com/olekukonko/tablewriter"
+ "github.com/tkrajina/gpxgo/gpx"
+)
+
+func Score(points []gpx.GPXPoint) float64 {
+ distance := Distance(points)
+ if distance == 0 {
+ return 0
+ }
+ // TODO: Expect NullableFloat64
+ slope := (points[len(points)-1].Elevation.Value() - points[0].Elevation.Value()) / distance * 100.0
+
+ return math.Abs(slope) * slope * distance / 1000.0
+}
+
+func Category(score float64) string {
+ switch {
+ case score < 35:
+ return "NO"
+ case score < 80:
+ return "Cat 4"
+ case score < 180:
+ return "Cat 3"
+ case score < 250:
+ return "Cat 2"
+ case score < 600:
+ return "Cat 1"
+ default:
+ return "HC"
+ }
+}
+
+type Climb struct {
+ start int
+ end int
+}
+
+func (c Climb) Score(points []gpx.GPXPoint) float64 {
+ return Score(points[c.start:c.end])
+}
+
+func BestUpHill(points []gpx.GPXPoint, start int, end int) Climb {
+ bestScore := Score(points[start:end])
+ bestStart := start
+ for i := start; i < end; i++ {
+ score := Score(points[i:end])
+ if score > bestScore {
+ bestStart = i
+ bestScore = score
+ }
+ }
+ bestEnd := end
+ for i := bestStart; i < end; i++ {
+ score := Score(points[bestStart:i])
+ if score > bestScore {
+ bestEnd = i
+ bestScore = score
+ }
+ }
+ return Climb{bestStart, bestEnd}
+}
+
+func BestDownHill(points []gpx.GPXPoint, start int, end int) Climb {
+ bestScore := Score(points[start:end])
+ bestStart := start
+ for i := start; i < end; i++ {
+ score := Score(points[i:end])
+ if score < bestScore {
+ bestStart = i
+ bestScore = score
+ }
+ }
+ bestEnd := end
+ for i := bestStart; i < end; i++ {
+ score := Score(points[bestStart:i])
+ if score < bestScore {
+ bestEnd = i
+ bestScore = score
+ }
+ }
+ return Climb{bestStart, bestEnd}
+}
+
+func Distance(points []gpx.GPXPoint) float64 {
+ distance := 0.0
+ for i, point := range points {
+ if i > 0 {
+ distance += point.Distance2D(&points[i-1])
+ }
+ }
+ return distance
+}
+
+func FindAllClimbs(points []gpx.GPXPoint, start int, end int) []Climb {
+ from := Distance(points[:start])
+ to := from + Distance(points[start:end])
+ fmt.Printf("Searching climbs between %.1fkm and %.1fkm (%d-%d)\n", from/1000, to/1000, start, end)
+ climb := BestUpHill(points, start, end)
+ descent := BestDownHill(points, start, end)
+ climbs := []Climb{}
+
+ if climb.Score(points) < 35 {
+ if descent.Score(points) < -35 {
+ if descent.start > start {
+ climbs = append(climbs, FindAllClimbs(points, start, descent.start)...)
+ }
+ if end > descent.end {
+ climbs = append(climbs, FindAllClimbs(points, descent.end, end)...)
+ }
+ }
+ } else {
+ climbs = append(climbs, climb)
+ if climb.start > start {
+ climbs = append(climbs, FindAllClimbs(points, start, climb.start)...)
+ }
+ if end > climb.end {
+ climbs = append(climbs, FindAllClimbs(points, climb.end, end)...)
+ }
+ }
+
+ return climbs
+}
+
+func main() {
+ example, err := gpx.ParseFile("examples/2022-07-21.Pogacar.gpx")
+ if err != nil {
+ panic(err)
+ }
+ segment := example.Tracks[0].Segments[0]
+ climbs := FindAllClimbs(segment.Points, 0, len(segment.Points)-1)
+ slices.SortFunc(climbs, func(i, j Climb) int { return i.start - j.start })
+ table := tablewriter.NewWriter(os.Stdout)
+ table.SetHeader([]string{"Score", "Distance", "Category", "Slope", "From", "To"})
+ for _, climb := range climbs {
+ table.Append([]string{fmt.Sprintf("%d", int(climb.Score(segment.Points))), fmt.Sprintf("%.1fkm", Distance(segment.Points[climb.start:climb.end])/1000), Category(climb.Score(segment.Points)), fmt.Sprintf("%.1f%%", (segment.Points[climb.end].Elevation.Value()-segment.Points[climb.start].Elevation.Value())/Distance(segment.Points[climb.start:climb.end])*100), fmt.Sprintf("%.1fkm", Distance(segment.Points[:climb.start])/1000), fmt.Sprintf("%.1fkm", Distance(segment.Points[:climb.end])/1000)})
+ }
+ table.Render()
+}