Store subset of opennames data in gob format and use that to restore index.
This commit is contained in:
parent
66d4c6243a
commit
86022818da
13 changed files with 195 additions and 77 deletions
BIN
pkg/placenames/placenames.bin
(Stored with Git LFS)
Normal file
BIN
pkg/placenames/placenames.bin
(Stored with Git LFS)
Normal file
Binary file not shown.
57
pkg/placenames/placenames.go
Normal file
57
pkg/placenames/placenames.go
Normal 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
117
pkg/placenames/summarize.go
Normal 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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue