Store subset of opennames data in gob format and use that to restore index.

This commit is contained in:
Ray Miller 2020-04-18 12:58:53 +01:00
parent 66d4c6243a
commit 86022818da
13 changed files with 195 additions and 77 deletions

BIN
pkg/placenames/placenames.bin (Stored with Git LFS) Normal file

Binary file not shown.

View file

@ -0,0 +1,57 @@
package placenames
//go:generate mule -p placenames -o data.go placenames.bin
import (
"bytes"
"encoding/gob"
"errors"
"io"
"github.com/dhconnelly/rtreego"
)
type NamedBoundary struct {
Name string
Xmin float64
Ymin float64
Xmax float64
Ymax float64
}
func (b *NamedBoundary) Bounds() *rtreego.Rect {
r, err := rtreego.NewRect(rtreego.Point{b.Xmin, b.Ymin}, []float64{b.Xmax - b.Xmin, b.Ymax - b.Ymin})
if err != nil {
panic(err)
}
return r
}
func (b *NamedBoundary) Contains(p rtreego.Point) bool {
if len(p) != 2 {
panic("Expected a 2-dimensional point")
}
return p[0] >= b.Xmin && p[0] <= b.Xmax && p[1] >= b.Ymin && p[1] <= b.Ymax
}
// Restore reads bounded places in gob format and constructs an RTree index
func RestoreIndex() (*rtreego.Rtree, error) {
data, err := dataResource()
if err != nil {
return nil, err
}
dec := gob.NewDecoder(bytes.NewReader(data))
var objs []rtreego.Spatial
for {
var b NamedBoundary
if err := dec.Decode(&b); err != nil {
if errors.Is(err, io.EOF) {
break
}
return nil, err
}
objs = append(objs, &b)
}
rt := rtreego.NewTree(2, 25, 50, objs...)
return rt, nil
}

117
pkg/placenames/summarize.go Normal file
View file

@ -0,0 +1,117 @@
package placenames
import (
"io"
"math"
"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() (*GPXSummarizer, error) {
trans, err := osgb.NewOSTN15Transformer()
if err != nil {
return nil, err
}
rt, err := RestoreIndex()
if err != nil {
return nil, err
}
return &GPXSummarizer{rt, trans}, nil
}
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(r io.Reader) (*TrackSummary, error) {
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 prevPlacePoint rtreego.Point
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).(*NamedBoundary)
if init {
s.Start = nn.Name
prevPlace = nn.Name
prevPlacePoint = thisPoint
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 nn.Contains(thisPoint) && nn.Name != prevPlace && distance(thisPoint, prevPlacePoint) > 0.2 {
s.PointsOfInterest = append(s.PointsOfInterest, POI{nn.Name, s.Distance})
prevPlace = nn.Name
prevPlacePoint = thisPoint
}
prevPoint = thisPoint
prevHeight = thisHeight
}
}
}
s.Finish = prevPlace
return &s, nil
}