Analyze all files in a directory, output JSON.
This commit is contained in:
parent
f41b1a01e3
commit
fb816c3132
2 changed files with 126 additions and 28 deletions
3
README.md
Normal file
3
README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# GPX Utils
|
||||
|
||||
Utilities for analyzing and indexing GPX routes.
|
|
@ -2,12 +2,17 @@ package main
|
|||
|
||||
import (
|
||||
"archive/zip"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dhconnelly/rtreego"
|
||||
"github.com/fofanov/go-osgb"
|
||||
|
@ -17,42 +22,84 @@ import (
|
|||
)
|
||||
|
||||
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")
|
||||
dirName := flag.String("dir", "", "Directory to scan for GPX files")
|
||||
flag.Parse()
|
||||
if *openNames == "" {
|
||||
log.Fatal("--open-names is required")
|
||||
log.Fatal("--opname is required")
|
||||
}
|
||||
if *gpxFile == "" {
|
||||
log.Fatal("--gpx is required")
|
||||
if (*gpxFile == "" && *dirName == "") || (*gpxFile != "" && *dirName != "") {
|
||||
log.Fatal("exactly one of --dir or --gpx is required")
|
||||
}
|
||||
rt, err := buildIndex(*openNames)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
points, err := readGPX(*gpxFile)
|
||||
trans, err := osgb.NewOSTN15Transformer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
var dist float64
|
||||
var prevPlace string
|
||||
var prevPoint rtreego.Point
|
||||
for i, p := range points {
|
||||
nn := rt.NearestNeighbor(p)
|
||||
loc, _ := nn.(*openname.Record)
|
||||
if i == 0 {
|
||||
fmt.Printf("%0.2f %s\n", dist, loc.Name)
|
||||
prevPlace = loc.Name
|
||||
prevPoint = p
|
||||
if *gpxFile != "" {
|
||||
err = summarizeSingleFile(rt, trans, *gpxFile)
|
||||
} else {
|
||||
err = summarizeDirectory(rt, trans, *dirName)
|
||||
}
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
dist += distance(prevPoint, p)
|
||||
if insideLoc(p, loc) && loc.Name != prevPlace {
|
||||
fmt.Printf("%0.2f %s\n", dist/1000, loc.Name)
|
||||
prevPlace = loc.Name
|
||||
filename := path.Join(dirName, f.Name())
|
||||
log.Printf("Analyzing %s", filename)
|
||||
summary, err := summarizeGPXTrack(rt, trans, filename)
|
||||
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 {
|
||||
|
@ -68,10 +115,26 @@ func distance(p1, p2 rtreego.Point) float64 {
|
|||
d := p1[i] - p2[i]
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -80,11 +143,21 @@ func readGPX(filename string) ([]rtreego.Point, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
trans, err := osgb.NewOSTN15Transformer()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
var s Summary
|
||||
s.Name = g.Metadata.Name
|
||||
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 _, seg := range trk.TrkSeg {
|
||||
for _, p := range seg.TrkPt {
|
||||
|
@ -93,11 +166,33 @@ func readGPX(filename string) ([]rtreego.Point, error) {
|
|||
if err != nil {
|
||||
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) {
|
||||
|
|
Loading…
Reference in a new issue