diff options
| author | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-11 20:36:48 +0200 |
|---|---|---|
| committer | Martin Kagamino Lehoux <martin@lehoux.net> | 2026-08-11 20:36:48 +0200 |
| commit | 89467f19b929b742c35ae14d89abb3ca816ec435 (patch) | |
| tree | 70c39698fd6d58c94de3d29774a0e3bcacb2909c /web/views.go | |
| parent | a487d2410b485fe5234ab3066620f2fb999bdd1d (diff) | |
feat: add interactive ride profile
Diffstat (limited to 'web/views.go')
| -rw-r--r-- | web/views.go | 81 |
1 files changed, 80 insertions, 1 deletions
diff --git a/web/views.go b/web/views.go index a644d1e..2ce9eb2 100644 --- a/web/views.go +++ b/web/views.go @@ -5,6 +5,7 @@ import ( "net/url" "time" + "github.com/martinlehoux/biking_home/mountain_pass" "github.com/martinlehoux/biking_home/ride" "github.com/martinlehoux/biking_home/rides" ) @@ -19,9 +20,42 @@ type RideDetailView struct { RideView Route GeoJSONFeatureCollection HasRoute bool + Profile RideProfile RouteError string } +type RideProfile struct { + Points []RideProfilePoint `json:"points"` + Climbs []RideProfileClimb `json:"climbs"` + Crossings []RideProfileCrossing `json:"crossings"` +} + +type RideProfilePoint struct { + DistanceKm float64 `json:"distanceKm"` + ElevationM float64 `json:"elevationM"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +type RideProfileClimb struct { + StartKm float64 `json:"startKm"` + EndKm float64 `json:"endKm"` + TopKm float64 `json:"topKm"` + TopElevationM float64 `json:"topElevationM"` + Name string `json:"name"` + Score float64 `json:"score"` + Category string `json:"category"` +} + +type RideProfileCrossing struct { + DistanceKm float64 `json:"distanceKm"` + PassElevation float64 `json:"passElevationM"` + RideElevation float64 `json:"rideElevationM"` + DistanceToM float64 `json:"distanceToM"` + ElevationDiff float64 `json:"elevationDiffM"` + Name string `json:"name"` +} + type GeoJSONFeatureCollection struct { Type string `json:"type"` Features []GeoJSONFeature `json:"features"` @@ -150,12 +184,13 @@ func rideDetailURL(id int64) string { return fmt.Sprintf("/rides/%d", id) } -func buildRideDetailView(item rides.Ride, parsed ride.Ride) RideDetailView { +func buildRideDetailView(item rides.Ride, parsed ride.Ride, passes []mountain_pass.MountainPass) RideDetailView { coordinates := make([][]float64, parsed.Len()) for i := 0; i < parsed.Len(); i++ { coordinate := parsed.Coord(i) coordinates[i] = []float64{coordinate.Lon, coordinate.Lat} } + profile := buildRideProfile(parsed, passes) return RideDetailView{ RideView: buildRideView(item), Route: GeoJSONFeatureCollection{ @@ -169,7 +204,51 @@ func buildRideDetailView(item rides.Ride, parsed ride.Ride) RideDetailView { }}, }, HasRoute: true, + Profile: profile, + } +} + +func buildRideProfile(parsed ride.Ride, passes []mountain_pass.MountainPass) RideProfile { + profile := RideProfile{ + Points: make([]RideProfilePoint, parsed.Len()), + Climbs: make([]RideProfileClimb, 0), + Crossings: make([]RideProfileCrossing, 0), + } + for i := 0; i < parsed.Len(); i++ { + coordinate := parsed.Coord(i) + profile.Points[i] = RideProfilePoint{ + DistanceKm: parsed.DistanceM(i) / 1000, + ElevationM: parsed.ElevationM(i), + Latitude: coordinate.Lat, + Longitude: coordinate.Lon, + } + } + climbs := parsed.AllClimbs() + for i := range climbs { + if matched, ok := mountain_pass.MatchClimb(climbs[i], passes, 300, 50); ok { + climbs[i].Name = matched.Name + } + profile.Climbs = append(profile.Climbs, RideProfileClimb{ + StartKm: climbs[i].StartDistanceM() / 1000, + EndKm: climbs[i].EndDistanceM() / 1000, + TopKm: climbs[i].TopDistanceM() / 1000, + TopElevationM: climbs[i].TopElevationM(), + Name: climbs[i].Name, + Score: climbs[i].Score(), + Category: ride.Category(climbs[i].Score()), + }) + } + for _, crossing := range mountain_pass.DetectCrossings(parsed, passes, 100, 25) { + profile.Crossings = append(profile.Crossings, RideProfileCrossing{ + DistanceKm: crossing.RideDistanceM / 1000, + PassElevation: float64(crossing.Pass.Elevation), + RideElevation: crossing.RideElevation, + DistanceToM: crossing.DistanceToM, + ElevationDiff: crossing.ElevationDiff, + Name: crossing.Pass.Name, + }) } + return profile } type SyncPageData struct { |