gpx-utils/pkg/cafes/common.go
Ray Miller bd7eb246d5 Improvements to duplicate suppression, cafe stop search, and configurability.
When suppressing duplicate cafes and place names, look back a certain distance along
the route rather than just the previous point of interest.

When searching for cafes, use SearchIntersect() to return all entries in the
bounding rectangle, not just the nearest.

Remove (most) hard-coded constants and allow these to be overriden by options
to the NewGPXSummarizer() constructor.
2023-01-19 16:21:32 +00:00

90 lines
1.8 KiB
Go

package cafes
import (
"errors"
"fmt"
"sync"
"time"
"github.com/dhconnelly/rtreego"
)
// Size (in metres) of the bounding box around a stop
const stopRectangleSize = 50
type RefreshmentStop struct {
Name string
Url string
Easting float64
Northing float64
}
func (s *RefreshmentStop) Bounds() *rtreego.Rect {
p := rtreego.Point{s.Easting, s.Northing}
return p.ToRect(stopRectangleSize)
}
func (s *RefreshmentStop) Contains(p rtreego.Point) bool {
if len(p) != 2 {
panic("Expected a 2-dimensional point")
}
bounds := s.Bounds()
for i := 0; i < 2; i++ {
if p[i] < bounds.PointCoord(i) || p[i] > bounds.PointCoord(i)+bounds.LengthsCoord(i) {
return false
}
}
return true
}
// TTL cache based on "9.7 Example: Concurrent Non-Blocking Cache" from
// "The Go Programming Language", Alan A. A. Donovan and Brian W. Kernighan
type result struct {
value *rtreego.Rtree
err error
}
type entry struct {
res result
expires time.Time
ready chan struct{} // closed when res is ready
}
type Cache struct {
mu sync.Mutex
entries map[string]*entry
}
func New() *Cache {
return &Cache{entries: make(map[string]*entry)}
}
func (c *Cache) Get(k string) (*rtreego.Rtree, error) {
c.mu.Lock()
e := c.entries[k]
if e == nil || e.expires.Before(time.Now()) {
e = &entry{ready: make(chan struct{}), expires: time.Now().Add(4 * time.Hour)}
c.entries[k] = e
c.mu.Unlock()
e.res.value, e.res.err = FetchStops(k)
close(e.ready)
} else {
c.mu.Unlock()
<-e.ready
}
return e.res.value, e.res.err
}
var ErrInvalidStops = errors.New("invalid stops")
func FetchStops(k string) (*rtreego.Rtree, error) {
switch k {
case "ctccambridge":
return FetchCtcCamIndex()
case "cyclingmaps":
return FetchCyclingMapsIndex()
default:
return nil, fmt.Errorf("%w: %s", ErrInvalidStops, k)
}
}