summaryrefslogtreecommitdiff
path: root/strava/sync.go
diff options
context:
space:
mode:
Diffstat (limited to 'strava/sync.go')
-rw-r--r--strava/sync.go110
1 files changed, 110 insertions, 0 deletions
diff --git a/strava/sync.go b/strava/sync.go
new file mode 100644
index 0000000..f104585
--- /dev/null
+++ b/strava/sync.go
@@ -0,0 +1,110 @@
+package strava
+
+import (
+ "fmt"
+ "net/url"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/martinlehoux/kagamigo/kcore"
+ "github.com/tkrajina/gpxgo/gpx"
+)
+
+type Activity struct {
+ ID int64 `json:"id"`
+ Name string `json:"name"`
+ Type string `json:"type"`
+ SportType string `json:"sport_type"`
+ StartDate time.Time `json:"start_date"`
+}
+
+func (c *Client) ListActivities(after time.Time, types ...string) ([]Activity, error) {
+ allowed := map[string]bool{}
+ for _, t := range types {
+ allowed[t] = true
+ }
+ var all []Activity
+ for page := 1; ; page++ {
+ q := url.Values{}
+ q.Set("per_page", "200")
+ q.Set("page", strconv.Itoa(page))
+ q.Set("after", strconv.FormatInt(after.Unix(), 10))
+ var batch []Activity
+ err := c.GetJSON("/athlete/activities?"+q.Encode(), &batch)
+ if err != nil {
+ return nil, kcore.Wrap(err, "failed to list Strava activities")
+ }
+ if len(batch) == 0 {
+ break
+ }
+ for _, activity := range batch {
+ if len(allowed) == 0 || allowed[activity.Type] || allowed[activity.SportType] {
+ all = append(all, activity)
+ }
+ }
+ if len(batch) < 200 {
+ break
+ }
+ }
+ return all, nil
+}
+
+func (c *Client) ActivityStreams(id int64) (latlng [][2]float64, altitude []float64, seconds []float64, err error) {
+ path := fmt.Sprintf("/activities/%d/streams?keys=latlng,altitude,time&key_by_type=true", id)
+ var streams struct {
+ LatLng *struct {
+ Data [][2]float64 `json:"data"`
+ } `json:"latlng"`
+ Altitude *struct {
+ Data []float64 `json:"data"`
+ } `json:"altitude"`
+ Time *struct {
+ Data []float64 `json:"data"`
+ } `json:"time"`
+ }
+ err = c.GetJSON(path, &streams)
+ if err != nil {
+ return nil, nil, nil, kcore.Wrap(err, "failed to fetch Strava streams")
+ }
+ if streams.LatLng != nil {
+ latlng = streams.LatLng.Data
+ }
+ if streams.Altitude != nil {
+ altitude = streams.Altitude.Data
+ }
+ if streams.Time != nil {
+ seconds = streams.Time.Data
+ }
+ return latlng, altitude, seconds, nil
+}
+
+func WriteActivityGPX(file string, activity Activity, latlng [][2]float64, altitude, seconds []float64) error {
+ points := make([]gpx.GPXPoint, len(latlng))
+ for i := range latlng {
+ point := gpx.GPXPoint{}
+ point.Latitude = latlng[i][0]
+ point.Longitude = latlng[i][1]
+ if i < len(altitude) {
+ point.Elevation = *gpx.NewNullableFloat64(altitude[i])
+ }
+ if i < len(seconds) {
+ point.Timestamp = activity.StartDate.Add(time.Duration(seconds[i]) * time.Second)
+ }
+ points[i] = point
+ }
+ doc := gpx.GPX{
+ Version: "1.1",
+ Creator: "biking_home",
+ Name: activity.Name,
+ Tracks: []gpx.GPXTrack{{
+ Name: activity.Name,
+ Segments: []gpx.GPXTrackSegment{{Points: points}},
+ }},
+ }
+ xmlData, err := doc.ToXml(gpx.ToXmlParams{})
+ if err != nil {
+ return kcore.Wrap(err, "failed to build GPX XML")
+ }
+ return os.WriteFile(file, xmlData, 0o644)
+}