Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Go Script for converting gpx and fit files from strava archive to GeoJson
package main
import (
"archive/zip"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"math"
"os"
"strings"
"github.com/beyoung/fit"
"github.com/twpayne/go-gpx"
)
type geoJson struct {
Type string `json:"type,omitempty"`
Features []geoFeature `json:"features,omitempty"`
}
type geoFeature struct {
Type string `json:"type,omitempty"`
Properties struct{} `json:"properties,omitempty"`
Geometry geometry `json:"geometry,omitempty"`
}
type geometry struct {
Type string `json:"type,omitempty"`
Coordinates [][]float64 `json:"coordinates,omitempty"`
}
type ErrLog struct {
FileName string `json:"fileName,omitempty"`
Error string `json:"error,omitempty"`
}
type ErrorFile struct {
Errors []ErrLog `json:"errors,omitempty"`
}
func main() {
fn := os.Args[1]
zf, err := zip.OpenReader(fn)
if err != nil {
panic(err)
}
defer zf.Close()
geo := geoJson{Type: "FeatureCollection"}
features := make([]geoFeature, 0)
errF := 0
sucF := 0
for _, file := range zf.File {
if !strings.Contains(file.Name, "activities/") {
continue
}
geoF, err := processFile(file)
if err != nil {
fmt.Println(file.Name, err)
errF++
continue
}
if len(geoF.Geometry.Coordinates) > 2 {
features = append(features, geoF)
sucF++
}
}
geo.Features = features
file, err := json.Marshal(geo)
if err != nil {
panic(err)
}
_ = ioutil.WriteFile(fn+".geojson", file, 0644)
fmt.Println(float32(errF)/float32(sucF+errF), errF, sucF)
}
func processFile(f *zip.File) (geoFeature, error) {
var rc, orc io.ReadCloser
geoF := geoFeature{
Type: "Feature",
}
geo := geometry{Type: "LineString"}
var err error
var coords [][]float64
rc, err = f.Open()
if err != nil {
return geoF, err
}
defer rc.Close()
switch {
case strings.HasSuffix(f.Name, "fit.gz"):
orc, err = gzip.NewReader(rc)
if err != nil {
return geoF, fmt.Errorf("error unzipping %s with error: %v", f.Name, err)
}
default:
orc = rc
}
switch {
case strings.HasSuffix(f.Name, ".fit"), strings.HasSuffix(f.Name, ".fit.gz"):
coords, err = processFit(orc)
case strings.HasSuffix(f.Name, ".gpx"), strings.HasSuffix(f.Name, ".gpx.gz"):
coords, err = processGpx(orc)
default:
return geoF, fmt.Errorf("cannot process")
}
if err != nil {
fmt.Println(f.Name, err)
}
if len(coords) > 0 {
coords := checkCoords(coords)
geo.Coordinates = coords
} else {
return geoF, fmt.Errorf("no valid coords")
}
geoF.Geometry = geo
return geoF, nil
}
func processGpx(rc io.ReadCloser) ([][]float64, error) {
coords := [][]float64{}
t, err := gpx.Read(rc)
if err != nil {
return coords, err
}
for _, tk := range t.Trk {
for _, sg := range tk.TrkSeg {
for _, tp := range sg.TrkPt {
coords = append(coords, []float64{tp.Lon, tp.Lat})
}
}
}
return coords, nil
}
func processFit(rc io.ReadCloser) ([][]float64, error) {
fit, err := fit.Decode(rc)
if err != nil {
return [][]float64{}, fmt.Errorf("fit decording error: %v", err)
}
activity, err := fit.Activity()
if err != nil {
return [][]float64{}, fmt.Errorf("fit activities error: %v", err)
}
coords := make([][]float64, 0, len(activity.Records))
for _, record := range activity.Records {
if !record.PositionLat.Invalid() && !record.PositionLong.Invalid() {
coords = append(coords, []float64{record.PositionLong.Degrees(), record.PositionLat.Degrees()})
}
}
return coords, nil
}
func checkCoords(coords [][]float64) [][]float64 {
var delete []int
rerun := false
for i := 1; i < len(coords); i++ {
d := distance(coords[i], coords[i-1])
if d > 0.1 {
delete = append(delete, i)
continue
}
if coords[i][0] == coords[i-1][0] && coords[i][1] == coords[i-1][1] {
delete = append(delete, i)
continue
}
}
for i := len(delete); i > 0; i-- {
coords = append(coords[:delete[i-1]], coords[delete[i-1]+1:]...)
rerun = true
}
if rerun {
return checkCoords(coords)
}
return coords
}
func distance(p1, p2 []float64) float64 {
return math.Sqrt(math.Pow(p1[0]-p2[0], 2) + math.Pow(p1[1]-p2[1], 2))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment