diff options
| -rw-r--r-- | mountain_pass/detection.go | 30 | ||||
| -rw-r--r-- | mountain_pass/detection_test.go | 10 | ||||
| -rw-r--r-- | rideanalysis/analysis.go | 2 | ||||
| -rw-r--r-- | web/rides.go | 9 | ||||
| -rw-r--r-- | web/views.go | 18 |
5 files changed, 36 insertions, 33 deletions
diff --git a/mountain_pass/detection.go b/mountain_pass/detection.go index 09792e3..139bc60 100644 --- a/mountain_pass/detection.go +++ b/mountain_pass/detection.go @@ -29,8 +29,8 @@ func LoadMountainPasses(db *sql.DB) ([]MountainPass, error) { return loadMountainPasses(db, nil) } -func LoadMountainPassesAroundRide(db *sql.DB, route ride.Ride, marginM float64) ([]MountainPass, error) { - return loadMountainPasses(db, rideBounds(route, marginM)) +func LoadMountainPassesAroundRide(db *sql.DB, routeCoords []geodist.Coord, marginM float64) ([]MountainPass, error) { + return loadMountainPasses(db, rideBounds(routeCoords, marginM)) } type coordinateBounds struct { @@ -40,11 +40,11 @@ type coordinateBounds struct { maxLongitude float64 } -func rideBounds(route ride.Ride, marginM float64) *coordinateBounds { - minLatitude, maxLatitude := route.Coord(0).Lat, route.Coord(0).Lat - minLongitude, maxLongitude := route.Coord(0).Lon, route.Coord(0).Lon - for index := 1; index < route.Len(); index++ { - coordinate := route.Coord(index) +func rideBounds(coords []geodist.Coord, marginM float64) *coordinateBounds { + minLatitude, maxLatitude := coords[0].Lat, coords[0].Lat + minLongitude, maxLongitude := coords[0].Lon, coords[0].Lon + for index := 1; index < len(coords); index++ { + coordinate := coords[index] minLatitude = math.Min(minLatitude, coordinate.Lat) maxLatitude = math.Max(maxLatitude, coordinate.Lat) minLongitude = math.Min(minLongitude, coordinate.Lon) @@ -95,13 +95,13 @@ func loadMountainPasses(db *sql.DB, bounds *coordinateBounds) ([]MountainPass, e return mountainPasses, rows.Err() } -func DetectCrossings(ride ride.Ride, passes []MountainPass, radiusM, elevationToleranceM float64) []Crossing { +func DetectCrossings(coords []geodist.Coord, distances, elevations []float64, passes []MountainPass, radiusM, elevationToleranceM float64) []Crossing { crossings := make([]Crossing, 0) for _, mountainPass := range passes { if mountainPass.Coord == nil { continue } - crossing, found := nearestCrossing(ride, mountainPass) + crossing, found := nearestCrossing(coords, distances, elevations, mountainPass) if found && crossing.DistanceToM <= radiusM && crossing.ElevationDiff <= elevationToleranceM { crossings = append(crossings, crossing) } @@ -138,16 +138,16 @@ func MatchClimb(climb ride.Climb, passes []MountainPass, radiusM, elevationToler return best, found } -func nearestCrossing(ride ride.Ride, mountainPass MountainPass) (Crossing, bool) { +func nearestCrossing(coords []geodist.Coord, distances, elevations []float64, mountainPass MountainPass) (Crossing, bool) { best := Crossing{Pass: mountainPass, DistanceToM: 1e18} - for i := 0; i < ride.Len(); i++ { - distanceKm, _ := geodist.HaversineDistance(ride.Coord(i), *mountainPass.Coord) + for i := 0; i < len(coords); i++ { + distanceKm, _ := geodist.HaversineDistance(coords[i], *mountainPass.Coord) distanceM := distanceKm * 1000 if distanceM < best.DistanceToM { best.DistanceToM = distanceM - best.RideDistanceM = ride.DistanceM(i) - best.RideElevation = ride.ElevationM(i) - best.ElevationDiff = absFloat64(ride.ElevationM(i) - float64(mountainPass.Elevation)) + best.RideDistanceM = distances[i] + best.RideElevation = elevations[i] + best.ElevationDiff = absFloat64(elevations[i] - float64(mountainPass.Elevation)) } } if best.DistanceToM > 1e17 { diff --git a/mountain_pass/detection_test.go b/mountain_pass/detection_test.go index f55532b..39bb9ae 100644 --- a/mountain_pass/detection_test.go +++ b/mountain_pass/detection_test.go @@ -18,7 +18,7 @@ func TestDetectCrossingsFindsPass(t *testing.T) { Name: "Pas de Magnan", Elevation: 460, Coord: &geodist.Coord{Lat: 43.62, Lon: 5.43}, } - crossings := mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{pass}, 100, 25) + crossings := mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{pass}, 100, 25) assert.Len(t, crossings, 1) assert.Equal(t, "Pas de Magnan", crossings[0].Pass.Name) assert.Less(t, crossings[0].DistanceToM, 10.0) @@ -46,7 +46,7 @@ func TestLoadMountainPassesAroundRideUsesExpandedBounds(t *testing.T) { []geodist.Coord{{Lat: 43.0, Lon: 5.0}, {Lat: 43.1, Lon: 5.1}}, make([]time.Time, 2), ) - passes, err := mountain_pass.LoadMountainPassesAroundRide(db, route, 10_000) + passes, err := mountain_pass.LoadMountainPassesAroundRide(db, route.Coords(), 10_000) require.NoError(t, err) require.Len(t, passes, 3) assert.Equal(t, "On route edge", passes[0].Name) @@ -81,13 +81,13 @@ func TestDetectCrossingsIgnoresDistantPass(t *testing.T) { Name: "Col de la Faucille", Elevation: 1320, Coord: &geodist.Coord{Lat: 46.37, Lon: 6.02}, } - assert.Empty(t, mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{far}, 100, 25)) + assert.Empty(t, mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{far}, 100, 25)) } func TestDetectCrossingsIgnoresPassWithoutCoordinates(t *testing.T) { r := rideFromPoint(t, 43.62, 5.43, 447) noCoord := mountain_pass.MountainPass{Name: "Col de la Gatasse", Elevation: 120} - assert.Empty(t, mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{noCoord}, 100, 25)) + assert.Empty(t, mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{noCoord}, 100, 25)) } func TestDetectCrossingsRequiresElevationAgreement(t *testing.T) { @@ -96,7 +96,7 @@ func TestDetectCrossingsRequiresElevationAgreement(t *testing.T) { Name: "Pas de Magnan", Elevation: 900, Coord: &geodist.Coord{Lat: 43.62, Lon: 5.43}, } - assert.Empty(t, mountain_pass.DetectCrossings(r, []mountain_pass.MountainPass{pass}, 100, 25)) + assert.Empty(t, mountain_pass.DetectCrossings(r.Coords(), r.Distances(), r.Elevations(), []mountain_pass.MountainPass{pass}, 100, 25)) } func rideFromPoint(t *testing.T, latitude, longitude, elevation float64) ride.Ride { diff --git a/rideanalysis/analysis.go b/rideanalysis/analysis.go index 55f77b8..5e08e65 100644 --- a/rideanalysis/analysis.go +++ b/rideanalysis/analysis.go @@ -59,7 +59,7 @@ func Analyze(parsed ride.Ride, passes []mountain_pass.MountainPass, officialClim return a.Segment.StartIndex() - b.Segment.StartIndex() }) result.Climbs = climbs - for _, crossing := range mountain_pass.DetectCrossings(parsed, passes, passCrossingRadiusM, passCrossingElevationToleranceM) { + for _, crossing := range mountain_pass.DetectCrossings(parsed.Coords(), parsed.Distances(), parsed.Elevations(), passes, passCrossingRadiusM, passCrossingElevationToleranceM) { result.Crossings = append(result.Crossings, crossing) } return result diff --git a/web/rides.go b/web/rides.go index 81017d9..71ab3fd 100644 --- a/web/rides.go +++ b/web/rides.go @@ -65,7 +65,7 @@ func (s *Server) handleRide(w http.ResponseWriter, r *http.Request) { }), w) return } - passes, err := mountain_pass.LoadMountainPassesAroundRide(s.db, parsed, rideDetailMountainPassMarginM) + passes, err := mountain_pass.LoadMountainPassesAroundRide(s.db, parsed.Coords(), rideDetailMountainPassMarginM) if err != nil { slog.Warn("Failed to load nearby mountain passes for ride detail", "ride_id", id, "error", err) } @@ -144,14 +144,15 @@ func (s *Server) handleCreateOfficialClimb(w http.ResponseWriter, r *http.Reques http.Error(w, "the recorded route is unavailable", http.StatusBadRequest) return } - if startIndex >= endIndex || endIndex >= parsed.Len() { + bounds := parsed.Coords() + if startIndex >= endIndex || endIndex >= len(bounds) { http.Error(w, "the official climb boundaries must be ordered route points", http.StatusBadRequest) return } created, err := official_climb.Create(s.db, official_climb.OfficialClimb{ Name: r.FormValue("name"), - StartCoord: parsed.Coord(startIndex), - EndCoord: parsed.Coord(endIndex), + StartCoord: bounds[startIndex], + EndCoord: bounds[endIndex], }) if err != nil { slog.Warn("Failed to create official climb", "ride_id", id, "error", err) diff --git a/web/views.go b/web/views.go index cfb31e5..8df860c 100644 --- a/web/views.go +++ b/web/views.go @@ -280,9 +280,9 @@ func officialClimbCreateURL(id int64) string { } func buildRideDetailView(item ride.StoredRide, parsed ride.Ride, passes []mountain_pass.MountainPass, officialClimbs []official_climb.OfficialClimb, matchPolicy official_climb.MatchPolicy) RideDetailView { - coordinates := make([][]float64, parsed.Len()) - for i := 0; i < parsed.Len(); i++ { - coordinate := parsed.Coord(i) + coords := parsed.Coords() + coordinates := make([][]float64, len(coords)) + for i, coordinate := range coords { coordinates[i] = []float64{coordinate.Lon, coordinate.Lat} } profile := buildRideProfile(parsed, passes, officialClimbs, matchPolicy) @@ -321,16 +321,18 @@ func buildRideMapPasses(passes []mountain_pass.MountainPass) []RideMapPass { } func buildRideProfile(parsed ride.Ride, passes []mountain_pass.MountainPass, officialClimbs []official_climb.OfficialClimb, matchPolicy official_climb.MatchPolicy) RideProfile { + coords := parsed.Coords() + distances := parsed.Distances() + elevations := parsed.Elevations() profile := RideProfile{ - Points: make([]RideProfilePoint, parsed.Len()), + Points: make([]RideProfilePoint, len(coords)), Climbs: make([]RideProfileClimb, 0), Crossings: make([]RideProfileCrossing, 0), } - for i := 0; i < parsed.Len(); i++ { - coordinate := parsed.Coord(i) + for i, coordinate := range coords { profile.Points[i] = RideProfilePoint{ - DistanceKm: parsed.DistanceM(i) / 1000, - ElevationM: parsed.ElevationM(i), + DistanceKm: distances[i] / 1000, + ElevationM: elevations[i], Latitude: coordinate.Lat, Longitude: coordinate.Lon, } |
