summaryrefslogtreecommitdiff
path: root/strava/sync.go
blob: f1045857a584db75dbe4123dfd584b0516f9ec5b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
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)
}