From e4e4ac260fe11317c8ea5ab5f3840ad9758bc313 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Thu, 16 Apr 2020 08:52:37 +0100 Subject: [PATCH] Refactor to pull useful functionailty into a package. --- .gitignore | 1 + cmd/analyze-gpx/main.go | 152 +++----------------------------------- pkg/openname/index.go | 42 +++++++++++ pkg/openname/summarize.go | 120 ++++++++++++++++++++++++++++++ 4 files changed, 172 insertions(+), 143 deletions(-) create mode 100644 pkg/openname/index.go create mode 100644 pkg/openname/summarize.go diff --git a/.gitignore b/.gitignore index 82f0c3a..5df8d32 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /data/ +/bin/ diff --git a/cmd/analyze-gpx/main.go b/cmd/analyze-gpx/main.go index f7ec2f8..03b4fcd 100644 --- a/cmd/analyze-gpx/main.go +++ b/cmd/analyze-gpx/main.go @@ -1,22 +1,14 @@ 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" - "github.com/twpayne/go-gpx" "github.com/ray1729/gpx-utils/pkg/openname" ) @@ -32,25 +24,25 @@ func main() { if (*gpxFile == "" && *dirName == "") || (*gpxFile != "" && *dirName != "") { log.Fatal("exactly one of --dir or --gpx is required") } - rt, err := buildIndex(*openNames) + rt, err := openname.BuildIndex(*openNames) if err != nil { log.Fatal(err) } - trans, err := osgb.NewOSTN15Transformer() + gs, err := openname.NewGPXSummarizer(rt) if err != nil { log.Fatal(err) } if *gpxFile != "" { - err = summarizeSingleFile(rt, trans, *gpxFile) + err = summarizeSingleFile(gs, *gpxFile) } else { - err = summarizeDirectory(rt, trans, *dirName) + err = summarizeDirectory(gs, *dirName) } if err != nil { log.Fatal(err) } } -func summarizeDirectory(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, dirName string) error { +func summarizeDirectory(gs *openname.GPXSummarizer, dirName string) error { files, err := ioutil.ReadDir(dirName) if err != nil { return err @@ -61,7 +53,7 @@ func summarizeDirectory(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, dir } filename := path.Join(dirName, f.Name()) log.Printf("Analyzing %s", filename) - summary, err := summarizeGPXTrack(rt, trans, filename) + summary, err := gs.SummarizeTrack(filename) if err != nil { return fmt.Errorf("error creating summary of GPX track %s: %v", filename, err) } @@ -82,8 +74,8 @@ func summarizeDirectory(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, dir return nil } -func summarizeSingleFile(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, filename string) error { - summary, err := summarizeGPXTrack(rt, trans, filename) +func summarizeSingleFile(gs *openname.GPXSummarizer, filename string) error { + summary, err := gs.SummarizeTrack(filename) if err != nil { return fmt.Errorf("error creating summary of GPX track %s: %v", filename, err) } @@ -93,7 +85,7 @@ func summarizeSingleFile(rt *rtreego.Rtree, trans osgb.CoordinateTransformer, fi return nil } -func writeSummary(s *Summary, w io.Writer) error { +func writeSummary(s *openname.TrackSummary, w io.Writer) error { enc := json.NewEncoder(w) enc.SetIndent("", " ") if err := enc.Encode(s); err != nil { @@ -101,129 +93,3 @@ func writeSummary(s *Summary, w io.Writer) error { } return nil } - -func insideLoc(p rtreego.Point, loc *openname.Record) bool { - return p[0] >= loc.MbrXMin && p[0] <= loc.MbrXMax && p[1] >= loc.MbrYMin && p[1] <= loc.MbrYMax -} - -func distance(p1, p2 rtreego.Point) float64 { - if len(p1) != len(p2) { - panic("Length mismatch") - } - var s float64 - for i := range p1 { - d := p1[i] - p2[i] - s += d * d - } - return math.Sqrt(s) / 1000.0 -} - -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 - } - g, err := gpx.Read(r) - 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 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 { - gpsCoord := osgb.NewETRS89Coord(p.Lon, p.Lat, p.Ele) - ngCoord, err := trans.ToNationalGrid(gpsCoord) - if err != nil { - return nil, err - } - 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 - } - } - } - s.Finish = prevPlace - return &s, nil -} - -func buildIndex(filename string) (*rtreego.Rtree, error) { - r, err := zip.OpenReader(filename) - if err != nil { - log.Fatal(err) - } - defer r.Close() - rt := rtreego.NewTree(2, 25, 50) - for _, f := range r.File { - if !(strings.HasPrefix(f.Name, "DATA/") && strings.HasSuffix(f.Name, ".csv")) { - continue - } - rc, err := f.Open() - if err != nil { - log.Fatal(err) - } - defer rc.Close() - s, err := openname.NewScanner(rc) - if err != nil { - log.Fatalf("Error reading %s: %v", f.Name, err) - } - for s.Scan() { - r := s.Record() - if r.Type == "populatedPlace" && r.MbrXMax != r.MbrXMin && r.MbrYMax != r.MbrYMin { - rt.Insert(r) - } - } - if err = s.Err(); err != nil { - log.Fatalf("Error parsing %s: %v", f.Name, err) - } - } - return rt, nil -} diff --git a/pkg/openname/index.go b/pkg/openname/index.go new file mode 100644 index 0000000..38c538b --- /dev/null +++ b/pkg/openname/index.go @@ -0,0 +1,42 @@ +package openname + +import ( + "archive/zip" + "log" + "strings" + + "github.com/dhconnelly/rtreego" +) + +func BuildIndex(filename string) (*rtreego.Rtree, error) { + r, err := zip.OpenReader(filename) + if err != nil { + log.Fatal(err) + } + defer r.Close() + rt := rtreego.NewTree(2, 25, 50) + for _, f := range r.File { + if !(strings.HasPrefix(f.Name, "DATA/") && strings.HasSuffix(f.Name, ".csv")) { + continue + } + rc, err := f.Open() + if err != nil { + log.Fatal(err) + } + defer rc.Close() + s, err := NewScanner(rc) + if err != nil { + log.Fatalf("Error reading %s: %v", f.Name, err) + } + for s.Scan() { + r := s.Record() + if r.Type == "populatedPlace" && r.MbrXMax != r.MbrXMin && r.MbrYMax != r.MbrYMin { + rt.Insert(r) + } + } + if err = s.Err(); err != nil { + log.Fatalf("Error parsing %s: %v", f.Name, err) + } + } + return rt, nil +} diff --git a/pkg/openname/summarize.go b/pkg/openname/summarize.go new file mode 100644 index 0000000..ed03f17 --- /dev/null +++ b/pkg/openname/summarize.go @@ -0,0 +1,120 @@ +package openname + +import ( + "math" + "os" + "strings" + "time" + + "github.com/dhconnelly/rtreego" + "github.com/fofanov/go-osgb" + "github.com/twpayne/go-gpx" +) + +type GPXSummarizer struct { + rt *rtreego.Rtree + trans osgb.CoordinateTransformer +} + +func NewGPXSummarizer(rt *rtreego.Rtree) (*GPXSummarizer, error) { + trans, err := osgb.NewOSTN15Transformer() + if err != nil { + if err != nil { + return nil, err + } + } + return &GPXSummarizer{rt: rt, trans: trans}, nil +} + +func insideLoc(p rtreego.Point, loc *Record) bool { + return p[0] >= loc.MbrXMin && p[0] <= loc.MbrXMax && p[1] >= loc.MbrYMin && p[1] <= loc.MbrYMax +} + +func distance(p1, p2 rtreego.Point) float64 { + if len(p1) != len(p2) { + panic("Length mismatch") + } + var s float64 + for i := range p1 { + d := p1[i] - p2[i] + s += d * d + } + return math.Sqrt(s) / 1000.0 +} + +type POI struct { + Name string + Distance float64 +} + +type TrackSummary struct { + Name string + Time time.Time + Link string + Start string + Finish string + Distance float64 + Ascent float64 + PointsOfInterest []POI +} + +func (gs *GPXSummarizer) SummarizeTrack(filename string) (*TrackSummary, error) { + r, err := os.Open(filename) + if err != nil { + return nil, err + } + g, err := gpx.Read(r) + if err != nil { + return nil, err + } + var s TrackSummary + 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 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 { + gpsCoord := osgb.NewETRS89Coord(p.Lon, p.Lat, p.Ele) + ngCoord, err := gs.trans.ToNationalGrid(gpsCoord) + if err != nil { + return nil, err + } + thisPoint := rtreego.Point{ngCoord.Easting, ngCoord.Northing} + thisHeight := ngCoord.Height + nn, _ := gs.rt.NearestNeighbor(thisPoint).(*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 + } + } + } + s.Finish = prevPlace + return &s, nil +}