Files
garminsync-go/internal/parser/gpx_parser.go
2025-08-24 18:16:04 -07:00

101 lines
2.3 KiB
Go

package parser
import (
"encoding/xml"
"math"
"time"
"github.com/yourusername/garminsync/internal/parser/activity"
)
// GPX represents the root element of a GPX file
type GPX struct {
XMLName xml.Name `xml:"gpx"`
Trk Trk `xml:"trk"`
}
// Trk represents a track in a GPX file
type Trk struct {
Name string `xml:"name"`
TrkSeg []TrkSeg `xml:"trkseg"`
}
// TrkSeg represents a track segment in a GPX file
type TrkSeg struct {
TrkPt []TrkPt `xml:"trkpt"`
}
// TrkPt represents a track point in a GPX file
type TrkPt struct {
Lat float64 `xml:"lat,attr"`
Lon float64 `xml:"lon,attr"`
Ele float64 `xml:"ele"`
Time string `xml:"time"`
}
// GPXParser implements the Parser interface for GPX files
type GPXParser struct{}
func (p *GPXParser) Parse(data []byte) (*activity.Activity, error) {
var gpx GPX
if err := xml.Unmarshal(data, &gpx); err != nil {
return nil, err
}
if len(gpx.Trk.TrkSeg) == 0 || len(gpx.Trk.TrkSeg[0].TrkPt) == 0 {
return nil, ErrNoTrackData
}
// Process track points
points := gpx.Trk.TrkSeg[0].TrkPt
startTime, _ := time.Parse(time.RFC3339, points[0].Time)
endTime, _ := time.Parse(time.RFC3339, points[len(points)-1].Time)
activity := &activity.Activity{
ActivityType: "hiking",
StartTime: startTime,
Duration: int(endTime.Sub(startTime).Seconds()),
StartLatitude: points[0].Lat,
StartLongitude: points[0].Lon,
}
// Calculate distance and elevation
var totalDistance, elevationGain float64
prev := points[0]
for i := 1; i < len(points); i++ {
curr := points[i]
totalDistance += haversine(prev.Lat, prev.Lon, curr.Lat, curr.Lon)
if curr.Ele > prev.Ele {
elevationGain += curr.Ele - prev.Ele
}
prev = curr
}
activity.Distance = totalDistance
activity.ElevationGain = elevationGain
return activity, nil
}
// haversine calculates the distance between two points on Earth
func haversine(lat1, lon1, lat2, lon2 float64) float64 {
const R = 6371000 // Earth radius in meters
φ1 := lat1 * math.Pi / 180
φ2 := lat2 * math.Pi / 180
Δφ := (lat2 - lat1) * math.Pi / 180
Δλ := (lon2 - lon1) * math.Pi / 180
a := math.Sin(Δφ/2)*math.Sin(Δφ/2) +
math.Cos(φ1)*math.Cos(φ2)*
math.Sin(Δλ/2)*math.Sin(Δλ/2)
c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
return R * c
}
func init() {
RegisterParser(".gpx", &GPXParser{})
}