Skip to content

Instantly share code, notes, and snippets.

@SemanticallyNull
Last active September 7, 2021 12:03
Show Gist options
  • Save SemanticallyNull/991af5d1c603dda6c834ec1040e6ed30 to your computer and use it in GitHub Desktop.
Save SemanticallyNull/991af5d1c603dda6c834ec1040e6ed30 to your computer and use it in GitHub Desktop.
timeline_parser
module github.com/semanticallynull/slh
go 1.16
require github.com/montanaflynn/stats v0.6.6
package main
import (
"encoding/json"
"fmt"
"math"
"os"
"path/filepath"
"strconv"
"time"
"github.com/montanaflynn/stats"
)
type slh struct {
TO []to `json:"timelineObjects"`
}
type to struct {
AS as `json:"activitySegment"`
}
type as struct {
Distance uint64 `json:"distance"`
ActivityType string `json:"activityType"`
Duration dur `json:"duration"`
}
type dur struct {
StartTimestampMs string `json:"startTimestampMs"`
EndTimestampMs string `json:"endTimestampMs"`
}
var activitiesDistance map[string]stats.Float64Data
var activitiesTime map[string]stats.Float64Data
func main() {
activitiesDistance = make(map[string]stats.Float64Data)
activitiesTime = make(map[string]stats.Float64Data)
files, err := filepath.Glob("*/*.json")
if err != nil {
panic(err)
}
for _, file := range files {
fmt.Printf("Processing %s...", file)
f, err :=os.Open(file)
if err != nil {
panic(err)
}
s := slh{}
err = json.NewDecoder(f).Decode(&s)
if err != nil {
fmt.Printf("error processing file %s: %w", file, err)
}
for _, t := range s.TO {
if t.AS.ActivityType != "" && t.AS.ActivityType != "UNKNOWN_ACTIVITY_TYPE" {
if _, ok := activitiesDistance[t.AS.ActivityType]; !ok {
activitiesDistance[t.AS.ActivityType] = stats.Float64Data{}
activitiesTime[t.AS.ActivityType] = stats.Float64Data{}
}
activitiesDistance[t.AS.ActivityType] = append(activitiesDistance[t.AS.ActivityType],
float64(t.AS.Distance))
endTS, err := strconv.ParseInt(t.AS.Duration.EndTimestampMs, 10, 64)
if err != nil {
panic(err)
}
startTS, err := strconv.ParseInt(t.AS.Duration.StartTimestampMs, 10, 64)
if err != nil {
panic(err)
}
duration := (endTS - startTS)/1000
activitiesTime[t.AS.ActivityType] = append(activitiesTime[t.AS.ActivityType],
float64(duration))
}
}
fmt.Println("")
}
fmt.Printf("%20s\t%15s\t\t%10s\t\t%10s\t\t%10s\n", "Mode", "Time", "Total Distance", "Mean", "Max")
for k, v := range activitiesDistance {
t, _ := activitiesTime[k].Sum()
d, _ := v.Sum()
s, _ := v.Mean()
l, _ := v.Max()
fmt.Printf("%20s:\t%15s\t\t%-10s\t\t%-10s\t\t%-10s\n",
fmtMode(k),
fmtDuration(time.Duration(t*1000*1000*1000)),
fmtDistance(uint64(d)),
fmtDistance(uint64(s)),
fmtDistance(uint64(l)))
}
}
func fmtDuration(input time.Duration) string {
s := int(math.Floor(input.Round(time.Second).Seconds()))
ds := s % 60
m := (s - ds) / 60
dm := m % 60
h := (m - dm) / 60
dh := h % 24
d := (h - dh) / 24
return fmt.Sprintf("%5dd %02dh %02dm %02ds", d, dh, dm, ds)
}
func fmtDistance(dist uint64) string {
if dist < 1000 {
return fmt.Sprintf("%5d m", dist)
}
return fmt.Sprintf("%5d km", dist/1000)
}
func fmtMode(input string)string {
switch input {
case "IN_TAXI":
return "Taxi"
case "IN_SUBWAY":
return "Metro/Subway"
case "FLYING":
return "Flying"
case "IN_TRAM":
return "Tram"
case "WALKING":
return "Walking"
case "IN_PASSENGER_VEHICLE":
return "Car"
case "CYCLING":
return "Bike"
case "RUNNING":
return "Running"
case "IN_BUS":
return "Bus"
case "IN_TRAIN":
return "Train"
}
return input
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment