diff options
Diffstat (limited to 'strava/sync.go')
| -rw-r--r-- | strava/sync.go | 107 |
1 files changed, 102 insertions, 5 deletions
diff --git a/strava/sync.go b/strava/sync.go index b8fa428..4630a1d 100644 --- a/strava/sync.go +++ b/strava/sync.go @@ -1,12 +1,15 @@ package strava import ( + "bytes" "errors" "fmt" "net/url" "strconv" "time" + "github.com/jftuga/geodist" + "github.com/martinlehoux/biking_home/ride" "github.com/martinlehoux/kagamigo/kcore" "github.com/tkrajina/gpxgo/gpx" ) @@ -68,20 +71,24 @@ func (c *Client) list(from, to time.Time, types ...string) ([]Activity, error) { return all, nil } -func (c *Client) Get(id int64) (Activity, []byte, error) { +func (c *Client) Get(id int64) (Activity, []byte, []byte, error) { var activity Activity if err := c.GetJSON(fmt.Sprintf("/activities/%d", id), &activity); err != nil { - return Activity{}, nil, kcore.Wrap(err, "failed to get Strava activity") + return Activity{}, nil, nil, kcore.Wrap(err, "failed to get Strava activity") } streams, err := c.activityStreams(id) if err != nil { - return Activity{}, nil, err + return Activity{}, nil, nil, err } gpxData, err := activityGPX(activity, streams) if err != nil { - return activity, nil, err + return activity, nil, nil, err } - return activity, gpxData, nil + fitData, err := activityFIT(activity, streams) + if err != nil { + return activity, nil, nil, err + } + return activity, gpxData, fitData, nil } type activityStreams struct { @@ -139,6 +146,96 @@ func (c *Client) activityStreams(id int64) (activityStreams, error) { return result, nil } +func activityFIT(activity Activity, streams activityStreams) ([]byte, error) { + if len(streams.LatLng) == 0 { + return nil, ErrNoTrackPoints + } + n := len(streams.LatLng) + distances := make([]float64, 0, n) + elevations := make([]float64, 0, n) + coords := make([]geodist.Coord, 0, n) + timestamps := make([]time.Time, 0, n) + heartRates := make([]float64, 0, n) + cadences := make([]float64, 0, n) + powers := make([]float64, 0, n) + heartRateComplete := true + cadenceComplete := true + powerComplete := true + + distance := 0.0 + var previous *geodist.Coord + for i := 0; i < n; i++ { + coord := geodist.Coord{Lat: streams.LatLng[i][0], Lon: streams.LatLng[i][1]} + if previous != nil { + _, km := geodist.HaversineDistance(*previous, coord) + d := km * 1000 + if d == 0 { + continue + } + distance += d + } else { + distance = 0 + } + previous = &coord + + var elev float64 + if i < len(streams.Altitude) { + elev = streams.Altitude[i] + } + // If altitude missing and we have no value, use 0 to keep ride valid. + // The GPX path requires elevation; this mirrors that requirement. + distances = append(distances, distance) + elevations = append(elevations, elev) + coords = append(coords, coord) + var ts time.Time + if i < len(streams.Seconds) { + ts = activity.StartDate.Add(time.Duration(streams.Seconds[i]) * time.Second) + } else { + ts = activity.StartDate.Add(time.Duration(i) * time.Second) + } + timestamps = append(timestamps, ts) + + if hr := streamValue(streams.HeartRate, i); hr == nil { + heartRateComplete = false + heartRates = append(heartRates, 0) + } else { + heartRates = append(heartRates, *hr) + } + if cad := streamValue(streams.Cadence, i); cad == nil { + cadenceComplete = false + cadences = append(cadences, 0) + } else { + cadences = append(cadences, *cad) + } + if pow := streamValue(streams.Power, i); pow == nil { + powerComplete = false + powers = append(powers, 0) + } else { + powers = append(powers, *pow) + } + } + if len(distances) < 2 { + return nil, errors.New("zero distance") + } + var hrCol, cadCol, powCol []float64 + if heartRateComplete { + hrCol = heartRates + } + if cadenceComplete { + cadCol = cadences + } + if powerComplete { + powCol = powers + } + r := ride.FromColumnsWithMetrics(distances, elevations, coords, timestamps, hrCol, cadCol, powCol) + + var buf bytes.Buffer + if err := ride.EncodeFIT(r, &buf); err != nil { + return nil, err + } + return buf.Bytes(), nil +} + func activityGPX(activity Activity, streams activityStreams) ([]byte, error) { if len(streams.LatLng) == 0 { return nil, ErrNoTrackPoints |
