diff options
Diffstat (limited to 'main.go')
| -rw-r--r-- | main.go | 134 |
1 files changed, 113 insertions, 21 deletions
@@ -1,47 +1,84 @@ package main import ( + "context" "database/sql" "flag" "log/slog" "os" + "path/filepath" "runtime/pprof" + "strings" "time" - mountainpass "github.com/martinlehoux/biking_home/mountain_pass" + "github.com/martinlehoux/biking_home/mountain_pass" + "github.com/martinlehoux/biking_home/osmpass" "github.com/martinlehoux/biking_home/ride" "github.com/martinlehoux/kagamigo/kcore" _ "github.com/mattn/go-sqlite3" ) var ( - download = flag.Bool("download", false, "download mountain passes into the database") - resume = flag.Bool("resume", false, "skip departments already cached on disk") - demo = flag.Bool("demo", false, "run the climb similarity demo") - cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") - parser = ride.GPXRideParser{} + download = flag.Bool("download", false, "download mountain passes into the database") + resume = flag.Bool("resume", false, "skip departments already cached on disk") + importCache = flag.Bool("import-cached", false, "import cached department CSVs into the database") + fetchOSM = flag.Bool("fetch-osm", false, "download the France OSM PBF (resumable) into france-latest.osm.pbf") + extractOSM = flag.String("extract-osm", "", "extract mountain passes from an OSM PBF file into the database") + enrich = flag.Bool("enrich", false, "backfill mountain pass coordinates from OSM data") + demo = flag.Bool("demo", false, "run the climb similarity demo") + chartFile = flag.String("chart", "", "render a climb/pass chart for a GPX file") + cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") + parser = ride.GPXRideParser{} ) func main() { flag.Parse() - if *download { - runDownload() - return - } - if *demo { - runDemo() - } -} - -func runDownload() { db, err := sql.Open("sqlite3", "biking_home.db") kcore.Expect(err, "failed to open database") defer db.Close() - err = mountainpass.DownloadMountainPasses(db, 5*time.Second, *resume) - kcore.Expect(err, "failed to download mountain passes") + switch { + case *download: + err = mountain_pass.DownloadMountainPasses(db, 5*time.Second, *resume) + kcore.Expect(err, "failed to download mountain passes") + case *importCache: + _, err = mountain_pass.ImportCachedDepartments(db, []string{"06", "13"}) + kcore.Expect(err, "failed to import cached departments") + case *fetchOSM: + err = osmpass.FetchFrancePBF("france-latest.osm.pbf") + kcore.Expect(err, "failed to download France OSM PBF") + case *extractOSM != "": + _, err = osmpass.ExtractMountainPasses(context.Background(), *extractOSM, db) + kcore.Expect(err, "failed to extract mountain passes from OSM") + case *enrich: + _, err = osmpass.EnrichMountainPasses(db) + kcore.Expect(err, "failed to enrich mountain passes") + case *demo: + runDemo(db) + case *chartFile != "": + runChart(db, *chartFile) + } +} + +func runChart(db *sql.DB, filename string) { + r, err := ride.ParseFile(parser, filename) + kcore.Expect(err, "failed to parse ride") + passes, err := mountain_pass.LoadMountainPasses(db) + kcore.Expect(err, "failed to load mountain passes") + + climbs := r.AllClimbs() + for i := range climbs { + if matched, ok := mountain_pass.MatchClimb(climbs[i], passes, 300, 50); ok { + climbs[i].Name = matched.Name + } + } + crossings := mountain_pass.DetectCrossings(r, passes, 100, 25) + + output := strings.TrimSuffix(filename, filepath.Ext(filename)) + "-chart.png" + renderChart(r, climbs, crossings, output) + slog.Info("Chart written", "file", output) } -func runDemo() { +func runDemo(db *sql.DB) { if *cpuprofile != "" { f, err := os.Create(*cpuprofile) kcore.Expect(err, "failed to create CPU profile") @@ -54,10 +91,63 @@ func runDemo() { for _, r := range []*ride.Ride{&ride19Jan, &ride30Mar, &ride20Apr} { slog.Info("Ride", "difficulty", r.DifficultyScore()) } + passes, passesErr := mountain_pass.LoadMountainPasses(db) + if passesErr != nil { + slog.Warn("No mountain passes in database, skipping crossings and climb naming", "error", passesErr) + passes = nil + } else { + rides := []struct { + name string + ride ride.Ride + }{ + {"2022-07-21.Pogacar", func() ride.Ride { + r, _ := ride.ParseFile(parser, "examples/2022-07-21.Pogacar.gpx") + return r + }()}, + {"2023-06-17.AlpesVerdonTour", func() ride.Ride { + r, _ := ride.ParseFile(parser, "examples/2023-06-17.AlpesVerdonTour.gpx") + return r + }()}, + {"2024-12-29.MimetArbois", func() ride.Ride { + r, _ := ride.ParseFile(parser, "examples/2024-12-29.MimetArbois.gpx") + return r + }()}, + {"activity_18043356988", ride19Jan}, + {"activity_18679866717", ride30Mar}, + {"activity_18880605641", ride20Apr}, + } + for _, entry := range rides { + crossings := mountain_pass.DetectCrossings(entry.ride, passes, 100, 25) + if len(crossings) > 0 { + slog.Info("Crossings", "ride", entry.name, "count", len(crossings)) + for _, crossing := range crossings { + slog.Info("Crossing", "pass", crossing.String()) + } + } + } + } + + nameClimbs := func(climbs []ride.Climb) { + for i := range climbs { + if matched, ok := mountain_pass.MatchClimb(climbs[i], passes, 300, 50); ok { + climbs[i].Name = matched.Name + } + } + } + + verdon, _ := ride.ParseFile(parser, "examples/2023-06-17.AlpesVerdonTour.gpx") + verdonClimbs := verdon.AllClimbs() + nameClimbs(verdonClimbs) + for _, climb := range verdonClimbs { + slog.Info("Climb", "climb", climb, "elevation", climb.Top().ElevationM, "duration", climb.Duration(), "speed", climb.Speed()*3.6) + } + index := ride.NewFlatRideClimbsIndex(30) index.Insert(ride19Jan) index.Insert(ride30Mar) - for _, climb := range ride20Apr.AllClimbs() { + ride20AprilClimbs := ride20Apr.AllClimbs() + nameClimbs(ride20AprilClimbs) + for _, climb := range ride20AprilClimbs { slog.Info("Climb", "climb", climb, "duration", climb.Duration(), "speed", climb.Speed()*3.6) if len(index.Similar(ride20Apr, climb)) > 0 { for _, similar := range index.Similar(ride20Apr, climb) { @@ -65,7 +155,9 @@ func runDemo() { } } } - for _, climb := range ride30Mar.AllClimbs() { + ride30MarchClimbs := ride30Mar.AllClimbs() + nameClimbs(ride30MarchClimbs) + for _, climb := range ride30MarchClimbs { slog.Info("Climb", "climb", climb, "duration", climb.Duration(), "speed", climb.Speed()*3.6) } ride.PlotScore(&ride30Mar, 61.2, 66.7, "Ride 30 Mar.png") |