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

View file

@ -1,44 +0,0 @@
package openname
import (
"archive/zip"
"fmt"
"strings"
"github.com/dhconnelly/rtreego"
)
func BuildIndex(filename string) (*rtreego.Rtree, error) {
r, err := zip.OpenReader(filename)
if err != nil {
return nil, fmt.Errorf("error opening %s fo reading: %v", filename, 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 {
return nil, fmt.Errorf("erorr opening %s: %v", filename, err)
}
s, err := NewScanner(rc)
if err != nil {
rc.Close()
return nil, fmt.Errorf("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 {
rc.Close()
return nil, fmt.Errorf("error parsing %s: %v", f.Name, err)
}
rc.Close()
}
return rt, nil
}

69
pkg/openname/io.go Normal file
View file

@ -0,0 +1,69 @@
package openname
import (
"archive/zip"
"encoding/gob"
"fmt"
"os"
"strings"
"github.com/ray1729/gpx-utils/pkg/placenames"
)
// ProcessFile reads the compressed OS Open Names data set and calls the handler for each record.
func ProcessFile(filename string, handler func(*Record) error) error {
r, err := zip.OpenReader(filename)
if err != nil {
return fmt.Errorf("error opening %s for reading: %v", filename, err)
}
defer r.Close()
for _, f := range r.File {
if !(strings.HasPrefix(f.Name, "DATA/") && strings.HasSuffix(f.Name, ".csv")) {
continue
}
rc, err := f.Open()
if err != nil {
return fmt.Errorf("error opening %s: %v", filename, err)
}
s, err := NewScanner(rc)
if err != nil {
rc.Close()
return fmt.Errorf("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 {
if err := handler(r); err != nil {
return err
}
}
}
if err = s.Err(); err != nil {
rc.Close()
return fmt.Errorf("error parsing %s: %v", f.Name, err)
}
rc.Close()
}
return nil
}
// Save processes the OS OpenNames zip file and outputs bounded places in gob format.
func Save(inFile string, outFile string) error {
wc, err := os.OpenFile(outFile, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer wc.Close()
enc := gob.NewEncoder(wc)
err = ProcessFile(inFile, func(r *Record) error {
b := placenames.NamedBoundary{
Name: r.Name,
Xmin: r.MbrXMin,
Ymin: r.MbrYMin,
Xmax: r.MbrXMax,
Ymax: r.MbrYMax}
return enc.Encode(b)
})
return err
}

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
}

View file

@ -1,4 +1,4 @@
package openname
package placenames
import (
"io"
@ -16,16 +16,16 @@ type GPXSummarizer struct {
trans osgb.CoordinateTransformer
}
func NewGPXSummarizer(rt *rtreego.Rtree) (*GPXSummarizer, error) {
func NewGPXSummarizer() (*GPXSummarizer, error) {
trans, err := osgb.NewOSTN15Transformer()
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
rt, err := RestoreIndex()
if err != nil {
return nil, err
}
return &GPXSummarizer{rt, trans}, nil
}
func distance(p1, p2 rtreego.Point) float64 {
@ -87,7 +87,7 @@ func (gs *GPXSummarizer) SummarizeTrack(r io.Reader) (*TrackSummary, error) {
}
thisPoint := rtreego.Point{ngCoord.Easting, ngCoord.Northing}
thisHeight := ngCoord.Height
nn, _ := gs.rt.NearestNeighbor(thisPoint).(*Record)
nn, _ := gs.rt.NearestNeighbor(thisPoint).(*NamedBoundary)
if init {
s.Start = nn.Name
prevPlace = nn.Name
@ -102,7 +102,7 @@ func (gs *GPXSummarizer) SummarizeTrack(r io.Reader) (*TrackSummary, error) {
if ascent := thisHeight - prevHeight; ascent > 0 {
s.Ascent += ascent
}
if insideLoc(thisPoint, nn) && nn.Name != prevPlace && distance(thisPoint, prevPlacePoint) > 0.2 {
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