Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement decoding TCX file #38

Merged
merged 3 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/components/OpenActivity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ import { Summary } from '@/spec/summary'
</template>

<script lang="ts">
import { ActivityFile, Lap, Record, Session } from '@/spec/activity'
import { ActivityFile, Lap, Record, SPORT_UNKNOWN, Session } from '@/spec/activity'
import { Feature } from 'ol'
import { GeoJSON } from 'ol/format'
import { shallowRef } from 'vue'
Expand Down Expand Up @@ -335,6 +335,8 @@ export default {
case 'Walking':
case 'Running':
case 'Swimming':
case 'Other':
case SPORT_UNKNOWN:
return true
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/wasm/activity-service/activity/creator.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func (c *Creator) ToMap() map[string]any {
if c.Product != nil {
m["product"] = *c.Product
}
if c.TimeCreated != (time.Time{}) {
if !c.TimeCreated.IsZero() {
m["timeCreated"] = c.TimeCreated.Format(time.RFC3339)
}

Expand Down
2 changes: 1 addition & 1 deletion src/wasm/activity-service/activity/fit/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func isBelongToSession(timestamp, sessionStartTime, sessionEndTime time.Time) bo
if timestamp.Equal(sessionStartTime) {
return true
}
if sessionEndTime == (time.Time{}) && timestamp.After(sessionStartTime) { // Last Session has no EndTime
if sessionEndTime.IsZero() && timestamp.After(sessionStartTime) { // Last Session has no EndTime
return true
} else if timestamp.After(sessionStartTime) && timestamp.Before(sessionEndTime) {
return true
Expand Down
8 changes: 4 additions & 4 deletions src/wasm/activity-service/activity/gpx/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ func NewRecord(trkpt *schema.Waypoint, prevRec *activity.Record) *activity.Recor
var pointDistance float64 // distance between two coordinates
ext := trkpt.TrackPointExtension
if ext != nil {
if prevRec != nil && prevRec.Distance != nil && ext.Distance != nil {
pointDistance = *ext.Distance - *prevRec.Distance
}
if ext.Distance != nil {
rec.Distance = ext.Distance
if prevRec != nil && prevRec.Distance != nil {
pointDistance = *ext.Distance - *prevRec.Distance
}
}
rec.Cadence = ext.Cadence
rec.HeartRate = ext.HeartRate
Expand Down Expand Up @@ -54,7 +54,7 @@ func NewRecord(trkpt *schema.Waypoint, prevRec *activity.Record) *activity.Recor
rec.Distance = kit.Ptr(prevDist + pointDistance)
}

if prevRec != nil && pointDistance > 0 {
if pointDistance != 0 {
elapsed := rec.Timestamp.Sub(prevRec.Timestamp).Seconds()
if elapsed > 0 {
speed := pointDistance / elapsed
Expand Down
4 changes: 4 additions & 0 deletions src/wasm/activity-service/activity/gpx/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ func (s *service) Decode(ctx context.Context, r io.Reader) ([]activity.Activity,
session.Laps = laps

sessions = append(sessions, session)

if act.Creator.TimeCreated.IsZero() {
act.Creator.TimeCreated = session.StartTime
}
}

if len(sessions) == 0 {
Expand Down
6 changes: 3 additions & 3 deletions src/wasm/activity-service/activity/lap.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,13 @@ func NewLapFromSession(session *Session) *Lap {
func (l *Lap) ToMap() map[string]any {
m := map[string]any{}

if l.Timestamp != (time.Time{}) {
if !l.Timestamp.IsZero() {
m["timestamp"] = l.Timestamp.Format(time.RFC3339)
}
if l.StartTime != (time.Time{}) {
if !l.StartTime.IsZero() {
m["startTime"] = l.StartTime.Format(time.RFC3339)
}
if l.EndTime != (time.Time{}) {
if !l.EndTime.IsZero() {
m["endTime"] = l.EndTime.Format(time.RFC3339)
}

Expand Down
2 changes: 1 addition & 1 deletion src/wasm/activity-service/activity/record.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type Record struct {
func (r *Record) ToMap() map[string]any {
m := map[string]any{}

if r.Timestamp != (time.Time{}) {
if !r.Timestamp.IsZero() {
m["timestamp"] = r.Timestamp.Format(time.RFC3339)
}
if r.PositionLat != nil {
Expand Down
6 changes: 3 additions & 3 deletions src/wasm/activity-service/activity/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,13 +144,13 @@ func NewSessionFromLaps(laps []*Lap, sport string) *Session {
func (s *Session) ToMap() map[string]any {
m := map[string]any{}

if s.Timestamp != (time.Time{}) {
if !s.Timestamp.IsZero() {
m["timestamp"] = s.Timestamp.Format(time.RFC3339)
}
if s.StartTime != (time.Time{}) {
if !s.StartTime.IsZero() {
m["startTime"] = s.StartTime.Format(time.RFC3339)
}
if s.EndTime != (time.Time{}) {
if !s.EndTime.IsZero() {
m["endTime"] = s.EndTime.Format(time.RFC3339)
}
if s.Sport != "" {
Expand Down
40 changes: 40 additions & 0 deletions src/wasm/activity-service/activity/tcx/record.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package tcx

import (
"github.com/muktihari/openactivity-fit/activity"
"github.com/muktihari/openactivity-fit/activity/tcx/schema"
)

func NewRecord(trackpoint *schema.Trackpoint, prevRec *activity.Record) *activity.Record {
if trackpoint == nil {
return nil
}

rec := &activity.Record{
Timestamp: trackpoint.Time,
Distance: trackpoint.DistanceMeters,
Altitude: trackpoint.AltitudeMeters,
Cadence: trackpoint.Cadence,
HeartRate: trackpoint.HeartRateBpm,
}

if trackpoint.Position != nil {
rec.PositionLat = &trackpoint.Position.LatitudeDegrees
rec.PositionLong = &trackpoint.Position.LongitudeDegrees
}

var pointDistance float64
if prevRec != nil && prevRec.Distance != nil && rec.Distance != nil {
pointDistance = *rec.Distance - *prevRec.Distance
}

if pointDistance != 0 {
elapsed := rec.Timestamp.Sub(prevRec.Timestamp).Seconds()
if elapsed > 0 {
speed := pointDistance / elapsed
rec.Speed = &speed
}
}

return rec
}
102 changes: 102 additions & 0 deletions src/wasm/activity-service/activity/tcx/schema/activity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package schema

import (
"encoding/xml"
"time"
)

type ActivityList struct {
Activity *Activity `xml:"Activity,omitempty"`
}

var _ xml.Unmarshaler = &ActivityList{}

func (a *ActivityList) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
for {
token, err := dec.Token()
if err != nil {
return err
}

switch elem := token.(type) {
case xml.StartElement:
switch elem.Name.Local {
case "Activity":
var activity Activity
if err := activity.UnmarshalXML(dec, elem); err != nil {
return err
}
a.Activity = &activity
}
case xml.EndElement:
if elem == se.End() {
return nil
}
}
}
}

type Activity struct {
Sport string `xml:"Sport,attr"`
ID time.Time `xml:"Id"`
Laps []ActivityLap `xml:"Lap"`
Notes string `xml:"Notes,omitempty"`
Creator *Device `xml:"Creator,omitempty"`
}

var _ xml.Unmarshaler = &ActivityList{}

func (a *Activity) UnmarshalXML(dec *xml.Decoder, se xml.StartElement) error {
for i := range se.Attr {
attr := &se.Attr[i]

switch attr.Name.Local {
case "Sport":
a.Sport = attr.Value
}
}

var targetCharData string
for {
token, err := dec.Token()
if err != nil {
return err
}

switch elem := token.(type) {
case xml.StartElement:
switch elem.Name.Local {
case "Lap":
var activityLap ActivityLap
if err := activityLap.UnmarshalXML(dec, elem); err != nil {
return err
}
a.Laps = append(a.Laps, activityLap)
case "Creator":
var device Device
if err := device.UnmarshalXML(dec, elem); err != nil {
return err
}
a.Creator = &device
default:
targetCharData = elem.Name.Local
}
case xml.CharData:
switch targetCharData {
case "Id":
t, err := time.Parse(time.RFC3339, string(elem))
if err != nil {
return err
}
a.ID = t
case "Notes":
a.Notes = string(elem)
}
targetCharData = ""
case xml.EndElement:
if elem == se.End() {
return nil
}
}
}
}
Loading
Loading