Analyze all files in a directory, output JSON.

This commit is contained in:
Ray Miller 2020-04-15 20:56:10 +01:00
parent f41b1a01e3
commit fb816c3132
2 changed files with 126 additions and 28 deletions

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# GPX Utils
Utilities for analyzing and indexing GPX routes.

View file

@ -2,12 +2,17 @@ package main
import ( import (
"archive/zip" "archive/zip"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"io"
"io/ioutil"
"log" "log"
"math" "math"
"os" "os"
"path"
"strings" "strings"
"time"
"github.com/dhconnelly/rtreego" "github.com/dhconnelly/rtreego"
"github.com/fofanov/go-osgb" "github.com/fofanov/go-osgb"
@ -17,42 +22,84 @@ import (
) )
func main() { func main() {
openNames := flag.String("open-names", "", "Path to Ordnance Server Open Names zip archive") openNames := flag.String("opname", "", "Path to Ordnance Server Open Names zip archive")
gpxFile := flag.String("gpx", "", "Path to GPX file") gpxFile := flag.String("gpx", "", "Path to GPX file")
dirName := flag.String("dir", "", "Directory to scan for GPX files")
flag.Parse() flag.Parse()
if *openNames == "" { if *openNames == "" {
log.Fatal("--open-names is required") log.Fatal("--opname is required")
} }
if *gpxFile == "" { if (*gpxFile == "" && *dirName == "") || (*gpxFile != "" && *dirName != "") {
log.Fatal("--gpx is required") log.Fatal("exactly one of --dir or --gpx is required")
} }
rt, err := buildIndex(*openNames) rt, err := buildIndex(*openNames)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
points, err := readGPX(*gpxFile) trans, err := osgb.NewOSTN15Transformer()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
var dist float64 if *gpxFile != "" {
var prevPlace string err = summarizeSingleFile(rt, trans, *gpxFile)
var prevPoint rtreego.Point } else {
for i, p := range points { err = summarizeDirectory(rt, trans, *dirName)
nn := rt.NearestNeighbor(p) }
loc, _ := nn.(*openname.Record) if err != nil {
if i == 0 { log.Fatal(err)
fmt.Printf("%0.2f %s\n", dist, loc.Name) }
prevPlace = loc.Name }
prevPoint = p
func summarizeDirectory(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, dirName string) error {
files, err := ioutil.ReadDir(dirName)
if err != nil {
return err
}
for _, f := range files {
if f.IsDir() || path.Ext(f.Name()) != ".gpx" {
continue continue
} }
dist += distance(prevPoint, p) filename := path.Join(dirName, f.Name())
if insideLoc(p, loc) && loc.Name != prevPlace { log.Printf("Analyzing %s", filename)
fmt.Printf("%0.2f %s\n", dist/1000, loc.Name) summary, err := summarizeGPXTrack(rt, trans, filename)
prevPlace = loc.Name if err != nil {
return fmt.Errorf("error creating summary of GPX track %s: %v", filename, err)
}
outfile := filename[:len(filename)-4] + ".json"
wc, err := os.OpenFile(outfile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0644)
if err != nil {
return fmt.Errorf("error creating output file %s: %v", outfile, err)
}
err = writeSummary(summary, wc)
if err != nil {
wc.Close()
return fmt.Errorf("error marshalling JSON to %s: %v", outfile, err)
}
if err = wc.Close(); err != nil {
return fmt.Errorf("error closing file %s: %v", outfile, err)
} }
prevPoint = p
} }
return nil
}
func summarizeSingleFile(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, filename string) error {
summary, err := summarizeGPXTrack(rt, trans, filename)
if err != nil {
return fmt.Errorf("error creating summary of GPX track %s: %v", filename, err)
}
if err = writeSummary(summary, os.Stdout); err != nil {
return fmt.Errorf("error marshalling summary for %s: %v", filename, err)
}
return nil
}
func writeSummary(s *Summary, w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
if err := enc.Encode(s); err != nil {
return err
}
return nil
} }
func insideLoc(p rtreego.Point, loc *openname.Record) bool { func insideLoc(p rtreego.Point, loc *openname.Record) bool {
@ -68,10 +115,26 @@ func distance(p1, p2 rtreego.Point) float64 {
d := p1[i] - p2[i] d := p1[i] - p2[i]
s += d * d s += d * d
} }
return math.Sqrt(s) return math.Sqrt(s) / 1000.0
} }
func readGPX(filename string) ([]rtreego.Point, error) { type POI struct {
Name string
Distance float64
}
type Summary struct {
Name string
Time time.Time
Link string
Start string
Finish string
Distance float64
Ascent float64
PointsOfInterest []POI
}
func summarizeGPXTrack(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, filename string) (*Summary, error) {
r, err := os.Open(filename) r, err := os.Open(filename)
if err != nil { if err != nil {
return nil, err return nil, err
@ -80,11 +143,21 @@ func readGPX(filename string) ([]rtreego.Point, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
trans, err := osgb.NewOSTN15Transformer() var s Summary
if err != nil { s.Name = g.Metadata.Name
return nil, err s.Time = g.Metadata.Time
for _, l := range g.Metadata.Link {
if strings.HasPrefix(l.HREF, "http") {
s.Link = l.HREF
break
}
} }
var points []rtreego.Point
var prevPlace string
var prevPoint rtreego.Point
var prevHeight float64
init := true
for _, trk := range g.Trk { for _, trk := range g.Trk {
for _, seg := range trk.TrkSeg { for _, seg := range trk.TrkSeg {
for _, p := range seg.TrkPt { for _, p := range seg.TrkPt {
@ -93,11 +166,33 @@ func readGPX(filename string) ([]rtreego.Point, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
points = append(points, rtreego.Point{ngCoord.Easting, ngCoord.Northing}) thisPoint := rtreego.Point{ngCoord.Easting, ngCoord.Northing}
thisHeight := ngCoord.Height
nn, _ := rt.NearestNeighbor(thisPoint).(*openname.Record)
if init {
s.Start = nn.Name
prevPlace = nn.Name
prevPoint = thisPoint
prevHeight = thisHeight
s.PointsOfInterest = append(s.PointsOfInterest, POI{nn.Name, 0.0})
init = false
continue
}
s.Distance += distance(thisPoint, prevPoint)
if ascent := thisHeight - prevHeight; ascent > 0 {
s.Ascent += ascent
}
if insideLoc(thisPoint, nn) && nn.Name != prevPlace {
s.PointsOfInterest = append(s.PointsOfInterest, POI{nn.Name, s.Distance})
prevPlace = nn.Name
}
prevPoint = thisPoint
prevHeight = thisHeight
} }
} }
} }
return points, nil s.Finish = prevPlace
return &s, nil
} }
func buildIndex(filename string) (*rtreego.Rtree, error) { func buildIndex(filename string) (*rtreego.Rtree, error) {